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.
Related
I have an update form to update information. Here problem is, product_title is updating but product_image is not working. Where is the problem that's for why the photo is not updating?
views.py:
def update_product(request,id):
product = Products.objects.get(pk=id)
form = update_product_info(request.POST or None, instance=product)
if request.method == 'POST' and form.is_valid():
form.save()
print(form.errors)
messages.success(request,"Successfully product information updated.")
return redirect("my_products")
context = {
'product':product,
"form":form
}
return render(request, "update_product.html", context)
update form:
class update_product_info(forms.ModelForm):
class Meta:
model = Products
fields = ('product_title','product_image')
widgets = {
'product_title':forms.TextInput(attrs={'class':'form-control', 'style':'font-size:13px;'}),
'product_image':forms.FileInput(attrs={'class':'form-control', 'style':'font-size:13px;'})
}
template:
<form action="" method="POST" class="needs-validation" style="font-size: 13px;" novalidate="" autocomplete="off" enctype="multipart/form-data">
{% csrf_token %}
{{form.as_p}}
<div class="d-flex align-items-center">
<button type="submit" class="btn btn-outline-dark ms-auto" value="Update" style="font-size: 13px;">Add</button>
</div>
You should pass both request.POST and request.FILES to the form:
from django.shortcuts import get_object_or_404
def update_product(request, id):
product = get_object_or_404(Products, pk=id)
if request.method == 'POST':
form = update_product_info(request.POST, request.FILES, instance=product)
if form.is_valid():
form.save()
messages.success(request, 'Successfully product information updated.')
return redirect('my_products')
else:
form = update_product_info(instance=product)
context = {'product': product, 'form': form}
return render(request, 'update_product.html', context)
Note: It is often better to use get_object_or_404(…) [Django-doc],
then to use .get(…) [Django-doc] directly. In case the object does not exists,
for example because the user altered the URL themselves, the get_object_or_404(…) will result in returning a HTTP 404 Not Found response, whereas using
.get(…) will result in a HTTP 500 Server Error.
Note: normally a Django model is given a singular name, so Product instead of Products.
Note: Usually a Form or a ModelForm ends with a …Form suffix,
to avoid collisions with the name of the model, and to make it clear that we are
working with a form. Therefore it might be better to use ProductInfoForm instead of
update_product_info.
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'))
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.
My problem is not to show django form fields on template.It's silly but I just haven't found any solution.
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['name', 'email', 'text']
def __init__(self, content_type, id, *args, **kwargs):
super(CommentForm, self).__init__(*args, **kwargs)
self.content_type = content_type
self.id = id
def save(self, commit=True):
post_type = ContentType.objects.get_for_model(Post)
comment_type = ContentType.objects.get_for_model(Comment)
comment = super(CommentForm, self).save(commit=False)
if self.content_type == 'post':
comment.content_type = post_type
comment.post = self.id
else:
parent = Comment.objects.get(id=self.id)
comment.content_type = comment_type
comment.post = parent.post
comment.object_id = self.id
if commit:
comment.save()
return comment
my view:
def add_comment(request, content_type, id):
if request.method == 'POST':
data = request.POST.copy()
form = CommentForm(content_type, id, data)
if form.is_valid():
form.save()
return redirect(reverse('index'))
my add_comment template:
<form method="post" action="{% url 'add_comment' 'post' post.id %}">
{% csrf_token %}
{% if not user.is_authenticated %}
{{ form.name.label_tag }}
{{ form.name }}
{{ form.email.label_tag }}
{{ form.email }}
{% endif %}
{{ form.text.label_tag }}
{{ form.text }}<br>
<input type="submit" value="Comment" />
</form>
and I included like:
<button id="button" type="button">Add Comment</button>
<div id="post_comment_form">{% include 'articles/add_comment.html' %}</div>
</article> <!-- .post.hentry -->
why not django rendered form fields,despite of showing buttons?
EDIT:
I'm rendering form in post view.
def post(request, slug):
post = get_object_or_404(Post, slug=slug)
comments = Comment.objects.filter(post=post.id)
return render(request,
'articles/post.html',
{'post': post,
'form': CommentForm,
'comments': comments,
# 'child_comments': child_comments
}
)
You forgot to instantiate the form, change this line:
'form': CommentForm,
to this
'form': CommentForm(),
In your view, you're not sending any context variables to the template, so your 'form' object isn't available for your template to process.
For example, the following return statement will render your .html and pass along all local variables, this isn't necessarily the best option (how much do you want your template to have access to), but is simple:
from django.shortcuts import render
...
return render(request, "template.html", locals())
you can also pass a dictionary instead of all local variables. Here's the documentation for render
I have a simple Django form being passed through a view to a template where it should display, but, for a reason that I -- after 5 hours -- have failed to deduce, it does not. Any and all ideas welcome, I'm dying to solve this irksome problem.
I have the following Django form:
class BandAddToCartForm(forms.Form):
LENGTH_CHOICES = ( ('XS', 'XS'),
('S', 'S'),
('M', 'M') )
length = forms.Select(choices=LENGTH_CHOICES)
quantity = forms.IntegerField(widget=forms.HiddenInput())
band_sku = forms.CharField(widget=forms.HiddenInput())
# override the default __init__ so we can set the request
def __init__(self, request=None, *args, **kwargs):
self.request = request
super(BandAddToCartForm, self).__init__(*args, **kwargs)
# custom validation to check for cookies
def clean(self):
if self.request:
if not self.request.session.test_cookie_worked():
raise forms.ValidationError("Cookies must be enabled.")
return self.cleaned_data
It is passed to the template through the following view:
def show_custom_watches(request,
template_name="catalog/custom_watches.html"):
bands = Band.objects.all()
page_title = "Custom Watches"
meta_keywords = "custom, watches, beaded"
meta_description = "Custom beaded watches for every occassion."
return render_to_response(template_name,
locals(),
context_instance=RequestContext(request))
# need to evaluate the HTTP method
if request.method == 'POST':
#add to cart, create bound form
postdata = request.POST.copy()
form = BandAddToCartForm(request, postdata)
#check if posted data is valid
if form.is_valid():
#add to cart and redirect to cart page
cart.add_band_to_cart(request)
# if test cookie worked, get rid of it
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
url = urlresolvers.reverse('show_cart')
return HttpResponseRedirect(url)
else:
# it's a GET, create the unbound form. Note request as a kwarg
band_form = BandAddToCartForm(request=request, label_suffix=':')
# set the test cookie on our first GET request
request.session.set_test_cookie()
return render_to_response("catalog/custom_watches.html",
locals(),
context_instance=RequestContext(request))
Lastly, here is the relevant bit of template where the form is failing to display:
{% for b in bands %}
<div class="watch_list_item">
<img class="" src="{{ MEDIA_URL }}images/bands/thumbnails/{{ b.image }}" alt="{{ b.name }}" />
<div class="watch_form_area">
<p>{{ b.name }}</p>
<form method="post" action="." class="cart">{% csrf_token %}
{{ band_form.as_p }}
<input type="submit" value="Add To Cart" name="add_product" alt="Add To Cart" class="add_to_cart_button" id="add_only_product" />
</form>
</div>
</div>
{% endfor %}
The Add to cart button appears as it should, but the length selector completely fails to display. Any ideas?
The first
return render_to_response(template_name,
locals(),
context_instance=RequestContext(request))
always happens before you initialise the form, remove it and it should work.