Django-Select2 Heavy Widget - python

I was trying to implement Django-select2 for the first time.... I referred their documentation and some of the stack overflow solutions to implement it.... I managed to get ajax functionality work properly, also i am able to select multiple choices... however when I submit and validate the form, I am getting error like -> "Select a valid choice. 123456 is not one of the available choices."
I am not understanding what I am doing wrong....
here is my form.
class MyCustReqForm(forms.ModelForm):
initial_customer = forms.MultipleChoiceField(
widget=HeavySelect2MultipleWidget(data_view='customer_ajax',
attrs={'data-minimum-input-length': 4, 'delay':200},
model=Customer),
)
end_customer = forms.MultipleChoiceField(
widget=HeavySelect2MultipleWidget(data_view='customer_ajax',
attrs={'data-minimum-input-length': 4, 'delay':200},
model=Customer),
)
class Meta:
model = Workflow_Customer
fields = [ 'initial_customer', 'end_customer' ]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['initial_customer'].widget.attrs.update({'style': 'width:100%', 'data-placeholder': 'Select Customer'})
self.fields['end_customer'].widget.attrs.update({'style':'width:100%', 'data-placeholder':'Select end customer'})
and customer_ajax view calls below function...
def customer_select2(request):
term = request.GET.get("term", None)
if term:
res = list(Customer.objects.filter(Q(customer_number__contains=term) | Q(customer_name__contains=term)).values('id', 'customer_number', 'customer_name'))[:10]
if res:
result = [{'id': value['id'], 'text': value['customer_number'] + ' ' + value['customer_name'] } for index, value in enumerate(res)]
return JsonResponse({'err': 'nil', 'results': result}, safe=False)
return JsonResponse(data={'success': False, 'errors': 'No mathing items found'})
when I checked in debug mode. I found that choices are empty...
I appreciate for the quick help... if possible, please provide one complete example which explains how form defined and view used for Ajax function...

That happens because when you send your form there aren't initial choices so the chosen option don't pass the validation. When you send your form, the class MyCustReqForm is initialized and in that moment you must do a requets to get choices of your form field. That choices must containt your chosen option. For example:
self.fields['customer'].choices = call_to_end_point()
When the request is 'GET' there isn't problem, but in the request 'POST' is necessary to have some basic options.
PD: Sorry for my english

Solution is to write custom clean methods so this way you are defining your own validations for a field thus overriding django form validation for the choice field.
def clean_initial_customer(self):
choices = self.cleaned_data["initial_customer"]
# write your custom logics and remove any errors
let me know if you find some issue in implementing the same.

Related

UPDATE: Dynamic MultipleChoiceField changes instances to strings

I have a django form that has a multiple choice field. The field should be dynamic, that is, only the records associated with the user who is currently logged in should be displayed. I've managed to put this together so far;
forms.py
class myForm(forms.ModelForm):
def __init__(self, someUser, *args, **kwargs):
super(myForm, self).__init__(*args, **kwargs)
someRecords = models.SomeModel.objects.filter(someUser = someUser)
#The line above gets records associated with a specific user
displayNames = []
for i in someRecords:
displayNames.append((i.someField, i.someOtherField + ' ' + i.someOtherField2))
#I am basically making a list of tuples, containing a field and a concatnation of two other fields. The latter will be what is displayed in the select box
self.fields['theSelectField'] = forms.ChoiceField(choices = displayNames)
class Meta:
#I defined model, fields and labels here
views.py
def myFormPage(request):
someUser = request.user.someextensionofuser.someUser
form = forms.myForm(someUser)
context = {'form': form}
if request.method == 'POST':
form = forms.myForm(someUser, data = request.POST)
if form.is_valid():
#Do Stuff if form is valid. However,this stuff doesn't get done, the page refreshes instead
So I've managed to make the select options dynamic. However, now I can't submit data.
EDIT: One of the comments helped me solve the previously stated problem. I've updated the views.py code. However, now I'm running into this error;
Cannot assign "'someString'": "someModel.someField" must be a
"someForeignModel" instance
The option values seem to be strings instead of references to objects. How do I solve this?
This limits the possible options of your select field:
self.fields['theSelectField'].queryset = SomeModel.objects.filter(someUser = someUser)
In your views you might want to use a Class Based View, because it handles a lot of stuff automatically and saves you time. Take a look here: https://ccbv.co.uk/
I firgured it out. Since my main problem was with how the options are displayed to a user, I decided to go with changing my str method in models.py to;
class someModel(models.Model):
#my fields
def __str__(self):
return self.someField + ' ' + self.someOtherField
Then in my forms.py, I went with #dmoe's answer;
self.fields['theSelectField'] = forms.ModelChoiceField(queryset = models.SomeModel.objects.filter(someUser = someUser))
So now both problems are solved. My options have custom labels, and I can submit my data without running into valueError.

Checking uniqueness contraint during form validation in App Engine

I am using Flask and WTforms in App Engine, trying to implement uniqueness contraint on one of the field. The question is big, please be patient and I have been stuck here from many hours, need some help from you people. Started learning App Engine, Flask and WTForms a month ago. Thanks in advance.
Application has model 'Team' as shown below:
class Team(db.Model):
name = db.StringProperty(required=True)
-- some other fields here --
Requirement: Name of the team has to be unique.
I have followed the links
http://www.codigomanso.com/en/2010/09/solved-anadir-claves-unicas-en-google-app-engine-en-3-lineas/
http://squeeville.com/2009/01/30/add-a-unique-constraint-to-google-app-engine/
http://csimms.botonomy.com/2012/07/there-are-only-two-ways-to-enforce-unique-constraints-in-google-app-engine.html
Have come up with the following code:
models.py: Created a separate table 'Unique' as given in the link:
class Unique(db.Model):
""" Handles uniqueness constriant on a field """
#classmethod
def unique_check(cls, form_name, field_data):
def tx(form_name, field_data):
key_name = "%s%s" % (form_name, field_data)
uk = Unique.get_by_key_name(key_name)
app.logger.debug("UK:" + str(uk))
if uk:
return False
uk = Unique(key_name=key_name)
uk.put()
return True
ret_val = db.run_in_transaction(tx, form_name, field_data)
app.logger.debug("ret_val:" + str(ret_val))
return ret_val
forms.py: I have overridden the __init__() and validate_on_submit() function in which uniqueness is checked and if it is not unique, error is attached to that field and validation error will be raised in the same way as wtforms's validators.
class TeamForm(wtf.Form):
def __init__(self, *args, **kwargs):
super(TeamForm, self).__init__(*args, **kwargs)
if kwargs.get('edit', None):
self.old_name = self.name.data.lower()
def validate_on_submit(self, edit=False):
if not super(TeamForm, self).validate_on_submit():
return False
if edit:
if self.old_name and self.old_name != self.name.data.lower():
Unique.delete_entity(self.__class__.__name__, self.old_name)
if not Unique.unique_check(self.__class__.__name__, self.name.data.lower()):
self.name.errors.append("Value '%s' is not unique" % self.name.data)
return False
else:
if not Unique.unique_check(self.__class__.__name__, self.name.data.lower()):
self.name.errors.append("Value '%s' is not unique" % self.name.data)
return False
return True
**---- Form fields declaration ----**
The above code works when new team is inserted.I mean it checks uniqueness properly. The problem occurs, when user edits the team information. Following two scenarios are problematic:
When the user tries to submit the form, application will throw "Not unique" error, it is obvious because "Unique" table has "key_name" for this team.
If user changes "team name", application has to delete the previous team name from the "Unique" table and has to check uniqueness for the "changed team name". I am not able to handle these two scenarios.
My edit_team function looks like this:
#app.route('/team/edit/<key>', methods=['GET','POST'])
#login_required
def edit_team(key):
k = db.Key(key)
team = db.get(k)
form = TeamForm(obj = team, edit=True) # to save old name, doesn't work.
if form.validate_on_submit(edit=True): # edit=True is given only in edit function
team.name = form.name.data
-- others fields are updated in the similar way --
team.put()
return redirect(url_for('teams_list'))
return render_template('edit_team.html', form=form)
Problem can be easily solved if I am able to find out 'old name' of the team, so that I can delete it from the "Unique" table. As you can see I am saving old name of the team in TeamForm __init__() function, but __init__() is called during GET(old name is saved) and also in POST(modified name will get saved!!). So, I cannot find out old name at all and it remains in the "Unique" table, nobody can use this "old team name" anymore.
I tried to explain as much as possible, please let me know if you want more info.
Edit: didn't answer your question properly the first time.
Separate instances of the Form object will be instantiated for the GET and POST requests, so you can't save the old_name to self.
You'll need to pass the old_name to the browser in the form, and have the browser submit the old_name back in the POST request.
The easyish way to do this is to create a hidden form field that the user doesn't see, but will get submitted by the POST request. I'm not too familiar with WTForms but I assume you can initialize the old_name field value in your the GET request handler.

Decorate GET URL using forms

I have some question:
I use django form, and fields like MultipleChoiceField
in view.py I clean data and get GET URL like this
http://localhost:8000/?category=&style=&sex=&brand=ASICS&brand=Be+Positive&low_price=&high_price=
Give me advise, can I regroup brand field and hide empty.
I want getting something like this:
http://localhost:8000/?brand=1+2
And else one question:
How can I set empty value(empty_label) for forms.ModelMultipleChoiceFIeld
forms.py:
brand = forms.MultipleChoiceField(required=False,
widget=forms.SelectMultiple(attrs={'size':1})
)
def __init__(self,app_label=None, *args, **kwargs):
super(Search, self).__init__(*args, **kwargs)
self.fields['brand'].choices = [('', 'All brands')]+[(brand.name, brand) for brand in Brand.objects.all() ]
views.py:
if request.method == 'GET' and request.GET:
form = SearchForm(app_label, request.GET)
if form.is_valid():
brands = form.cleaned_data['brand']
kwargs.update({"brand__name__in": brands})
This is how the browser submits multiple data. It's part of the HTML specification, trying to change it would be folly and technically I can't understand why you would try to care about how your url GET data looks.
That being said, if you want to change the way it submits you'll need javascript to transform the data on form submit. Django has nothing to do with the matter.
Using jQuery for example:
$('#form').submit(function(){
//Get form data
//Transform into my custom set of vars
//Redirect to form's ACTION with my querystring appended.
});
Please keep in mind you will not get any automatic parsing of the values on the Django side. Normally it would turn it into a list for you, but now you're responsible for parsing the 'value+value+value' yourself.
For empty label in forms you could do this -
class SomeForm(forms.Form):
h=forms.CharField(label=u'',widget=forms.TextInput(attrs={'value':'Search'}))
By keeping label as '', you get the label as empty. The attrs are basically the HTML attributes of the form text field.
UPDATE: I didn't understand the first part of your Q, elaborate...

How to validate django form only when adding not editing

How could we make the django form to not validate if we are editing, not adding a new record. The code as following :
class PageForm(forms.Form):
name = forms.CharField(max_length=100,widget=forms.TextInput(attrs={'class':'textInput'}))
description = forms.CharField(max_length=300, required=False,widget=forms.TextInput(attrs={'class':'textInput'}))
body = forms.CharField(widget=forms.Textarea)
template = forms.CharField(max_length=30,widget=forms.TextInput(attrs={'class':'textInput'}))
navbar = forms.BooleanField(required=False, widget=forms.Select(choices=(('True','True'),
('False', 'False'))))
publish = forms.BooleanField(widget=forms.Select(choices=(('Published','Publish Now'),
('Private','Private'),
('Draft','Draft'))))
def save(self, page=None, commit=True):
data = self.cleaned_data
if not page:
page = models.Page(key_name=data['name'].replace(' ','-'))
page.name = data['name']
page.description = data['description']
page.body = data['body']
page.template = data['template']
page.publish = data['publish']
if commit: page.put()
return page
# prevent the same page 's name
def clean_name(self):
name = self.cleaned_data['name']
query = models.Page.all(keys_only=True)
query.filter('name = ', name)
page = query.get()
if page:
raise forms.ValidationError('Page name "%s" was already used before' % name)
return name
The purpose of this name validation is to prevent the records with the same name. BUt i found that, it also validate on edit, so we couldn't edit records, since it will said 'records with same name already exist'.
Actually for editing, the page param on save function wont be none, but prev record instead, and wil be none on saving a new one. But how we read this param, on clean_name function so we can now whether it is editing or creating?
Thanks a lot!
in your clean method, you can use self.initial to know whether it is adding or editing. If it is editing, the self.initial will not be empty. But when it is adding, self.initial will be dictionary of what the previous value.
If you are editing form, then the form has some instance, and you can check if that exists.
If it does, then you are probably editing existing object.. right?
Example:
If you are editing object with form, you create form object much like this:
form = MyForm(instance = myobject)
Then in your form class methods you can check if form has saved instance in a way that it is described here:
Test if Django ModelForm has instance
in your clean_name function exclude the current object from queryset
query.filter('name = ', name).exclude(pk=self.pk)
or change the if condition to check that page and current object are not the same.
Sorry, I couldn't comment below your guys post, don't know why.
#sunn0 : I didn't use django models, coz deploy the app in appengine, so use appengine model instead.
#Zayatzz : May you show a little code how to do it? Since whether we are adding or editing, we always bound the form to request.POST before validation, so don't know how to differentiate.
#Ashok : I made a workaround based on your suggestion. Since previously I didn't pass the pk to form, but passing the prev object as param instead, so couldn't exclude by using pk. So, I change the code and put additional key as pk (if create, let key empty, but if edit fill key with pk) and just check in if condition, if key field not empty, then it means we are editing. Not sure if it is best practice, but it works anyway.
I can suggest to override form's init method
https://stackoverflow.com/a/70845558/15080117
because there is an argument instance.

Django forms: making a disabled field persist between validations

At some point I need to display a "disabled" (greyed out by disabled="disabled" attribute) input of type "select". As specified in the standard (xhtml and html4), inputs of type "select" can not have the "readonly" attribute. Note that this is for presentation purposes only, the actual value must end up in the POST. So here is what I do (quoting a part of the form declaration in django):
from django import forms
_choices = ['to be', 'not to be']
class SomeForm(forms.Form):
field = forms.ChoiceField(choices=[(item, item) for item in _choices],
widget=forms.HiddenInput()) # the real field
mock_field = forms.ChoiceField(required=False, # doesn't get submitted
choices=[(item, item) for item in _choices],
label="The question",
widget=forms.Select(attrs={'disabled':'disabled'}))
Then it is initialized like this:
initial_val = 'to be'
form = SomeForm(ititial={'field':initial_val,
'mock_field':initial_val})
And all is well. Well, until the form gets validated and one of the other fields fails the validation. When this happens, the form is reloaded and the values are preserved, but not the one of the "mock_field" - it never got submitted (it is disabled). So it is not preserved. While this doesn't affect the data integrity, it is still not so good presentation-wise.
Is there any way to preserve that field, with as little hackery as possible? The form is a part of a django.contrib.formtools.FormWizard and the initial values (and some fields) are generated dynamically. Basically, there is a lot of stuff going on already, it'd be great if it was possible not to overcomplicate things.
Browsers don't POST disabled fields.
You can try to copy fields initial value to mock_field in your Form's __init__
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
mock_initial = self.fields['field'].initial
self.fields['mock_field'].initial = mock_initial
Code is not tested. Normally you would be concerned about form.data as well, but in this case it won't be different than initial
Well, this will be the first time I answer my question, but I've found a solution and (while it cerainly is a hack) it works.
Instead of getting the initial value from the form instance, - self.fields['whatever'].initial seems to be None inside the constructor, I am getting the value from keyword argument "initial". And then I set it as the only choice for the "mock" field. Like this:
from django import forms
_choices = ['to be', 'not to be']
class SomeForm(forms.Form):
field = forms.ChoiceField(choices=[(item, item) for item in _choices],
widget=forms.HiddenInput()) # the real field
mock_field = forms.ChoiceField(required=False, # doesn't get submitted
choices=[(item, item) for item in _choices],
label="The question",
widget=forms.Select(attrs={'disabled':'disabled'}))
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
mock_initial = kwargs['initial']['field']
self.fields['mock_field'].choices = [(mock_initial, mock_initial),]
This probably needs some error handling. Obviously, this will not work if the initial value is not provided for the actual field.

Categories

Resources