Ok so I actually solved this one by accident and would simply like to understand what happened.
I have my own user registration form BaseCreationForm which extends a ModelForm and uses a UserProfile as its model. All the validation methods were working fine, but the save method was giving me grief. Whenever I tried to create a user (the profile is created in the view, I may refactor this), Django would tell me that "BaseCreationForm object has no attribute cleaned data".
BUT, when out of frustration and running out of ideas I added a simple "print self" statement before creating the user in the save() method, the problem disappeared and users are being created normally. Below are a couple of clean() methods that work, the save() method and a snippet from the view that calls the clean() and save() method.
clean() methods working normally
#example clean methods, both work beautifully
def clean_email(self):
email = self.cleaned_data["email"]
if not email:
raise forms.ValidationError(self.error_messages['no_email'])
try:
User.objects.get(email=email)
except User.DoesNotExist:
return email
raise forms.ValidationError(self.error_messages['duplicate_email'])
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError(
self.error_messages['password_mismatch'])
return password2
save() method:
#save method requiring wizardry
def save(self, commit=True):
#This line makes it work. When commented, the error appears
print self
###
user = User.objects.create_user(
username=self.cleaned_data.get("username"),
first_name=self.cleaned_data["first_name"],
last_name=self.cleaned_data["last_name"],
email=self.cleaned_data["email"],
)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
And the view (some stuff left out):
class RegistrationView(FormView):
template_name = 'register.html'
form_class = BaseCreationForm
model = UserProfile
success_url = '/account/login/'
def form_valid(self, form):
form = BaseCreationForm(self.request.POST,
self.request.FILES)
user = form.save()
profile = user.get_profile()
profile.user_type = form.cleaned_data['user_type']
profile.title = form.cleaned_data['title']
profile.company_name = form.cleaned_data['company_name']
.
.
.
profile.save()
return super(RegistrationView, self).form_valid(form)
You shouldn't be re-instantiating the form inside the form_valid method. That's called when the form is already valid, and indeed the form is passed into the method. You should use that instead.
(Note that the actual error is because you haven't called form.is_valid() at all, but as I say above you shouldn't, because the view is already doing it.)
Related
In django forms, for saving other data I usually use form_valid() but as I can also use save() method of formclass.
Today I overrided save() instead of form_valid() and I got problem with my manytomanyfield.
When using , the values of manytomanyfield are not saved but when I use form_valid() they start saving. Can anybody tell me the reason and what are the differences between both, which is the most convenient method and in what situation?
Here is my overriding of save() method:
class ProductCreateForm(forms.ModelForm):
sizes = make_ajax_field(ProductCreateModel,'sizes','sizes')
colours = make_ajax_field(ProductCreateModel,'colours','colours')
class Meta:
model = ProductCreateModel
fields = ('title','category',
'regions',)
def __init__(self,*args,**kwargs):
self.request = kwargs.pop("request")
super(ProductCreateForm, self).__init__(*args, **kwargs)
def save(self):
product = super(ProductCreateForm, self).save(commit=False)
user = self.request.user
product.location = user.user_location
product.save()
return product
When I override form_valid() method:
def get_form_kwargs(self):
kwargs = super(ProductCreateView,self).get_form_kwargs()
kwargs.update({'request':self.request})
return kwargs
def form_valid(self, form):
product = form.save(commit=False)
user = self.request.user
form.instance.user = user
form.instance.location = user.user_location
form.save()
return super(ProductCreateView, self).form_valid(form)
sizes,colours and regions are m2m fields, as I mentioned when I overrides save() values of m2m not get saved but when I overrides form_valid they start saving.
If you save a form with commit=False, you must call the form's save_m2m method to save the many-to-many data. See the docs for more info.
If you decide to use the form_valid method, I would change the following things:
update the instance returned by form.save() and save it, instead of calling form.save() again.
explicitly call form.save_m2m()
return a redirect response instead of calling super().form_valid() (which will save the form again)
Putting that together, you get:
def form_valid(self, form):
product = form.save(commit=False)
product.user = self.request.user
product.location.location = user.user_location
product.save()
form.save_m2m()
return redirect('/success-url/')
About your problem with manytomany i guess is the order they do things... Form > Admin > Models, when you use form_valid is the first thing they do before check other things in chain, while using save is the last, maybe can be because or other things too...
The best way is always use form_valid instead of raw save
form_valid first check the Clean function if there is any native validations errors or custom validations and only then save your models
save just save it without validate then with your form with your validations
Example
from django import forms
class ContactForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = super().clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject:
# Only do something if both fields are valid so far.
if "help" not in subject:
raise forms.ValidationError(
"Did not send for 'help' in the subject despite "
"CC'ing yourself."
)
Source: https://docs.djangoproject.com/en/2.0/ref/forms/validation/
I am trying to create signup form adding email field in django from base class UserCreationForm. The code goes as
form.py
class ussignup(UserCreationForm):
email=forms.EmailField(required=True)
first_name=forms.CharField(required=False)
last_name=forms.CharField(required=False)
class Meta:
model=User
fields=('username','password1','password2','first_name','last_name','email',)
#fields=('username','password','first_name','last_name','email')
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.email=self.cleaned_data["email"]
user.first_name=self.cleaned_data["first_name"]
user.last_name=self.cleaned_data["last_name"]
if commit:
user.save()
return user
view.py
def signup(request):
if request.method=='POST':
form=ussignup(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/accounts/rgsuc')
args={}
args.update(csrf(request))
args['form']=form
return render_to_response('register.html',args)
urls.py
url(r'^accounts/signup',signup),
Error
output shows the form however the password entered in the field is not saved in database. Thus shows no password when viewed from admin and thus unable to login.
ok I finally got it, I need to add
user.set_password(self.cleaned_data["password1"])
which I thought it will be saved by django itself however I have overriden the save function thus need to save it also
Or else you could have only changed this (i.e. your class name ussignup, instead of the base class UserCreationForm) in the method, and it would have worked.
def save(self, commit=True):
user = super(ussignup, self).save(commit=False)
user.email=self.cleaned_data["email"]
user.first_name=self.cleaned_data["first_name"]
user.last_name=self.cleaned_data["last_name"]
if commit:
user.save()
return user
Hope this will help.
I'm trying to learn Django, and I am reading this link right now:
https://docs.djangoproject.com/en/1.5/topics/forms/modelforms/
If you scroll down in the link provided, it says that there are two main steps involved in validating a form and that the first step is 'validating the form' which leads to this link:
https://docs.djangoproject.com/en/1.5/ref/forms/validation/#form-and-field-validation
It says that the first step in every validation is to use the to_python() method on a field. I don't understand what they mean when they say
"It coerces the value to correct datatype and raises ValidationError if that is not possible. This method accepts the raw value from the widget and returns the converted value."
So suppose I have a model like this
class User(models.Model):
user_id = models.AutoField(unique=True, primary_key=True)
username = models.SlugField(max_length=50, unique=True)
first_name = models.CharField(max_length=50)
I created a form like so
class UserForm(forms.ModelForm):
class Meta:
model = User
now, how exactly do I use to_python() method? Do I use it in a view? Or do I have to use it in the forms.py file? If I use it in a view, what would the function be called?
Django does validates and deserializes fields input automatically.
Example view when posting form:
def my_view(request):
form = UserForm()
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid(): # here to_python() is run for each field
form.save()
# redirect
return render_to_response('home.html', { 'form': form })
You don't need to worry about the to_python() unless you are creating a custom field. If you are going to use the ModelForm to create simple forms, you can use the clean methods.
If you want to validate only one field, you can do this:
class UserForm(forms.ModelForm):
def clean_username(self):
username = self.cleaned_data['username']
if len(username) > 10:
raise forms.ValidationError("Please shorten your username")
# Always return the cleaned data, whether you have changed it or
# not.
return username
if you want to clean multiple fields, you can do this:
class Userform(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = super(UserForm, self).clean()
username = cleaned_data.get("username")
first_name = cleaned_data.get("first_name")
if len(username) > 10:
raise forms.ValidationError("Please shorten your username")
if len(first_name) < 1:
raise forms.ValidationError("First name is too short")
# Always return the full collection of cleaned data.
return cleaned_data
I am trying to program a Django CreateView (CBV), which takes instead of the user id the user email and determines (or creates) the user based on the email.
My model does not contain anything special:
class Project(models.Model):
name = models.CharField(_('Title'), max_length=100,)
user = models.ForeignKey(User, verbose_name=_('user'),)
...
My forms.py adds the additional email field to the form:
class ProjectCreateForm(forms.ModelForm):
email = forms.EmailField(required=True, )
class Meta:
model = Project
fields = ('name', ...,)
In my views.py, I am trying to determine if the user exists or should be created. In both cases, the user id should be saved as part of the Project instance.
class ProjectCreateDetails(CreateView):
form_class = ProjectCreateForm
template_name = '...'
success_url = reverse_lazy('login')
model = Project
def form_valid(self, form):
try:
user = User.objects.get(email=form.email)
except User.DoesNotExist:
user = User.objects.create_user(form.email, form.email, ''.join([random.choice(string.digits + string.letters) for i in range(0, 10)]))
user.save()
form.instance.user = user
return super(ProjectCreateDetails, self).form_valid(form)
However I am facing an error that the 'Solution' object has no attribute 'email'.
Do I need to switch to a FormView instead of a CreateView?
You get the error 'Solution' object has no attribute 'email' because form.email is invalid. Validated data is never available as attributes of a form or model form. When forms (including model forms) are valid, the successfully validated data is available in the form.cleaned_data dictionary.
Note that you don't need to call user.save(). The create_user call has already added the user to the database. You don't have to generate a random password either -- if password is None, then create_user will set an unusable password.
Finally, make sure that you do not include the user field in the ProjectCreateForm. You probably do not, but your code says fields = ('name', ...,) so I can't tell for sure.
Put it together and you get the following (untested) code:
def form_valid(self, form):
try:
user = User.objects.get(email=form.cleaned_data['email'])
except User.DoesNotExist:
user = User.objects.create_user(form.cleaned_data['email'], form.cleaned_data['email'])
form.instance.user = user
return super(ProjectCreateDetails, self).form_valid(form)
I ran today into a special situation. Previously I had the following in my view.py
def register_page(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username=form.cleaned_data['username'],
password=form.cleaned_data['password2'],
email=form.cleaned_data['email']
)
return HttpResponseRedirect('/register/success/')
else:
form = RegistrationForm()
variables = RequestContext(request, {'form':form})
return render_to_response('registration/register.html', variables)
It was pretty straight forward retrieving the username, email and password to create a new user after she has registered. But now I have refactored it to use a hash code as the username and utilize the email alone to register and login.
The shortened RegistrationForm looks like this:
class RegistrationForm(forms.ModelForm):
email = forms.EmailField(label=_("Email"))
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput))
class Meta:
model = User
fields = ("email",)
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
email = self.cleaned_data['email']
user.username = md5(email).digest().encode('base64')[:-1]
if commit:
user.save()
return user
The new form doesn't have the username any longer, since it is calculated and not entered by the user any more. But how do I retrieve the username from the view ? The new code is not from me and I have it from a blog. Maybe the key is here in the Meta class? From the documentation I wasn't able to fully understood what he is trying to achieve with the Meta class here...
Many Thanks,
EDIT:
Ok I think I understand now how the subclassing should work. I tried to subclass the User class like this:
class cb_user_model_backend(ModelBackend):
def create_user(self, email=None, password=None):
"""
Creates and saves a User with the given email and password only.
"""
now = timezone.now()
username = md5(email).digest().encode('base64')[:-1]
email = UserManager.normalize_email(email)
user = self.model(username=username, email=email,
is_staff=False, is_active=True, is_superuser=False,
last_login=now, date_joined=now)
user.set_password(password)
user.save(using=self._db)
return user
The problem I am facing now are two errors, self._db and self.model, were meant to be on the base user class. How do get to them from here?
Edit 2:
PyCharm complains that the two self._db and seld.model don't exit on current cb_user_model_backend.
Note the View is refactored to take two parameters:
user = User.objects.create_user(
password=form.cleaned_data['password2'],
email=form.cleaned_data['email']
)
When running it stack trace is:
Exception Type: TypeError
Exception Value:
create_user() takes at least 2 arguments (3 given)
Try subclassing your save method in your models.py:
def save(self, *args, **kwargs):
if not self.id:
self.username = md5(self.email).digest().encode('base64')[:-1]
super(ModelName, self).save(*args, **kwargs)
After calling user.save(), user.username should yield the generated username in your views. Hope this helps.
EDIT:
If you want to call create_user(**kwargs), you could do the following in your views.py:
email = self.cleaned_data['email']
username = md5(email).digest().encode('base64')[:-1]
u = User.objects.create_user(username = username, email = email, password = password)