related question: Test if Django ModelForm has instance
According to the question&answer above, we can check if a modelform has instance as hasattr(form.instance, 'pk'), because instance must have pk.
I misunderstood the related question. it says
Try checking if form.instance.pk is None.
But in my case where the model's primary key is customized as:
class MyModel(Model):
myid = models.CharField(max_length=10, primary_key=True)
...
And the modelform:
class MyModelForm(ModelForm):
class Meta:
model = MyModel
has pk attribute on instance, after is_valid():
data = {'myid': '123'}
form = MyModelForm(data=data, instance=None)
form.is_valid()
if form.instance.pk is not None:
print('detect: modelform received an instance')
else:
print('detect: modelform didnt receive an instance')
My question is:
In this case, how to check if a modelform was set with an existing instance?
Or, how to check if the mode of modelform is "edit on existed entry" / "new entry to our DB"?
If your model has a primary key column, than pk property of that model will always be there and be an alias to that field.
In your case you do not want to check if your form.instance has a property named pk (that's the hasattr line). Instead you have to check if the property pk of form.instance is empty or not:
data = {'myid': '123'}
form = MyModelForm(data=data, instance=None)
form.is_valid()
if form.instance.pk:
print('This has a pk! It was saved before!')
else:
print('This has no pk! It was never saved!')
If it is a new model, not yet saved, than the form.instance.pk field's value will be u'' (empty string), which evaluates to False in if statement, which does what you what it to.
Related
I'm working on a ModelForm in Django that uses a Model, which has a custom CharField.
When handling form errors I want to show the user valid examples (I thought this shouldn't be part of the raised ValidationError). However, when I try to access the fields using myform.fields by getting the invalid fields and the corresponding error messages using myform.errors.items() type(myform.fields["myfield"]) returns <class 'django.forms.fields.CharField'> instead of my custom MyField.
Therefore myform.fields["myfield"].test() raises AttributeError: 'CharField' object has no attribute 'test'.
How do I get the correct MyField instance from form.fields?
class MyField(models.CharField):
pass
class MyModel(models.Model):
myfield = MyField(max_length=20)
class MyForm(forms.ModelForm):
class Meta:
exclude = tuple()
model = MyModel
myform = MyForm()
print(type(myform.fields["myfield"]))
A model field is different than a form field. A model field focusses on storing the data in the database, a form field.
You can define an extra form field, for example:
# app_name/forms/fields.py
from django.forms import CharField
class MyField(Field):
# …
pass
Now we can specify this form field as the default field for the MyField model field:
app_name/fields.py
class MyField(models.CharField):
# …
def formfield(self, **kwargs):
return super().formfield(**{
'form_class': app_name.forms.fields.MyField,
**kwargs,
})
If you thus now use your MyField model field (the second code fragment), it will use the MyField form field (the first code fragment).
django 2.0
I have a django model, with different slug fields:
from django.core.validators import validate_slug
class MyModel(models.Model):
# with slug field
slug_field = models.SlugField(max_length=200)
# or charfield with slug validator (should be exactly the same)
char_field = models.CharField(max_length=200, validators=[validate_slug])
The first problem i have, in my form i have a clean method, to validate the values of multiple fields, not individually. This method should theoretically be called after the clean_fields method, but it is called even if clean_fields raise an error.
My forms.py:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = '__all__'
def clean(self):
cleaned_data = super().clean()
print(cleaned_data.get('slug_field')) # > None
print(cleaned_data.get('char_field')) # > ééé; uncleaned data
print(self.errors) # only from slug_field
return cleaned_data
With SlugField, slug_field is not set in cleaned_data, when it's invalid, and after the error is raised and returned to the user by the form. (I don't see why clean() is even reached, because clean_fields() have raised the error before)
The problem is that with the CharField with any custom validator (validate_slug or a self made one), the uncleaned value is returned in cleaned_data. However, the validation error is still raised, but after.
This is quite dangerous for me, because i used to trust cleaned_data, to modify data not saved inside the model.
The clean() method is called after the field's validator. If the alias is invalid, then it won't be in cleaned_data. Your clean method should handle this case, for example:
def clean():
cleaned_data = super().clean()
print(self.errors) # You should see the alias error here
if 'alias' in cleaned_data:
print(cleaned_data['alias'])
# do any code that relies on cleaned_data['alias'] here
return cleaned_data
See the docs on cleaning fields that depend on each other for more info.
For ease of use, one of the fields in my form can have multiple foos, but eachfoo should/will create it's own record in the database. I have a ModelForm (MyForm) that captures the fields that are repeated for each instance of foo.
Inside my views.py I copy MyForm for each instance of foo. All that is working, except for the bit where the logged in user is set as the submitter of the form. The following error is thrown: null value in column "submitter_id" violates not-null constraint.
models.py
class MyModel(models.Model):
foo_bar = models.CharField(max_length=10)
foo_baz = models.CharField(max_length=10)
submitter = models.ForeignKey(User)
forms.py
class MyForm(forms.ModelForm):
class Meta:
Model = MyModel
exclude = ['foo_bar','foo_baz','submitter']
views.py
Note: obj is a dictionary of however many foos were entered into the form
my_form = MyForm(request.POST)
if my_form.is_valid():
for k,v in obj:
copy = MyForm(request.post)
copy.save(commit=False)
copy.foo_bar = k
copy.foo_baz = v
copy.submitter = request.user # I have inspected this, and request.user is an instance of the User model
copy.save() # <-- here is where I get the error above
You need to set it on the model instance, which is returned from the form save. Also, for some reason you are re-instantiating the form after checking it is valid; you should not do that.
if my_form.is_valid():
instance = copy.save(commit=False)
instance.submitter = request.user # I have inspected this, and request.user is an instance of the User model
instance.save()
(Instead of messing about with dicts containing copies of forms, you should use formsets.)
Try setting it in the following way
copy.instance.submitter = request.user
I'm totally stuck here. Why does this test case fail?
class BogusForm(forms.Form):
bogus_bool = forms.BooleanField()
class TestBogusForm(TestCase):
def test_bogus_false(self):
query_dict = QueryDict('', mutable=True)
query_dict.update({'bogus_bool': False})
bogus_form = BogusForm(query_dict)
self.assertTrue(bogus_form.is_valid())
It fails form field validation, but only if bogus_bool is False when I update the QueryDict. If I say:
query_dict.update({'bogus_bool': True})
Then it passes validation. What's going on here? Is this a bug in Django Forms?
If I look at the QueryDict before I pass it to the BogusForm constructor, it looks like this:
<QueryDict: {u'bogus_bool': [False]}>
Which looks totally legit and correct to me.
From django's documentation
Since all Field subclasses have required=True by default, the
validation condition here is important. If you want to include a
boolean in your form that can be either True or False (e.g. a checked
or unchecked checkbox), you must remember to pass in required=False
when creating the BooleanField.
I agree that this is incorrect behavior.
This should do for a specific field:
class BogusForm(forms.Form):
bogus_bool = forms.BooleanField(required=False)
def clean_bogus_bool(self):
field_name = 'bogus_bool'
if field_name not in self.data:
raise forms.ValidationError("This field is required.")
return self.cleaned_data[field_name]
This should do it for all bool fields on the form:
class BooleanFieldsRequiredMixin(forms.Form):
def clean(self):
for field_name, field in self.fields.iteritems():
# Only BooleanField not subclasses of it.
if type(field) is not forms.BooleanField:
continue
if field_name not in self.data:
self._errors[field_name] = self.error_class(["This field is required."])
return super(BooleanFieldsRequiredMixin, self).clean()
class BogusForm(BooleanFieldsRequiredMixin, forms.Form):
bogus_bool = forms.BooleanField(required=False)
There is a way to make this nicer by not requiring that required=False bit on the boolean field, but it's not worth the effort at the moment.
It's because your bogus_bool is a required field by default.
class BogusForm(forms.Form):
bogus_bool = forms.BooleanField(required=False)
should do the trick.
Let's say I've got a model and it has a foreign key to another one.
class ModelA(models.Model):
field = models.CharField(max_length=100)
class ModelB(models.Model):
model_a = models.ForeignKey(ModelA)
Than I've got this form:
class FormB(models.ModelForm):
model_a = forms.CharField(required=True)
def clean(self):
model_a = self.cleaned_data["model_a"]
try:
v = ModelA.objects.get(model_a=model_a)
self.cleaned_data["model_a"] = v
except Exception:
self._errors['model_a'] = ErrorList(["ModelA not found"])
return self.cleaned_data
Now, whenever I enter a char value in FormB, it'll search for it in the ModelA and
return the cleaned data.
When I use the form to list the pre-existing instance it shows the ID and not the value.
def my_view(request):
instance = ModelB.objects.get()[0]
form = FormB(instance=instance)
return render_to_response("bla.html", {"form" : form})
Does anybody knows how I could show the value in this CharField when I pass the instance?
Thanks,
Nico
I can think of two options:
Make model_a field on ModelB hidden with editable=false, and add a CharField to ModelB to store the text the user entered. Then show this field in the form, but use its value to populate model_a.
Use an autocomplete field, for example using django-autocomplete. This allows the user to type the name of the object instead of using a select widget. (But falls back to a select with no JavaScript).