I'm trying to understand the difference between Django's ModelForm save method and saving the Model instance directly.
Personally, I find saving directly more intuitive and more clearly shows when the data is saved. Plus, if I need to modify the instance before saving, then I have to use the Model save method as the Django documentation explains here.
So, once the form is validated, what is the difference? Would there be a difference if the form used multiple models or some other more complex use case?
I'm using Django version 1.4 if that matters. And below is some code showing how I tend to save validated form data.
Thanks in advance!
# models.py
class Project(models.Model):
project_name = models.CharField(unique=True, null=False, blank=False)
# views.py
def add_project(request):
if request.method == 'POST':
project = Project()
form = ProjectForm(request.POST, instance=project)
if form.is_valid():
project.save() ### <-- project.save() vs form.save() ###
return HttpResponseRedirect(reverse('view_project', args=(project.id,)))
else:
form = ProjectForm()
return render_to_response(
'add_project.html',
{
'form': form,
},
context_instance=RequestContext(request)
)
# forms.py
class ProjectForm(ModelForm):
class Meta:
model = Project
In the commented line you have, project.save() simply won't do anything. The instance has not been updated with the form data, it is simply the empty instance you created two lines before. The only way to update an existing instance is by saving its form.
ModelForm.save() returns an object saved from the data that was put into the form, Model.save() returns an object from the data that the object was initialized with or values that were set after it was created. So when it comes to getting the data from what the user inputted on the form to a persisted object, it makes more sense to call ModelForm.save() rather than going through the work of validating the data yourself, initializing the object and then saving it because all that work is handled by the ModelForm.
Related
I have two separate models:
1. A MyUser model which inherits from AbstractBaseUser, and has a field of profile_page = models.OneToOneField(Profile, null=True)
2. A Profile model with a user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True) relationship to the User model.
I am attempting to allow users of the site to edit their own Profile information by providing them a ProfileForm ModelForm.
In my user_profile/views.py I have this profile_edit FBV:
def profile_edit(request):
if request.method == 'POST':
form = ProfileForm(request.POST)
if form.is_valid():
form.instance.user = request.user
form.instance.save()
return redirect('/profile/edit')
else:
form = ProfileForm(instance=request.user)
print(request.user)
print('hello get req here')
context = {
'form': form,
}
return render(request, 'profile_edit.html', context)
When I attempt to update profile information in profile_edit.html, the POST data will go through the first time, but not get saved to the DB. On a second attempt, I receive a UNIQUE constraint failed: user_profile_profile.user_id error.
form.instance.save() is pointed to as the direct cause of the exception.
In my estimation the error has something to do with the fact that upon creation of a new user, an initial unique ID is created for the user. So when I try to save() the Profile object, I think it is attempting to save() a new User, thereby causing the Unique Constraint failure.
How can I configure the two models in such a way that upon creation of a new User, the User has the ability to update their own Profile information? What should change in my views.py?
You're doing two things wrong, both to do with the instance argument.
Firstly, in the GET block, you're passing request.user as the instance. But this is a Profile form; you need to pass the user profile, not the user itself:
form = ProfileForm(instance=request.user.profile)
Secondly, in the POST block, you're not passing an instance at all. This means that Django won't know to update an existing item, but will try and create a new one. Again you need to pass the profile:
form = ProfileForm(request.POST, instance=request.user.profile)
Note also though, you should consider whether you need a separate Profile model at all. Initially that was the recommended way to provide custom user information, but for several versions Django has given you a way to use a customised User model - which you are in fact doing. You probably want to put the profile data directly in MyUser, rather than in a separate model with a one-to-one relationship.
Edit after comment It sounds like you're not automatically creating a profile when you create the user. You could do something like this in the view:
def profile_edit(request):
try:
profile = request.user.profile
except Profile.DoesNotExist:
profile = Profile(user=request.user)
if request.method == 'POST':
form = ProfileForm(request.POST, instance=profile)
...
else:
form = ProfileForm(instance=profile)
I am learning Django so I don´t know about this.
What is happening is that I have two tables.
Table BlogPost : Save all post.
Table Categoria : Save the ID of the category of register post.
My model.py
class BlogPost(models.Model):
title=models.CharField(max_length=150)
author = models.ForeignKey(User)
categorias_post = models.ManyToManyField(Categoria)
body = RichTextField(('Content of post'))
creada_en = models.DateTimeField(auto_now_add=True)
actualizada_al = models.DateTimeField(auto_now=True)
My forms.py
class FormularioPost(forms.ModelForm):
class Meta:
model = BlogPost
fields = ('title', 'author', 'categorias_post', 'body')
My views.py
def postregistrado(request):
if request.method == "POST":
form = FormularioPost(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save
messages.success(request, 'Su post ha sido registrado con éxito.')
else:
form = FormularioPost()
return render_to_response(
"postRegistrado.html",
locals(),
context_instance=RequestContext(request),
)
I want to insert in two different tables from the same views.py. Can anyone help me with that?
When you are using commit=False, you have to explicitly call save_m2m() to save the many to many fields.
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save() #Note that it is a function call.
post.save_m2m()
You can read more on this in the documentation here
Another side effect of using commit=False is seen when your model has
a many-to-many relation with another model. If your model has a
many-to-many relation and you specify commit=False when you save a
form, Django cannot immediately save the form data for the
many-to-many relation. This is because it isn’t possible to save
many-to-many data for an instance until the instance exists in the
database.
To work around this problem, every time you save a form using
commit=False, Django adds a save_m2m() method to your ModelForm
subclass. After you’ve manually saved the instance produced by the
form, you can invoke save_m2m() to save the many-to-many form data.
Another thing is, make sure you add the login_required decorator on this view, so that you don't run into weird issues when post.author = request.user evaluates to anonymous users
I am building my first project in Django 1.8 with python 3.4. I have the following model called Lid in models.py:
class Lid(models.Model):
...
vereniging = models.ManyToManyField(Vereniging, blank=True)
I use the following ModelForm, forms.py
class LidForm(forms.ModelForm):
class Meta:
model = Lid
exclude = []
When I use this ModelForm to create a form to make a new object, a multiple select box appears and I am able to select multipe Vereniging objects. This is my view in views.py:
def add_lid(request):
if request.method == 'POST':
form = LidForm(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.success(request, 'Succes.')
return HttpResponseRedirect(reverse('leden:home'))
else:
form = LidForm()
return render(request, 'leden/lid/addlid.html', {'formset': form})
When I want to edit my objects however, I am not able to change the selected selected Vereniging objects.
def edit_lid(request, lid_id):
lid = get_object_or_404(Lid, pk=lid_id)
if request.method == 'POST':
form = LidForm(request.POST, request.FILES, instance=lid)
if form.is_valid():
nieuwlid = form.save(commit=False)
nieuwlid.inschrijving_oras = lid.inschrijving_oras
nieuwlid.save()
messages.success(request, 'Success.')
return HttpResponseRedirect(reverse('leden:lid', kwargs={'lid_id': lid_id}))
else:
form = LidForm(instance=lid)
return render(request, 'leden/lid/editlid.html', {'formset': form, 'lid': lid})
So this is basically my problem: when using a ModelForm, I am only able to set ManyToMany relationships when creating an object. I am not able to update these m2m relationships. Do you know what I am doing wrong?
Use save_m2m(). From the docs:
Another side effect of using commit=False is seen when your model has a many-to-many relation with another model. If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save many-to-many data for an instance until the instance exists in the database.
To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you’ve manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data.
I have a django model and a ModelForm. The model has about 10 fields, but I only want to show a few with the ModelForm for the user. So I have something like this:
class Create_EventForm(ModelForm):
class Meta:
model = Event
fields = ['event_name','event_datetime','event_venue','event_url','event_tags','event_zip','event_category','event_description']
However, I can't create an object based on this data, because there are still missing fields. When I receive the data via POST in my view I am doing one of these:
if request.user.is_authenticated:
if request.POST:
f = Create_EventForm(request.POST)
if f.is_valid():
ne = f.save()
What I want to do is right before the f.save() I want to do lookups based on existing data I have to pull in other data I need for the model and create the object. Any idea how this can be done?
Save the form with commit=False, edit the returned instance, then save it to the db.
if request.POST:
f = Create_EventForm(request.POST)
if f.is_valid():
event = f.save(commit=False)
event.other_value = lookup_value()
event.save()
See the doc's on ModelForm's save method for more info.
I'm struggling to get my head round django forms.. I've been reading various documentation but just can't quite grasp the concepts. I have got to grips with models, views and templates. What I am trying to do is to create a form with various fields composing of dropdown lists and checkboxes which are populated by values in a database.
I have a working app called vms. Using the models.py I have a built a simple schema that holds size and type. Size consists of 'small', 'medium' & 'large'. Type is 'windows' & 'linux'. Using the admin site, I can add an extra size, for example 'Extra Large'.
What I would like to do is create a form that has a drop down list of the vm sizes. If an extra size gets added via the admin site, I would like that size to appear in the drop down list.
I would submit my attempts at the code, but actually am struggling with the concepts. Can anyone help guide me in how to accomplish the above?
Thanks
Oli
Forms are just a tool to simplify and speed-up (the development of) the process of fetching POST data from the request. A manual way would be to do request.POST.get('somefield') for all the fields there are in some HTML form. But Django can do better than that...
In its essence, a Form class holds a number of Fields and performs these tasks:
display HTML inputs,
collect and validate data when user submits it,
if fields don't validate, return the values along with error messages to HTML,
if all fields validate, provide form.cleaned_data dictionary as a convenient way to access these values in view.
With these values, I could then manually create a new instance of a MyModel and save it. Of course, I would have to define a Field in the Form for every Field in MyModel model.
This means that, basically, I could do something like this:
(forgive me for not testing this code, so I can't vouch that it's 100% correct)
models.py:
class MyModel(models.Model):
field1 = models.CharField(max_length=40, blank=False, null=False)
field2 = models.CharField(max_length=60, blank=True, null=True)
forms.py:
class FormForMyModel(forms.Form):
form_field1 = forms.CharField(max_length=40, required=True)
form_field2 = forms.CharField(max_length=60, required=False)
views.py:
def create_a_my_model(request):
if request.method == 'POST':
form = FormForMyModel(request.POST)
if form.is_valid():
my_model = MyModel()
my_model.field1 = form.cleaned_data.get('form_field1', 'default1')
my_model.field2 = form.cleaned_data.get('form_field2', 'default2')
my_model.save()
else:
form = FormForMyModel()
context_data = {'form': form}
return HttpResponse('templtate.html', context_data)
(this could be written with a few lines of code less, but it's meant to be as clear as possible)
Notice there are no relation between model Fields and form Fields! We have to manually assign values to MyModel instance when creating it.
The above example outlines generic form workflow. It is often needed in complex situations, but not in such a simple one as is this example.
For this example (and a LOT of real-world examples), Django can do better than that...
You can notice two annoying issues in the above example:
I have to define Fields on MyModel and Fields on FormForMyModel separately. However, there is a lot of similarity between those two groups (types) of Fields, so that's kind of duplicate work. The similarity grows when adding labels, validators, etc.
creating of MyModel instance is a bit silly, having to assign all those values manually.
This is where a ModelForm comes in.
These act basically just like a regular form (actually, they are extended from regular forms), but they can save me some of the work (the two issues I just outlined, of course :) ).
So back to the two issues:
Instead of defining a form Field for each model Field, I simply define model = MyModel in the the Meta class. This instructs the Form to automatically generate form Fields from model Fields.
Model forms have save method available. This can be used to create instance of model in one line in the view, instead of manually assigning field-by-field.
So, lets make the example above with a ModelForm:
models.py:
class MyModel(models.Model):
field1 = models.CharField(max_length=40, blank=False, null=False)
field2 = models.CharField(max_length=60, blank=True, null=True)
forms.py:
class MyModelForm(forms.ModelForm): # extending ModelForm, not Form as before
class Meta:
model = MyModel
views.py:
def create_a_my_model(request):
if request.method == 'POST':
form = MyModelForm(request.POST)
if form.is_valid():
# save the model to database, directly from the form:
my_model = form.save() # reference to my_model is often not needed at all, a simple form.save() is ok
# alternatively:
# my_model = form.save(commit=False) # create model, but don't save to database
# my.model.something = whatever # if I need to do something before saving it
# my.model.save()
else:
form = MyModelForm()
context_data = {'form': form}
return HttpResponse('templtate.html', context_data)
Hope this clears up the usage of Django forms a bit.
Just one more note - it is perfectly ok to define form Fields on a ModelForm. These will not be used in form.save() but can still be access with form.cleaned_data just as in a regular Form.
Have you tried working with ModelForms before? As I understand, you're looking to create a form based on the model you created right?
Lets say your model is called Temp. You can create a form that correlates with this model (and your question) like this:
forms.py
from django.forms import ModelForm
class TempForm(ModelForm):
class Meta:
model = Temp
The ModelForm will automatically map the selections/choices from your model to a form version.
If you plan on using this in a template later, doing something like this will automatically create a drop-down menu with choices:
<form>
<label for="id_size">Size</label>
{{ form.size }}
</form>
Hope that answers your question!
Simply use CharField in your modelform as below:
SIZES_CHOICES = (
('size1', 'M'),
('size2', 'L'),
)
size = models.CharField(max_length=100, choices=SIZES_CHOICES, default=size1)
in the above code, size1 is the value which will be going to store in your database as name 'size1' and in the drop-down menu, there will be an option is 'M' of right side.you can mentioned any name to these options.