Django Add Field Error to Non-Model Form Field - django

Can anyone help me understand how to properly send a field error back to a non-model form field in django that is not using the standard django form validation process? Rendering the form again with error and user data still entered for correction and resubmission?
Example html for a simple username field that is validated on the model level:
<!--form-->
<form id="profile" class="small" method="POST" action="{% url 'profile' %}">
{% csrf_token %}
<!--Username-->
<label for="username">Username <span style="font-style: italic;">(create a unique display name that will appear to other users on the site)</span></label>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="username">#</span>
</div>
<input type="text" class="form-control" placeholder="Username" aria-label="Username" aria-describedby="username" name="username" value="{% if profile and profile.username is not None %}{{ profile.username }}{% endif %}">
</div>
<button type="submit" class="btn btn-primary" name="profile_form" value="profile_form">Save</button>
</form>
View
class ProfileView(View):
def get(self, request, *args, **kwargs):
# get request...
def post(self, request, *args, **kwargs):
if request.method == "POST":
# check if profile_form submitted
if 'profile_form' in request.POST:
# get user form data
profile_data = request.POST.dict()
# get current user profile
user_profile = Profile.objects.get(my_user=request.user)
# check username entry against current
if user_profile.username == profile_data['username']:
messages.success(request, "This is the current user.")
else:
try:
# try to save the new username
user_profile.username = profile_data['username']
user_profile.save(update_fields=['username'])
messages.success(request, "Success: Username was updated.")
except:
# unique constraint error on username
# ERROR PROCESSING NEEDED
# Need to send error to form for user to correct and resubmit???
# return get request to process any updated data
return HttpResponseRedirect(reverse('profile'))

You should use a form class that takes care of the form data and it could also repopulate the data.
forms.py
class ProfileForm(forms.Form):
username = forms.CharField(
required=True,
label="Username",
help_text="(create a unique display name that will appear to other users on the site)",
)
views.py
class ProfileView(View):
def get(self, request, *args, **kwargs):
# get request...
def post(self, request, *args, **kwargs):
if request.method == "POST":
form = ProfileForm(request.POST)
if form.is_valid():
username = form.cleaned_data["username"]
user_profile = Profile.objects.get(my_user=request.user)
if user_profile.username == username:
messages.success(request, "This is the current user.")
else:
try:
user_profile.username = username
user_profile.save(update_fields=["username"])
messages.success(request, "Success: Username was updated.")
except IntegrityError:
messages.error(request, "Error message which should be displayed")
return render(
request, "your_form.html", {"form": form}
)
return HttpResponseRedirect(reverse('profile'))

Related

The view users.views.view didn't return an HttpResponse object. It returned None instead

I'm making a to-do list web with my friend. So, I have created the register function with email authentication, and it works fin. I pushed it to the GitHub and my friend pulled it and tried it in his laptop.
But when he clicks "register" he got this error The view users.views.view didn't return an HttpResponse object. It returned None instead.
We literally have the same code since he pulled the code from my repository, we both user virtual environment, installing from the same requirements.txt, and we both uses WSL2.
This is the code
views.py
class UserRegister(CreateView):
form_class = UserRegisterForm
template_name = 'users/form_register.html'
redirect_authenticated_user = True
success_url = reverse_lazy('tasks')
# Forbid logged in user to enter register page
def get(self, *args, **kwargs):
if self.request.user.is_authenticated:
return redirect('tasks')
return super(UserRegister, self).get(*args, **kwargs)
# Send email verification
def post(self, request, *args, **kwargs):
form = UserRegisterForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.is_active = False
user.save()
current_site = get_current_site(request)
subject = 'Activate your account'
message = render_to_string('users/account_activation_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
user.email_user(subject, message)
return redirect('login')
else:
form = UserRegisterForm()
form_register.html
-- Register -->
<section>
<div class="register-card">
<h2>Register for an account</h2>
<form method="POST">
{% csrf_token %}
<div class="txt_field">
<input type="text" name="username" required placeholder="Username">
<input type="email" name="email" required placeholder="Email">
<input type="password" name="password1" required placeholder="Password">
<input type="password" name="password2" required placeholder="Confirm password">
</div>
<div class="register-button"><input type="submit" value="Register"></div>
You need to always return some HttpResponse. If form is not valid, then you have nothing in return. Method self.form_invalid is returning such response with errors, so you should use it.
def post(self, request, *args, **kwargs):
form = UserRegisterForm(request.POST)
if form.is_valid():
...
return redirect('login')
else:
return self.form_invalid(form)

Custom Reset password templates forms doesn't show

I modified the first password reset page successfully. (Where the user insert his email address)
Where it doesn't work it the Password Reset Confirm page (Where the users write his new password). When I click the link that I received by email to reset the password, it load my html template but it doesn't load my form. (Only load h1 tag and other things)
html template
{% load static %}
<div class="box" align="center">
<h1>Write your new password</h1>
<div class="small-line"></div>
<form method="POST">
{% csrf_token %}
{{ context.as_p }}
<button type="submit" class="sendbut">Reset Password</button>
</form>
</div>
forms.py
class SetPasswordForm(forms.Form):
def __init__(self, *args, **kwargs):
super(SetPasswordForm, self).__init__(*args, **kwargs)
# …
new_password1 = forms.CharField(
label=("New password"),
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
strip=False,
help_text=password_validation.password_validators_help_text_html(),
)
new_password2 = forms.CharField(
label=("New password confirmation"),
strip=False,
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
)
views.py
#sensitive_post_parameters()
#never_cache
def PasswordResetConfirmView(request, uidb64=None, token=None,
template_name='users/password_reset_confirm.html',
token_generator=default_token_generator,
set_password_form=SetPasswordForm,
post_reset_redirect=None,
current_app=None, extra_context=None):
"""
View that checks the hash in a password reset link and presents a
form for entering a new password.
"""
UserModel = get_user_model()
assert uidb64 is not None and token is not None # checked by URLconf
if post_reset_redirect is None:
post_reset_redirect = reverse('password_reset_complete')
else:
post_reset_redirect = resolve_url(post_reset_redirect)
try:
# urlsafe_base64_decode() decodes to bytestring on Python 3
uid = force_str(urlsafe_base64_decode(uidb64))
user = UserModel._default_manager.get(pk=uid)
except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
user = None
if user is not None and token_generator.check_token(user, token):
validlink = True
title = ('Enter new password')
if request.method == 'POST':
form = set_password_form(user, request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(post_reset_redirect)
else:
form = set_password_form(user)
else:
validlink = False
form = None
title = ('Password reset unsuccessful')
context = {
'form': form,
'title': title,
'validlink': validlink,
}
if extra_context is not None:
context.update(extra_context)
if current_app is not None:
request.current_app = current_app
return TemplateResponse(request, template_name, context)
urls.py
urlpatterns = [
path('password_reset/', views.password_reset, name='password_reset'),
path('password_reset/done/', views.password_reset_done, name="password_reset_done"),
path(r'reset/<uidb64>/<token>/', views.PasswordResetConfirmView, name='password_reset_confirm'),
]
please write {{form.as_p}} insted of {{context.as_p}}
{% load static %}
<div class="box" align="center">
<h1>Write your new password</h1>
<div class="small-line"></div>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="sendbut">Reset Password</button>
</form>
</div>

django doesn't show ValidationError

I have a problem: when i try to login with information that doesn't get validated, the page is just refreshes, and nothing happens. My goal is to pop up any validation error, but they literally refuse to appear. Could you please check my code, and help me to find out what to do.
template
<form method="post" action="">
{% csrf_token %}
{% bootstrap_form_errors form %}
{% bootstrap_form form %}
<input type="hidden" name="next" value="{{ request.path }}">
<input type="submit" value="Войти">
</form>
form
class LoginForm(forms.ModelForm):
username = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = CustomUser
fields = ('username', 'password')
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username is not None and password:
user = authenticate(username=self.cleaned_data.get('username'), password=self.cleaned_data.get('password'))
if user is None:
raise ValidationError('Неверное имя пользователя или пароль')
if username is None or password is None:
raise ValidationError('Неверные данные')
return self.cleaned_data
view
class LoginView(FormView):
form_class = LoginForm
template_name = 'user/login.html'
def get_success_url(self):
return self.request.META.get('HTTP_REFERER')
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
ctx['form'] = self.form_class
return ctx
def form_valid(self, form):
user = authenticate(username=form.cleaned_data.get('username'),
password=form.cleaned_data.get('password'))
login(self.request, user)
return super().form_valid(form)
def dispatch(self, request, *args, **kwargs):
if self.request.user.is_authenticated():
return redirect('post-list')
return super().dispatch(request, *args, **kwargs)
UPD: It's not bootsrap. When i use the default forms, there are no validationErrors aswell
Your problem is in get_context_data, where you pass the form class, rather than the object instantiated with the post data.
However you should not be overriding that method at all. Even if you fixed this issue, it would only be duplicating what the method already does. Remove the method altogether.

Django form NEVER posts

This is rather weird. I've been using Django forms for a long time and can't figure this out.
I have a small form with 1 field for "Quantity". Whenever I submit the form nothing happens and it NEVER get's into my condition to check if the request method is a POST. I have put a pdb in the code as well and it never reaches. I am not sure why. Here is the code.
views.py
def show_product(request, product_slug, template_name='catalog/product.html'):
product_cache_key = request.path
product = cache.get(product_cache_key)
if not product:
product = get_object_or_404(Product, slug=product_slug)
cache.set(product_cache_key, product, settings.CACHE_TIMEOUT)
categories = product.categories.filter(is_active=True)
if request.method == 'POST':
import pdb; pdb.set_trace() # it NEVER hit's this
postdata = request.POST.copy()
form = ProductAddToCartForm(request, postdata)
if form.is_valid():
cart.add_to_cart(request)
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
url = urlresolvers.reverse('show_cart')
return redirect(url)
else:
form = ProductAddToCartForm(request=request)
form.fields['product_slug'].widget.attrs['value'] = product_slug
request.session.set_test_cookie()
context = RequestContext(request, locals())
return render_to_response(template_name, context)
forms.py
class ProductAddToCartForm(forms.Form):
quantity = forms.IntegerField(widget=forms.TextInput(attrs={'class': 'input-quantity', 'placeholder': 'Qty'}), error_messages={'invalid': 'Please enter a valid quantity.'}, min_value=1)
product_slug = forms.CharField(widget=forms.HiddenInput())
def __init__(self, request=None, *args, **kwargs):
self.request = request
super(ProductAddToCartForm, self).__init__(*args, **kwargs)
def clean(self):
if self.request:
if not self.request.session.test_cookie_worked():
raise forms.ValidationError("Sorry, please enable your cookies.")
return self.cleaned_data
template
<form method="post" action=".">
{% csrf_token %}
{{ form.quantity.errors }}
{{ form.quantity }}
<input type="submit" name="submit" value="Add to Cart" class="btn btn-danger" />
{{ form.product_slug }}
</form>
When I click "Add to Cart" the URL goes from http://localhost:8000/product/arm-glove/ to this one http://localhost:8000/product/arm-glove/?csrfmiddlewaretoken=RFG0F1Lg0Eu3GcDhtYwPPCpy9Oct5zCX&quantity=2&submit=Add+to+Cart&product_slug=arm-glove
What am I missing here?
Turns out there was an unclosed tag used for the search which is a GET request so the form's POST was never being seen.

How to get Django view to return form errors

This is my view:
def main_page(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username=form.clean_data['username'],
password=form.clean_data['password1'],
email=form.clean_data['email']
)
return HttpResponseRedirect('/')
else:
form = RegistrationForm()
variables = {
'form': form
}
return render(request, 'main_page.html', variables)
and this is my main_page.html:
{% if form.errors %}
<p>NOT VALID</p>
{% for errors in form.errors %}
{{ errors }}
{% endfor %}
{% endif %}
<form method="post" action="/">{% csrf_token %}
<p><label for="id_username">Username:</label>{{ form.username }}</p>
<p><label for="id_email">Email Address:</label>{{ form.email }}</p>
<p><label for="id_password">Password:</label>{{ form.password1 }}</p>
<p><label for="id_retypePassword">Retype Password:</label>{{ form.password2 }}</p>
<input type="hidden" name="next" />
<input type="submit" value="Register" />
</form>
When I go to the url which uses the main_page view, it just displays the form. When I submit the form with errors (with blank fields and without a proper email address) it just redirects me to the same page and doesn't display any errors. It doesn't even say "NOT VALID".
When I change
{% if form.errors %}
to
{% if not form.is_valid %}
it always says "NOT VALID" (even if it is the first time going to the url and even if I didn't submit anything yet).
This is my RegistrationForm:
from django import forms
import re
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
class RegistrationForm(forms.Form):
username = forms.CharField(label='Username', max_length=30)
email = forms.EmailField(label='Email')
password1 = forms.CharField(label='Password', widget=forms.PasswordInput())
password2 = forms.CharField(label='Password (Again)', widget=forms.PasswordInput())
def clean_password2(self):
if 'password1' in self.cleaned_data:
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 == password2:
return password2
raise forms.ValidationError('Passwords do not match.')
def clean_username(self):
username = self.cleaned_data['username']
if not re.search(r'^\w+$', username): #checks if all the characters in username are in the regex. If they aren't, it returns None
raise forms.ValidationError('Username can only contain alphanumeric characters and the underscore.')
try:
User.objects.get(username=username) #this raises an ObjectDoesNotExist exception if it doesn't find a user with that username
except ObjectDoesNotExist:
return username #if username doesn't exist, this is good. We can create the username
raise forms.ValidationError('Username is already taken.')
It is redirecting you because you always return HttpResponseRedirect if the method is POST, even if the form is not vaild. Try this:
def main_page(request):
form = RegistrationForm()
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username=form.clean_data['username'],
password=form.clean_data['password1'],
email=form.clean_data['email']
)
return HttpResponseRedirect('/')
variables = {
'form': form
}
return render(request, 'main_page.html', variables)
That way, the form instance, on which is_valid was called, is passed to the template, and it has a chance to display the errors. Only if the form is valid, the user is redirected. If you want to be fancy, add a message using the messages framework before redirecting.
If you want it a little bit more concise:
def main_page(request):
form = RegistrationForm(request.POST or None)
if form.is_valid():
user = User.objects.create_user(
username=form.clean_data['username'],
password=form.clean_data['password1'],
email=form.clean_data['email']
)
return HttpResponseRedirect('/')
variables = {
'form': form
}
return render(request, 'main_page.html', variables)
Make your view something like this:
if form.is_valid():
pass
# actions
else:
# form instance will have errors so we pass it into template
return render(request, 'template.html', {'form': form})
And in the templates you can iterate over form.errors or simple:
{{ forms.as_p }}
You can reformat your view to display the form errors in the console as below
def main_page(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username=form.clean_data['username'],
password=form.clean_data['password1'],
email=form.clean_data['email']
)
else:
print(form.errors)
return HttpResponse("Form Validation Error")
return HttpResponseRedirect('/')
else:
form = RegistrationForm()
variables = {
'form': form
}
return render(request, 'main_page.html', variables)
As always, keep the indent as above. If the form has any error , it will display in the console like
<ul class="errorlist"><li>date<ul class="errorlist"><li>Enter a valid date.</li></ul></li></ul>
Hope it helps