new in the field and struggling with a form validation in practice. I have created a form which i use as data input for a search in the DB. Form validation is only being triggered for the first field while the others seems not to be taken in consideration, bellow the code:
Form description:
class SearchForm(forms.Form):
cnp_nbr = forms.IntegerField(label='CNP', widget=forms.TextInput(attrs={'class': 'form-control' }), required=False)
first_name = forms.CharField(label='First Name', widget=forms.TextInput(attrs={'class': 'form-control'}), required=False)
last_name = forms.CharField(label='Last Name', widget=forms.TextInput(attrs={'class': 'form-control'}), required=False)
class Meta():
model = Clients
fields = ('cnp_nbr','first_name','last_name')
def clean(self): # most used!!!
all_clean_data = super().clean()
cnp_nbr = all_clean_data['cnp_nbr']
first_name = all_clean_data['first_name']
last_name = all_clean_data['last_name']
if cnp_nbr is None or (first_name is None and last_name is None):
raise forms.ValidationError("Enter f1 or f2&f3")
super().clean()
Views:
class ClientsSearch(generic.FormView):
form_class = forms.SearchForm
template_name = "administration/clients_search.html"
success_url = reverse_lazy('administration:searchresults')
def form_valid(self, form):
self.request.session['cnp_nbr'] = form.cleaned_data['cnp_nbr']
self.request.session['first_name'] = form.cleaned_data['first_name']
self.request.session['last_name'] = form.cleaned_data['last_name']
return super().form_valid(form)
class SearchResults (generic.ListView):
model = models.Clients
template_name='administration/search_results.html'
context_object_name = 'all_search_results'
def get_queryset(self):
return self.model.objects.filter(
Q(cnp_nbr__exact=self.request.session['cnp_nbr']) | Q(first_name__exact=self.request.session['first_name']) & Q(last_name__exact=self.request.session['last_name'])
)
HTML for search form:
<form method="POST" >
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary" id="search_submit" name = "search_submit" type="submit" > Search</button>
</form>
Validation is working only for cnp_nbr, i even tested them one by one.
This works :
if cnp_nbr is None:
raise forms.ValidationError("Enter field")
This doesnt
if first_name is None:
raise forms.ValidationError("Enter field")
Search is working just fine! Thanks in advance
That's because a not required IntegerField will be None if it's empty, whereas a not required CharField will be '' (the empty string) if it's empty. So you should not check last_name is None but just not last_name:
if cnp_nbr is None or not (first_name or last_name):
Note: Also I'd advise you to never directly assume the keys are present in the form's cleaned_data: Do first_name = all_clean_data.get('first_name') instead.
Note2: Why don't you just make cnp_nbr required since your ValidationError will trigger in any case if cnp_nbr isn't filled?
Related
i'm trying to allow a user to update their user profile with a city, description, website address etc.
Using Django 2.0, I have two forms in one view:
EditProfileForm (EPF) = Form for email, first and last name and password
The EditProfileForm seems to be able to save data. However, EditUserProfile seems to not.
EditUserProfile (EUP) = Form for further user info such as city, description, website address etc.
When entering the data and submitting the form, the data for EUP form doesn't appear to save or update the user information
I've also tried methods such as:
if form_EUP.is_valid():
obj = form_EUP.save(commit=False)
obj.user = request.user
obj.save()
and trying to create a similar custom save method to the format used in RegistrationForm but i've had no luck
I'm a bit of a beginner to the django framework so any ideas would be much appreciated, cheers.
views.py
def edit_profile(request):
if request.method == 'POST':
form_EPF = EditProfileForm(request.POST, instance=request.user)
form_EUP = EditUserProfile(request.POST, instance=request.user)
if form_EPF.is_valid():
form_EPF.save()
return redirect(reverse('accounts:view_profile'))
if form_EUP.is_valid():
form_EUP.save()
return redirect(reverse('accounts:view_profile'))
else:
form_EPF = EditProfileForm(instance=request.user)
form_EUP = EditUserProfile(instance=request.user)
args = {'form_EPF': form_EPF, "form_EUP": form_EUP}
return render(request, 'accounts/edit_profile.html', args)
forms.py
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = (
'username',
'first_name',
'last_name',
'email',
'password1',
'password2'
)
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
class EditProfileForm(UserChangeForm):
class Meta:
model = User
fields = (
'email',
'first_name',
'last_name',
'password',
)
class EditUserProfile(ModelForm):
description = forms.CharField(required=False)
city = forms.CharField(required=False)
website = forms.URLField(required=False)
class Meta:
model = UserProfile
fields = (
'description',
'city',
'website',
'image',
)
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
description = models.CharField(max_length=100, default='')
city = models.CharField(max_length=100, default='')
website = models.URLField(default='')
phone = models.IntegerField(default=0)
image = models.ImageField(upload_to='profile_image', blank=True)
def __str__(self):
return self.user.username
def create_profile(sender, **kwargs):
if kwargs['created']:
user_profile = UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_profile, sender=User)
edit_profile.html
{% block body %}
<div class="container">
<h1>Edit profile</h1>
<form method="post">
{% csrf_token %}
{{ form_EPF.as_p }}
{{ form_EUP.as_p }}
<button type="submit">Submit</button>
</form>
</div>
{% endblock %}
Here:
if form_EPF.is_valid():
form_EPF.save()
return redirect(reverse('accounts:view_profile')) # !!!!!
if form_EUP.is_valid():
form_EUP.save()
return redirect(reverse('accounts:view_profile'))
You are returning just after saving the user form, so the profile form is indeed never saved (unless the user form is invalid xD).
You want:
if form_EPF.is_valid() and form_EUP.is_valid():
form_EPF.save()
form_EUP.save()
return redirect(reverse('accounts:view_profile')) # !!!!!
Also, you have this which won't work:
form_EUP = EditUserProfile(request.POST, instance=request.user)
you're passing a ̀Userasinstance, but the form expects aUserProfile`.
And - while it won't prevent your code from running - you definitly want to think twice about your naming. "EditProfileForm" edits the User so it should really be named "EditUserForm" (or "UserEditForm" etc), and the variables in your view aren't any better - ̀form_EPFandform_EUPrequires active thinking to parse the suffix and match it with the (already counter-intuitive) form class names, and you _don't_ want to have to do any effort to understand what a name means when reading code. Just use the all_lower form of the class instead, so assumin you renamed your form classes to a much sanerUserEditFormandProfileEditForm, the variables would beuser_edit_formandprofile_edit_form`. That's a couple additional keystrokes indeed but on average you spend about 1000 times more time reading code than writing it so it's really worth optimizing for reading.
Problem description: UserProfile form doesn't save any data.
I am creating a new User and automatically create a UserProfile object for him (so I'm extending UserProfile), so I can go to admin page and fill all the fields . But when I'm trying to do it from client side, my form just doesn't catch the data.
Also the strangest moment is that I can change username and email using UserChangeForm, so I'm trying to do the same for UserProfileObject.
models.py:
class UserProfile(models.Model):
user = models.OneToOneField(User)
image = models.ImageField(upload_to='profile_image', blank=True)
title = models.CharField(max_length=100, default = '')
first_name = models.CharField(max_length=200, default = '')
last_name = models.CharField(max_length=200, default = '')
subject = models.ManyToManyField('Subject', related_name='tutor_type', default = '', help_text="Select a subject")
AREA_STATUS = (
('Jerusalem', 'Jerusalem'),
('Tel Aviv', 'Tel Aviv'),
('Haifa', 'Haifa'),
('Eilat', 'Eilat')
)
area = models.CharField(max_length=200, choices=AREA_STATUS, blank=True, default='', help_text='Tutor area')
# Foreign Key used because tutor can only have one area, but area can have multiple tutors
# Author as a string rather than object because it hasn't been declared yet in file.
description = models.TextField(max_length=4000, help_text="Enter a brief description about yourself")
charge = models.IntegerField(default = '0')
# ManyToManyField used because Subject can contain many tutors. Tutors can cover many subjects.
# Subject declared as an object because it has already been defined.
LANGUAGE_CHOICES = (
('English','English'),
('Hebrew','Hebrew'),
('Russian','Russian'),
('French','French'),
('Arabic','Arabic'),
)
language = models.CharField('Language', choices = LANGUAGE_CHOICES, max_length=50, null=True)
def __str__(self):
return self.user.username
def display_subject(self):
"""
Creates a string for the subject. This is required to display subject in Admin.
"""
return ', '.join([ subject.name for subject in self.subject.all()[:3] ])
display_subject.short_description = 'Subject'
def create_profile(sender, **kwargs):
if kwargs['created']:
user_profile = UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_profile, sender = User)
forms.py::
class EditProfileForm(UserChangeForm):
class Meta:
model = User
fields = (
'username',
'email',
'password'
)
class EditExtendedProfileForm(UserChangeForm):
class Meta:
model = UserProfile
fields = '__all__'
exclude = ('user',)
views.py:
def edit_profile(request):
if request.method == 'POST':
form = EditProfileForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
return redirect(reverse('accounts:view_profile'))
else:
form = EditProfileForm(instance=request.user)
args = {'form': form}
return render(request, 'accounts/edit_profile.html', args)
def edit_extended_profile(request):
if request.method == "POST":
form = EditExtendedProfileForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
return redirect(reverse('accounts:view_profile'))
else:
return redirect(reverse('accounts:edit_extended_profile'))
else:
form = EditExtendedProfileForm(instance = request.user)
args = {'form':form}
return render(request, 'accounts/edit_extended_profile.html', args)
edit_extended_profile.html:
{% extends "base.html" %}
{% block head %}
<title>Profile</title>
{% endblock %}
{% block body %}
<div class = "container">
<form method="POST">
{% csrf_token %}
{{form.as_p}}
<button type = "submit" class = "btn btn-success">Submit</button>
</form>
</div>
{% endblock %}
and it is the same template as for edit_profile view.
No traceback, no errors. Any help will be appreciated. Thanks in advance.
I'm having a great deal of difficulty trying to submit a quick form to update a user's "About Me" section. It's an optional field, users can leave it blank if they wish. I can't figure it out!
models.py:
class UserProfile(models.Model):
user = models.OneToOneField(User)
activation_key = models.CharField(max_length=40, blank=True)
key_expires = models.DateField(default=datetime.date.today())
about_me = models.CharField(max_length=500, blank=True, null=True, default='')
portfolio_site = models.URLField(max_length=200, blank=True, null=True, default='')
def __str__(self):
return self.user.username
forms.py:
class UserForm(forms.Form):
class Meta:
model = User
fields = ['first_name', 'last_name', 'password', 'email', 'username']
class ProfileForm(forms.Form):
class Meta:
model = UserProfile
fields = ['about_me', 'portfolio_site']
views.py:
#login_required(login_url='sign_in')
def update_about(request, user_id):
# Accquire submitted data and place to "data"
data = request.POST
# Isolate submitted data under "id_about_me", and place it to "about_me"
about_me = data.get('id_about_me')
new_about = UserProfile(id=request.user.id, about_me=about_me)
new_about.save()
return HttpResponse('Great Job!')
If I use "user_id=request.user.id", then it says:
IntegrityError at /update_about/1/
UNIQUE constraint failed: register_userprofile.user_id
If I use "id=request.user.id", then it says:
IntegrityError at /update_about/1/
NOT NULL constraint failed: register_userprofile.user_id
I can handle other updates just fine, but this one has me stumped!
I think it's because you're creating a new instance of your UserProfile model and assigning the same user_id to it, leading to the Unique constraint error. You should first retrieve your already existing model and modify it like so:
new_about = UserProfile.objects.get(user_id=user_request_id)
new_about.about_me = about_me
new_about.save()
Tell me if this works. If user_id is your auto-primary-key field, though, this shouldn't be an issue.
You need to create, or fetch a profile if it already exists:
profile,created = UserProfile.objects.get_or_create(user=request.user)
profile.about_me = about_me
profile.save()
But why don't you use the form?
from django.shortcuts import redirect, render
from .forms import ProfileForm
#login_required(login_url='sign_in')
def update_about(request, user_id):
form = ProfileForm(request.POST or None)
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save()
return redirect('/')
return render(request, 'update_profile.html', {'form': form})
Your template just has the normal form rendering logic:
<form method="POST">
{% csrf_token %}
{{ form }}
<input type="submit">
</form>
I had to create a form from which some details go to default.auth.user model and some to my custom model so after searching from various sources I did this:
Django Version :1.7
model.py
class UserProfile(models.Model):
user = models.OneToOneField(User)
title_id = models.ForeignKey('Title')
mobile_number = models.CharField(max_length=10)
alternate_number = models.CharField(max_length=10)
date_of_birth = models.DateField()
profession_id = models.ForeignKey('Profession', null=True, blank=True)
house_no = models.CharField(max_length=100, blank=True, default='NA')
city_id = models.ForeignKey('City', null=True)
country_id = models.ForeignKey('Country', null=True)
state_id = models.ForeignKey('State', null=True)
locality_id = models.ForeignKey('Locality', null=True)
profile_picture_path = models.CharField(max_length=100, blank=True, default='NA')
forms.py:
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput(attrs={'id': 'password'}))
email = forms.CharField(widget=forms.TextInput(attrs={'id': 'email_id'}))
username = forms.CharField(widget=forms.TextInput(attrs={'id': 'username'}))
first_name = forms.CharField(widget=forms.TextInput(attrs={'id': 'first_name'}))
last_name = forms.CharField(widget=forms.TextInput(attrs={'id': 'last_name'}))
class Meta:
model = User
fields = ('email', 'username', 'first_name', 'last_name', 'password')
class ExtraDetailsForm(UserForm):
confirm_password = forms.CharField(widget=forms.PasswordInput(attrs=
{'id':'confirm_password'}),max_length=32,
required=True,)
class Meta:
model = UserProfile
fields = ('email', 'username', 'title_id', 'first_name', 'last_name',
'password', 'confirm_password',
'date_of_birth', 'mobile_number', )
My view.py is :
def register(request):
# A boolean vakue for telling whether the registration was successful
registered = False
if request.method == 'POST':
user_form = UserForm(data=request.POST)
additional_details_form = ExtraDetailsForm(data=request.POST)
if user_form.is_valid() and additional_details_form.is_valid():
user = user_form.save()
user.set_password(user.password)
user.save()
additional_details = additional_details_form.save(commit=False)
additional_details.user = user
additional_details.save()
registered = True
else:
print(user_form.errors, additional_details_form.errors)
else:
user_form = UserForm
additional_details_form = ExtraDetailsForm
return render(request,
'users/register.html',
{'user_form' : user_form, 'additional_details_form': additional_details_form, 'registerered': registered})
regsiter.html:
{% if registerered %}
<p>Thank you for register. check ur email , entered email was</p>
{% else %}
<form action="/users/register/" method="post">{% csrf_token %}
{{ additional_details_form.as_p }}
<input type="submit" value="Register" />
</form>
{% endif %}
Now the good thing is that everything is working fine and details are being stored as they should be.
But the bad thing is I do not know whether it is a correct approach or not as I did not find any tutorial/resource where this approach is used?
This is correct approach and you do it almost right. Just couple notes:
if user_form.is_valid() and additional_details_form.is_valid():
In this line if user_form is invalid then validation for additional_details_form will not run. To always validate both change it to:
if all([user_form.is_valid(), additional_details_form.is_valid()]):
In else statement you set form class to *_form variables. It should be form instances instead:
user_form = UserForm()
additional_details_form = ExtraDetailsForm()
And it may be a good idea to wrap your save code into one transaction :-)
I would recommend that you use just one form here that contains all fields.
There is no benefit to using two forms, especially since one inherits the other, this is odd behaviour when you are then passing the POST data into each of them.
Consolidate the fields into a single form and then override the 'clean' method of the form to be able to check that the two password fields match.
You can create a single form to save data into one or many different models and this is especially useful in your case since you need to validate the data for these different models together.
Ok, firstly ExtraDetailsForm shouldn't inherit from UserForm because they are for different models. It should look something like this instead:
class UserForm(forms.ModelForm):
confirm_password = forms.CharField(widget=forms.PasswordInput(attrs=
{'id':'confirm_password'}),max_length=32,
required=True,)
class Meta:
model = User
fields = ('email', 'username', 'first_name', 'last_name', 'password',
'confirm_password')
class ExtraDetailsForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('title_id', 'date_of_birth', 'mobile_number')
Then in your view:
from django.contrib.auth import login
from django.shortcuts import redirect, render
def register(request):
user_form = UserForm(data=request.POST or None)
profile_form = ExtraDetailsForm(data=request.POST or None)
if all([user_form.is_valid(), profile_form.is_valid()]):
user = user_form.save(commit=False)
user.set_password(user.password)
user.save()
profile = profile_form.save()
# probably at this point you want to login the new user:
login(request, user)
# it's good practice to do a redirect here, after a successful
# form post, eg to display success page, as this will
# prevent accidental re-posting data if user reloads the page
return redirect('registration_success')
else:
print(user_form.errors, profile_form.errors)
return render(
request,
'users/register.html',
{
'user_form' : user_form,
'profile_form' : profile_form,
}
)
def registration_success(request):
return render('registration_success.html')
Finally you need to output both forms in the template:
<form action="/users/register/" method="post">{% csrf_token %}
{{ user_form.as_p }}
{{ profile_form.as_p }}
<input type="submit" value="Register" />
</form>
and a new template registration_success.html:
<p>Thank you for registering. Check your email, entered email was: {{ request.user.email }}</p>
I'm really stuck with this. To show my problem I created a new Django project and started from scratch, focusing only on one single form.
What I'm trying to do is to create a form with several fields of the same name. I tried using modelformset_factory to achieve this but it looks to me like it's not what I really need.
Below is my code (also on dpaste) which currently works fine with one single field called name. How can I create and process a form which would have several name fields? Could somebody point me in the right direction?
# models.py
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, blank=True, null=True)
class Meta:
verbose_name_plural = "Ingredience Categories"
def __unicode__(self):
return self.name
# forms.py
class CategoryForm(ModelForm):
class Meta:
model = Category
fields = ('name',)
# views.py
def home(request):
if request.method == 'POST':
catform = CategoryForm(request.POST)
catformInstance = catform.save(commit = False)
catformInstance.save()
return HttpResponseRedirect('')
else:
catform = CategoryForm()
context = {'catform': catform}
return render_to_response('home.html', context, context_instance=RequestContext(request))
# home.html template
<h3>Insert new Category</h3>
<form action="/" method="post" id="ingr-cat-form">{% csrf_token %}
{{ catform.as_p }}
<input type="submit" name="ingrCatForm" value="Save" />
</form>
UPDATE: to clarify, I want to allow user to insert several categories within one form. I think I'm getting close, here is my new version of views.py but it still stores just one category (the last one in the list):
def home(request):
if request.method == 'POST':
catform = CategoryForm(request.POST)
names = request.POST.getlist('name')
catformInstance = catform.save(commit = False)
for name in names:
catformInstance.name = name
catformInstance.save()
return HttpResponseRedirect('')
else:
catform = CategoryForm()
context = {'catform': catform}
return render_to_response('home.html', context, context_instance=RequestContext(request))
You cannot have fields with the same name (on the same Model). If you only need to change the html label in the html form, use
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
name2 = models.CharField(max_length=30, unique=True, verbose_name="name")
user = models.ForeignKey(User, blank=True, null=True)
or
class CategoryForm(ModelForm):
def __init__(self , *args, **kwargs):
super(CategoryForm, self).__init__(*args, **kwargs)
self.fields['name2'].label = "name"
Here is a working solution. Thanks to #YardenST for pointing me in the right direction. I managed to solve my initial problem by following this tutorial.
# models.py
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, blank=True, null=True)
class Meta:
verbose_name_plural = "Ingredience Categories"
def __unicode__(self):
return self.name
# forms.py
class CategoryForm(ModelForm):
class Meta:
model = Category
fields = ('name',)
# views.py
def home(request):
if request.method == 'POST':
catforms = [CategoryForm(request.POST, prefix=str(x), instance=Category()) for x in range(0,3)]
if all([cf.is_valid() for cf in catforms]):
for cf in catforms:
catformInstance = cf.save(commit = False)
catformInstance.save()
return HttpResponseRedirect('')
else:
catform = [CategoryForm(prefix=str(x), instance=Category()) for x in range(0,3)]
context = {'catform': catform}
return render_to_response('home.html', context, context_instance=RequestContext(request))
# home.html template
<h3>Insert new Category</h3>
<form action="/" method="post" id="ingr-cat-form">{% csrf_token %}
{% for catform_instance in catform %} {{ catform_instance.as_p }} {% endfor %}
<input type="submit" name="ingrCatForm" value="Save" />
</form>