Dealing with model forms in Django - django

I thought it would be easier to start using models form instead of regular forms(giving up on all the easy things modelform provides).
But when I try to do this:
>>> m = Model.objects.get(pk=1)
>>> f = ModelForm(instance=m)
>>> f.is_valid()
False
>>> f.save()
AttributeError: 'ModelForm' objects has no attribute 'cleaned_data'
I think django documentation is wrong by saying:
Every form produced by ModelForm also has a save() method. This
method creates and saves a database object from the data bound to
the form. A subclass of ModelForm can accept an existing model
instance as the keyword argument instance; if this is supplied, save()
will update that instance. If it’s not supplied, save() will create a
new instance of the specified model
Cause this is just not working for me.
Am I right that the django documentation is wrong?
Thanks in advance.

You may have forgotten to add "data" into the ModelForm instantiation according to the user's request.POST data.
f = ModelForm(data=request.POST, instance=m)
f.is_valid() # is True if the data is ok.
In any case, it would be better to post your relevant code: The model class, the model form class and your view.
EDIT: you must add a data= parameter (or the first parameter, if you don't name it) to a ModelForm initialization, if you want is_valid() to work. is_valid is here to check the given data against the various validation rules, and only if it's ok, lets you save the ModelForm. Initializing a ModelForm just with the instance= named parameter does not trigger any validation, because there is nothing new to validate.

Related

Django form field validation - How to tell if operation is insert or update?

I'm trying to do this in Django:
When saving an object in the Admin I want to save also another object of a different type based on one of the fields in my fist object.
In order to do this I must check if that second object already exists and return an validation error only for the particular field in the first object if it does.
My problem is that I want the validation error to appear in the field only if the operation is insert.
How do I display a validation error for a particular admin form field based on knowing if the operation is update or insert?
P.S. I know that for a model validation this is impossible since the validator only takes the value parameter, but I think it should be possible for form validation.
This ca be done by writing a clean_[name_of_field] method in a Django Admin Form. The insert or update operation can be checked by testing self.instance.pk.
class EntityAdminForm(forms.ModelForm):
def clean_field(self):
field = self.cleaned_data['field']
insert = self.instance.pk == None
if insert:
raise forms.ValidationError('Some error message!')
else:
pass
return field
class EntityAdmin(admin.ModelAdmin):
form = EntityAdminForm
You have to use then the EntityAdmin class when registering the Entity model with the Django admin:
admin.site.register(Entity, EntityAdmin)
You can write your custom validation at the model level:
#inside your class model ...
def clean(self):
is_insert = self.pk is None
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
#do your business rules
if is_insert:
...
if __some_condition__ :
raise ValidationError('Dups.')
Create a model form for your model. In the clean method, you can set errors for specific fields.
See the docs for cleaning and validating fields that depend on each other for more information.
That is (probably) not an exact answer, but i guess it might help.
Django Admin offers you to override save method with ModelAdmin.save_model method (doc is here)
Also Django api have a get_or_create method (Doc is here). It returns two values, first is the object and second one is a boolean value that represents whether object is created or not (updated an existing record).
Let me say you have FirstObject and SecondObject
In your related admin.py file:
class FirstObjectAdmin(admin.ModelAdmin):
...
...
def save_model(self, request, obj, form, change):
s_obj, s_created = SecondObject.objects.get_or_create(..., defaults={...})
if not s_created:
# second object already exists... We will raise validation error for our first object
...
For the rest, I do not have a clear idea about how to handle it. Since you have the form object at hand, you can call form.fields{'somefield'].validate(value) and write a custom validation for admin. You will probably override clean method and try to trigger a raise ValidationError from ModelAdmin.save_model method. you can call validate and pass a value from there...
You may dig django source to see how django handles this, and try to define some custom validaton steps.

Initial form data from model - Django

I am trying to create an edit form for my model. I did not use a model form because depending on the model type, there are different forms that the user can use. (For example, one of the forms has a Tinymce widget, while the other doesn't.)
Is there any way of setting the initial data of a form (not a ModelForm) using a model?
I tried the following but getting an error:
b = get_object_or_404(Business, user=request.user)
form = f(initial = b)
where f is a subclass of forms.Form
The error I am getting is AttributeError: 'Business' object has no attribute 'get'
The initial data needs to be a dict (or at least have a dict-like interface, which a Django model does not have).
You can construct a dict from your model using django.forms.models.model_to_dict:
from django.forms.models import model_to_dict
b_as_dict = model_to_dict(b)
This is the same function Django's built in ModelForm class uses to set its initial data, and while you've specified that you don't want to use a ModelForm it may be more convenient to find some way of structuring your code that allows you to do so.
There is a more direct way of using model data than dict. See sample in documentation.
b = get_object_or_404(Business, user=request.user)
form = f(instance = b)

Why does form.is_valid() update the related model for Django ModelForms?

Can anyone explain this? It's really unintuitive that a form validation method would update the related model. I understand it doesn't commit to the database, but why even modify the model in memory? Why not do it with form.save()?
I believe the reason is this:
The first time you call is_valid() or
access the errors attribute of a
ModelForm has always triggered form
validation, but as of Django 1.2, it
will also trigger 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.
From here. Logically, this does actually make sense. Suppose I have a model like this:
class SomeModel(models.Model):
Somefield = models.CharField(unique=True)
Then as far as the modelform is concerned, validating the field TextInputField would work for any value. However, from the model's perspective, not any value will do - that value must be unique.
The validation mechanisms are described here. Basically, calling full_clean on a model causes that model to check its own fields as per the OO paradigm. So for this reason, the in memory image of the model can be updated and validating it will check it can actually be written to the database.
Thus it makes sense, because your guard:
if form.is_valid():
Really ought to ensure that:
form.save()
Can actually happen.

Instanting ModelForm with where modelfields are overwritten

I have a ModelForm field that is based on the following Model:
class Phrase(models.Model):
subject = models.ForeignKey(Entity) # Entity is unique on a per Entity.name basis
object = models.ForeignKey(Entity) # Entity is unique on a per Entity.name basis
The modelform (PhraseForm) has a field 'subject' that is a CharField. I want users to be able to enter a string. When the modelform is saved, and the string does not match an existing Entity, a new Entity is created.
This is why I had to overwrite the "subject" field of the Modelform, as I cannot use the automatically generated "subject" field of the Modelform (I hope I'm making myself clear here).
Now, all tests run fine when creating a new Phrase through the modelform. But, when modifying a Phrase:
p = Phrase.objects.latest()
pf = PhraseForm({'subject': 'anewsubject'}, instance=p).
pf.is_valid() returns False. The error I get is that "object" cannot be None. This makes sense, as indeed, the object field was not filled in.
What would be the best way to handle this? I could of course check if an instance is provided in the init() function of the PhraseForm, and then assign the missing field values from the instance passed. This doesn't feel as if it's the right way though, so, is there a less cumbersome way of making sure the instance's data is passed on through the ModelForm?
Now that I'm typing this, I guess there isn't, as the underlying model fields are being overwritten, meaning the form field values need to be filled in again in order for everything to work fine. Which makes me rephrase my question: is the way I've handled allowing users to enter free text and linking this to either a new or existing Entity the correct way of doing this?
Thanks in advance!
Why are you modifying using the form.
p = Phrase.objects.latest()
p.subject = Entity.objects.get_or_create(name='anewsubject')[0]
docs for get_or_create
If you are actually using the form it should work fine:
def mod_phrase(request, phrase_id=None):
phrase = get_object_or_404(Phrase, pk=phrase_id)
if request.method == 'POST':
form = PhraseForm(request.POST, instance=phrase)
if form.is_valid():
form.save()
return HttpResponse("Success")
else:
form = PhraseForm(instance=phrase)
context = { 'form': form }
return render_to_response('modify-phrase.html', context,
context_instance=RequestContext(request))
Setting the instance for the ModelForm sets initial data, and also lets the form know which object the form is working with. The way you are trying to use the form, you are passing an invalid data dictionary (lacks object), which the form is correctly telling you isn't valid. When you set the data to request.POST in the example above, the request.POST includes the initial data which allows the form to validate.

Django ModelForm fails validation with no errors

Ok, I have been staring at this for hours trying to figure out what's going on, to no avail.
I am trying to create a ModelForm using the 'instance' keyword to pass it an existing model instance and then save it.
Here is the ModelForm (stripped considerably from the original in my attempts to identify the cause of this problem):
class TempRuleFieldForm(ModelForm):
class Meta:
model = RuleField
and here is the code I'm running:
>>> m = RuleField.objects.get(pk=1)
>>> f = TempRuleFieldForm(instance=m)
>>> f.is_valid()
False
The model object (m above) is valid and it saves just fine, but the form will not validate. Now, as far as I can tell, this code is identical to the Django docs example found here: http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method, though obviously I am missing something. I would greatly appreciate some fresh eyes to tell me what I've got wrong.
Thanks
Note that your link doesn't call f.is_valid(), it just saves directly. This is potentially a bit misleading.
The point is that instantiating a form with just an instance parameter but no data does not bind it to data, and the form is therefore not valid. You will see that f.is_bound is False.
Behind the scenes, instance is really just the same as passing initial data, which as the docs note is only used to display the data initially and is not used for saving. You would probably benefit from reading the notes on bound and unbound forms.
If u still want to validate the object that was in the database, you can serialize it first and then create the Form with it.
from django.utils import simplejson
from django.core.serializers import serialize
(...)
fields_dict = simplejson.loads(serialize('json', [obj]))[0]['fields']
form = forms.MyForm(fields_dict)
if form.is_valid
This is probably not the best way to do it but the only one that I have found to get a bound form from a model. I need it because I want to validate the current data in the database. I create a question since I don't think this is the best way of doing it:
Transform an unbound form to a bound one?
This isn't a solution for OP, but it is for the post title, which is quite high in Google. So I'll post it anyway, from here:
If you're already giving request.POST to your form using request.POST or None, but it's still invalid without errors, check that there isn't any redirect going on. A redirect loses your POST data and your form will be invalid with no errors because it's unbound.
Not so much a solution for OP but this was a problem I ran into, specifically when running unit tests on ModelForms, it was a nuisance to keep on having to bind the form then also define an instance with the very same data. I created a small helper function to make things easier which others may find useful — I'm only using this for testing purposes and would be cautious to deploy it anywhere else without significant tweaks (if at all)
def testing_model_form(instance, model_form_class):
"""
A function that creates instances ModelForms useful for testing, basically takes an instance as an argument and will take care
of automatic binding of the form so it can be validated and errors checked
"""
fields = model_form_class.Meta.fields
data_dict = {}
for field in fields:
if hasattr(instance, field):
# The field is present on the model instance
data_dict[field] = getattr(instance, field)
x = model_form_class(data=data_dict)
x.instance = instance
return x