I am currently doing the latest tango with django course and have just about finished '8. Fun with Forms', I however cannot solve an error I am getting. The course tells us to make an add_page form and supplies us with the view and the form, all we really have to do is create the URL, I have done this however I cannot get it to recognize the URL for the add_page view.
Sorry I cannot post pictures as I do not have high enough reputation so I have given links.
This is the error I get
http://i.stack.imgur.com/UrFxv.png
Here is my code
URLS -
from django.conf.urls import patterns, url
from rango import views
urlpatterns = patterns('',
url(r'^$', views.index, name='index'),
url(r'^about/', views.about, name="about"),
url(r'^add_category/$', views.add_category, name='add_category'),
url(r'^category/(?P<category_name_slug>[\w\-]+)/add_page/$', views.add_page, name='add_page'),
url(r'^category/(?P<category_name_slug>[\w\-]+)/$', views.category, name='category'),
)
VIEWS -
from django.shortcuts import render
from django.http import HttpResponse
from rango.models import Category, Page
from rango.forms import CategoryForm, PageForm
def index(request):
# Query the database for a list of ALL categories currently stored.
# Order the categories by no. likes in descending order.
# Retrieve the top 5 only - or all if less than 5.
# Place the list in our context_dict dictionary which will be passed to the template engine.
category_list = Category.objects.order_by('-likes')[:5]
page_list = Page.objects.order_by('-views')[:5]
context_dict = {'categories': category_list, 'pages': page_list}
# Render the response and send it back!
return render(request, 'rango/index.html', context_dict)
def about(request):
message = "Rango says here is the about page. <a href='/rango/'>Index</a> "
return HttpResponse(message)
def category(request, category_name_slug):
# Create a context dictionary which we can pass to the template rendering engine.
context_dict = {'category_name_slug': category_name_slug}
try:
# Can we find a category name slug with the given name?
# If we can't, the .get() method raises a DoesNotExist exception.
# So the .get() method returns one model instance or raises an exception.
category = Category.objects.get(slug=category_name_slug)
context_dict['category_name'] = category.name
# Retrieve all of the associated pages.
# Note that filter returns >= 1 model instance.
pages = Page.objects.filter(category=category)
# Adds our results list to the template context under name pages.
context_dict['pages'] = pages
# We also add the category object from the database to the context dictionary.
# We'll use this in the template to verify that the category exists.
context_dict['category'] = category
except Category.DoesNotExist:
# We get here if we didn't find the specified category.
# Don't do anything - the template displays the "no category" message for us.
pass
# Go render the response and return it to the client.
return render(request, 'rango/category.html', context_dict)
def add_category(request):
# A HTTP POST?
if request.method == 'POST':
form = CategoryForm(request.POST)
# Have we been provided with a valid form?
if form.is_valid():
# Save the new category to the database.
form.save(commit=True)
# Now call the index() view.
# The user will be shown the homepage.
return index(request)
else:
# The supplied form contained errors - just print them to the terminal.
print form.errors
else:
# If the request was not a POST, display the form to enter details.
form = CategoryForm()
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
return render(request, 'rango/add_category.html', {'form': form})
def add_page(request, category_name_slug):
try:
cat = Category.objects.get(slug=category_name_slug)
except Category.DoesNotExist:
cat = None
if request.method == 'POST':
form = PageForm(request.POST)
if form.is_valid():
if cat:
page = form.save(commit=False)
page.category = cat
page.views = 0
page.save()
# probably better to use a redirect here.
return category(request, category_name_slug)
else:
print form.errors
else:
form = PageForm()
# made the change here
context_dict = {'form': form, 'category': cat, 'category_name_slug': category_name_slug}
return render(request, 'rango/add_page.html', context_dict)
FORMS -
from django import forms
from rango.models import Page, Category
class CategoryForm(forms.ModelForm):
name = forms.CharField(max_length=128, help_text="Please enter the category name.")
views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
likes = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
slug = forms.CharField(widget=forms.HiddenInput(), required=False)
# An inline class to provide additional information on the form.
class Meta:
# Provide an association between the ModelForm and a model
model = Category
fields = ('name',)
class PageForm(forms.ModelForm):
title = forms.CharField(max_length=128, help_text="Please enter the title of the page.")
url = forms.URLField(max_length=200, help_text="Please enter the URL of the page.")
views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
class Meta:
# Provide an association between the ModelForm and a model
model = Page
# What fields do we want to include in our form?
# This way we don't need every field in the model present.
# Some fields may allow NULL values, so we may not want to include them...
# Here, we are hiding the foreign key.
# we can either exclude the category field from the form,
exclude = ('category',)
#or specify the fields to include (i.e. not include the category field)
#fields = ('title', 'url', 'views')
def clean(self):
cleaned_data = self.cleaned_data
url = cleaned_data.get('url')
# If url is not empty and doesn't start with 'http://', prepend 'http://'.
if url and not url.startswith('http://'):
url = 'http://' + url
cleaned_data['url'] = url
return cleaned_data
I think that is all the code necessary however just let me know if there are another files you would like to see and I'll put them up. Any help appreciated, thanks
A simple mistake, in my browser I was doing this 'http://127.0.0.1:8000/rango/category/other-frameworks/add_page.html', however my URL Tuple was not looking for a '.html' as the end so I removed it so it looked like this 'http://127.0.0.1:8000/rango/category/other-frameworks/add_page' and that solved my problem. Thanks to sgmart.
Related
I'm new to django but something I don't understand is the need for accommodating both the GET and POST request types when developing a form. Please refer to code below from django docs:
from .forms import NameForm
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()
return render(request, 'name.html', {'form': form})
The reason this confuses me is because I have developed a GET based form and it is working and I have no need for the POST portion above? See below:
# views.py
def simplifier_form(request):
form = LabelForm()
return render(request, "simplifier.html", {"form": form})
def process_simplifier(request):
label_name = request.GET.get('labels')
results = Disturbance.objects.filter(labeldisturbances__label_name=label_name)
painsresults = Pain.objects.filter(disturbances__pk__in=results).distinct()
total_disturbances = Disturbance.objects.filter(pain__pk__in=painsresults)
total_labels = Label.objects.filter(disturbances__pk__in=total_disturbances).distinct()
context = {'results': results, 'painsresults': painsresults, 'total_disturbances': total_disturbances, 'total_labels': total_labels}
return render(request,'process_simplifier.html', context)
# forms.py
class LabelForm(ModelForm):
labels = forms.ModelChoiceField(
queryset=Label.objects.all(),
to_field_name='label_name',
widget=forms.Select(attrs={'class': 'labels'}),
)
class Meta:
model = Label
fields = ['labels']
So why do the django docs and most examples include code for both methods when you only really use one like in my example above?
The question is... How do you want to process your from when submitted on POST?
Remember, your form can also be submitted on GET... You can decide what you would want to do if submitted on GET.
I have a model with 2 CharFields selected from nearly the same list of choices. They are the same except for the 2nd inclination having a "None" option.
class Pawn(models.Model):
primary_inclination = models.CharField(max_length=30, choices=PRIMARY_INCLINATION_CHOICES)
secondary_inclination= models.CharField(max_length=30, choices=SECONDARY_INCLINATION_CHOICES)
I want to ensure the same value can not be selected for both fields. For example, if my choices are selected from A, B, C, then A and B is fine, but A and A is not. It is ok for another Pawn to also have A and B.
Some things I've looked into:
Specifying unique fields in Meta, but this makes the inclinations only selectable by 1 Pawn
unique_together, no other pawn can have the same values for the two fields
You can use the clean method in your model to add custom validation and raise a ValidationError if the fields are the same:
from django.core.exceptions import ValidationError
class Pawn(models.Model):
primary_inclination = models.CharField(max_length=30, choices=PRIMARY_INCLINATION_CHOICES)
secondary_inclination= models.CharField(max_length=30, choices=SECONDARY_INCLINATION_CHOICES)
def clean(self):
if self.primary_inclination == self.secondary_inclination:
raise ValidationError('Primary and secondary inclinations should be different.')
It's a good idea to validate as close to the source as possible. Implementing methods such as clean or save_model (within Django Admin) validates at the server level which is the last line of defense. You can also use JavaScript to keep your form's submit button disabled unless the selected inclinations are different. Another technique is to use form validation in the view:
from django.core.exceptions import ValidationError
from django import forms
class PawnForm(forms.Form):
primary_inclination = forms.CharField()
secondary_inclination = forms.CharField()
def clean(self):
# Assume posted data includes pinc (primary_inclination) and sinc (secondary inclination)
if 'pinc' in self.cleaned_data and 'sinc' in self.cleaned_data:
if self.cleaned_data['pinc'] == self.cleaned_data['sinc']:
raise forms.ValidationError(
("Primary and Secondary inclinations must be different"))
else:
raise forms.ValidationError(
("Both Primary and Secondary inclinations required"))
return self.cleaned_data
In your view you could have something like:
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views.generic.edit import FormView
class PawnView(FormView):
def get(self, request):
context = {}
context['form'] = PawnForm(request.POST or None)
return render(request, 'pawn.html', context)
def post(self, request):
context = {}
form = PawnForm(request.POST or None)
context['form'] = form
if form.is_valid():
# Form is valid...go ahead and process data
new_pawn = Pawn.create(form.cleaned_data.get(‘pinc’), form.cleaned_data.get(‘sinc’)
return redirect(reverse('pawn-view'))
else:
for k, v in form.errors.items():
# Retrieve the text of our validation error or you can simply use form.errors
context['error'] = v
return render(request, 'pawn.html', context)
You can show the validation error in pawn.html very easily:
<div class="pawn-error">
<h3>{{ error | striptags }}</h3>
</div>
I am new to Django and have been doing lots of reading so perhaps this is a noob question.
We have applications that involve many forms that users fill out along the way. One user might fill out the budget page and another user might fill out the project description page. Along the way any data they input will be SAVED but NOT validated.
On the review page only data is shown and no input boxes / forms. At the bottom is a submit button. When the user submits the application I then want validation to be performed on all the parts / pages / forms of the application. If there are validation errors then the application can not be submitted.
My model fields are mostly marked as blank=True or null=True depending on the field type. Some fields are required but most I leave blank or null to allow the users to input data along the way.
Any advice on best practices or do not repeat yourself is greatly appreciated.
There is an app in django called form wizard. Using it you can split form submission process for multiple steps.
After a lot of learning, playing and reading I think I have figured a few things and will share them here. I do not know if this is right, however it is progress for me.
So first comes the models. Everything needs to accept blank or null depending on the field type. This will allow the end user to input data as they get it:
class exampleModel(models.Model):
field_1 = models.CharField(blank=True, max_length=25)
field_2 = models.CharField(blank=True, max_length=50)
.........
Then we create our model form:
from your.models import exampleModel
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Row, Column
class exampleForm(ModelForm):
class Meta:
model = exampleModel
fields = ('field_1','field_2')
def __init__(self, *args, **kwargs):
# DID WE GET A VALIDATE ARGUMENT?
self.validate = kwargs.pop('validate', False)
super(ExampleForm, self).__init__(*args, **kwargs)
# SEE IF WE HAVE TO VALIDATE
for field in self.fields:
if self.validate:
self.fields[field].required = True
else:
self.fields[field].required = False
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Row(
Column('field_1', css_class='col-lg-4 col-md-4'),
Column('field_2', css_class='col-lg-4 col-md-4')
)
)
def clean(self):
cleaned_data = super(ExampleForm, self).clean()
field_1 = cleaned_data.get('field1')
field_2 = cleaned_data.get('field2')
if self.validate and field_2 != field_2:
self.add_error('field_1', 'Field 1 does not match field2')
return cleaned_data
Here is the important part. I've learned a lot about forms and binding. As I mentioned I needed users to be able to fill out forms and not validate the data till the very end. This is my solution which helped me. I could not find a way to bind a form to the model data, so I created a function in my lib called bind_queryset_to_form which looks like this:
def bind_queryset_to_form(qs, form):
form_data = {}
my_form = form()
for field in my_form.fields:
form_data[field] = getattr(qs, field, None)
my_form = form(data=form_data, validate=True)
return my_form
The view:
from your.models import exampleModel
from your.form import exampleForm
from your.lib.bind_queryset_to_form import bind_queryset_to_form
from django.shortcuts import render, get_object_or_404
def your_view(request, pk):
query_set = get_object_or_404(exampleModel, id=pk)
context = dict()
context['query_set'] = query_set
# SAVE THE FORM (POST)
if request.method == 'POST':
form = exampleForm(request.POST, instance=query_set)
form.save()
context['form'] = form
# GET THE DATA.
if request.method == 'GET':
if request.session.get('validate_data'):
# BIND AND VALIDATE
context['form'] = bind_queryset_to_form(query_set, exampleForm)
else:
# NO BIND, NO VALIDATE
context['form'] = exampleForm(instance=query_set)
return render(request, 'dir/your.html', context)
The template:
{% load crispy_forms_tags %}
<div id="div_some_tab">
<form id="form_some_tab" action="{% url 'xx:xx' query_set.id %}" method="post">
{% crispy form form.helper %}
</form>
</div>
What does all the above allow?
I have many views with many data inputs. The user can visit each view and add data as they have it. On the review page I set the flag / session "validate_data". This causes the app to start validating all the fields. Any errors will all be displayed on the review page. When the user goes to correct the errors for the given view the bind_queryset_to_form(query_set, exampleForm) is called binding the form with data from the queryset and highlighting any errors.
I cut out a lot of the exceptions and permission to keep this as transparent as possible (the goat would hate that). Hope this idea might help someone else or someone else might improve upon it.
I have a form which looks like this:
class AddressSearchForm(forms.Form):
"""
A form that allows a user to enter an address to be geocoded
"""
address = forms.CharField()
I'm not storing this value, however, I am geocoding the address and checking to make sure it is valid:
def clean_address(self):
address = self.cleaned_data["address"]
return geocode_address(address, True)
The geocode function looks like this:
def geocode_address(address, return_text = False):
""" returns GeoDjango Point object for given address
if return_text is true, it'll return a dictionary: {text, coord}
otherwise it returns {coord}
"""
g = geocoders.Google()
try:
#TODO: not really replace, geocode should use unicode strings
address = address.encode('ascii', 'replace')
text, (lat,lon) = g.geocode(address)
point = Point(lon,lat)
except (GQueryError):
raise forms.ValidationError('Please enter a valid address')
except (GeocoderResultError, GBadKeyError, GTooManyQueriesError):
raise forms.ValidationError('There was an error geocoding your address. Please try again')
except:
raise forms.ValidationError('An unknown error occured. Please try again')
if return_text:
address = {'text':text, 'coord':point}
else:
address = {'coord':point}
return address
What I now need to do is create a view that will query a model using the address data to filter the results. I'm having trouble figuring out how to do this. I'd like to use CBV's if possible. I can use FormView to display the form, and ListView to display the query results, but how do I pass the form data between the two?
Thanks in advance.
UPDATE: I know how to query my model to filter the results. I just don't know how to properly combine using a Form and Class Based Views so that I can access the cleaned_data for my filter. e.g:
Process should be:
1) Display form on get
2) Submit form and validate (geocode address) on post
3) Run query and display results
address = form.cleaned_data['address']
point = address['coord']
qs = model.objects.filter(point__distance_lte=(point, distance)
Ok, here's a generic version of what ended up working based on psjinx direction:
from django.views.generic.base import TemplateResponseMixin, View
from django.views.generic.edit import FormMixin
from django.views.generic.list import MultipleObjectMixin
class SearchView(FormMixin, MultipleObjectMixin, TemplateResponseMixin, View):
"""
A View which takes a queryset and filters it via a validated form submission
"""
queryset = {{ initial queryset }} # you can use a model here too eg model=foo
form_class = {{ form }}
def get(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
queryset = self.get_queryset()
search_param = form.cleaned_data['{{ form field }}']
object_list = queryset.filter({{ filter operation }}=search_param)
context = self.get_context_data(object_list=object_list, form=form, search_param=search_param)
return self.render_to_response(context)
This is similar to a question asked here or I will say a combination of two questions.
Django: Search form in Class Based ListView
Search multiple fields of django model without 3rd party app
django simple approach to multi-field search (if your models similar to one mentioned in this question)
Please have a look at above questions and their answers and if you still have any question then reply in comment to this answer.
Update 1
from django.views.generic.base import TemplateResponseMixin, View
from django.views.generic.edit import FormMixin
from django.views.generic.list import MultipleObjectMixin
class SearchView(FormMixin, MultipleObjectMixin, TemplateResponseMixin, View):
model = SomeModel
form_class = AddressSearchForm
template = "search.html"
def get_queryset():
## Override it here
pass
def post():
## If your form is invalid then request won't reach here
## because FormMixin is in left most position
## do something
## call self.render_to_response()
def form_valid():
## Your form is valid do something
## if form is invalid then next method will be called
pass
def form_invalid(self):
## form is not valid
## render to some template
Useful Links:
https://github.com/django/django/blob/1.4.3/django/views/generic/base.py
https://github.com/django/django/blob/1.4.3/django/views/generic/edit.py
https://github.com/django/django/blob/1.4.3/django/views/generic/list.py
Related Question:
Django - Mixing ListView and CreateView
First Post, so forgive any noob-iness -- I'm trying to create a multi-page product sign-up form. I can get the form to display and the submit button, but I can't find any examples of URL patterns for my urls.py file. I found another similar question with the following suggestions:
"You need to write a view to handle your request.(did that) You need to edit urls.py to map your quiz url to the function in views.py (trying, but failing). So when a request with that quiz url comes Django applies that view function.
When I Redirect the user to the new url is the problem. I can't seem to find an example of what the next pattern should be. Here's my urls.py code (index.html and details.html are my templates so far):
url(r'^signup/$', 'signup.views.select_product', name='select_product'),
url(r'^signup/(?P<product_id>\d+)/$', 'signup.views.subscriber_signup', name='subscriber_signup'),
#...
Here's my view code:
def select_product(request):
title = "get yourself an e-edition. wurd."
pform = ProductForm(request.POST)
if request.method == 'POST': # If the form has been submitted...
pform = ProductForm(request.POST) # A form bound to the POST data
if pform.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('signup/index.html') # Redirect after POST
else:
form = ProductForm() # An unbound form
return render_to_response('signup/index.html', {'title': title, 'pform': pform}, context_instance=RequestContext(request))
def subscriber_signup(request, product_id):
signup = Product.objects.get(id=product_id)
title = "get yourself an e-edition. wurd."
sform = SubscriberForm(request.POST)
if request.method == 'POST': # If the form has been submitted...
sform = SubscriberForm(request.POST) # A form bound to the POST data
if sform.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('signup/detail.html') # Redirect after POST
else:
sform = SubscriberForm() # An unbound form
return render_to_response('signup/detail.html', {'title': title, 'sform': sform, 'signup': signup,}, context_instance=RequestContext(request))
Or use Django's form wizard which was designed for this.
so part of the problem was I was using the (User) model in my Subscriber class, which the SubscriberForm was based. I ditched that and made a form based on a normal class with the objects defined individually.
Here's the URL patterns that eventually worked.
url(r'^signup/$', 'signup.views.select_product', name='select_product'),
url(r'^signup/(?P<product_id>\d+)/$', 'signup.views.subscriber_signup', name='subscriber_signup'),
url(r'^signup/(?P<product_id>\d+)/thankyou/$', 'signup.views.thankyou', name='thankyou'),
thanks for the responses.
Anthony