I'm displaying an Entry using DetailView and also have a comment form on the same page.
The commment form works fine for submissions but it doesn't display validation errors.
I can't figure out how to pass both the slug (required for the DetailView) AND the form (which contains the validation errors).
I'm not trying to display the slug in my html template -- I need the slug to retrieve the Entry's detailview.
Url:
...
url(r'^(?P<slug>[\w-]+)/$', EntryDetailView.as_view(), name='entry_detail'),
...
View:
class EntryDetailView(DetailView):
template_name = "entry_detail.html"
def get_context_data(self, **kwargs):
context = super(EntryDetailView, self).get_context_data(**kwargs)
context['comments'] = [(comment, comment.get_children_count()) for comment in EntryComment.get_tree()]
entry_comment_form = EntryCommentForm()
context['entry_comment_form'] = entry_comment_form
return context
def get_object(self, **kwargs):
# If the user created the entry:
try: return Entry.objects.get(Q(slug=self.kwargs['slug']), author=self.request.user.id)
except: pass
# If the user received the entry:
try: return Entry.objects.get(Q(slug=self.kwargs['slug']), recipients=self.request.user)
except: pass
# Otherwise, bye-bye.
raise Http404
def post(self, request, *args, **kwargs):
entry_comment_form = EntryCommentForm(request.POST)
if entry_comment_form.is_valid():
entry_comment_form.add_method(
author=entry_comment_form.cleaned_data['author'],
body=entry_comment_form.cleaned_data['body'],
date_created=datetime.datetime.now()
)
success(request, 'Success')
slug = self.kwargs['slug']
# Proper? It works.
return HttpResponseRedirect(reverse('entry_detail', subdomain='blog', kwargs={'slug':slug}))
else:
error(request, 'Error')
slug = self.kwargs['slug']
# Here's where I need to render the same Entry but include the form context so I can display the validation errors.
return render(
request,
reverse('entry_detail', kwargs={'slug':slug}),
{'entry_comment_form':entry_comment_form}
)
Seems like it's just a rookie mistake somewhere due to my lack of python/django foo.
The end goal is to display the same DetailView page (is there a way to do this without passing the slug to the url?) and include the form in context so I can display comment form validation errors.
Searched around, couldn't find any relevant answers.
What if you refactor it to use one of the customtags that come with the commenting module?
https://docs.djangoproject.com/en/1.5/ref/contrib/comments/#quickly-rendering-the-comment-form
Can you just use the get_comment_form tag with object to avoid passing the form around?
What you are trying makes no sense. You are passing the result of reverse - ie a URL - as the template in a call to the render function. Why are you doing that?
In order to actually help you, though, we'd need to know what you are actually doing. Where is this strange code? Is it in a view function, a class-based view method, a template tag, or somewhere else?
Edit after full code posted
There are several things wrong with that view code (blank excepts that just do pass?), but I will concentrate on the issue at hand. You seem to be making this much harder than it needs to be: if you want to pass both the form and the slug/URL to the template, you just put them in the template context, and pass the context to render as normal. There's nothing out of the ordinary or complicated here at all.
return render(
request,
self.template_name,
{'entry_comment_form':entry_comment_form, 'slug': slug}
)
Note that it's probably best to pass the slug in the context and use the url tag in the template:
{% url 'entry_detail' slug=slug %}
Related
Good afternoon,
I have a simple Django 2.2 application for users to check in equipment they have checked out. A table of users and items they have checked out dominates the top of the page. On the very bottom row, a single text/submit form. I would like this to happen:
user enters equipment id and submits
page re-displays with: name removed from table (if success), form cleared, success/fail message next to cleared form.
I am close. All of my logic and queries work, my item gets checked back in. However, the page re-renders with no table of users, just the form with the old data still in it.
views.py
class EquipmentReturn(View):
def get(self, request, *args, **kwargs):
# get checked out items for display table -this works
form = ExpressCheckInForm(request.POST)
return render(request, 'eq_return.html',
context={'master_table': master_table,
'form': form}
def post(self, request):
if request.method == 'POST'
form = ExpressCheckInForm(request.POST)
if form.is_valid():
# this checks the item back in (or not) and creates messages-works
else:
form - ExpressCheckInForm()
return render(request, 'eq_return.html', context={'form': form}
I know there is a better way to do this. For instance, my form would not appear until I declared it in the get function. How can I make all of this happen on one page? Thanks!
I think something like this might work. I assume that there is missing code here, for example where you get the master_table.
class EquipmentReturn(View):
def get(self, request, *args, **kwargs):
# get checked out items for display table -this works
form = ExpressCheckInForm()
return render(
request, 'eq_return.html',
context={'master_table': master_table, 'form': form},
)
def post(self, request):
form = ExpressCheckInForm(request.POST)
if form.is_valid():
# this checks the item back in (or not) and creates messages-works
# after saving the form or whatever you want, you just need to redirect back
# to your url. It will call get again and start over
return HttpResonseRedirect(reverse('your-url-name'))
return render(request, 'eq_return.html', context={'form': form})
It looks like you are still in the function based view mindset. Search differences and how to understand and use class based views.
views.py
from forms.py import PersonCreateForm
class PersonCreateView(CreateView):
model = Person
form_class = PersonCreateForm
template_name = "my_app/create_person.html"
def form_valid(self, form):
self.object = form.save()
return redirect('/homepage/')
class PeopleListView(ListView):
[...]
context.update({
'task_form': TaskCreateForm(),
return context
In my template I just add in action url which handle PersonCreateView.
<form action="{% url 'people_create' %}" method="post">{% csrf_token %}
When Form is valid then all data are saved without problems and it redirects me to '/homepage/.
But when my form is invalid then it redirects me to to {% url 'people_create' %} and shows errors at /homepage/people_create/
How can I avoid that? I want all errors show at same page without redirect.
Handle the form on the same view you build it, otherwise the page will change. You may mix django.views.generic.edit.ModelFormMixin into your PeopleListView so it has most of the features you need.
class PeopleListView(ModelFormMixin, ListView):
success_url = '/homepage/' # should use reverse() here
def get_context_data(self, **kwargs):
# only add the form if it is not already given to us
if not 'task_form' in kwargs:
kwargs['task_form'] = self.get_form()
return super(PeopleListView, self).get_context_data(**kwargs)
def post(self, request, *args, **kwargs):
# ListView won't have a post method, we define one
form = self.get_form()
if form.is_valid():
return self.form_valid(form) # default behavior will save and redirect
else:
return self.form_invalid(form) # default behavior has to be overridden (see below)
def form_invalid(self, form):
# Whatever you wanna do. This example simply reloads the list
self.object_list = self.get_queryset()
context = self.get_context_data(task_form=form)
return self.render_to_response(context)
There, you have three code paths:
Initial display will load the listview as usual, only an empty form will be added to the context.
On submitting valid input, the form_valid method is invoked, which will redirect to /homepage/.
On submitting invalid input, our overridden form_invalid method is invoked, which will render the page normally, except the form will contain the validation errors.
You may make the whole thing a bit more staightforward using a cached property for the form, but then you'd start working against Django's shipped views instead of with it, and might as well just use the basic View class and implement all logic by yourself. I'd stick with Django's views, but ymmv.
This question may seem obvious, but I have been thrown for a loop the past few days. The vast majority of tutorials and documentation I find on django-forms shows them as their own solitary views.
Take this example from django 1.6 official docs as a typical example, 'Using a form in a view':
from django.shortcuts import render
from django.http import HttpResponseRedirect
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render(request, 'contact.html', {
'form': form,
})
The problem I have is that I almost never dedicate a whole page or view just to one form. I can't really {% include %} the form view due to context variables. I also can't {% extend %} it without bringing in way to many items or reworking all of my templates and views.
Is it a good practice to combine form logic along with other references and variables in the same view? I would just like to confirm that yes this is normal and acceptable or that no I am doing things unacceptably. Off-hand there looks like some non DRY boilerplate if I do this in each view that needs a form.
There also appears to be some debate in the blogs & tutorials for form logic. Its hard to tell whether modifying a CBV or using some good ol' FBV is preferred. I just don't want to get any bad habits while I am still new and learning.
This is my currently "working" code for my view:
def home_page(request):
all_sliders = Slider.objects.all()
all_marketing = Marketing.objects.all()
all_features = Feature.objects.all()
skill_set = Skills.objects.all()
#clunky way of adding the form...yes? no?
errors = []
if request.method == 'POST':
form = ContactForm(request.POST)
if not request.POST.get('subject', ''):
errors.append('Enter a subject.')
if not request.POST.get('message', ''):
errors.append('Enter a message.')
if request.POST.get('email') and '#' not in request.POST['email']:
errors.append('Enter a valid e-mail address.')
if not errors:
send_mail(
request.POST['subject'],
request.POST['message'],
request.POST.get('email', 'noreply#mysite.com'),
# email address where message is sent.
['email#mysite.com'],
)
return HttpResponseRedirect('frontpage/thanks/')
else:
form = ContactForm()
context = {'sliders': all_sliders, 'marketing': all_marketing,
'feature': all_features, 'skills': skill_set,
#this is tied to my form logic
'form': form, 'errors': errors,
}
return render(request, 'frontpage/home.html', context)
In closing, I don't want to use an add-on library. I am learning django and want to learn it well before using libraries for basic things like forms.
There is nothing wrong with providing extra context/whatnot to a view with a form. In fact, it's pretty easy to do with CBVs. The docs for the Generic FormView provide a good example of a contact form, and you can extend it to add your extra context by overriding the get_context_data. What you should not do is form validation in the view. Leave that to your form class in forms.py. Here's an example pieced together from the docs:
# forms.py
class ContactForm(forms.Form):
subject = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
email = forms.CharField()
# Whatever fields you had.
def clean(self):
"""This is where you should be checking for required
fields and making sure the submitted data is safe."""
pass
def send_email(self):
# send email using the self.cleaned_data dictionary
send_mail(
self.cleaned_data['subject'],
self.cleaned_data['message'],
self.cleaned_data.get('email', 'noreply#mysite.com'),
['email#mysite.com']
)
# views.py
class HomePageFormView(FormView):
template_name = 'frontpage/home.html'
form_class = ContactForm
success_url = 'frontpage/thanks/'
def get_context_data(self, **kwargs):
context = super(HomePageFormView, self).get_context_data(**kwargs)
context['sliders'] = Slider.objects.all()
context['marketing'] = Marketing.objects.all()
context['feature'] = Feature.objects.all()
context['skills'] = Skills.objects.all()
return context
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super(HomePageFormView, self).form_valid(form)
Is it a good practice to combine form logic along with other
references and variables in the same view? I would just like to
confirm that yes this is normal and acceptable or that no I am doing
things unacceptably.
It is perfectly acceptable to add other things to the context in your views (otherwise, how would you render things?).
The problem comes when you are talking about form logic, and here is where there are some issues with your code/approach.
There is a convention in django (which you are free to violate), is that forms go in a forms.py file that is part of your application.
You can perfectly declare all your form classes in your views.py, there is nothing wrong with this approach but once you start collaborating with others, or start combining public django apps into your code, it is best to use a convention. After all, a software development framework is nothing but a bunch of conventions and some helpers all bundled together nicely.
However, a more serious problem with your logic is that you are not using form validation - and this you must absolutely stop right now.
Forms are - at their core - a way to validate dictionaries, and they are one of the most powerful features of the django framework. They allow you to validate any dictionary and are used anywhere you are working with models or data.
The code you have written is almost exactly what the form validation does - it checks if required fields are missing in the form (or, think of another way - required keys are None in the dictionary) and then adds error messages and marks the form (or dictionary) as invalid.
The basic logic of using forms is like this:
def someview(request):
form = SomeForm() # This creates an unbound (empty) form
if request.method == 'POST':
form = SomeForm(request.POST, request.FILES) # Bind the form to
# POST data
if form.is_valid():
# perform validation
# do something with form's data
data = form.cleaned_data['somefield']
# Or, if its a model form (a form that is tied to a
# model), save the model since the form is validated
obj = form.save()
# All post requests should redirect
return redirect('index')
else:
# The form was not valid, return the form
# to the view, except this time it will
# contain helpful error messages
return render(request, 'form.html', {'form': form})
else:
# Return an empty form to the view
# for the user to fill in, as this is a GET not POST
# request
return render(request, 'form.html', {'form': form})
You can always customize the validation rules for a form either on a field-by-field basis, or on the overall data in the form. This is discussed in the documentation on form and field validation.
Off-hand there looks like some non DRY boilerplate if I do this in
each view that needs a form.
The new CBV have solved this problem by taking care of the repeated logic (one of the benefits of inheritance and classes). The code I pasted above can be minimized to the following when using FormView:
from django.core.urlresolvers import reverse_lazy
from django.views.generic.edit import FormView
class SomeView(FormView):
template_name = 'form.html'
form_class = SomeForm
success_url = reverse_lazy('index')
def form_valid(self, form):
data = form.cleaned_data['somefield']
return super(SomeView, self).form_valid(form)
There also appears to be some debate in the blogs & tutorials for form
logic. Its hard to tell whether modifying a CBV or using some good ol'
FBV is preferred. I just don't want to get any bad habits while I am
still new and learning.
There is nothing wrong with using FBV - they are still perfectly valid django. The benefit you get with CBV is that common functionality is only written once. My advice is to use CBV when you have common logic that you want to modify on a per-view basis. Forms is a good example, displaying models, pagination, rendering simple templates, downloading data (for example, you can have one base view that converts objects to Excel, and then inherit from here in any view that needs to provide a download feature) are good candidates for CBV.
I am trying to create a new URL level in my django-powered ecommerce site, meaning if a user is in domain.com/category/foo/ I am trying to add the next level down, in which they click on some element in foo and wind up in domain.com/category/foo/tag/bar/. To that end, I have created my urls.py line for detecting this, and I believe there is no problem here:
(r'^category/(?P<category_slug>[-\w]+)/tag/(?P<tag_slug>[-\w]+)/$', 'show_tag', {
'template_name':'catalog/product_list.html'},'catalog_tag'),
Once the request has been mapped through the urls.py, I know it is going to need some variables from views.py in order for get_adsolute_url to do its thing, so I create the view:
def show_tag(request,
tag_slug,
template_name="catalog/product_list.html"):
t = get_object_or_404(Tag,
slug=tag_slug)
products = t.product_set.all()
page_title = t.name
meta_keywords = t.meta_keywords
meta_description = t.meta_description
return render_to_response(template_name,
locals(),
context_instance=RequestContext(request))
And lastly, in my Tag model, I set up my get_absolute_url to fill the keyword arguments:
#models.permalink
def get_absolute_url(self):
return ('catalog_tag', (), { 'category_slug': self.slug, 'tag_slug': self.slug })
I made sure the category and tag I'm about to request exist, and then I type domain.com/category/foo/tag/bar and I receive a
TypeError at /category/foo/tag/bar/
show_tag() got an unexpected keyword argument 'category_slug'
I believe I know where the error is, but I don't know how to solve it. my get_abolute_url sets 'category_slug': self.slug but that definition, as I said, lives in my Tag model. My Category model lives in the same models.py, but how do I tell my get_absolute_url to go find it?
Your view show_tag must have a parameter to accept the category_slug which is not there right now:
def show_tag(request,
category_slug, tag_slug,
template_name="catalog/product_list.html")
I'd like to write an except clause that redirects the user if there isn't something in a queryset. Any suggestions welcome. I'm a Python noob, which I get is the issue here.
Here is my current code:
def get_queryset(self):
try:
var = Model.objects.filter(user=self.request.user, done=False)
except:
pass
return var
I want to do something like this:
def get_queryset(self):
try:
var = Model.objects.filter(user=self.request.user, done=False)
except:
redirect('add_view')
return var
A try except block in the get_queryset method isn't really appropriate. Firstly, Model.objects.filter() won't raise an exception if the queryset is empty - it just returns an empty queryset. Secondly, the get_queryset method is meant to return a queryset, not an HttpResponse, so if you try to redirect inside that method, you'll run into problems.
I think you might find it easier to write a function based view. A first attempt might look like this:
from django.shortcuts import render
def my_view(request):
"""
Display all the objects belonging to the user
that are not done, or redirect if there are not any,
"""
objects = Model.objects.filter(user=self.request.user, done=False)
if not objects:
return HttpResponseRedirect("/empty-queryset-url/")
return render(request, 'myapp/template.html', {"objects": objects})
The advantage is that the flow of your function is pretty straight forward. This doesn't have as many features as the ListView generic class based view (it's missing pagination for example), but it is pretty clear to anyone reading your code what the view is doing.
If you really want to use the class based view, you have to dig into the CBV documentation for multiple object mixins and the source code, and find a suitable method to override.
In this case, you'll find that the ListView behaviour is quite different to what you want, because it never redirects. It displays an empty page by default, or a 404 page if you set allow_empty = False. I think you would have to override the get method to look something like this (untested).
class MyView(ListView):
def get_queryset(self):
return Model.objects.filter(user=self.request.user, done=False)
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
if len(self.object_list == 0):
return HttpResponseRedirect("/empty-queryset-url/")
context = self.get_context_data(object_list=self.object_list)
return self.render_to_response(context)
This is purely supplemental to #Alasdair's answer. It should really be a comment, but couldn't be formatted properly that way. Instead of actually redefining get on the ListView, you could override simply with:
class MyView(ListView):
allow_empty = False # Causes 404 to be raised if queryset is empty
def get(self, request, *args, **kwargs):
try:
return super(MyView, self).get(request, *args, **kwargs)
except Http404:
return HttpResponseRedirect("/empty-queryset-url/")
That way, you're not responsible for the entire implementation of get. If Django changes it in the future, you're still good to go.