I have standard Django models with ForeignKey.
Django docs:
"ForeignKey is represented by django.forms.ModelChoiceField, which is a ChoiceField whose choices are a model QuerySet."
and
"If the model field has choices set, then the form field’s widget will be set to Select, with choices coming from the model field’s choices."
Now I have dropdown menu with choices.
I don't want dropdown menu where user can see options. I want CharField(textfield or similar) where user type, but still
that must be one of the options from the database for that field. He must type a valid entry.
I tried:
class TransakcijeForm(forms.ModelForm):
model = models.Transakcije
fields = .....
labels = .....
widgets ={'subscriber':forms.TextInput()}
but I receive the message:
"Select a valid choice. That choice is not one of the available choices."
(entry is correct and it works with dropdown menu)
This is my first question here and I'm sorry if I miss the form.
The reason you are getting that error is because your form is still treating the subscriber field as a ModelChoiceField because you are only overriding what widget is rendered to html. You need to change the actual field type of your field. You can define your form like this:
from django.core.exceptions import ValidationError
class TransakcijeForm(forms.ModelForm):
subscriber = forms.CharField()
class Meta:
model = models.Transakcije
fields = ....
labels = ....
def clean_subscriber(self):
subscriber_id = self.cleaned_data['subscriber']
try:
# adjust this line to appropriately get the model object that you need
subscriber = SubscriberModel.objects.get(id=subscriber_id)
return subscriber
except:
raise ValidationError('Subscriber does not exist')
The line subscriber = forms.CharField() will change the form to treat the field as a CharField rather than a ModelChoiceField. Doing this will cause the form to return the subscriber field value as a string, so you will need to get the appropriate model object based on the value of the field. That is what the clean_subscriber(self) function is for. It needs to be named like clean_<field name>(). That function will take the string that is returned by the form, try and find the correct model object and return it if an object is found. If it finds no matching objects it will raise a ValidationError so the form doesn't submit with a bad value.
Related
I have a form (ModelForm) in Django, where I am adding a field for users in the init method as so:
self.fields["users"] = forms.ModelMultipleChoiceField(
queryset=users, widget=forms.CheckboxSelectMultiple, required=False,label="Add Designer(s)"
)
In the save method how I can iterate over the queryset for this field, however, I do not know how I can test if the particular model has been selected/checked. Help, please.
EDIT:
Let's say that you have a form where you want to be able to add users to a certain project, I set the users field as above (also usedMultipleChoiceField) but my real question is how do you determine the state of those checkboxes (which users should be added)?
Managed to fix it using MultipleChoiceField instead of ModelMultipleChoiceField. Then populated the choices with existing event IDs and passed it to the template.
In forms:
choices = forms.MultipleChoiceField(widget = forms.CheckboxSelectMultiple())
In views:
form.fields['choices'].choices = [(x.eventID, "Event ID: " + x.eventID) for x in unapproved]
Had to change some of the logic for finding and editing Event objects too.
The Django documentation states that a ModelMultipleChoiceField normalizes to a QuerySet of model instances. That means in your example, it will only return the users that have been checked. If none have been checked, it will return an empty QuerySet.
If you are overriding your ModelForm save method, you could include something like this:
selected_users = self.cleaned_data.get('users')
for user in selected_users:
project_users.add(user)
Currently, if a field is required, this can be enforced via the blank = False argument, such as:
models.py
address1 = models.CharField(max_length=255,null=False,blank=False)
However, the validation is performed prior to the POST action, yielding something like this when trying to submit the form containing an empty field:
I would prefer the validation to be done during the post step, like this:
models.py
address1 = models.CharField(max_length=255,null=False,blank=true)
forms.py
class AddressForm(forms.ModelForm):
def __init__(self,*args,**kwargs):
super(AddressForm,self).__init__(*args,**kwargs)
self.fields['address1'].required = True
And this yields the following result when trying to submit the form containing an empty field:
But the problem with this, (as far as I can tell) is that I need to explicitly state the required attribute for each field on a case-by-case basis.
Is there any way that I can associate blank=False as being representative of the required=True attribute, suppressing the first form validation (above), in favour of the second?
ModelForm runs form validation, then model validation:
There are two main steps involved in validating a ModelForm:
Validating the form
Validating the model instance
So you have to manually add the extra form validation that you want before the inherited model validations.
However, default ModelForm field for blank field is already required:
If the model field has blank=True, then required is set to False on
the form field. Otherwise, required=True
You can change the error message. If you use this additional validations a lot, you can use a Mixin:
class BlankToRequiredMixin(object):
def set_required(self):
model = self._meta.model
for field_name,form_field in self.fields.iteritems():
if not model._meta.get_field(field_name).blank:
form_field.error_messages={'required': 'This field is required'} # to make it required in addtion to non-blank set .required=True
Then, to set required=True for all fields that are non-blank in the model:
class AddressForm(forms.ModelForm,BlankToRequiredMixin):
def __init__(self,*args,**kwargs):
super(AddressForm,self).__init__(*args,**kwargs)
self.set_required()
In a similar way you can add other validations to the form fields, based on the model validation attributes. For the appearance, change the widget and set the field widget in the mixin.
I am trying to create a form with ModelChoice field. The field is filtered by ajax request.
self.fields['center'].queryset = TrainingCenter.objects.all()
this is works fine. but I am loading the values using ajax based on another field.
if I use a empty queryset to load the form without choices, I am getting "Invalid Choice" error on submit.
how to avoid loading all the choices without making error on submission
You can write an EmptySelect widget class that ignores the choices that are passed to it, like this:
from django import forms
class EmptySelect(forms.Select):
def _get_choices(self):
return ()
def _set_choices(self, value):
pass
choices = property(_get_choices, _set_choices)
Then, define your field with the queryset that you want to validate against and configure EmptySelect as your widget.
center = forms.ModelChoiceField(
queryset=TrainingCenter.objects.all(),
widget=EmptySelect
)
You can inherit from other choice widgets too, or even turn EmptySelect into a mixin that you can use with any choice widget.
I have a model and a form like this:
class MyModel(models.Model):
param = models.CharField()
param1 = models.CharField()
param2 = models.CharField()
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('param', 'param1', 'param2')
Then I have one drop down menu with different values and based on what value is selected I'm hiding and showing fields of MyForm. Now I have to take one step further and render param2 as a CheckboxInput widget if user selects a certain value from a drop down but in other cases it should be standard text field. So how would I do that?
I know this post is almost a year old, but it took me multiple hours to even find a post related to this topic (this is the only one I found, which came up as related when submitting my own question), so I felt the need to share my solution.
I wanted to have a form that would show and require a text field if an option from a dropdown menu matched a value stored in another model. I had a foreignKey relation between two models and I passed an instance of Model1 into the ModelForm for Model2. If a value chosen for a variable in Model2 matched a variable already set in Model1, I wanted to show and require a textfield. It was basically a "choose Other and then enter your own description" scenario.
I did not want the page to reload (I was trying to have this work in both mobile and desktop browsers with the least delay/reloads and using the same code for both), so I could not use the mentioned multiple forms loading in a view option. I started trying to do it with AJAX as suggested above when I realized I was over thinking the problem.
The answer was using JS and clean methods in the form. I added a non-required field (field1) that was not in Model2 to my Model2Form. I then hid this using jQuery and only displayed it (using jQuery) if the value of another field (field2) matched the value of the variable from Model1. To make that work, I did decide to have a hidden < span > in my template with the pk of the variable so I could easily grab it with jQuery. This jQuery worked perfectly for hiding and showing the field correctly so the user could choose the "other" value and then decided to choose a different one instead (and go back and forth endlessly).
I then used a clean method in my Model2Form for field1 that raised a ValidationError if no value was entered when the value in field2 matched my Model1 variable. I accessed that variable by using "self.other = Model1.variable" in my __ init __ method and then referencing that in the clean_field1 method.
I would have liked to have been able to accomplish this without having to hide and show a field with JS, but I think the only solutions for doing so with views or ajax caused delays/reloads that I did not want. Also, I liked the general simplicity of the method I used, rather than having to figure out how to pass partial forms back and forth through the HTTPRequest.
Update:
In my situation, I was creating entries for lost and found items and if the location where the item was found was not a provided option, then I wanted to show a textbox for the user to enter the location. I created a location object that was set as the "other" location and then displayed the textbox when that object was selected as the "found" location.
In forms.py, I added an extra CharField and use a clean method to check if the field is required and then throw a ValidationError if it wasn't filled in:
class Model2Form(forms.ModelForm):
def __init__(self, Model1, *args, **kwargs):
self.other = Model1.otherLocation
super(Model2Form, self).__init__(*args, **kwargs)
...
otherLocation = forms.CharField(
label="Location Description",
max_length=255,
required=False
)
def clean_otherLocation(self):
if self.cleaned_data['locationFound'] == self.other and not self.cleaned_data['otherLocation']:
raise ValidationError("Must describe the location.")
return self.cleaned_data['otherLocation']
Then in my JavaScript, I checked if the value of the "found" location was the "other" location (the value of which I had in a hidden span on my html page). I then used .show() and .hide() on the textbox's parent element as necessary:
$("#id_locationFound").change( function(){
if ($("#id_locationFound").val() == $("#otherLocation").attr("value")){ //if matches "other" location, display textbox; otherwise, hide textbox
$("#id_otherLocation").parent().show();
}else
$("#id_otherLocation").parent().hide();
});
Your best guess would be to trigger a "POST" request when you select something from your drop down menu.
The Value of that "POST" has to correspond your values you use to determine which field you would like to output.
Now you will actually need two forms:
class MyBaseForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('param', 'param1', 'param2')
class MyDropDownForm(MyBaseForm):
class Meta:
widgets = {
'param2': Select(attrs={...}),
}
So as you can see the DropDownForm has been derived from MyBaseForm to make sure it will have all the same properties. But we have modified the widget of one of the fields.
Now you can update your view. Please note, this is untested Python + Pseudocode
views.py
def myFormView(request):
if request.method == 'POST': # If the form has been submitted...
form = MyBaseForm(request.POST)
#submit button has not been pressed, so the dropdown has triggered the submission.
#Hence we won't safe the form, but reload it
if 'my_real_submitbotton' not in form.data:
if 'param1' == "Dropdown":
form = MyDropDownForm(request.POST)
else:
#do your normal form saving procedure
else:
form = ContactForm() # An unbound form
return render(request, 'yourTemplate.html', {
'form': form,
})
This mechanism does the following:
When the form is submitted it checks if you have pressed the "submit" button or have used a dropdown onChange to trigger a submission. My solution doesn't contain the javascript code you need to trigger the submission with an onChange. I just like to provide a way to solve it.
To use the 'my_real_submitbutton' in form.data construct you will be required to name your submit button:
<input type="submit" name="my_real_submitbutton" value="Submit" />
Of course you can choose any string as Name. :-)
In case of a submit by your dropdown field you must check which value has been selected in this drop down menu. If this value satisfies the condition you want to return a Dropdown Menu you create an instance of DropDownForm(request.POST) otherwise you can leave everything as it is and rerender your template.
On the downside this will refresh your page.
On the upside it will keep all the already entered field values. So no harm done here.
If you would like to avoid the page refresh you can keep my proposed idea but you need to render the new form via AJAX.
I have a model which represents a work order. One of the fields is a DateField and represents the date the work order was created (aptly named: dateWOCreated). When the user creates a new work order, I want this dateWOCreated field to be automatically populated with todays date and then displayed in the template. I have a few other fields that I want to set without user's intervention but should show these values to the user in the template.
I don't want to simply exclude these fields from the modelForm class because there may be a need for the user to be able to edit these values down the road.
Any help?
Thanks
When you define your model, you can set a default for each field. The default object can be a callable. For your dateWOCreated field we can use the callable date.today.
# models.py
from datetime import date
class WorkOrder(models.Model):
...
dateWOCreated = models.DateField(default=date.today)
To display dates in the format MM/DD/YYYY, you need to override the widget in your model form.
from django import forms
class WorkOrderModelForm(forms.ModelForm):
class Meta:
model = WorkOrder
widgets = {
'dateWOCreated': forms.DateInput(format="%m/%d/%Y")),
}
In forms and model forms, the analog for the default argument is initial. For other fields, you may need to dynamically calculate the initial field value in the view. I've put an example below. See the Django Docs for Dynamic Initial Values for more info.
# views.py
class WorkOrderModelForm(forms.ModelForm):
class Meta:
model = WorkOrder
def my_view(request, *args, **kwargs):
other_field_inital = 'foo' # FIXME calculate the initial value here
form = MyModelForm(initial={'other_field': 'other_field_initial'})
...