Modifying Django ModelForm.cleaned_data has no effect on saved Model - django

I have a Model where one boolean ModelField is dependent upon another. The Model is configured like so:
class Situation(models.Model):
ctcs = models.BooleanField(verbose_name="Cross-Technology Critical Situation", blank=True)
has_been_ctcs = models.BooleanField(editable=False, default=False)
The ctcs field is rendered as a checkbox in the ModelForm for this model. What I want to do is, if the ctcs field has been checked, I also want to set has_been_ctcs to True. So what I am doing is setting cleaned_data['has_been_ctcs'] = True on the ModelForm. I've tried doing this both in my view that handles the POST request, as well as within the ModelForm clean function like so:
class SituationForm(forms.ModelForm):
def clean(self):
cleaned_data = super(SituationForm, self).clean()
ctcs = cleaned_data.get("ctcs")
if ctcs:
self.cleaned_data['has_been_ctcs'] = True
return cleaned_data
And here is the snippet from the view that handles creation of a new Situation model:
sit_form = SituationForm(request.POST)
if sit_form.is_valid():
print sit_form.cleaned_data['ctcs'] # Prints True
if sit_form.cleaned_data['ctcs']:
print "Checking form has_been_ctcs"
# Have also tried setting sit_form.cleaned_data['has_been_ctcs'] here, no difference from doing it in `def clean()`
print sit_form.cleaned_data['has_been_ctcs'] # Prints True
sit = sit_form.save()
print sit.has_been_ctcs # Prints False
I cannot seem to get the has_been_ctcs value of True to propagate to the Situation model. How can I go about doing this?

cleaned_data only works for fields that are included in the form. What you want to do is:
sit_form = SituationForm(request.POST)
if sit_form.is_valid():
if sit_form.cleand_data['ctcs']:
sit_form.instance.has_been_ctcs = True
sit = sit_form.save()

Related

OneToOneFiled value not updated after using save() command

I'm trying to update the value of a OneToOneField from its default value (None), everything seems to work apart the fact that when I call save() the change is not written to the DB.
Instead, if I change the value from the admin console everything works.
My models.py looks like this:
class Payment(m.Model):
[stuff...]
class Order(m.Model):
[stuff...]
active = m.BooleanField(default = False)
payment = m.OneToOneField(Payment, null = True, blank = True, on_delete=m.SET_NULL)
I have a function that activates the order, going like this:
def activate(self, ipn):
[stuff..]
self.payment = Payment(ipn = ipn)
self.payment.save()
self.active = True
self.save()
After i call activate() on an order, the active value gets correctly set to True, but the payment value remains set to None. I can't figure out what is wrong. Any idea?
Ps: as i said, if i manually set the payment from the admin interface everything works.
The payment_id is set when you assign the Payment instance to self.payment. However, the Payment instance does not have an id at that point. You need to save it before you assign it to the OneToOneField.
def activate(self, ipn):
[stuff..]
payment = Payment(ipn = ipn)
payment.save()
self.payment = payment
self.active = True
self.save()
If you try to run your activate code in a shell you will probably see something like this:
ValueError: Cannot assign "...." instance isn't saved in the database.
So basically firstly you need to create a Payment instance:
def activate(self, ipn):
[stuff..]
payment = Payment(ipn = ipn)
payment.save()
self.payment = payment
self.active = True
self.save()

Django inline_formset with can_delete=True and can_order=True doesn't work

I have a form with models and foreign key modes represented as inline formsets.
I'm having a helluva time saving the ordered formsets. In fact, every time I try to delete one, it gets multiplied.
in forms.py:
class PublicationForm(ModelForm):
class Meta:
model = Publication
fields = ['title']
SectionFormSet = inlineformset_factory(Publication, Section, can_delete=True, can_order=True, extra=2)
and in views.py:
if publication_form.is_valid():
pub = publication_form.save(commit=False)
section_formset = SectionFormSet(request.POST, instance=pub, prefix='section')
if section_formset.is_valid():
pub.save()
for s in section_formset.ordered_forms:
s.instance.order = s.cleaned_data['ORDER']
s.save()
I've loked on S.O. but found nothing.
Does anybody have a solution?
Thanks!!
Yes, you have the saving all jumbled up. Something like this:
1 - Your formset holds the sets of SectionForm, so you are testing for the form to be valid in the wrong loop. You want this:
if request.method == 'POST':
section_formset = SectionFormSet(request.POST, instance=<an instance of Publication>, prefix='section')
if section_formset.is_valid():
instances = section_formset.save(commit=False)
for instance in instances:
#do something
instance.save()
return HttpResponseRedirect('<a url>')
else:
section_formset = SectionFormSet(instance=<an instance of Publication>, prefix='section')
You don't need to do the instances loop if you're just saving the section forms 'order' value to the section models order attr - it's done for you. You can just call section_formset.save().

Django - preventing duplicate records

I have a list of client records in my database. Every year, we generate a single work order for each client. Then, for each work order record, the user should be able to create a note that is specific to the work order. However, not all work orders need a note, just some.
Now, I can't simply add a note field to the work order because some times, we need to create the note before the work order is even generated. Sometimes this note is specific to a work order that won't happen for 2-3 years. Thus, the notes and the work order must be independent, although they will "find" each other when they both exist.
OK, so here's the situation. I want the user to be able to fill out a very simple note form, where they have two fields: noteYear and note. Thus, all they do is pick a year, and then write the note. The kicker is that the user should not be able to create two notes for the same year for the same client.
What I'm trying to get as is validating the note by ensuring that there isn't already a note for that year for that client. I'm assuming this would be achieved by a custom is_valid method within the form, but I can't figure out how to go about doing that.
This is what I tried so far (note that I know it's wrong, it doesn't work, but it's my attempt so far):
Note that systemID is my client record
My model:
class su_note(models.Model):
YEAR_CHOICES = (
('2013', 2013),
('2014', 2014),
('2015', 2015),
('2016', 2016),
('2017', 2017),
('2018', 2018),
('2019', 2019),
('2020', 2020),
('2021', 2021),
('2022', 2022),
('2023', 2023),
)
noteYear = models.CharField(choices = YEAR_CHOICES, max_length = 4, verbose_name = 'Relevant Year')
systemID = models.ForeignKey(System, verbose_name = 'System ID')
note = models.TextField(verbose_name = "Note")
def __unicode__(self):
return u'%s | %s | %s' % (self.systemID.systemID, self.noteYear, self.noteType)
And my form:
class SU_Note_Form(ModelForm):
class Meta:
model = su_note
fields = ('noteYear', 'noteType', 'note')
def is_valid(self):
valid = super (SU_Note_Form, self).is_valid()
#If it is not valid, we're done -- send it back to the user to correct errors
if not valid:
return valid
# now to check that there is only one record of SU for the system
sysID = self.cleaned_data['systemID']
sysID = sysID.systemID
snotes = su_note.objects.filter(noteYear = self.cleaned_data['noteYear'])
for s in snotes:
if s.systemID == self.systemID:
self._errors['Validation_Error'] = 'There is already a startup note for this year'
return False
return True
EDIT -- Here's my solution (thanks to janos for sending me in the right direction)
My final form looks like this:
class SU_Note_Form(ModelForm):
class Meta:
model = su_note
fields = ('systemID', 'noteYear', 'noteType', 'note')
def clean(self):
cleaned_data = super(SU_Note_Form, self).clean()
sysID = cleaned_data['systemID']
sysID = sysID.systemID
try:
s = su_note.objects.get(noteYear = cleaned_data['noteYear'], systemID__systemID = sysID)
print(s)
self.errors['noteYear'] = "There is already a note for this year."
except:
pass
return cleaned_data
For anyone else looking at this code, the only confusing part is the line that has: sysID = sysID.systemID. The systemID is actually a field of another model - even though systemID is also a field of this model -- poor design, probably, but it works.
See this page in the Django docs:
https://docs.djangoproject.com/en/dev/ref/forms/validation/
Since your validation logic depends on two fields (the year and the systemID), you need to implement this using a custom cleaning method on the form, for example:
def clean(self):
cleaned_data = super(SU_Note_Form, self).clean()
sysID = cleaned_data['systemID']
sysID = sysID.systemID
try:
su_note.objects.get(noteYear=cleaned_data['noteYear'], systemID=systemID)
raise forms.ValidationError('There is already a startup note for this year')
except su_note.DoesNotExist:
pass
# Always return the full collection of cleaned data.
return cleaned_data

Mapping a model fields with a non model fields in a ModelForm

I've spent nearly the whole day reading the documentation about forms and ModelForms. I managed to use the basic stuff but now I'm having really trouble because I don't find any hints in the documentation about mapping model field with non-model fields. That's what I mean:
I have this model:
class Process(models.Model):
key = models.CharField(max_length=32, default="")
name = models.CharField(max_length=30)
path = models.CharField(max_length=215)
author = models.CharField(max_length=100)
canparse = models.NullBooleanField(default=False)
last_exec = models.DateTimeField(null = True)
last_stop = models.DateTimeField(null = True)
last_change = models.DateTimeField(null = True, auto_now=True)
The only fields that the users are going to modify are name and author. path is the absolute real path of the configuration file of my Process. The directory is fixed, users are not going to care whether the directory is /home/measure/conf or /var/whatever), they only care about the filename. That's why I want a filename field in my ModelForm.
My form looks like:
class ProcessForm(MonitorForm):
filename = forms.CharField(max_length=250)
class Meta:
model = Process
fields = ('name', 'filename', 'author')
Now, what I want is that filename contains the file name stored in Process.path and not the entire path, that's what I mean:
>>> from monitor.forms import ProcessForm
>>> from remusdb.models import Process
>>>
>>> p = Process(name="test1", path="/tmp/config/a.cnf", author="pablo")
>>> f = ProcessForm(instance=p)
>>> print f["filename"].value()
---> here I want to get "a.cnf"
The problem is I don't know how to write a.cnf to the filename field once I call ProcessForm(instance=p). I thought about doing it in the clean function but I'm not sure whether this is a good place at all. I assume that at this point it would be too late, because the fields are more or less read-only, once initilaized you cannot change their value. So how should I do it? Should I create a custom field and override __init__?
I got this idea from reading form field default cleaning and wanted to test if first. I didn't want to override init first and I thought of first playing with theto_python method like in the documentation. So I created the class RemusFilenameField:
class RemusFilenameField(forms.CharField):
def to_python(self, value):
print "to_python's value is %s" % value
return value.upper()
def clean(self, value):
print "clean's value is %s" % value
return value.upper()
and changed the filename line on ProcessForm to
filename = RemusFilenameField(max_length=250)
I added the prints to see where/when this methods get called. But the methods are not called at all. I suspect because the form is not bounded. So I did this instead:
>>> p = {"name": "test1", "filename": "a.cnf", "author": "pablo"}
>>> f = ProcessForm(p)
>>> f.is_valid()
clean's value is a.cnf
True
>>> print f["filename"].value()
a.cnf
The to_python method is also not getting called and I expected to see A.CNF because clean returns the something different.
I have no idea how to solve this and even whether this was a good idea at all. My next probelm is when f.save() is executed the correct path has to be generated out of filename and stored in the instance. I would do that in the clean method or is there a better options for this?
EDIT: I think I have a solution for the creation of the form (I had to read the whole source code to identify in python2.6/site-packages/django/forms/models.py the model_to_dict usage:
from django.forms.models import model_to_dict
import os
class ProcessForm(MonitorForm):
filename = RemusFilenameField(max_length=250)
def __init__(self, *args, **kwargs):
super(ProcessForm, self).__init__(*args, **kwargs)
try:
proc = kwargs["instance"]
filename = os.path.basename(proc.path)
self.initial.update({'filename': unicode(filename)})
except:
pass
class Meta:
model = Process
fields = ('name', 'filename', 'author')
It know works :) now I have to figure out how to fix the save() method
The save() method is the appropriate place to put any additional save logic in your form.
def save(self, commit=True):
proc = super(ProcessForm, self).save(commit=False)
filename = cleaned_data['filename']
# additional logic to alter filename
proc.path = filename
if commit:
proc.save()
return proc
In terms of calling to_python() method on the field in your form your code is correct, but you have to get value from cleaned_data dictionary:
>>> p = {"name": "test1", "filename": "a.cnf", "author": "pablo"}
>>> f = ProcessForm(p)
>>> f.is_valid()
clean's value is a.cnf
True
>>> f.cleaned_data['filename']
'A.CNF'
If you need some logic on model save you can override save() in model. It's also described in django docs.
You should also consider to override save() method in your ModelForm as drewman said - it won't affect your code on calling save() method on model instance from other places in your code.

Django : Validate data by querying the database in a model form (using custom clean method)

I am trying to create a custom cleaning method which look in the db if the value of one specific data exists already and if yes raises an error.
I'm using a model form of a class (subsystem) who is inheriting from an other class (project).
I want to check if the sybsystem already exists or not when i try to add a new one in a form.
I get project name in my view function.
class SubsytemForm(forms.ModelForm):
class Meta:
model = Subsystem
exclude = ('project_name')
def clean(self,project_name):
cleaned_data = super(SubsytemForm, self).clean(self,project_name)
form_subsystem_name = cleaned_data.get("subsystem_name")
Subsystem.objects.filter(project__project_name=project_name)
subsystem_objects=Subsystem.objects.filter(project__project_name=project_name)
nb_subsystem = subsystem_objects.count()
for i in range (nb_subsystem):
if (subsystem_objects[i].subsystem_name==form_subsystem_name):
msg = u"Subsystem already existing"
self._errors["subsystem_name"] = self.error_class([msg])
# These fields are no longer valid. Remove them from the
# cleaned data.
del cleaned_data["subsystem_name"]
return cleaned_data
My view function :
def addform(request,project_name):
if form.is_valid():
form=form.save(commit=False)
form.project_id=Project.objects.get(project_name=project_name).id
form.clean(form,project_name)
form.save()
This is not working and i don't know how to do.
I have the error : clean() takes exactly 2 arguments (1 given)
My model :
class Project(models.Model):
project_name = models.CharField("Project name", max_length=20)
Class Subsystem(models.Model):
subsystem_name = models.Charfield("Subsystem name", max_length=20)
projects = models.ForeignKey(Project)
There are quite a few things wrong with this code.
Firstly, you're not supposed to call clean explicitly. Django does it for you automatically when you call form.is_valid(). And because it's done automatically, you can't pass extra arguments. You need to pass the argument in when you instantiate the form, and keep it as an instance variable which your clean code can reference.
Secondly, the code is actually only validating a single field. So it should be done in a specific clean_fieldname method - ie clean_subsystem_name. That avoids the need for mucking about with _errors and deleting the unwanted data at the end.
Thirdly, if you ever find yourself getting a count of something, iterating through a range, then using that index to point back into the original list, you're doing it wrong. In Python, you should always iterate through the actual thing - in this case, the queryset - that you're interested in. However, in this case that is irrelevant anyway as you should query for the actual name directly in the database and check if it exists, rather than iterating through checking for matches.
So, putting it all together:
class SubsytemForm(forms.ModelForm):
class Meta:
model = Subsystem
exclude = ('project_name')
def __init__(self, *args, **kwargs):
self.project_name = kwargs.pop('project_name', None)
super(SubsystemForm, self).__init__(*args, **kwargs)
def clean_subsystem_name(self):
form_subsystem_name = self.cleaned_data.get("subsystem_name")
existing = Subsystem.objects.filter(
project__project_name=self.project_name,
subsytem_name=form_subsystem_name
).exists()
if existing:
raise forms.ValidationError(u"Subsystem already existing")
return form_subsystem_name
When you do form=form.save(commit=False) you store a Subsystem instance in the variable form but the clean method is defined in SubsystemForm. Isn't it?