I've created a form which is a forms.ModelForm. On the "view" side, I've created a view which is a generic.UpdateView.
In those 2 differents classes, I have is_valid() on one side, and form_valid() on the other side.
class ProfileForm(FormForceLocalizedDateFields):
class Meta:
model = Personne
fields = ('sexe', 'statut', 'est_fumeur',
'est_physique', 'date_naissance')
exclude = ('user', 'est_physique')
# blabla fields declaration
def is_valid(self):
pass
and edit view:
class EditView(LoginRequiredMixin, generic.UpdateView):
model = Personne
template_name = 'my_home/profile/edit.html'
form_class = ProfileForm
success_url = reverse_lazy('my_home_index')
# blabla get_initial() and get_object() and get_context_data()
def form_valid(self, form):
# username = form.cleaned_data['username']
# Hack: redirect on same URL:
# - if user refreshes, no form re-send
# - if user goes back, no form re-send too, classical refresh
site_web = u"{0}://{1}".format(
self.request.scheme, self.request.META['HTTP_HOST']
)
return HttpResponseRedirect(u'{0}{1}'.format(
site_web, self.request.META['PATH_INFO']
))
My form shows 3 fields of 3 different models :
User,
Person which has a foreign key to User
Picture which has a foreign key to Person
Where should I create the code that update those fields, and why?
generic.UpdateView is supposed to help us when updating fields, but it seems that when you have fields not belonging to the model you edit, you have to write all the "update" by hand.
is_valid on the surface just tells you whether or not the form is valid, and thats the only job it should ever do..
From the source code:
def is_valid(self):
"""
Returns True if the form has no errors. Otherwise, False. If errors are
being ignored, returns False.
"""
return self.is_bound and not self.errors
Underneath this, what it also does is (from docs)
run validation and return a boolean designating whether the data was valid:
The validation is ran because errors is a property that will call full_clean if the validation hasn't been called yet.
#property
def errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors
Where should I create the code that update those fields, and why?
In the form_valid method because by this point you've found out that your validation has verified that it is safe to update your model.
Related
I have a custom create_user() method that I use to create the user but I would like to shift to the CreateView view instead.
I need to supply some additional parameters before the user is created and need the user object created to do some more processing (send an email) etc.
How can I manipulate the object creation in CreateView. I can put my create_user method in the form_valid method of the view but what do I return then (return super(UserCreate, self).form_valid(form)) will create a new object.
class UserCreate(generic.CreateView):
model = User
template_name = 'crm2/create_user.html'
fields = ['email', 'username', 'namespaces']
success_url = reverse_lazy('crm2:userCreate')
def form_valid(self, form):
user = create_user(self.request.POST['username'],
self.request.POST['email'], str(uuid4()),
self.request.POST['namespaces'])
reset_link = get_reset_link(user)
send_mail_set_password(user, reset_link)
return HttpResponseRedirect(self.get_success_url())
[[ create_user needs username, email, password, namespaces ]]
The error is: 'NoneType' object has no attribute '__dict__'
I figured it out by looking at how CreateView works.
The incoming request is processed by the post() method of ProcessFormView, which calls get_form_class() and get_form(). This latter method deals with a POST request, so the code of get_form_kwargs() in FormMixin adds to the keywords dictionary the submitted data.
Now the form is bound (that is, it contains user supplied data or files) and the post() method now tests the result of is_valid() and acts accordingly calling either form_valid() or form_invalid().
The FormMixin class puts the result of form.save() into self.object. The form.save() method for modelforms is defined by BaseModelForm and basically saves the instance of the Django model connected with the modelform, that is implements the actual creation at the base of the CreateView form view. As form.save() returns the object saved to the database, it makes sense to store it in self.object and pass it to the template.
Hence,
class UserCreate(generic.CreateView):
model = User
template_name = 'crm2/create_user.html'
fields = ['email', 'username', 'namespaces']
success_url = reverse_lazy('crm2:userCreate')
def get_form(self, form_class):
form = super(UserCreate, self).get_form(form_class)
return form
def form_valid(self, form):
self.object = create_user(self.request.POST['username'],
self.request.POST['email'], str(uuid4()),
self.request.POST['namespaces'])
reset_link = get_reset_link(self.object)
send_mail_set_password(self.object, reset_link)
return HttpResponseRedirect(self.get_success_url())
My django form has errors in the initial page load, before the form even has a chance to be submitted.
My view:
def example_function(request):
if request.method == 'POST':
# the request is GET
else:
form = MyForm(user=request.user)
import pdb;pdb.set_trace()
return render_to_response('templates/example.html', locals(), context_instance=RequestContext(request),)
Where I have my pdb imported, in the console I can see that my form already has errors. The output of form.errors in my console is all the fields in the model which are set to not null.
(Pdb) form.errors
{'example_field_1': [u'This field is required.'], 'example_field_2': [u'This field is required.']}
The form has not submit yet, but I am still getting errors. Can someone explain?
I'm using django 1.4.
My form:
class MyForm(forms.ModelForm):
captcha = ReCaptchaField()
_readonly_template = form.TextInput(attrs={'readonly':'readonly'})
first_name = forms.CharField(widget = _readonly_tempalte)
def __init__(self, data=None, *args, **kwargs):
data = data or {}
if 'user' in kwargs:
user = kwargs['user']
del kwargs['user']
data.update({
'first_name' : user.first_name,
})
super(MyForm, self).__init__(data, *args, **kwargs)
class Meta:
model = MyModel
My model:
class MyModel(models.Model):
first_name = models.CharField(max_length=255)
example_field_1 = models.CharField(max_length=255)
example_field_2 = models.CharField(max_length=255)
https://docs.djangoproject.com/en/1.8/ref/forms/validation/
accessing the form.errors attribute will trigger the various form validation methods. Those errors shouldn't show up when you render the form.
I'm not sure how the user field is structured, but keep in mind that if you want the user name, you may want to change that from request.user to request.user.username.
I hope you resolved your issue, but in case you haven't, I had a similar issue which I was able to resolve by using "or None" when setting the form after checking if it is a POST (or GET) request.
In your case it looks like this may be a slightly different issue, but I wondered if this snippet might fix things up:
if request.method == "POST":
form = MyForm(request.POST or None)
# .. do stuff....
else: #.....this is a GET
data = {'user': request.user.username} #note this is changed to username
form = MyForm(data)
Not sure if still useful, but adding it here, as I just ran into this for my ChoiceField items within my form.
I was getting the same error messages, but eventually found out I had forgotten to ad 'or None' when initiating the form inside my view.
The initial code inside my view function that was displaying the error messages from the start:
form=FormName(request.POST)
I just added the 'or None' to it:
form=FormName(request.POST or None)
And all good after that.
Don't you need to do something like this
form = NameForm(request.POST)
Rather then attempting to use the user object to populate the form? Will the user object have an example_field_1 in it?
https://docs.djangoproject.com/en/1.8/topics/forms/
This is the normal behavior.
Some properties of fields are checked on client side. The error messages belong to the form, are part of the html but are not displayed until needed. It saves a client-server request.
I want to compare a field (manytomany) before and after a .save() to know which entries have been deleted. I have tried:
def save(self):
differentiate_before_subscribed = Course.objects.get(id=self.id).subscribed.all()
super(Course, self).save() # Call the "real" save() method.
differentiate_after_subscribed = Course.objects.get(id=self.id).subscribed.all()
#Something
But both differentiate_before_subscribed and differentiate_after_subscribed have same value. I have to use signals? And how?
Edit :
def addstudents(request, Course_id):
editedcourse = Course.objects.get(id=Course_id) # (The ID is in URL)
# Use the model AssSubscribedForm
form = AddSubscribedForm(instance=editedcourse)
# Test if its a POST request
if request.method == 'POST':
# Assign to form all fields of the POST request
form = AddSubscribedForm(request.POST, instance=editedcourse)
if form.is_valid():
# Save the course
request = form.save()
return redirect('Penelope.views.detailcourse', Course_id=Course_id)
# Call the .html with informations to insert
return render(request, 'addstudents.html', locals())
# The course model.
class Course(models.Model):
subscribed = models.ManyToManyField(User, related_name='course_list', blank=True, null=True, limit_choices_to={'userprofile__status': 'student'})
When you save a model form, first the instance is saved, then the method save_m2m is called separately (save_m2m is called automatically unless you save the form with commit=False, in which case you must call it manually). You get the same result for both query sets because the many to many field is saved later.
You could try using the m2m_changed signal to track changes to the many to many field.
Initial suggestion (didn't work):
Django querysets are lazy. In this case, the first queryset is not evaluated until after the model has been saved, so it returns the same results as the second queryset.
You can force the queryset to be evaluated by using list.
differentiate_before_subscribed = list(Course.objects.get(id=self.id).subscribed.all())
I am trying to edit django.contrib.auth.forms.UserChangeForm. Basically, auth_user's user edit page.
https://github.com/django/django/blob/master/django/contrib/auth/forms.py
According to source code, the form does not have a save() method, so it should inherit from forms.ModelForm right?
For full code, see here
class MyUserAdminForm(forms.ModelForm):
class Meta:
model = User
def __init__(self, *args, **kwargs):
super(MyUserAdminForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id: # username and user id
... the rest of the __init__ is setting readonly fields
.... some clean methods .....
def save(self, *args, **kwargs):
kwargs['commit'] = True
user = super(MyUserAdminForm, self).save(*args, **kwargs)
print user.username
print 'done'
return user
When I hit save, it said 'UserForm' object has no attribute 'save_m2m'. I've googled quite a bit, and tried to use add() but didn't work. What's causing this behaviour?
The thing is: the two print statements are printed. But the value never saved into database. I thought that the 2nd line would have saved once already.
Thanks
Remove the kwargs['commit'] = True line and see what happen.
Django Admin would invoke form.save_m2m(), which is hooked to the form when commit is False, here. The unconditional overriding of kwargs['commit'] = True would break the setattr of save_m2m() to form thus no attribute error is raised. The actual affected logic is here:
def save_form(self, request, form, change):
"""
Given a ModelForm return an unsaved instance. ``change`` is True if
the object is being changed, and False if it's being added.
"""
return form.save(commit=False)
You could find out that your version of form.save() overriding commit=False to commit=True unconditionally, thus Django Admin fails to continue as it believes form.save(commit=False) is invoked and thus form.save_m2m() needs to be called.
Refs the doc:
Another side effect of using commit=False is seen when your model has
a many-to-many relation with another model. If your model has a
many-to-many relation and you specify commit=False when you save a
form, Django cannot immediately save the form data for the
many-to-many relation. This is because it isn't possible to save
many-to-many data for an instance until the instance exists in the
database.
To work around this problem, every time you save a form using
commit=False, Django adds a save_m2m() method to your ModelForm
subclass. After you've manually saved the instance produced by the
form, you can invoke save_m2m() to save the many-to-many form data.
I have a Django form with a username and email field. I want to check the email isn't already in use by a user:
def clean_email(self):
email = self.cleaned_data["email"]
if User.objects.filter(email=email).count() != 0:
raise forms.ValidationError(_("Email not available."))
return email
This works, but raises some false negatives because the email might already be in the database for the user named in the form. I want to change to this:
def clean_email(self):
email = self.cleaned_data["email"]
username = self.cleaned_data["username"]
if User.objects.filter(email=email, username__ne=username).count() != 0:
raise forms.ValidationError(_("Email not available."))
return email
The Django docs say that all the validation for one field is done before moving onto the next field. If email is cleaned before username, then cleaned_data["username"] won't be available in clean_email. But the docs are unclear as to what order the fields are cleaned in. I declare username before email in the form, does that mean I'm safe in assuming that username is cleaned before email?
I could read the code, but I'm more interested in what the Django API is promising, and knowing that I'm safe even in future versions of Django.
Update
.keyOrder no longer works. I believe this should work instead:
from collections import OrderedDict
class MyForm(forms.ModelForm):
…
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
field_order = ['has_custom_name', 'name']
reordered_fields = OrderedDict()
for fld in field_order:
reordered_fields[fld] = self.fields[fld]
for fld, value in self.fields.items():
if fld not in reordered_fields:
reordered_fields[fld] = value
self.fields = reordered_fields
Previous Answer
There are things that can alter form order regardless of how you declare them in the form definition. One of them is if you're using a ModelForm, in which case unless you have both fields declared in fields under class Meta they are going to be in an unpredictable order.
Fortunately, there is a reliable solution.
You can control the field order in a form by setting self.fields.keyOrder.
Here's some sample code you can use:
class MyForm(forms.ModelForm):
has_custom_name = forms.BooleanField(label="Should it have a custom name?")
name = forms.CharField(required=False, label="Custom name")
class Meta:
model = Widget
fields = ['name', 'description', 'stretchiness', 'egginess']
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
ordered_fields = ['has_custom_name', 'name']
self.fields.keyOrder = ordered_fields + [k for k in self.fields.keys() if k not in ordered_fields]
def clean_name(self):
data = self.cleaned_data
if data.get('has_custom_name') and not data.get('name'):
raise forms.ValidationError("You must enter a custom name.")
return data.get('name')
With keyOrder set, has_custom_name will be validated (and therefore present in self.cleaned_data) before name is validated.
The Django docs claim that it's in order of the field definition.
But I've found that it doesn't always hold up to that promise.
Source: http://docs.djangoproject.com/en/dev/ref/forms/validation/
These methods are run in the order
given above, one field at a time. That
is, for each field in the form (in the
order they are declared in the form
definition), the Field.clean() method
(or its override) is run, then
clean_(). Finally, once
those two methods are run for every
field, the Form.clean() method, or its
override, is executed.
There's no promise that the fields are processed in any particular order. The official recommendation is that any validation that depends on more than one field should be done in the form's clean() method, rather than the field-specific clean_foo() methods.
The Form subclass’s clean() method. This method can perform any
validation that requires access to multiple fields from the form at
once. This is where you might put in things to check that if field A
is supplied, field B must contain a valid email address and the like.
The data that this method returns is the final cleaned_data attribute
for the form, so don’t forget to return the full list of cleaned data
if you override this method (by default, Form.clean() just returns
self.cleaned_data).
Copy-paste from https://docs.djangoproject.com/en/dev/ref/forms/validation/#using-validators
This means that if you want to check things like the value of the email and the parent_email are not the same you should do it inside that function. i.e:
from django import forms
from myapp.models import User
class UserForm(forms.ModelForm):
parent_email = forms.EmailField(required = True)
class Meta:
model = User
fields = ('email',)
def clean_email(self):
# Do whatever validation you want to apply to this field.
email = self.cleaned_data['email']
#... validate and raise a forms.ValidationError Exception if there is any error
return email
def clean_parent_email(self):
# Do the all the validations and operations that you want to apply to the
# the parent email. i.e: Check that the parent email has not been used
# by another user before.
parent_email = self.cleaned_data['parent_email']
if User.objects.filter(parent_email).count() > 0:
raise forms.ValidationError('Another user is already using this parent email')
return parent_email
def clean(self):
# Here I recommend to user self.cleaned_data.get(...) to get the values
# instead of self.cleaned_data[...] because if the clean_email, or
# clean_parent_email raise and Exception this value is not going to be
# inside the self.cleaned_data dictionary.
email = self.cleaned_data.get('email', '')
parent_email = self.cleaned_data.get('parent_email', '')
if email and parent_email and email == parent_email:
raise forms.ValidationError('Email and parent email can not be the same')
return self.cleaned_data