How to overwrite clean and clean_fieldname for ModelFormMixin - django

I found this
How to properly overwrite clean() method
and this
Can `clean` and `clean_fieldname` methods be used together in Django ModelForm?
but it seems to work differently if one is using the generic class mixins ModelFormMixin.
My class is also derived from ProcessFormView.
Is def form_valid(self, form): the only point where I can overwrite the form handling process?

You are confusing views and forms. A CreateView for example makes use of a ModelForm to create an object. But instead of letting the view construct the ModelForm, you can specify such form yourself and then pass this as form_class to the view.
For example say you have a Category model with a name field, and you wish to validate that the name of the Category is all written in lowercase, you can define a ModelForm to do that:
from django import forms
from django.core.exceptions import ValidationError
class CategoryForm(forms.ModelForm):
def clean_name(self):
data = self.cleaned_data['recipients']
if not data.islower():
raise ValidationError('The name of the category should be written in lowercase')
return data
class Meta:
model = Category
fields = ['name']
now we can plug in that ModelForm as form for our CategoryCreateView:
from django.views.generic import CreateView
class CategoryCreateView(CreateView):
model = Category
form_class = CategoryForm
The validation thus should be done in the ModelForm, and you can then use that form in your CreateView, UpdateView, etc.

Related

Two model in one UpdateView "Django"

my views :
from .models import Settings, SocialMediaSetting
class UpdateSocialMediaSetting(LoginRequiredMixin, UpdateView):
model = SocialMediaSetting
fields = '__all__'
template_name = "staff/settings/social-media-settings-update.html"
class UpdateSettings(LoginRequiredMixin, UpdateView):
model = Settings
fields = '__all__'
template_name = "staff/settings/settings-update.html"
two different class in models without any relation, I want show both in one html and one form both models has just one object
you'll need to ditch the UpdateView, but this way won't take long to implement
Use a TemplateView (at least you won't have to change your other mixin)
Create two ModelForms
override the get_context_data() to pass the forms to the template
when forms are submitted, override the post() method to save them yourself
mchesler613 wrote an awesome article explaining how to do this here.
(make sure to give him a star)

how to remove specific form field from validating (is_valid) django

There are four field in django form and i am using is_valid() function to validate, but instead of validating the whole form i want to exclude one field which should not be validate using is_valid() function
You can use exclude parameter inside your form to exclude the fields you don't want to get validated.
Let's say you have a model named MyModel and a model form for it named MyForm
# forms.py
from django import forms
from .models import MyModel
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = ['field_name_you_want_to_exclude']
You can use the clean() method in your model to specify the fields you want to validate https://docs.djangoproject.com/en/2.1/ref/models/instances/#django.db.models.Model.clean

Django form.clean() run before field validators

https://docs.djangoproject.com/en/1.10/ref/forms/validation/
States that run_validators() is run before the form subclass’s clean().
My model looks like:
def validate_ascii(value):
try:
value.encode('ascii')
except UnicodeEncodeError:
raise ValidationError("Contains non-ascii characters")
class Keyword(models.Model):
name = models.CharField(max_length=50, unique=True, validators=[validate_ascii])
In my form's clean() method
class KeywordAdminForm(ModelForm):
class Meta:
model = Keyword
def clean(self):
import pdb; pdb.set_trace()
cleaned_data = super(KeywordAdminForm, self).clean()
import pdb; pdb.set_trace()
return super(KeywordAdminForm, self).clean()
After that, the validators for each field in the form is run. This is causing issues because my clean method assumes each field has had the validator run first and crashes.
Why is my form's clean() method being run before the validators on the field?
Change your forms clean() method to call cleaned_data = super(KeywordAdminForm, self).clean() first before performing the rest of your validation. This is how the docs recommend you do it
This section of the docs has an explanation for your issue.
Model validation (Model.full_clean()) is triggered from within the
form validation step, right after the form’s clean() method is called.
This would suggest that you cannot rely on any model validation in your clean method
According to "Validation on a ModelForm" paragraph of the docs:
There are two main steps involved in validating a ModelForm:
Validating the form
Validating the model instance
This defines that there are two completely different layers of validation, one in the model level and one in the form level.
Therefore it is clearly wrong to expect that these layers of validation are somehow related.
However, there is a sound solution as described in the "Overriding the Default Fields" paragraph of the same chapter:
If you want to specify a field’s validators, you can do so by defining
the field declaratively and setting its validators parameter.
Your example could become:
from django.forms import CharField, ModelForm
from myapp.models import Keyword
class KeywordAdminForm(ModelForm):
slug = CharField(max_length=50, validators=[validate_ascii])
class Meta:
model = Keyword
fields = '__all__'
Bear in mind though to read the green "Note" that follows this example which states that:
Similarly, fields defined declaratively do not draw their attributes
like max_length or required from the corresponding model. If you want
to maintain the behavior specified in the model, you must set the
relevant arguments explicitly when declaring the form field.
Alternatively, you can do something like this:
from django.forms import CharField, ModelForm
from myapp.models import Keyword, validate_ascii
class KeywordAdminForm(ModelForm):
def clean_slug(self):
slug = self.cleaned_data.get('slug')
validate_ascii(slug)
return slug
def clean(self):
cleaned_data = super().clean()
if self.errors:
return cleaned_data
...
return cleaned_data
class Meta:
model = Keyword
fields = '__all__'
The above code works because it potentially raises a ValidationError inside clean_<field>() that is called before clean().

'UpdateView with a ModelForm as form_class'-issue

The code I would like to get is for a page that has a simple form of one field to change a user's email address using an UpdateView.
Sounds simple, but the difficulty is that I want the URL mapping url(r'email/(?P<pk>\d+)/$', EmailView.as_view(),) not to use the id of the Model used in my ModelForm (User) but the id of another Model (Profile).
The id of a Profile instance of a specific user can be called as follows inside a view: self.user.get_profile().id. I am using the Profile model of the reusable app userena if you are wondering.
A (afaik not optimally implemented ¹) feature of an UpdateView is that if you want to use your own ModelForm instead of letting the UpdateView derive a form from a Model you need to(otherwise produces an Error) define either model, queryset or get_queryset.
So for my EmailView case I did the following:
forms.py
class EmailModelForm(forms.ModelForm):
class Meta:
model = User
fields = (
"email",
)
def save(self, *args, **kwargs):
print self.instance
# returns <Profile: Billy Bob's Profile> instead of <User: Billy Bob> !!!
return super(EmailModelForm, self).save(*args, **kwargs)
views.py
class EmailView(UpdateView):
model = Profile # Note that this is not the Model used in EmailModelForm!
form_class = EmailModelForm
template_name = 'email.html'
success_url = '/succes/'
I then went to /email/2/. That is the email form of the user that has a profile with id 2.
If I would run a debugger inside EmailView I get this:
>>> self.user.id
1
>>> profile = self.user.get_profile()
>>> profile.id
2
So far so good. But when I submit the form it won't save. I could overwrite the save method in the EmailModelForm but I'd rather override something in my EmailView. How can I do that?
¹ Because UpdateView could just derive the model class from the ModelForm passed to the form_class attribute in case it is a ModelForm.
Having your view and model form correspond to different models seems a bad idea to me.
I would set model = User in your EmailView, then override get_object so that it returns the user corresponding to the given profile id.

Reorder users in django auth

I have a model that has a ForeignKey to the built-in user model in django.contrib.auth and I'm frustrated by the fact the select box in the admin always sorts by the user's primary key.
I'd much rather have it sort by username alphabetically, and while it's my instinct not to want to fiddle with the innards of Django, I can't seem to find a simpler way to reorder the users.
The most straightforward way I can think of would be to dip into my Django install and add
ordering = ('username',)
to the Meta class of the User model.
Is there some kind of monkeypatching that I could do or any other less invasive way to modify the ordering of the User model?
Alternatively, can anyone thing of anything that could break by making this change?
There is a way using ModelAdmin objects to specify your own form. By specifying your own form, you have complete control over the form's composition and validation.
Say that the model which has an FK to User is Foo.
Your myapp/models.py might look like this:
from django.db import models
from django.contrib.auth.models import User
class Foo(models.Model):
user = models.ForeignKey(User)
some_val = models.IntegerField()
You would then create a myapp/admin.py file containing something like this:
from django.contrib.auth.models import User
from django import forms
from django.contrib import admin
class FooAdminForm(forms.ModelForm):
user = forms.ModelChoiceField(queryset=User.objects.order_by('username'))
class Meta:
model = Foo
class FooAdmin(admin.ModelAdmin):
form = FooAdminForm
admin.site.register(Foo, FooAdmin)
Once you've done this, the <select> dropdown will order the user objects according to username. No need to worry about to other fields on Foo... you only need to specify the overrides in your FooAdminForm class. Unfortunately, you'll need to provide this custom form definition for every model having an FK to User that you wish to present in the admin site.
Jarret's answer above should actually read:
from django.contrib.auth.models import User
from django.contrib import admin
from django import forms
from yourapp.models import Foo
class FooAdminForm(forms.ModelForm):
class Meta:
model = Foo
def __init__(self, *args, **kwds):
super(FooAdminForm, self).__init__(*args, **kwds)
self.fields['user'].queryset = User.objects.order_by(...)
class FooAdmin(admin.ModelAdmin):
# other stuff here
form = FooAdminForm
admin.site.register(Foo, FooAdmin)
so the queryset gets re-evaluated each time you create the form, as opposed to once, when the module containing the form is imported.