How to serialise request.POST to a database and back again - django

I'm trying to implement a simple "checkpointing" system to save partially completed formsets. I've got a set of large forms (say 100 entries) for a data-entry project. Now, if the person quits or whatever, halfway through, then I'd like this progress saved - but I don't want the half-entered data saved in the database until it's complete.
As far as I can see the best way to deal with this is to save request.POST to a database field and pull it out again e.g.
def myview(request, obj_id):
obj = get_object_or_404(Task, obj_id)
if request.POST:
# save checkpoint
obj.checkpoint = serializers.serialize("json", request.POST)
else:
# load last version from database.
request.POST = serializers.deserialize("json", obj.checkpoint)
formset = MyFormSet(request.POST)
# etc.
But, this gives me the following error:
'unicode' object has no attribute '_meta'
I've tried simple json and pickle and get the same errors. Is there any way around this?

Django's serializer interface works with django model objects. It will not work with other objects.
You may try to use json
if request.POST:
# save checkpoint
obj.checkpoint = json.dumps(request.POST)
post_data = request.POST
else:
# load last version from database.
post_data = json.loads(obj.checkpoint)
formset = MyFormSet(post_data)

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.

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

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!.

How to check whether or not a Django form is correctly bound?

Situation
Using Django 1.5, I am using forms.ModelForms to let the user edit database contents. However I can't get the form to update the database upon form.save().
Each of my models correspond to a setting form (the application is a the direct porting of a desktop software in which the user can store several settings). I needed to implement a Reset to default feature, so I thought of having a default object (imported with Django fixtures) which I would use only to reset a second one. The user would only interact with the second model.
pk=1 refers to the base object
pk=2 refers to the custom object
I have several forms on the same page (only foobar here), so basically this what I planned to do:
No POST data
Building form from either pk=1 or pk=2, depending pk=2 has been found or not
Rendering the forms to the template
AJAX request, with POST datas
Getting form content
Checking whether or not the user has permission to edit the model (checksum)
Update the model form POST datas
Returning AJAX response
Code
I have put two debug prints to illustrate the issue I am facing. The form I fetch doesn't seem to be bound to my model.
# Response codes to use in the template
RESPONSES = {
200: {'code':'0xB16B00B5', 'message':'Success'},
400: {'code':'0x8BADF00D', 'message':'Form is not valid'},
403: {'code':'0xBAADF00D', 'message':'No permission to edit the database'},
501: {'code':'0xDEADC0DE', 'message':'POST datas not found'},
}
# Those are the setting labels
TYPES = {
'foobar': {'model':FooBar, 'form':FooBarForm },
}
def index(request):
# Handling form datas
if request.method == 'POST':
response = HttpResponse(simplejson.dumps({'code':RESPONSES[501]['code']}), 'application/json')
for label in TYPES:
# Filtering the right form to handle
if label in request.POST:
model = _fetch_setting(label, mode='model')
form = _fetch_setting(label, mode='form', post=request.POST)
checksum = model.checksum # Somehow, 'form.is_valid()' is altering 'model', need to backup the checksum
if form.is_valid():
# The user has permission to edit the model
if form.cleaned_data['checksum'] == checksum:
if form.has_changed():
print form.cleaned_data['foo'] # Outputs the form data, as expected
form.save()
print model.foo # Outputs the old data
model.checksum = str(uuid4()).replace('-', '')
model.save()
response = HttpResponse(simplejson.dumps({'code':RESPONSES[200]['code']}), 'application/json')
# This one does not
else:
response = HttpResponse(simplejson.dumps({'code':RESPONSES[403]['code']}), 'application/json')
break # We are still inside the label loop
# The form is not valid
else:
response = HttpResponse(simplejson.dumps({'code':RESPONSES[400]['code']}), 'application/json')
# Form not submitted yet, building the HTML forms
else:
forms = {}
label = 'foobar'
for label in TYPES:
forms[label] = _fetch_setting(label, mode='form')
context = {'errors':RESPONSES, 'forms':forms}
response = render(request, 'home/index.html', context)
return response
# Return a setting object (model or form) corresponding to the given label
def _fetch_setting(label, mode='model', post=None):
try:
result = None
default = TYPES[label]['model'].objects.get(pk=1)
try:
model = TYPES[label]['model'].objects.get(pk=2)
except TYPES[label]['model'].DoesNotExist:
model = TYPES[label]['model'].objects.create(
checksum = default.checksum,
foo = default.foo,
bar = default.bar,
)
if mode == 'model':
result = model
if mode == 'form':
print model
result = TYPES[label]['form'](data=post, instance=model) # The 'instance' attribute doesn't seem to be applied
except KeyError:
result = None
finally:
return result
Update
07.10
It does work when I pass the instance to bound with to _fetch_setting. So I guess this issue is coming from the form validation.
def _fetch_setting(label, mode='model', post=None, instance=None):
# ...
if mode == 'form':
if instance:
model = instance
result = TYPES[label]['form'](data=post, instance=model)
# ...
As I commented in my code, form.is_valid() seems to alter the object.
Will flag as answered if no one come with a clean solution.
The issue is, you are creating a new model object with each form.save()
You need to update the same model object with commit=False
if form.cleaned_data['checksum'] == checksum:
if form.has_changed():
print form.cleaned_data['foo'] # Outputs the form data, as expected
model = form.save(commit=False)
model.checksum = str(uuid4()).replace('-', '')
model.save()
From the fabulous manual:
The first time you call is_valid() or access the errors attribute of a ModelForm triggers form validation as well as model validation. This has the side-effect of cleaning the model you pass to the ModelForm constructor. For instance, calling is_valid() on your form will convert any date fields on your model to actual date objects. If form validation fails, only some of the updates may be applied. For this reason, you’ll probably want to avoid reusing the model instance passed to the form, especially if validation fails.

Django admin: How to populate a new object from GET variables?

In Django's admin, I've seen how I can set the fields of an 'add object' form using GET variables (e.g. /admin/app/model/add?title=lol sets the 'Title' field to 'lol').
However, I want to be able to do something along the lines of /admin/app/model/add?key=18 and load default data for my fields from an instance of another model. Ideally, I'd also like to be able to do some processing on the values that I populate the form with. How do I do this?
I managed to figure it out. Thankfully, Django allows you to replace a request's GET dict (which it uses to pre-populate the admin form). The following worked for me:
class ArticleAdmin(admin.ModelAdmin):
# ...
def add_view(self, request, form_url='', extra_context=None):
source_id = request.GET.get('source', None)
if source_id is not None:
source = FeedPost.objects.get(id=source_id)
# any extra processing can go here...
g = request.GET.copy()
g.update({
'title': source.title,
'contents': source.description + u"... \n\n[" + source.url + "]",
})
request.GET = g
return super(ArticleAdmin, self).add_view(request, form_url, extra_context)
This way, I obtain the source object from a URL parameter, do what I want with them, and pre-populate the form.
You can override method add_view of ModelAdmin instance. Add getting an object there, set object's pk to None and provide that object as an instance to the form. Object with pk == None will be always inserted as a new object in the database on form's save()

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():
....