Django - Assign default value to field in ModelForm - python

In my application I have a CreateView that must initialize some fields of the model with a default value, different from the default defined inside the model.
I do not want the user to edit the value, thus I put the field in the exclude list
class AForm(ModelForm):
class Meta:
model = AModel
exclude = ['a_field']
class AView(CreateView):
form_class = AForm
The question is: where do I set the value of a_field?
I tried to define clean methods inside AForm, like thus
class AForm(ModelForm):
[...]
def clean(self):
d = super(AForm, self).clean()
d['a_field'] = 10
return d
however a_field is set to whatever its default value defined in the model is, rather than 10. I also tried defining clean_a_field, but that is simply not executed.
If I remove a_field from the exclude list, then the clean and clean_a_field will work, but the form won't validate unless I render some <input name="a_field"> inside the template, which is not optimal.

I managed to solve the issue in a way that makes me satisfied, although I'm still not 100% happy with the code.
a_field is a required (by the model) field, thus it is necessary to render an <input name="a_field"> inside the template. The trick was to make a_field non-required:
class AForm(ModelForm):
a_field = Field(required=False,
widget=forms.HiddenInput)
class Meta:
model = AModel
def clean_a_field(self):
return 10
This way I can avoid rendering the field in my template, and the form will still validate. And even if the form is rendered with {{ form.as_p }}, widget=forms.HiddenInput saves my day.

Exclude the field from the form, then in the view you can set the value before you save the form:
form = AForm(request.POST)
if form.is_valid():
new_record = form.save(commit=False)
new_record.a_field = 10
new_record.save()
You also might want to avoid the exclude list and specify which fields you'd like to include with the fields attr of the form definition. See the first paragraph here in the docs.

You set a default value in the model. From the official document,
a_field = models.CharField(max_length=7, default=''), for example

I have a way to Face this situation. Follow the following process:
Remove 'a_field' from the excludes in AForm.
Do not expose 'a_field' in HTML template. i.e. Don't give the user option to change the value via Form in Template. This would ensure that normal user's wont modify the value.
To prevent completely, over-ride get_form_kwargs in the View.
This would provide or over-ride your desired value to 'a_field' and save that
e.g.
class AView(CreateView):
form_class = AForm
def get_form_kwargs(self):
kwargs = super(AView, self).get_form_kwargs()
if self.request.method in {'POST', 'PUT'}:
# Change post data to over-ride or provide value of 'a_field'
data = self.request.POST.copy()
data.update({
'a_field': 'value'
})
kwargs['data'] = data
return kwargs

Related

How to instantiate a Django ModelForm with pre-filled required fields?

I have a subclass of ModelForm, FamilyDemographicsForm, for which two ChoiceFields are required: point_of_contact and birth_parent. For example, the following tests pass:
class FamilyDemographicsFormTest(TestCase):
def test_empty_form_is_not_valid(self):
'''The choice fields 'point_of_contact' and 'birth_parent' are
the only two required fields of the form'''
form = FamilyDemographicsForm(data={})
# The form is not valid because the required fields have not been provided
self.assertFalse(form.is_valid())
self.assertEqual(form.errors,
{'point_of_contact': ['This field is required.'],
'birth_parent': ['This field is required.']})
def test_form_with_required_fields_is_valid(self):
'''The form's save() method constructs the expected family'''
data = {'point_of_contact': Family.EMPLOYEE,
'birth_parent': Family.PARTNER}
form = FamilyDemographicsForm(data=data)
self.assertTrue(form.is_valid())
# The family returned by saving the form has the expected attributes
family = form.save()
self.assertEqual(family.point_of_contact, Family.EMPLOYEE)
self.assertEqual(family.birth_parent, Family.PARTNER)
# The family exists in the database
self.assertTrue(Family.objects.filter(id=family.id).exists())
In the second test case, a new instance of Family is created upon form.save(). I'd like to try instead to update an existing family. To get me started, I tried the following:
def test_update_existing_family(self):
initial = {'point_of_contact': Family.EMPLOYEE,
'birth_parent': Family.PARTNER}
data = {'employee_phone': '4151234567',
'employee_phone_type': Family.IPHONE,
'partner_phone': '4157654321',
'partner_phone_type': Family.ANDROID}
form = FamilyDemographicsForm(data=data, initial=initial)
import ipdb; ipdb.set_trace()
However, when I dropped into the debugger, I noticed that form.is_valid() is False and form.errors indicates that the required fields are not provided:
ipdb> form.errors
{'point_of_contact': ['This field is required.'], 'birth_parent': ['This field is required.']}
My question is: is there any way to instantiate a valid ModelForm with data that does not include the required fields? E.g. by providing an appropriate initial or instance argument? (This is not immediately clear to me from the source code for BaseModelForm on https://github.com/django/django/blob/master/django/forms/models.py).
You can modify the ModelForm and Form that Django provides to fit them your needs. You can override every method as you wish. The most base and essential prefill would be providing an initial dict with data {field_name: value, ...} that the form accepts without any modification.
So for example you have this
class Model1(models.Model):
name = models.CharField(max_length=100)
and this form
class Model1ModelForm(forms.ModelForm):
class Meta:
model = Model1
fields = ('name', )
and you can provide the initial data in the view as
initial = {'name': 'Initial name'}
form = Model1ModelForm(initial=initial)
so name in this form will be prefilled.
django docs: Providing initial values
stack overflow: Pass initial value to a modelform in django

Change form field value in django, after __init__ but before cleaning

I want to reset the value of some fields in my django form to None, inside the __init__ method of my form.
This is what I have so far:
class MyFormForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['field1', ...]
field1 = forms.IntegerField(max_value=100)
def __init__(self, *args, **kwargs):
values_changed = kwargs.pop('values_changed', False)
super(MyFormForm, self).__init__(*args, **kwargs)
self.data = self.data.copy()
if not values_changed:
for field in self.fields:
self.data[field] = None
Unfortunately, when the form is displayed in my template, the value that has been POSTed is still in it. How do I get rid of the value, so that the change takes effect in the template, which renders the form?
Things that don't work:
Setting the initial parameter. Since there is a value present, it will be ignored
Using cleaned values. The form is not cleaned at this stage, since it is not valid. Because of this cleaned_data does not exist
I'm accessing the values like this:
{{ form.field1.value|default_if_none:"Please enter." }}
edit: I just tried
{{ form.data.field1|default_if_none:"Please enter." }}
no change.
I'm a moron. There were two forms, form and form_2.
I used the wrong variable name. My bad. It works like it should with the code above.

django admin - populate field with callable

I can't find a single example of anyone doing this apart from this example, which doesn't help me other than to know where the code needs to sit.
How to prepopulate UserProfile fields in the Django admin?
so this is my code
class QuoteMaterial(models.Model):
name = models.CharField(_('name'), max_length=255)
content = models.TextField(_('content'),
help_text=_('A static priced item used when doing a job. Selectable when creating a quote. '))
price = models.DecimalField(_('price'), max_digits=6, help_text="not sure if this is before or after VAT yet", decimal_places=2, default="0.00")
def get_companies():
return CompanyProfile.objects.filter(user=request.user)
company = models.ForeignKey(CompanyProfile, default=get_companies)
If its not obvious, im trying in the admin section to populate a dropdown with the available companies that belong to the user that is logged in.
my problem is that i dont know how to pass the request object to "get_companies". anyone know of any examples.
You will have to do this overriding in your admin class that extends the ModelAdmin, not in your class that extends models.Model. Specifically, you need to override formfield_for_foreignkey.
From the docs:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
For your case, it would seem like:
if db_field.name == "company":
kwargs['queryset'] = request.user.company_set.all()
You're mixing up terms.
"Prepopulating" means to fill in a field from another field. It's not how you filter things for the admin popups, since you aren't actually setting the field, but simply limiting choices and letting the user set the field from those.
Aditionally, the default value for a field needs to be a constant, since this is passed down to the database, which can't use a query to set a default.
What you really want is something like the limit_choices_to (docs) parameter for your ForeignKey, but even then, you can't use request for this; it has to work using fields in the model. The reason for this is that, if you based it on the user, then some users would be unable to select the current value set by another user. You don't want company changing itself when the user just wants to change content, for example, just because user doesn't yield the current company in the filter.

Django file upload: filename not sticking

I'm uploading files and storing metadata in a db. Part of the metadata is the file name itself. However, somewhere down the line, the filename seems to not be getting saved! I will paste only what I think are relevant parts of the code to keep this short.
class UploadFile(models.Model):
...
theFile = models.FileField(upload_to = "Genius/Uploads/", null = True)
filename = models.CharField(max_length = 50, blank = True, null = False)
class UploadFileForm(ModelForm):
class Meta:
model = UploadFile
fields = ('title', 'theFile', 'date_uploaded',) # Don't prompt for filename
def files_upload(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
form.filename = request.FILES['theFile'].name # TODO: sanitize!
# form.filename = 'foo'
form.save()
return HttpResponseRedirect('/files/upload/successful/')
else:
form = UploadFileForm()
return render_to_response('files/upload_file.html', { 'form': form })
I have checked the value of request.FILES['theFile'].name before & after saving the form. For whatever reason it is intact but never seems to make it into the DB.
That's because form.filename is the form field, not the value it will be saving.
You are looking for something like this:
class UploadFileForm(ModelForm):
def save(self, commit=True):
instance = ModelForm.save(self, commit=False)
instance.filename = self.files['theFile'].name
if commit:
instance.save()
return instance
class Meta:
model = UploadFile
fields = ('title', 'theFile', 'date_uploaded',) # Don't prompt for filename
Alternative solution:
upload_file = form.save(commit=False)
upload_file.filename = request.FILES['theFile'].name
upload_file.save()
Form field values aren't accessed via attributes on the form. So setting 'form.filename' doesn't set the value to be saved in the filename field. Instead, set the value on the instance returned by form.save().
upload_file = form.save(commit=False)
upload_file.filename = filename
upload_file.save()
I just wanted to add that, in the future, you might try to avoid putting such business logic on the model form. While WoLpH's answer is correct and a great example of how to handle additional model instance processing through ModelForm, particular cases of having fields dependent on other fields data is handled in the Model, Form and ModelForm API through their respected clean() methods and is mentioned in several places in the official reference docs (here's one on forms, though the same holds true for the Model and ModelForm APIs).
In your case this would mean:
import os
class UploadFile(models.Model):
# ...
def clean(self):
# Field data has already been populated by this point.
# Note that `FieldFile` inherits from `File` and that
# `File.name` is actually the full path to the file
# so we need to get the base path component sans the extension
path, extension = os.path.splitext(self.thefile.file.name)
self.filename = os.path.basename(path)
And that's about it! If you properly set the editable attribute on your model fields, you'll find that you can rely on Django to automatically generate the ModelForm for the UploadFile model. That means you don't have to define a ModelForm for the generic create, update views or your model's ModelAdmin and that's less lines of code to manage!
The general rule of thumb is that you think twice about whether overriding default behavior is ever justified or self-contained, especially when your working at the far end of the business logic chain, otherwise you may feel the wrath of unexpected flops.

How do I associate input to a Form with a Model in Django?

In Django, how do I associate a Form with a Model so that data entered into the form are inserted into the database table associated with the Model? How do I save that user input to that database table?
For example:
class PhoneNumber(models.Model):
FirstName = models.CharField(max_length=30)
LastName = models.CharField(max_length=30)
PhoneNumber = models.CharField(max_length=20)
class PhoneNumber(forms.Form):
FirstName = forms.CharField(max_length=30)
LastName = forms.CharField(max_length=30)
PhoneNumber = forms.CharField(max_length=20)
I know there is a class for creating a form from the the model, but even there I'm unclear on how the data actually gets to the database. And I'd like to understand the inner workings before I move on to the time-savers. If there is a simple example of how this works in the docs, I've missed it.
Thanks.
UPDATED:
To be clear -- I do know about the ModelForm tool, I'm trying to figure out how to do this without that -- in part so I can better understand what it's doing in the first place.
ANSWERED:
With the help of the anwers, I arrived at this solution:
Form definition:
class ThisForm(forms.Form)
[various Field assignments]
model = ThisModel()
Code in views to save entered data to database:
if request_method == 'POST':
form = ThisForm(request.POST)
if form.is_valid():
for key, value in form.cleaned_data.items():
setattr(form.model, key, value)
form.model.save(form.model)
After this the data entered in the browser form was in the database table.
Note that the call of the model's save() method required passage of the model itself as an argument. I have no idea why.
CAVEAT: I'm a newbie. This succeeded in getting data from a browser to a database table, but God only knows what I've neglected or missed or outright broken along the way. ModelForm definitely seems like a much cleaner solution.
Back when I first used Forms and Models (without using ModelForm), what I remember doing was checking if the form was valid, which would set your cleaned data, manually moving the data from the form to the model (or whatever other processing you want to do), and then saving the model. As you can tell, this was extremely tedious when your form exactly (or even closely) matches your model. By using the ModelForm (since you said you weren't quite sure how it worked), when you save the ModelForm, it instantiates an object with the form data according to the model spec and then saves that model for you. So all-in-all, the flow of data goes from the HTML form, to the Django Form, to the Django Model, to the DB.
Some actual code for your questions:
To get the browser form data into the form object:
if request.method == 'POST':
form = SomeForm(request.POST)
if form.is_valid():
model.attr = form.cleaned_data['attr']
model.attr2 = form.cleaned_data['attr2']
model.save()
else:
form = SomeForm()
return render_to_response('page.html', {'form': form, })
In the template page you can do things like this with the form:
<form method="POST">
{{ form.as_p }}
<input type="submit"/>
</form>
That's just one example that I pulled from here.
I'm not sure which class do you mean. I know that there were a helper, something like form_for_model (don't really remember the exact name; that was way before 1.0 version was released). Right now I'd it that way:
import myproject.myapp.models as models
class PhoneNumberForm(forms.ModelForm):
class Meta:
model = models.PhoneNumber
To see the metaclass magic behind, you'd have to look into the code as there is a lot to explain :]. The constructor of the form can take instance argument. Passing it will make the form operate on an existing record rather than creating a new one. More info here.
I think ModelForm.save documentation should explain it. With its base class (Form) you would need to use the Form.cleaned_data() to get the field values and set them to appropriate Model fields "by hand". ModelForm does all that for you.
The Django documentation is pretty clear on this subject. However, here is a rough guide for you to get started: You can either override the form's save method or implement that functionality in the view.
if form.is_valid() # validation - first the fields, then the form itself is validated
form.save()
inside the form:
def save(self, *args, **kwargs):
foo = Foo()
foo.somefield = self.cleaned_data['somefield']
foo.otherfield = self.cleaned_data['otherfield']
...
return foo.save()

Categories

Resources