Programmatically set field value after POST in Django Forms - django

I have a user registration form, and I want the initial password to be the same as the social security number. For some reason, I need to keep the actual password input, so I only hid it.
Now I'm struggling with setting the value of the password before it gets validated. I was under the impression that clean() calls the validation stuff, so naturally I wrote this:
def clean(self):
self.data['password1'] = self.data['password2'] = self.data['personal_number']
return super(SomeForm, self).clean()
This is however not working, because the field apparently gets validated before I can populate it. Help?

def clean_password1(self):
return self.cleaned_data['personal_number']
def clean_password2(self):
return self.cleaned_data['personal_number']

Related

PassWordChangeForm for other user than current one

I tried to follow some tutorials to implement the password change functionality, but the problem is, that its always for the currently authenticated user. I want for example an admin to be able to change the password of another user (not through the admin panel).
class PasswordChangeForm(PasswordChangeForm):
def __init__(self, *args, **kwargs):
super(PasswordChangeForm, self).__init__(*args, **kwargs)
self.fields['old_password'].widget.attrs['class'] = 'form-control'
self.fields['new_password1'].widget.attrs['class'] = 'form-control'
self.fields['new_password2'].widget.attrs['class'] = 'form-control'
My view looks like this:
def changepassword(request):
user = User.objects.get(id = request.POST.get("id"))
if request.POST.get("type") == "user_changepw":
form = PasswordChangeForm(user=user)
else:
form = PasswordChangeForm(data=request.POST, user=user)
if form.is_valid():
form.save()
return render(request, 'user_changepw.html')
The user is coming here from a list of all the users with corresponding buttons that have as input the id of that user and also a hidden input "user_changepw", so that the form isnt throwing errors the first time you get on the site. But this also seems to be the problem, because after that the "id" value in the POST request is lost, so that the attempt to fetch the user form the DB always fails, beacuse request.POST.get("id") is now None. What we be a good solution to keep the user-id in the function or how to pass it through so that it persists also cases, where the form threw errors?

How do you only run a validator on a form field at the end after no validation errors have been raised?

I would like to only run a specific checksum validation if things like required, min and max validations as well as a custom is_digit() validation is run.
The reason is I do not want to show the error message for the checksum validation if some other validation is failing.
I've tried:
id_number = ZaIdField(
required=False,
max_length=13,
min_length=13,
validators=[validate_numeric, ]
)
then I have the checksum validator after others run in super():
class ZaIdField(forms.CharField):
'''
Field for validating ZA Id Numbers
'''
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def validate(self, value):
"""Check if id is valid"""
# Use the parent's handling of required fields, etc.
super().validate(value)
validate_sa_id(value)
Update:
In other words, my final validation is dependent on the length being correct and all digits.
So I just want to ensure that is correct before running it.
Check the django docs for more information. It's pretty simple actually.
def clean_id_number(self):
data = self.cleaned_data['id_number']
if checksum:
raise forms.ValidationError("Checksum error!")
return data
This has probably been answered somewhere before but it looks like the right palce to do this is in the form's clean():
def clean(self):
cleaned_data = super().clean()
id_num = cleaned_data.get('id_number')
if id_num:
validate_sa_id(id_num)
return cleaned_data
The key part of the docs is:
By the time the form’s clean() method is called, all the individual
field clean methods will have been run (the previous two sections), so
self.cleaned_data will be populated with any data that has survived so
far.
So you just check if the field has survived, if it has then it has passed prior validations
If you want to mess with the order of the validators, I would override the ZaIdFieldsrun_validators method.
Note that the fields validate method that you're overriding will always be called before.
Example (untested):
class ZaIdField(forms.CharField):
'''
Field for validating ZA Id Numbers
'''
def run_validators(self, value):
super().run_validators(value) # will potentially throw ValidationError, exiting
validate_sa_id(value) # your late validator, will throw its own ValidationError

Form validation on insert vs. update

I am trying to make one form for both inserting and updating data. I have read these:
Model validation on update in django
django exclude self from queryset for validation
In my project, however, I am not using ModelForm.
forms.py:
This is the form the user sees when registering his/her username and first_name. It is also the form an existing user sees when trying to change his/her username and/or first_name.
from django import forms
from .models import User
class SettingsForm(forms.Form):
username = forms.CharField(max_length=16)
first_name = forms.CharField(max_length=32)
# ... and many more form fields
def clean_slug(self):
"""Make sure that the username entered by the user will be unique in the database"""
username = self.cleaned_data['username']
try:
product = User.objects.get(username=username)
except User.DoesNotExist:
# Good, there is no one using this username
pass
else:
# There is alreaady a user with this username
raise forms.ValidationError('This username has been used. Try another.')
return username
The form cleaning works as intended for inserting data. However, when updating data, it complains that the username has been used (naturally, since the username already exists in the database).
How do I update the data without raising ValidationError when using a Form (and without using ModelForm)?
(The reasons for not using ModelForm in this case are: we may stop using the the orm, SettingsForm may contain a mix of fields from different models, some fields may be repeated hundreds of times in the form that is displayed to the user, we also need custom fields not tied to any model, ... and other scenarios that make using ModelForm quite challenging (impossible?). The purpose of this question is to find out if there are ways of achieving the desired result without using ModelForm.)
You have three cases:
The user is new
The user exists and doesn't change his/her username
The user exists and changes his/her username
You need to check if the username already exists only in the first two cases.
If it's an existing user you should pass the User instance to the form so you can use it in the clean_slug function, let's say in self.user variable.
Then you could just add two lines in the clean_slug function and it should work as you wish:
def clean_slug(self):
"""Make sure that the username entered by the user will be unique in the database"""
username = self.cleaned_data['username']
# If the user exists and the username has not been changed,
# just return the username
if self.user and self.user.username == username:
return username
try:
product = User.objects.get(username=username)
except User.DoesNotExist:
# Good, there is no one using this username
pass
else:
# There is alreaady a user with this username
raise forms.ValidationError('This username has been used. Try another.')
return username
The ValidationError is obviously because you're instantiating the SettingsForm when the username already exists, as you've already stated.
So if you want to add a form that can do double-duty, I would add an __init__ to SettingsForm that takes an is_update and saves it as a member variable, like so...
def __init__(self, is_update=False, **kwargs):
self.is_update = is_update
return super(SettingsForm, self).__init__(**kwargs)
then modify your clean_slug() to be:
def clean_slug(self):
username = self.cleaned_data['username']
try:
product = User.objects.get(username=username)
except User.DoesNotExist:
# Good, there is no one using this username
pass
else:
if not self.is_update: # for 'create' usage
# There is already a user with this username
raise forms.ValidationError('This username has been used. Try another.')
else: # for 'update' usage
pass
return username
You actually want your form to do two different things depending if it is a create or an update.
So either have two forms with a different clean_slug method or pass in an argument telling the form if it is an update or a create (there is another answer from neomanic showing this way).
Personally I think the easiest way would be to subclass your form and change the clean slug method. The use the new form for updates and your original form for creates.
class UpdateSettingsForm(settingsForm):
def clean_slug(self):
username = self.cleaned_data['username']
return username

Getting the value from a field in a Django ModelForm before validation

Alright, so I have a form set up like so:
class LeadForm(forms.ModelForm):
class Meta:
model = Lead
fields = ['email']
In my views, I want to get the text the users puts into the field before validating it.
def index(request):
if request.method == 'POST':
lead_form = LeadForm(request.POST)
// This is the problem part
email = str(lead_form.fields['email'])
print email
if Lead.objects.get(email=email).exists():
return HttpResponse('Already signed up')
if lead_form.is_valid():
// Do something
else:
lead_form = LeadForm()
return render(request, 'myapp/index.html', {
'lead_form' : lead_form,
})
As you can see, I am trying to get the text from the email field and before validating, I want to check something. When I print the email, I get this in my terminal:
<django.forms.fields.EmailField object at 0x109c35d90>
What I want is the text however, as I am trying to see if a lead with that email exists or not. How can I do this? Any help will be appreciated!
This is validation. It should be done in the form; you can define a clean_email method on LeadForm to do it.
To answer your actual question though, you can access request.POST['email'] directly if you want. But don't do that; do it in the form.
Try like this,
email=request.POST.get('email')
print email

Any danger in manually setting cleaned_data in Django?

This may be a dumb question, but I'm a bit unsure if it's safe to manually set the cleaned_data. The docs says:
Once is_valid() returns True, you can process the form submission
safe in the knowledge that it conforms to the validation rules defined
by your form. While you could access request.POST directly at this
point, it is better to access form.cleaned_data. This data has not
only been validated but will also be converted in to the relevant
Python types for you.
For more context, say we have a modelform which has several fields such as a book's title, book's author, and a field which asks for a url.
The form conditions are: if the url field is empty, the user must provide the title and author. If the url field is given and nothing else, I would parse the html from the given url and extract the title and author automatically for the user.
In the case where I automatically grab the title and author from the url, what would be the best way to handle saving this data to the model, since the form would return an empty cleaned_data for author and title? I made sure the data parsed will conform to the validate rules I have in the model, but setting cleaned_data like this seems suspicious.
In modelform class:
def save(self, commit = True, *args, **kwargs):
parsed_title = ... # String returned by my html parsing function
parsed_author = ... # String returned by my html parsing function
self.cleaned_data['title'] = parsed_title
self.cleaned_data['author'] = parsed_author
EDIT:
Thanks, I made it like so:
def save(self, commit=True, *args, **kwargs):
instance = super(BookInfoForm, self).save(commit=commit, *args, **kwargs)
....
instance.title = parsed_title
instance.author = parsed_author
return instance
This is a bit off topic since you've already answered the original question, but the above code breaks some other part. Instead of saving the compiled info to http://..../media/books/<id> where <id> is the book id, it saves it to http://..../media/books/None.
I have a add/edit function in my views.py that handles adding and editing:
def insert_or_modify(request, id=None):
if id is not None:
book = BookModel.objects.get(pk=id)
else:
book = BookModel()
if request.method == 'POST':
form = BookInfoForm(request.POST, instance=book)
if form.is_valid():
form.save()
....
return render_to_response(...)
Is there a way to make sure the id is present so that I won't get id=None? I guess more specifically, in the save() in the modelform, is there a way to create a new instance with an id if instance.id = None? Although I thought calling super(ModelForm, self).save(...) would do that for me?
Thanks again!
In the case you present, your intention isn't setting the cleaned_data, but the model data. Therefore, instead of setting cleaned_data in the save method, just set the attributes of self.instance and then save it.
About setting cleaned_data manually, I don't think it's necessarily wrong, it may make sense to do it in the form's clean method for some cross-field validation, although it's not a common case.