Form validation syntax - calling form.save() versus foo.save() - django

I am processing a ModelForm, and both of these seem to return identical results:
if request.method == 'POST':
form = FooForm(request.POST)
if form.is_valid():
bar = form.save(commit = False)
bar.user = request.user
form.save()
if request.method == 'POST':
form = FooForm(request.POST)
if form.is_valid():
bar = form.save(commit = False)
bar.user = request.user
bar.save() # calling save on bar instead of on the form
In terms of how these save data and relations between data, is there any difference in these two? The second approach doesn't seem to work when I need to use bar.save_m2m(), which makes me think that the first approach is the right way to go. However, the logic of the second approach makes more sense to me. Can anyone clarify for me which one of these is more correct and why? I am worried that I am unintentionally processing data incorrectly and setting my app up for problems down the line.

From Django's website, probably this will clear up what you should do:
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)
# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)
# Modify the author in some way.
>>> new_author.some_field = 'some_value'
# Save the new instance.
>>> new_author.save()
# Now, save the many-to-many data for the form.
>>> f.save_m2m()
From https://docs.djangoproject.com/en/1.6/topics/forms/modelforms/
This is extracted from the source code of Django 1.6.2:
if commit:
# If we are committing, save the instance and the m2m data immediately.
instance.save()
save_m2m()
else:
# We're not committing. Add a method to the form to allow deferred
# saving of m2m data.
form.save_m2m = save_m2m
return instance
This is the reason why if after Ex1:form.save() you call bar.save_m2m() everything will be fine, you will be executing a normal save() step by step. The Base class is returning its instance each time so if you call save() it will save the same instance you are pointing to from outside and that you just modified (Warning: are you using it as a global variable?)...and anyway you can not be sure for how long this method will work as you expect since nowhere in the documentation says the instance returned when calling form.save(commit=False) is the same instance used when you call form.save() few lines below.
As far as I know you wanted to know which way is better to avoid future problems...my recommendation is: go with the second one!.

Related

django update model while saving another form

I am saving a from and updating another model in the form, but the update is not saved to database.
if request.method == 'POST':
form = InventoryTransactionForm(request.POST, instance=InventoryTransaction())
if form.is_valid():
quantity = request.POST['quantity']
part_id = request.POST['part_id']
item_template_id = request.POST['supply']
try:
item_object = Item.objects.get(pk=part_id)
masterQty = item_object.masterQty - int(quantity)
item_object.save(force_update=True)
except Exception, e:
messages.error(request, e.message)
can anybody please help what is wrong in above code, thanks.
EDIT: updated code as below
#transaction.commit_manually
def post(self, request, *args, **kwargs):
if request.method == 'POST':
form = InventoryTransactionForm(request.POST, instance=InventoryTransaction())
if form.is_valid():
quantity = request.POST['quantity']
part_id = request.POST['part_id']
item_template_id = request.POST['supply']
try:
item_object = Item.objects.get(pk=part_id)
masterQty = item_object.masterQty - int(quantity)
item_object.save(force_update=True)
transaction.commit()
except Exception, e:
transaction.rollback()
messages.error(request, e.message)
form.save(True)
You are using force_update=True in the save method. From the docs:
In some rare circumstances, it’s necessary to be able to force the save() method to perform an SQL INSERT and not fall back to doing an UPDATE. Or vice-versa: update, if possible, but not insert a new row. In these cases you can pass the force_insert=True or force_update=True parameters to the save() method. Obviously, passing both parameters is an error: you cannot both insert and update at the same time!
You are saving the object to the database for the first time so it doesn't make sense to call force_update=True as it will prevent the object from being created in the first place. So change
item_object.save(force_update=True)
to
item_object.save()
I think your problem is about working with django ORM here.
You get your object from the database here : item_object = Item.objects.get(pk=part_id) but you lose the instance of this object when doing item_object = Item(masterQty=masterQty, item_template_id=item_template_id, id=part_id).
That's why you think you need a force update but you actually just need to keep the right instance of your object.
Why don't you just update the fields on the object you got from the db and then save it ?
Like so :
item_object = Item.objects.get(pk=part_id)
item_object.masterQty -= int(quantity)
item_object.item_template_id = item_template_id
item_object.save()
You don't even need a force update as you'll be working on an instance you got from the db.

Django - How to retrieve value of form fields before save?

What is the correct way to retrieve the value of a field before it is saved when the model form is submitted?
For instance, I am trying to get the field 'name' as it was before changed in the form. I was doing like below, and it works, but I am not sure that's the correct way to do it.
views.py:
if formset.is_valid():
for form in formset:
if form.has_changed and not form.empty_permitted:
cd = form.cleaned_data
new_fieldName = cd.get('name')
old_fieldName = str(cd.get('id'))
form.save()
Any suggestion?
formset.is_valid will call each form's is_valid method, which in turn will call full_clean, which calls _post_update, which updates the form's instance with the values submitted with the form. It would be too late to find references to the old values after you call formset.is_valid; you have two options:
Store copies of the instance fields before you call formset.is_valid:
saved = {}
for form in formset.forms:
saved[form.instance.pk] = form.instance.__dict__.copy()
# or keep only specific fields
Retrieve a fresh instance of the record before you call its save:
original = form._meta.model.objects.get(pk=form.instance.pk)
# compare original against form.instance and observe differences
# save your form when you're ready:
form.save()
You have also pre_save(). I use is_valid to validate any errors or restrictions to the fields and pre_save to automate processes.
Hope it helps.

Getting 'builtin_function_or_method object is not iterable' on file upload

I am unable to upload the file. I am getting
Type error builtin_function_or_method' object is not iterable
models.py
class seeker(models.Model):
user = models.OneToOneField(User)
birthday = models.DateField()
class Upload(models.Model):
user = models.ForeignKey(Seekers)
resume = models.FileField(upload_to ='resume', blank = True, null = True)
forms.py
class SeekersForm(forms.Form):
resume = forms.FileField(label = 'Select a file',help_text = 'max.3 MB')
views.py
def List(request):
# Handle file upload
if request.method == 'POST':
form = SeekersForm(request.POST, request.FILES)
if form.is_valid():
#id = User.object.get(id)
newdoc = Seekers.objects.get(user_id)
newdoc.resume =Upload(resume = request.FILES['resume'])
newdoc.save()
#seekers_edit = Seekers.objects.get(id)
#seekers_edit.resume = Seekers(resume = request.FILES['resume'])
#seekers_edit.save()
#Redirect to the document list after POST
return HttpResponseRedirect('/profile/')
else:
form = SeekersForm() # A empty, unbound form
#Load documents for the list page
seekers = Seekers.objects.all()
#Render list page with the documents and the form
return render_to_response('list.html',{'seekers':seekers,'form':form},context_instance=RequestContext(request))
It's hard to say where your problem is, but I think the following line of code is the main problem:
newdoc.resume =Upload(resume = request.FILES['resume'])
You have to save a file in a FileField explicitly before you save the entire model instance. Also, if you have a ForeignKey field in one of your models and you want to assign it an instance of another model, please save that instance first before you do the assignment. Without knowing your Seekers model, all I can do is guessing what might help you. Something like the following might get you started:
your_file = request.FILES['resume']
upload_instance = Upload()
upload_instance.resume.save(name=your_file.name, content=your_file, save=False)
upload_instance.user = ... # Here goes an instance of your Seekers model
upload_instance.save() # Here you save the whole instance of your Upload model
Also, please note the following:
Your model Seekers should rather be named Seeker using the singular, not the plural. This should generally be like that with all your models.
Python functions should always start with a lowercase letter, i.e. list instead of List. However, this name is a bad choice here anyway, because a function called list is already present in Python's standard library.
Please take a closer look at Django's documentation. It's all in there what you need to know. I recommend you to read especially these sections:
https://docs.djangoproject.com/en/1.4/ref/models/fields/#filefield
https://docs.djangoproject.com/en/1.4/ref/files/file/
Problems in your code:
Your form definition duplicates information from your model — just use forms.ModelForm (with exclude so as not to display the user field)
As currently pasted, newdoc = Seekers.objects.get(user_id) will raise a TypeError ('foo' object is not iterable); .get() accepts keyword parameter filters, not anything else.
Accessing request.FILES['resume'] manually isn't necessary or recommended
So, in short, you're almost there; just let Django forms do more of the work for you:
# forms.py
class SeekerForm(forms.ModelForm)
class Meta:
model = Seeker
# views.py
def seeker_list(request):
# Opinions are divided as to whether it's ever appropriate to
# modify the database like this on a GET request, but it seems
# to make sense here
seeker = Seekers.objects.get_or_create(user=request.user)
if request.method == 'POST':
form = SeekerForm(request.POST, request.FILES, instance=seeker)
if form.is_valid():
form.save()
return HttpResponseRedirect('/profile/')
else:
form = SeekerForm(instance=seeker)
seekers = Seekers.objects.all()
#Render list page with the documents and the form
return render_to_response('list.html', {
'seekers':seekers,
'form':form
}, context_instance=RequestContext(request))
It's not clear what the significance (if any) of the commented-out sections of your code is — I've assumed you always want to modify the current user's Seeker, but if not then adapt as appropriate.

Django ModelForms - 'instance' not working as expected

I have a modelform that will either create a new model or edit an existing one - this is simple and should work, but for some reason I'm getting a new instance every time.
The scenario is this is the first step in an ecommerce order. The user must fill out some info describing the order (which is stored in the model). I create the model, save it, then redirect to the next view for the user to enter their cc info. I stick the model in the session so I don't have to do a DB lookup in the next view. There is a link in the template for the second (cc info) view that lets the user go back to the first view to edit their order.
# forms.py
class MyForm(forms.ModelForm):
class Meta:
fields = ('field1', 'field2')
model = MyModel
# views.py
def create_or_update(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
m = form.save(commit=False)
# update some other fields that aren't in the form
m.field3 = 'blah'
m.field4 = 'blah'
m.save()
request.session['m'] = m
return HttpResponseRedirect(reverse('enter_cc_info'))
# invalid form, render template
...
else:
# check to see if we're coming back to edit an existing model
# this part works, I get an instance as expected
m = request.session.get('m', None)
if m:
instance = get_object_or_None(MyModel, id=m.id)
if instance:
form = MyForm(instance=instance)
else:
# can't find it in the DB, but it's in the session
form = MyForm({'field1': m.field1, 'field2': m.field2})
else:
form = MyForm()
# render the form
...
If I step through in the debugger when I go back to the view to edit an order that the form is created with the instance set to the previously created model, as expected. However, when the form is processed in the subsequent POST, it creates a new instance of the model when form.save() is called.
I believe this is because I've restricted the fields in the form, so there is nowhere in the rendered HTML to store the id (or other reference) to the existing model. However, I tried adding both a 'pk' and an 'id' field (not at the same time), but then my form doesn't render at all.
I suspect I'm making this more complicated than it needs to be, but I'm stuck at the moment and could use some feedback. Thanks in advance.
This is interesting. Here is my stab at it. Consider this line:
form = MyForm(request.POST)
Can you inspect the contents of request.POST? Specifically, check if there is any information regarding which instance of the model is being edited. You'll find that there is none. In other words, each time you save the form on POST a new instance will be created.
Why does this happen? When you create a form passing the instance=instance keyword argument you are telling the Form class to return an instance for an instance of the model. However when you render the form to the template, this information is used only to fill in the fields. That is, the information about the specific instance is lost. Naturally when you post pack there is way to connect to the old instance.
How can you prevent this? A common idiom is to use the primary key as part of the URL and look up an instance on POST. Then create the form. In your case this would mean:
def create_or_update(request, instance_id):
# ^^^^^
# URL param
if request.method == 'POST':
instance = get_object_or_None(Model, pk = instance_id)
# ^^^^^
# Look up the instance
form = MyForm(request.POST, instance = instance)
# ^^^^^^^
# pass the instance now.
if form.is_valid():
....

Django Forms with get_or_create

I am using Django ModelForms to create a form. I have my form set up and it is working ok.
form = MyForm(data=request.POST)
if form.is_valid():
form.save()
What I now want though is for the form to check first to see if an identical record exists. If it does I want it to get the id of that object and if not I want it to insert it into the database and then give me the id of that object. Is this possible using something like:
form.get_or_create(data=request.POST)
I know I could do
form = MyForm(instance=object)
when creating the form but this would not work as I still want to have the case where there is no instance of an object
edit:
Say my model is
class Book(models.Model):
name = models.CharField(max_length=50)
author = models.CharField(max_length=50)
price = models.CharField(max_length=50)
I want a form which someone can fill in to store books. However if there is already a book in the db which has the same name, author and price I obviously don't want this record adding again so just want to find out its id and not add it.
I know there is a function in Django; get_or_create which does this but is there something similar for forms? or would I have to do something like
if form.is_valid():
f = form.save(commit=false)
id = get_or_create(name=f.name, author=f.author, price=f.price)
Thanks
I like this approach:
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
book, created = Book.objects.get_or_create(**form.cleaned_data)
That way you get to take advantage of all the functionality of model forms (except .save()) and the get_or_create shortcut.
You just need two cases in the view before the postback has occurred, something like
if id:
form = MyForm(instance=obj)
else
form = MyForm()
then you can call form.save() in the postback and Django will take care of the rest.
What do you mean by "if an identical record exists"? If this is a simple ID check, then your view code would look something like this:
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
form.save()
else:
if get_id:
obj = MyModel.objects.get(id=get_id)
form = MyForm(instance=obj)
else:
form = MyForm()
The concept here is the check occurs on the GET request, such that on the POST to save, Django will already have determined if this is a new or existing record.
If your check for an identical record is more complex, it might require shifting the logic around a bit.
I would do this -
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
author = form.cleaned_data['author']
price = form.cleaned_data['prince']
if name and author and price:
book, created = Book.objects.get_or_create(name=name, \
author=author, price=price)
if created:
# fresh entry in db.
else:
# already there, maybe update?
book.save()
Based on the answers and comments, I had to create a different solution for my case, which included the use of unique_together on the base model. You may find this code useful as well, as I actually made it fairly generic.
I have custom code in the form.save() method that I want to utilize for creating a new object, so I don't want to simply not use the form.save() call. I do have to put my code check in the form.save() method, which I think is a reasonable place to put it.
I have a utility function to flatten iterables.
def flatten(l, a=list()):
"""
Flattens a list. Just do flatten(l).
Disregard the a since it is used in recursive calls.
"""
for i in l:
if isinstance(i, Iterable):
flatten_layout(i, a)
else:
a.append(i)
return a
In the ModelForm, I overwrite the validate_unique() method:
def validate_unique(self):
pass
This is about what my save method looks like:
def save(self, commit=True):
unique_fields = flatten(MyObject._meta.unique_together)
unique_cleaned_data = {k: v for k, v in self.cleaned_data.items() if k in unique_fields}
# check if the object exists in the database based on unique data
try:
my_object = MyObject.objects.get(**unique_cleaned_data)
except MyObject.DoesNotExist:
my_object = super(MyModelFormAjax, self).save(commit)
# -- insert extra code for saving a new object here ---
else:
for data, value in self.cleaned_data.items():
if data not in unique_fields:
# only update the field if it has data; otherwise, retain
# the old value; you may want to comment or remove this
# next line
if value:
setattr(my_object, data, value)
if commit:
my_object.save()
return my_object