I am fairly new to Django and class based forms, and I am having trouble understanding how these interact with each other. Following from the django project example, I have tried to build a "search form", which would sit on all pages of my project:
# forms.py
from django import forms
class SearchForm(forms.Form):
myquery = forms.CharField(max_length=255,label="", help_text="sq")
def __unicode__(self):
return self.myquery
# views.py
from searchapp.forms import SearchForm
from django.views.generic.edit import FormView
from django.views.generic import TemplateView
class SearchView(FormView):
template_name = 'index.html'
form_class = SearchForm
success_url = '/searchres/'
def form_valid(self, form):
thequery=form.cleaned_data.get('myquery')
return super(SearchView, self).form_valid(form)
class Meta:
abstract = True
class SearchResView(SearchView):
template_name = 'searchres.html'
#urls.py
from django.conf.urls import patterns, include, url
from django.conf import settings
from deals.views import IndexView
from searchapp.views import SearchView, SearchResView
urlpatterns = patterns('',
url(r'^index/', SearchView.as_view(),name="home"),
url(r'^searchres/', SearchResView.as_view(),name="searchresx"),
)
The plan is the start off with a simple form for user to enter the search query, and also show the input form on the results page. I have the following questions here (sorry - I am a Django newbie esp. to Class Based Views):
How does one pass data ("thequery") to the success_url? i.e I would like success_url to have access to "thequery" so that I can use something like {{thequery}} on my template tags.
Upon submitting the form(name="home"), I see POST data from the form on my firebug, but I am able to see just "myquery" rather than "thequery". How does one use get_context_data() here to add/post "thequery" variable aswell?
Finally, I was wondering if it would be possible to construct the success_url based on "thequery" string i.e something like success_url = '/searchres/?q=' + thequery
Thank you in advance - I am hoping to learn more.
I would suggest using function based views for this. If you choose to subclass a generic view you will need to dig through a lot of documentation and possibly source code, to find the right methods to override. (If you're really keen then look at the ListView class along with the get_queryset(), get() and post() methods)
A single django view will normally handle both rendering the empty form AND processing the submitted form.
So the search page (both the form and the results), live at http://your-site.com/search. Your url conf is -
urlpatterns = patterns('',
#...
(r'^search/$', 'searchapp.views.search'),
)
And your view looks something like this -
def search(request):
if request.method == 'POST':
form = SearchForm(request.POST)
if form.is_valid():
my_query = form.cleaned_data['myquery']
object_list = YourModel.objects.filter(# some operation involving my_query)
return render_to_response('search_results.html', {'object_list': object_list})
else:
form = SearchForm()
render_to_response('search_form.html', {'form': form})
(Note I've assumed your form method is post rather than get - I know this isn't great http but it's a common pattern with django)
To respond to your questions -
Don't use your own method for cleaning data. Add a clean_myquery method to your form and access it with form.fields['myquery'].clean() (or if you've called is_valid() on your form, it's accessible with just form.cleaned_data['myquery']).
You want to try and avoid passing data for processing to the template. Do as much processing as you can in the view, then render the template. However if you want to pass myquery as a string for the template to render, then add it in to the context dictionary (the second non-key-word argument) in render_to_response -
return render_to_response('search.html', {'object_list': object_list, 'myquery': my query})
The post data is constructed from the form fields. You don't have a form field thequery. The view is processing the POST data - it's not creating it that's done by the html (which in turn is constructed by the Form class). Your variable thequery is declared in the view.
Django's URL dispatcher ignores query strings in the URL so http://your_site.com/ssearch will be processed by the same view as http://your_site.com/search?myquery=findstuff. Simply change the html in the template from <form method='post'> to and access the data in django with request.GET. (You'll need to change the code from the view I described above to include a new check to see whether you're dealing with a form submission or just rendering a blank form)
Have a good read of the docs on views, forms and the url dispatcher.
Related
I have several function based views in a django project, and I noticed that they have some repeated code, in fact, they all do the same thing:
def my_view(request):
form = MyForm(request.POST or None)
if requqest.POST:
if form.is_valid():
do_somethin()
and_something_else()
return redirect('another:page')
return render(request, 'my/tempalate.html', {'form': form})
The only thing that is different is the url where the user is redirected to in case of a succcesful form validation, the template, and maybe the form in the future.
Is it a good idea to use something like this to avoid that repetition?:
def a_view(request, success_redirect_url, template):
form = MyForm(request.POST or None)
if request.POST:
if form.is_valid():
do_something()
and_something_else()
return redirect(success_redirect_url)
return render(request, template, {'form': form})
and then reuse it in other views that have the repeated code? like:
def my_view1(request, url='another_page', template='my/template.html'):
return a_view(request, url, template)
Typically such patterns are defined in class-based views where one can use inheritance to override only some parts of the code flow.
Class-based views
Your view for example looks quite similar to a FormView [Django-doc]. Indeed, we can define a view with:
# app_name/views.py
from django.urls import reverse_lazy
from django.views.generic import FormView
class MyView(FormView):
template_name = 'my/template.html'
success_url = reverse_lazy('another_page')
form_class = MyForm
def form_valid(self, form):
do_somethin()
and_something_else()
return super().form_valid(form)
Here the class-based view has implemented a handler for a POST request that will first construct the form, then check if that form is valid, and if it is invalid rerender the template with the form that now contains the errors. It thus minimizes the amount of code that one has to write to handle a simple form.
Such views also explain clearly what they are doing. A CreateView for example explains by its name that it is used to create new objects, it will thus trigger the .save() method of the form it is operating on. A DeleteView on the other hand makes it clear that a POST request to that view will delete an object.
You can also override the attributes in the urls.py by specifying a value in the .as_view(…) [Django-doc] call:
# app_name/urls.py
from app_name.views import MyView
from django.urls import path
urlpatterns = [
# ⋮,
path('some/path/', MyView(template_name='other/template.html'), name='some-name'),
# ⋮
]
A class-based function thus acts as a template method pattern
Decorators
Another way to do this is to work with decorators that implement small pieces of logic that run before/after a call to the function-based view, and can alter the request/response, or decide to raise an error instead of calling the view.
For example the #require_http_methods(…) decorator [Django-doc] [GitHub] is implemented to first check if the method is one of the listed ones. This is implemented as:
def require_http_methods(request_method_list):
# ⋮
def decorator(func):
#wraps(func)
def inner(request, *args, **kwargs):
if request.method not in request_method_list:
response = HttpResponseNotAllowed(request_method_list)
log_response(
'Method Not Allowed (%s): %s', request.method, request.path,
response=response,
request=request,
)
return response
return func(request, *args, **kwargs)
return inner
return decorator
here the decorator thus checks if the request.method is a member of the request_method_list. If that is not the case, it will return a HTTP 405 response, and specify that that method is not allowed.
While Django offers a lot of decorators, most decorators have a mixin counterpart for a class-based views, and some are implemented already in the View class. For example if the View does not contains a get method, then it will return a HTTP 405 response, so here the required_http_method is not needed as a mixin/decorator for a class-based view.
Skeleton functions
You can implement a view function and use parameters instead to pass values. Usually however this will not be as flexible as a class-based view: it is rather easy to pass some parameters, but it is less useful to specify behavior in a pattern: in that case you need to pass a reference to a function, but then the question arises what parameters should be passed to that.
For example if we want to make a function that renders the template, we can work with a view that looks like:
from django.shortcuts import render
def render_some_template(request, parameter, context_generator, template='our/template.html'):
context = context_generator()
return render(request, template, context)
but perhaps the context_generator function should be called together with the parameter? or perhaps know what template will be rendered. This is one of the reasons why altering code flow is usually done with a class-based view, and less with a function-based view.
While a function-based view can work with a skeleton function, it is usually less exendible than the class-based counterpart. Django's builtin apps (the ones defined in the django.contrib module) are moving mainly towards class-based views, since it is easier to extend these.
You can do a lot better if just give a name to any button wich lunch this function:
<button name='lukaku'>
<button name='ronaldo'>
then into the view check for the name
if form.get('lukaku'):
do something
if form.get('ronaldo'):
do other thing
and so on. this can be limitless
I am trying to simulate Google's behavior where the user types something on the address bar of the browser and the Django server checks for any exact matches in the database. If so, a detailview of the object is rendered. If not an exact match, then a list of matches on substrings are rendered with ListView.
This behavior works fine when the user types into a search form. For instance, when the user just types the letter 'j' in the search form and hits submit, the Django server matches on 3 objects in the data base 'Django, Java, node.js' and renders this list through ListView. If there is an exact match, say the user typed 'java', then the Django server renders details about the object 'java' in a Detail view.
However, I could not figure out how to derive the same behavior when applied to what the user types on the address bar of the browser. If the user happens to type just the exact spelling of an item in the db, the details of the item is rendered, otherwise we get a 404 error.
The relevant segments of the html form, urls.py, and views.py are displayed below
<form method="GET" action="{% url 'searchwiki' %}">
<input class="search" type="text" name="q" placeholder="Search Encyclopedia">
</form>
urls.py :
urlpatterns = [
path("", views.EntryListView.as_view(), name="index"),
path("entries/",views.EntryListView.as_view(),name='entries'),
path("entry/<int:pk>",views.EntryDetailView.as_view(),name="entry_detail"),
path("<int:pk>", views.EntryDetailView.as_view(), name="path_entry-detail"),
# path("<slug:subject>",views.get_obj_orlist),
path("<slug:subject>",views.EntryDetailView.as_view()),
path("random/",views.randompage,name="randompage"),
path("searchwiki/",views.searchwiki,name="searchwiki"),
]
views.py :
from django.urls import path
from . import views
from .models import Entry
from django.shortcuts import get_object_or_404, render, redirect
from django.views import generic
from django.views.generic.edit import CreateView, UpdateView, DeleteView
def searchwiki(request):
searchtoken = request.GET.get('q')
try:
entry = Entry.objects.get(subject=searchtoken)
except Entry.DoesNotExist:
entries = Entry.objects.filter(subject__icontains=searchtoken)
print("Inside exception code. qset size is ",len(entries))
return render(request, 'wikiencyc/searchencyc.html','entries':entries,'searchtoken':searchtoken})
return redirect(entry)
def get_obj_orlist(request):
model = Entry
slug_field = 'subject'
slug_url_kwarg = 'subject'
# below is the solution if it works
slug = kwargs.get(slug_url_kwarg)
try:
entry = Entry.objects.get(subject=slug)
except Entry.DoesNotExist:
entries = Entry.objects.filter(subject__icontains=slug)
return render(request, 'wikiencyc/searchencyc.html', {'entries':entries,'searchtoken':slug} )
return redirect(entry)
class EntryDetailView(generic.DetailView):
model = Entry
slug_field = 'subject'
slug_url_kwarg = 'subject'
The "searchwiki/" path in urls.py and the searchwiki(request) function in views.py work perfectly together for said functionality in response to search form.
As for parsing the slug from the address bar, the EntryDetailView(generic.DetailView) does a perfect job for an exact match of the parsed slug in the database, but responds with a 404 exception to the screen instead of a list of substring matches.
My attempt at replicating the searchwiki function for the addressbar is the function get_obj_orlist(request). It fails because I could not figure out how to get the slug from the address bar and urlconf to said function. It is probably something very simple but after 2 days of searching through the Django server code and docs I am saying AARRGG!!! when I see args and kwargs. Any help is deeply appreciated. I am still struggling with regular expressions, so I would appreciate it if these can be avoided in the solution presentation.
I found the solution. As I had expected, it was a very trivial overlook. Just including subject as a parameter into the function allowed its use in the Entry model for querying purposes. The working function is depicted below.
def get_obj_orlist(request, subject):
model = Entry
try:
entry = Entry.objects.get(subject=subject)
except Entry.DoesNotExist:
entries = Entry.objects.filter(subject__icontains=subject)
return render(request, 'wikiencyc/searchencyc.html', {'entries':entries,'searchtoken':subject} )
return redirect(entry)
Here is my code that I believe pertains to this situation. I'm sorry, I'm new to django.
views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import SearchForm
def result_one(request):
return render(request, "testresult.html", {})
def get_results(request):
if request.method == 'POST':
form = SearchForm(request.POST)
if form.is_valid():
return HttpResponseRedirect('/result/')
else:
form = SearchForm()
return render(request, 'index.html', {'form': form})
urls.py
from django.conf.urls import url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^search/$', "search.views.get_results"),
url(r'^result/$', "search.views.result_one"),
]
forms.py
from django import forms
class SearchForm(forms.Form):
client_group_number=forms.IntegerField(label='Group Number', widget=forms.TextInput(attrs={'placeholder': 'Group Number'}))
From my understanding, what I believe should happen is that an input will be put into a html page. When the user hits submit, the input gets saved into forms.py as data. This data gets manipulated in views.py which gets displayed in a different html page. (I hope this is correct)
What I want it to do is take in an input for client_group_number(in forms.py) from index.html(for example: 123), that can be accessed in views.py and displayed in another html template that I have called testresult.html, which would display Group Number = 123 (the 123 coming from either the forms.py or views.py).
This might be a very simple thing to accomplish and I apologize if it is, but I can't seem to find what I need on the internet.
Django validate the form input data in the cleaned_data dictionary. You would need to pass this to the new template either as arguments in the redirect or using session. Here is one simple example to give you an idea, there are probably better ways.
if form.is_valid():
group_number = form.cleaned_data["client_group_number"]
HttpResponseRedirect("/result/?group_number=" + group_number)
I've recently learned Django forms by subclassing FormView, where the desired form is assigned to the FormView.form_class attribute. When the form validates, the form_valid() method is invoked (for that one form). For example:
from accounts.forms import SignUpForm, UpdateAccountForm, UpdateBillingForm
class SignUpView(FormView):
form_class = SignUpForm
def form_valid(self, form):
# code when form validates...
However, I now have a situation where I need three unique forms on one page (with only one form visible to the user at a time). So, I'd like to handle them all in the same View.
Are multi-form pages possible using FormView? I'm not sure how to handle it, both in terms of passing multiple forms to the View (e.g. the other UpdateAccountForm and UpdateBillingForm), as well as distinguishing which one was submitted/validated? What would be the best way?
Well, for what it's worth here's what ultimately worked for me, using a generic View.
1) I added a hidden input field (named 'action') to each individual form on the page. For example, this is the form for updating user's info, which is pulling in UserForm:
<form action='/account/' method='post'>{% csrf_token %}
<input type='hidden' name='action' value='edit_user'>
{{ user_form.as_p }}
<input type='submit' value='Update'>
</form>
2) In my View logic, I can distinguish the forms by applying a prefix (per other SO posts and Django docs). Then, depending on the incoming 'action', I only bind the applicable form to the POST request (so validations aren't applied across all of them). In my case, I had two forms defined in forms.py, UserForm and BillingForm:
from django.views.generic.edit import View
from django.shortcuts import render
from django.http import HttpResponse
from accounts.forms import UserForm, BillingForm
class AccountView(View):
def get(self, request):
# code for GET request...
def post(self, request):
#instantiate all unique forms (using prefix) as unbound
user_form = UserForm(prefix='user_form')
billing_form = BillingForm(prefix='billing_form')
# determine which form is submitting (based on hidden input called 'action')
action = self.request.POST['action']
# bind to POST and process the correct form
if (action == 'edit_user'):
user_form = UserForm(request.POST, prefix='user_form')
if user_form.is_valid():
# user form validated, code away..
elif (action == 'edit_billing'):
billing_form = BillingForm(request.POST, prefix='billing_form')
if billing_form.is_valid():
# billing form validated, code away..
# prep context
context = {
'user_form': user_form,
'billing_form': billing_form,
}
return render(request, 'accounts/account.html', context)
Seems to work well, hopefully this is the right approach (?)
You can write a plain python class mimicking the Form API (at least the useful parts) and wrapping your three forms. Detecting which form has been submitted is just a matter of adding a hidden input with the form's identifier in each form (hint : use prefixes for your forms and use that same prefix as identifier).
The other solution is to use a simple function-based view instead, but even there I'd still use the same "form wrapper" pattern as far as I'm concerned.
I am asking user to fill extra fields with custom form. And in one of the fields, I have to let user choose multiple hierarchical tags. For this, I need to pass the tags from a view to the template signup.html
from classes.Tags import Tags
from django.shortcuts import render_to_response
from allauth.socialaccount import views as signup_views
def signup_view(request):
tags = Tags()
parameters={}
all_tags = tags.get_tags()
parameters['all_tags'] = all_tags
response = signup_views.signup(request)
return response
And in urls.py, I added this line before the allauth urls include line.
url(r'^accounts/social/signup/', 'mainapp.signup_views.signup_view', name = 'account_signup'),
url(r'^accounts/', include('allauth.urls')),
What I need is that I need to add all_tags to the response so that I can access it from the template. How do I do that?
This link has some details on using your own signup form. IMO, you can define your own form (eventually with a custom widget for the tags) and use it directly, without having to mess with the view.
Otherwise, #PauloAlmeida is correct. You could inherit a new class off SignupView with something like:
class MySignupView(SignupView):
def get_context_data(self, **kwargs):
ret = super(MySignupView, self).get_context_data(**kwargs)
ret['all_tags'] = Tags.get_tags()
return ret
I'd rather use the custom form approach as it won't mess up the urls.py.