forms ModelChoiceField queryset + extra choice fields django forms - django

I am trying to create a form in that ModelChoiceField loads from queryset and i want add few custom values to ModelChoiceField for extend i have used choice field, like below but while updating the form,getting below error
Form Error :
Select a valid choice. That choice is not one of the available choices.
Code :
self.fields['lead'] = forms.ModelChoiceField(queryset = Pepole.objects.filter(poc__in = ('lead','sr.lead')))
self.fields['lead2'] = forms.ModelChoiceField(queryset = Pepole.objects.filter(role__in = ('lead','sr.lead')))
choice_field = self.fields['lead']
choice_field.choices = list(choice_field.choices) + [('None', 'None')]
choice_field = self.fields['lead2']
choice_field.choices = list(choice_field.choices) + [('None', 'None')]
Am i doing any thing wrong here?

That's not going to work. Look at how a ModelChoiceField works:
try:
key = self.to_field_name or 'pk'
value = self.queryset.get(**{key: value})
except self.queryset.model.DoesNotExist:
raise ValidationError(self.error_messages['invalid_choice'])
return value
You can't add something randomly to it.
Use a ChoiceField instead and custom process the data.
class TestForm(forms.Form):
mychoicefield = forms.ChoiceField(choices=QS_CHOICES)
def __init__(self, *args, **kwargs):
super(TestForm, self).__init__(*args, **kwargs)
self.fields['mychoicefield'].choices = \
list(self.fields['mychoicefield'].choices) + [('new stuff', 'new')]
def clean_mychoicefield(self):
data = self.cleaned_data.get('mychoicefield')
if data in QS_CHOICES:
try:
data = MyModel.objects.get(id=data)
except MyModel.DoesNotExist:
raise forms.ValidationError('foo')
return data

It appears that you just want to allow those form fields to be optional. Don't make it hard on yourself. See the documentation regarding marking a form field as required.
lead = forms.ModelChoiceField(queryset=People.objects.filter(poc__in=('lead', 'sr.lead')), required=False)

Do you have a Person object with pk 'None'?
i think you should be using
self.fields['lead'] = forms.ModelChoiceField(queryset = Pepole.objects.filter(poc__in = ('lead','sr.lead')), empty_label="None")
self.fields['lead2'] = forms.ModelChoiceField(queryset = Pepole.objects.filter(role__in = ('lead','sr.lead')), empty_label="None")
edit:
Since you are using a modelchoicefield, i would think that all your choices would be either of that model type or none.
You can "extend" the choices of that type by modifying the queryset you pass into the constructor for the modlechoicefield, e.g.:
qs = People.objects.filter(poc__in = ('lead','sr.lead'))
ext = People.objects.filter(role__in = ('lead', 'sr.lead'))
qs = qs | ext
self.fields['lead'] = forms.ModelChoiceField(queryset = qs, empty_label='None')
or for updating
self.fields['lead'].queryset = qs
this question talks about the modelchoicefield a bit and might be of interest to you:
How do I filter ForeignKey choices in a Django ModelForm?

Related

Django combine two fields data into a queryset

I have a model say Club where we have fields like:
manager = models.ForeignKey(Users, related_name="return_manager", on_delete=models.CASCADE)
members = models.ManyToManyField(Users, related_name="return_members", blank=True)
Now I want to create a drop down in a form where I can add both the manager and members to it. I tried making two requests for Club.objects.filter(pk=mypk).members.all() and Club.objects.filter(pk=mypk).manager. I tried chain function and using '| ' operator but none worked. I think the manager is a single User and not a queryset, that is what the main problem is. Any workarounds?
One possible way getting all of the information together involves modifying your form choices.
In your view you would need to pass the choices along as context to your form.
def my_view(request, club_pk):
context = {}
context.update({
"manager": Club.objects.get(pk=club_pk).manager,
"members": Club.objects.get(pk=club_pk).members.all()
}
form = MyForm(request.POST or None, request=request, context=context)
In your form, you would need to modify the __init__ method to update your choices like so:
class MyForm(forms.Form):
all_club_members = forms.ChoiceField('Manager + Members', required=True)
def __init__(self, *args, **kwargs):
self.context = kwargs.pop('context', None)
super(MyForm, self).__init__(*args, **kwargs)
manager_tuple = [(self.context['manager'].id, self.context['manager'].display_name)]
members_tuples = [(member.id, member.display_name) for member in self.context['members']
self.fields['all_club_members'].choices = manager_tuple + members_tuples
Try this:
manager = [Club.objects.filter(pk=mypk).manager]
members = Club.objects.filter(pk=mypk).members.all()
userlist = list(manager) + list(members)
return Users.objects.filter(pk__in=userlist)
Should create a queryset of all users

Filter foreignkey select options on a form based on an object id passed by a view

In my project I need to filter foreign key select options on a form. I can do that passing a parameter to the __init__ method of the form like so:
class ResultadoForm(forms.ModelForm):
frequencia = forms.CharField(max_length=50)
tolerancia = forms.CharField(max_length=255)
def __init__(self, equipamento_id, *args, **kwargs):
equipamento_id = kwargs.pop('equipamento_id')
super (ResultadoForm, self ).__init__(*args, **kwargs)
self.fields['teste'].queryset = Teste.objects.filter(equipamento=equipamento_id)
class Meta:
model = Resultado
exclude = ['actividade']
In the view all I have to do is:
form1 = ResultadoForm(equipamento_id)
The problem is I am unable to pass the parameter equipamento_id to an inlineformset_factory:
form1 = inlineformset_factory(Actividade, Resultado, form=ResultadoForm(equipamento_id), exclude=('actividade',), extra=len(testes_list))
This raises an error. How can I pass the parameter equipamento_id to an inlineformset_factory in order to be able to filter the foreignkey options?
If you are using django 1.9 you could use form_kwargs to pass the data:
FormSet1 = inlineformset_factory(Actividade,
Resultado,
form=ResultadoForm,
exclude=('actividade',),
extra=len(testes_list))
formset = FormSet1(form_kwargs={'equipamento_id': equipamento_id})
django doc about form_kwargs.

ModelChoiceField in forms.Form won't validate if queryset is overridden

I have a django ModelChoiceField that won't validate if I override the queryset.
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
form = PersonalNote()
form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)
The form.is_valid() error is: "Select a valid choice. That choice is not one of the available choices".
If Tile.objects.none() is replaced with Tile.objects.all() it validates, but loads far too much data from the database. I've also tried:
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
def __init__(self, *args, **kwargs):
yyy = kwargs.pop('yyy', None)
super(PersonalNote, self).__init__(*args, **kwargs)
if yyy:
self.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)
What might be wrong here? Note the real application also overrides the label, but that does not seem to be a factor here:
class ModelChoiceField2(forms.ModelChoiceField):
def label_from_instance(self, obj):
assert isinstance(obj,Tile)
return obj.child_title()
After 2 hours I found the solution. Because you specified a queryset of none in the class definition, when you instantiate that PersonalNote(request.POST) to be validated it is referenceing a null query set
class PersonalNote(forms.Form):
tile = ModelChoiceField(queryset=Tile.objects.none())
note = forms.CharField()
To fix this, when you create your form based on a POST request be sure to overwrite your queryset AGAIN before you check is_valid()
def some_view_def(request):
form = PersonalNote(request.POST)
**form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)**
if form.is_valid():
#Do whatever it is
When you pass an empty queryset to ModelChoiceField you're saying that nothing will be valid for that field. Perhaps you could filter the queryset so there aren't too many options.
I also had this problem. The idea is to dynamically change the queryset of a ModelChoiceField based on a condition (in my case it was a filter made by another ModelChoiceField).
So, having the next model as example:
class FilterModel(models.Model):
name = models.CharField()
class FooModel(models.Model):
filter_field = models.ForeignKey(FilterModel)
name = models.CharField()
class MyModel(models.Model):
foo_field = models.ForeignKey(FooModel)
As you can see, MyModel has a foreign key with FooModel, but not with FilterModel. So, in order to filter the FooModel options, I added a new ModelChoiceField on my form:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
# your code here
self.fields['my_filter_field'] = forms.ModelChoiceField(FilterModel, initial=my_filter_field_selected)
self.fields['my_filter_field'].queryset = FilterModel.objects.all()
Then, on your Front-End you can use Ajax to load the options of foo_field, based on the selected value of my_filter_field. At this point everyting should be working. But, when the form is loaded, it will bring all the posible options from FooModel. To avoid this, you need to dynamically change the queryset of foo_field.
On my form view, I passed a new argument to MyForm:
id_filter_field = request.POST.get('my_filter_field', None)
form = MyForm(data=request.POST, id_filter_field=id_filter_field)
Now, you can use that argument on MyForm to change the queryset:
class MyForm(forms.ModelForm):
# your code here
def __init__(self, *args, **kwargs):
self.id_filter_field = kwargs.pop('id_filter_field', None)
# your code here
if self.id_filter_field:
self.fields['foo_field'].queryset = FooModel.objects.filter(filter_field_id=self.id_filter_field)
else:
self.fields['foo_field'].queryset = FooModel.objects.none()

Django mutable POST doesn't appear in cleaned_data

I have been searching around for this for two days now, but I couldn't find any realiable solution.
form:
class SMSSettingsForm(forms.ModelForm):
smsQuota = forms.IntegerField(label=_("Account Quota"), max_value=432000, min_value=1, required=True, help_text=_('(mins)'), error_messages={'required': _('This field cannot be empty')})
smsTimeout = forms.IntegerField(label=_("Timeout"), max_value=9999999, min_value=1,required=False, help_text=_("(mins)"))
class Meta:
model = Settings
fields = ("smsQuota", "smsTimeout")
def __init__(self, *args, **kwargs):
super(SMSSettingsForm, self).__init__(*args, **kwargs)
def save(self):
settings = SettingsManager.get()
settings.smsQuota = self.cleaned_data['smsQuota']
settings.smsTimeout = self.cleaned_data['smsTimeout']
# Following lines are extra fields, rendered by JS in HTML
settings.ck = self.cleaned_data['ck']
settings.ck_per = self.cleand_data['ck_per']
settings.save()
view:
form_with_extra_elem = request.POST.copy()
form_with_extra_elem['ck'] = request.POST.get("ck")
form_with_extra_elem['ck_per'] = request.POST.get("ck_per")
# The two lines above didn't work, so I tried the following, but didn't work again
#form_with_extra_elem.update({'ck': request.POST.get("ck")})
#form_with_extra_elem.update({'ckper': request.POST.get("ck_per")})
form = SMSSettingsForm(form_with_extra_elem)
Do you have any idea how to solve this? What I think is the new element doesn't pass by the validation, so I cannot use them. But how to make them to do so? Actually, I don't need any validation - is there any other way than cleaned_data, to access form parameters?
I don't understand why you want to add extra fields via JS only. If you want them to appear in cleaned_data, they have to be part of the form. You can declare extra fields on a ModelForm simply by specifying them like you have with the other fields:
class SMSSettingsForm(forms.ModelForm):
smsQuota = forms.IntegerField(...)
smsTimeout = forms.IntegerField(...)
ck_per = forms.IntegerField()
ck = forms.IntegerField()

Validate a dynamic select field in Django

I'm using Django 1.4 with Python 2.7 on Ubuntu 12.10.
I have a form where I need to populate a few drop-downs dynamically (using jQuery) but need 2 of them to be required and the 3rd to be optional.
I'm using Tastypie to help with the API to get the options. Basically the first drop-down is populated with industry level codes for schools. Once a code is selected a category drop-down is populated for all categories for that code. Once the category is chosen a subcategory drop-down is populated for all subcategories for that combination of code and category.
I'm able to require the code drop-down (it's not dynamically populated). However, I'm having a tough time getting the category drop-down to be required. There are basically 2 routes I can take - front-end validation or back-end validation. I'm trying to go with back-end validation so I can easily create further validation if needed.
Here is the form:
class SchoolProductForm(forms.ModelForm):
cip_category = forms.ChoiceField(required=True,
choices=(('', '----------'),))
def __init__(self, *args, **kwargs):
super(SchoolProductForm, self).__init__(*args, **kwargs)
self.fields['short_description'].widget = TA_WIDGET
self.fields['salary_info'].widget = TA_WIDGET
self.fields['job_opportunities'].widget = TA_WIDGET
self.fields['related_careers'].widget = TA_WIDGET
self.fields['meta_keywords'].widget = TI_WIDGET
self.fields['meta_description'].widget = TI_WIDGET
self.fields['cip'].queryset = models.CIP.objects.filter(
parent_id__isnull=True)
class Meta:
model = models.SchoolProduct
exclude = ('campus',)
I've tried to override the clean method. I've tried to create a field specific clean method. Neither seem to work.
Variations of the following:
def clean(self):
super(SchoolProductForm, self).clean()
if cip_category in self._errors:
del self._errors['cip_category']
if self.cleaned_data['cip_category'] == '----------':
self._errors['cip_category'] = 'This field is required.'
return self.cleaned_data
This gives an error that there is no cip_category in cleaned_data, which makes sense because it didn't validate.
I've tried variations with the field specific clean:
def clean_cip_category(self):
data = self.cleaned_data['cip_category']
self.fields['cip_category'].choices = data
return data
But get a validation error on the page stating my choice is not one of the available choices.
I've tried to create a dynamic field type (several variations):
class DynamicChoiceField(forms.ChoiceField):
def valid_value(self, value):
return True
class SchoolProductForm(forms.ModelForm):
cip_category = DynamicChoiceField(required=True,
choices=(('', '----------'),))
But it accepts ---------- as a valid option (which I don't want) and causes an error since the ORM tries to match a value of ---------- in the database (which it won't find).
Any ideas?
I was able to solve this with a little overriding of a method in ChoiceField.
I added the field to the form and handled the pre-population with the self.initial:
class SchoolProductForm(forms.ModelForm):
cip_category = common_forms.DynamicChoiceField(
required=True, choices=(('', '----------'),))
def __init__(self, *args, **kwargs):
super(SchoolProductForm, self).__init__(*args, **kwargs)
self.fields['short_description'].widget = TA_WIDGET
self.fields['salary_info'].widget = TA_WIDGET
self.fields['job_opportunities'].widget = TA_WIDGET
self.fields['related_careers'].widget = TA_WIDGET
self.fields['meta_keywords'].widget = TI_WIDGET
self.fields['meta_description'].widget = TI_WIDGET
self.fields['cip'].queryset = models.CIP.objects.filter(
parent_id__isnull=True)
# Get the top parent and pre-populate
if 'cip' in self.initial:
self.initial['cip'] = models.CIP.objects.get(
pk=self.initial['cip']).top_parent()
class Meta:
model = models.SchoolProduct
exclude = ('campus',)
Where DynamicChoiceField is:
class DynamicChoiceField(forms.ChoiceField):
def valid_value(self, value):
return True
Then, in the view I added handling in the form_valid override:
def form_valid(self, form):
self.object = form.save(commit=False)
# Handle the CIP code
self.object.cip_id = self.request.POST.get('cip_subcategory')
if self.object.cip_id == '':
self.object.cip_id = self.request.POST.get('cip_category')
self.object.save()