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

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.

Related

How to dinamically access to a field in django form to change instance

Using a generic CreateView in Django I'm trying to save only the fields that have been changed by user.
I'm trying to do this in my view:
def form_valid(self, form):
if form.has_changed():
for field in form:
if field.name in form.changed_data:
continue
else:
form.instance.field=None
In the last line I got this error:
object has no attribute 'field'
Is there a way to dynamically access to each field in the form? so I could change it?
Is there a way to dynamically access to each field in the form.
Yes.
form.fields[field_name]
So I could change it?
Yes.
form.fields[field_name] = django.forms.Charfield(required=False)
But in your code, probably you want to achieve value of the field:
field_value = form[field_name].value
If you have a data bounded field, you can change also data of form:
form.data(form.add_prefix(field_name)) = new_value
But this is already wrong approach to change something here.
I can imagine: in your case you don't understand how form / modelform saves the data and therefore you want to do something unnecessary:
Once more time, how ModelForm "saves" the data in DB:
Form validate the data.
Form clean the data.
on form.save(**kwargs) - Form create the instance and:
call instance.save() if form.save(commit=True)
dont call instance.save() if form.save(commit=False)
Instance saves the data.
Form don't save anything itself, instance.save - There is all what you need.
instance.save(self, force_insert=False, force_update=False, using=None, update_fields=None)
In your case you use CreateView - there all fields should be initiated, you can not UPDATE something, this something not exists yet. You can only avoid empty values, if you want.
def form_valid(self, form):
if form.has_changed():
form.cleaned_data = {key:val for key,val in form.cleaned_data.items() if val and key in form.changed_data}
return super().form_valid(form)
al last: form.instance.field is ridiculous
form has fields. You can achieve every field by name.
Model class has fields. You can achieve every field by name.
instance has attributes or properties. You can achieve every attribute by name.
And right now: what you are really want to do?

Why we use cleaned_data on forms in django

Why we use cleaned_data firstname= form.cleaned_data.get("first_name")
What is the point in this and why is it necessary?
When you call is_valid() method on a form, it results in validation and cleaning of the form data. In the process, Django creates an attribute called cleaned_data , a dictionary which contains cleaned data only from the fields which have passed the validation tests.
There 2 are two types: basic Form (forms.Form) and ModelForm (forms.ModelForm).
If you are using a ModelForm then there is no any need of using a cleaned_data dictionary because when you do form.save() it's already be matched and the clean data is saved. But you are using basic Form then you have to manually match each cleaned_data to its database place and then save the instance to the database not the form.
For example basic Form:
if form.is_valid():
ex = Example()
ex.username = form.cleaned_data['username']
ex.save()
For example ModelForm:
if form.is_valid():
form.save()
IMPORTANT: If the form pass from is_valid() stage then there is no any unvalidated data.
When data has been submitted to the database through the forms, it has to be validated or the user has to be authenticated.
When it's being returned to the user this is how it's being accessed
name = form.cleaned_data['name']
here I've used just a name but one can also use an email
When the data is returned it's returned in a more readable format.

Django Model Formset ignore Form Instance on is_valid()

I have a Formset made up by a Form where I exclude a required field that I want to fill programatically instead of asking the user to fill it.
My expectation is that I can exclude it from my request.POST dictionary, and add it with the line below, and that the is_valid() method will both use the request.POST data, and the initial data added to the instance passed to the form, to validate and save it.
form_kwargs={"instance": MyModel(sale=5)}
# My view.py
formset = self.get_formset(
data=self.request.POST,
form_kwargs={"instance": MyModel(sale=5)}
)
# Error here, 'sale' is not set.
if formset.is_valid():
formset.save()
The get_formset() method returns an instance of the formset.
# My formset factory method
def get_formset(self, **kwargs):
MyFormSet = forms.modelformset_factory(MyModel, form=MyForm)
...
return MyFormSet(**kwargs)
No, Django will never use initial data in place of missing posted data - otherwise how could you ever use a form to set a field to empty? Instead, you should exclude that field from the form, in which case the existing instance value will be preserved.
Either do this explicitly in the Meta class of MyForm, or pass the exclude parameter to modelformset_factory.

django update only one field of a model through a form

I have this models:
class Balanta(models.Model):
data = models.DateField()
class Conturi(models.Model):
cont=models.PositiveIntegerField()
cont_debit=models.DecimalField(default=0, max_digits=30, decimal_places=2)
cont_credit=models.DecimalField(default=0, max_digits=30, decimal_places=2)
balanta = models.ForeignKey(Balanta)
And i have formsets working ok in a template and this view:
def balanta_introducere(request):
balanta=Balanta()
ConturiInlineFormSet=inlineformset_factory(Balanta, Conturi, extra=3)
if request.method=='POST':
balanta_form=BalantaForm(request.POST, instance=balanta)
if balanta_form.is_valid():
balanta, created=Balanta.objects.get_or_create(**balanta_form.cleaned_data)
#return HttpResponseRedirect('/sitfin/balantaok')
formset=ConturiInlineFormSet(request.POST, request.FILES, instance=balanta)
if formset.is_valid():
for form in formset:
data={
'cont':form.cleaned_data.get('cont'),
'cont_debit':form.cleaned_data.get('cont_debit'),
'cont_credit':form.cleaned_data.get('cont_credit'),
'balanta':form.cleaned_data.get('balanta'),
}
try:
c=Conturi.objects.get(cont=data['cont'])
except Conturi.DoesNotExist:
cont_complete,created=Conturi.objects.get_or_create(**data)
else:
cont_complete,created=Conturi.objects.get_or_create(cont=data['cont'],cont_debit=data['cont_debit'],cont_credit=data['cont_credit'],balanta=data['balanta'])
else:
balanta_form=BalantaForm()
formset=ConturiInlineFormSet(instance=balanta)
return render_to_response('sitfin/balanta_introducere.html',{'balanta_form':balanta_form,'formset':formset}, context_instance=RequestContext(request))
If i hit the first submit, all the data goes in the database (foreignkey and all)
After the second submit with the same data, the form doesn't do anything and this is ok.
If i change a value in the form (in a "cont_credit" of a "cont" for example) and hit submit again, i get another Conturi object with only the modified "cont" with the updated "cont_credit" value and this is not good!
What is the approach for updating only some fields of an existing Conturi model with the help of a form?
Something like:
If the cont it is not in the database,
create a Conturi objects with the data in the form,
If the "cont" is already in the database,
Update the cont_credit and cont_debit data with the new values entered in the form
Thank you very much.
get_or_create is trying a get with all the parameters you pass it, so if anything changes on the form, it won't find the existing object, and instead will create a new one.
If your forms are ModelForms, then you can just use form.save() to save the instance bound to the form, and formset.save() to save all the instances bound to the formset.
EDIT:
I now noticed another thing: you are using
balanta=Balanta()
and then
balanta_form=BalantaForm(request.POST, instance=balanta)
so you are forcing the form to use a new instance. try getting the specific Balanta you're editing, and pass that as the instance.

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