I'm still getting used to class based views, while I get the general purpose, some things still get past me. I'm following a tutorial which basically walks you through the motions but tends to ignore the fuzzy details, such as this piece of code:
class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView):
"""Generic class-based view listing books on loan to current user."""
model = BookInstance
template_name ='books/bookinstance_list_borrowed_user.html'
paginate_by = 1
def get_queryset(self):
return BookInstance.objects.filter(
borrower=self.request.user
).filter(status__exact='o').order_by('due_back')
I get the model, template_name and paginate_by parts, they're attributes of the ListView class, but what I don't get is the get_queryset part, where is it executed? As seen in the code below it's called nowhere. Where is it returned to? I guess my first question can be chalked down to "What do functions in class based views do?"
{% extends "base_generic.html" %}
{% block content %}
<h1>Borrowed books</h1>
{% if bookinstance_list %}
<ul>
{% for bookinst in bookinstance_list %}
<li class="{% if bookinst.is_overdue %}text-danger{% endif %}">
{{bookinst.book.title}} ({{ bookinst.due_back }})
</li>
{% endfor %}
</ul>
{% else %}
<p>There are no books borrowed.</p>
{% endif %}
So, two issues, first, where did the get_queryset return to, and second, what is
bookinstance_list? It's not a context variable, but seems to be used out of the blue, why is this variable usable?
Class Based Views calls get_queryset() in the get() method of your view, I'll present some example code from Django 1.11.
# django/views/generic/list.py:159
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
...
context = self.get_context_data()
return self.render_to_response(context)
The ListView class sets self.object_list = self.get_queryset() in this method, however this doesn't explain where it sets it in the context passed to your template. If we take a quick look at get_context_data():
# django/views/generic/list.py:127
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
queryset = kwargs.pop('object_list', self.object_list)
page_size = self.get_paginate_by(queryset)
context_object_name = self.get_context_object_name(queryset)
if page_size:
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
context = {
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
}
else:
context = {
'paginator': None,
'page_obj': None,
'is_paginated': False,
'object_list': queryset
}
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
return super(MultipleObjectMixin, self).get_context_data(**context)
context is assigned a dictionary with 'object_list': queryset, so when you're trying to access the resulting QuerySet from get_queryset in your template you should access object_list.
The Django documentation on class-based generic views has a section on extending your context data with additional info. https://docs.djangoproject.com/en/2.1/topics/class-based-views/generic-display/#dynamic-filtering
Related
I have created a custom mixin GetVerboseNameMixin in order to get the verbose name of model fields, and then display these in my html template using a DetailView. However, whenever I try and render the list of verbose names nothing is returned, and I cannot work out why.
Mixin.py:
class GetVerboseNameMixin:
def get_verbose_name(model, fields=[]):
verbose_names = []
for field in fields:
verbose_names.append(str(model._meta.get_field(field)))
return verbose_names
View:
class ShowProfileView(GetVerboseNameMixin, DetailView):
model = Profile
template_name = 'profiles/user_profile.html'
verbose_model_fields = GetVerboseNameMixin.get_verbose_name(model=Profile, fields=['first_name', 'surname', 'date_of_birth', 'phone_number', 'bio', 'gender', 'emergency_contact_name', 'emergency_contact_number'])
def get_context_data(self, *args, **kwargs):
context = super(ShowProfileView, self).get_context_data(*args, **kwargs)
user_profile = get_object_or_404(Profile, id=self.kwargs['pk'])
context["user_profile"] = user_profile
return context
def get_object(self, *args, **kwargs):
obj = Profile.objects.filter(id=self.kwargs['pk']).values('first_name', 'surname', 'date_of_birth', 'phone_number', 'bio', 'gender', 'emergency_contact_name', 'emergency_contact_number') # list of dictionaries
object = obj[0]
return object
Html template:
{% extends "base.html" %}
{% block content %}
<h1>Profile</h1>
<br/><br/>
{% csrf_token %}
<ul>
{% for v in object.values %}
{% for field_name in verbose_model_fields %}
<p>{{field_name}}: {{ v }}</p>
{% endfor %}
{% endfor %}
</ul>
<a href='{% url "profiles:edit_profile" pk=user.profile.id %}'>Edit Profile</a>
{% endblock %}
Even if I just render:
{{ verbose_model_fields }}
In my html file nothing is being displayed. This leads me to think maybe the problem is in my mixin, or perhaps the function is not being called properly?
I do not get how verbose_model_fields is getting passed to the template from the view, and also, I did not find any reference in DetailView documentation. I assume you want to have this custom implementation, if so, then you need to pass along this parameter via context:
class ShowProfileView(GetVerboseNameMixin, DetailView):
...
def get_context_data(self, *args, **kwargs):
context = super(ShowProfileView, self).get_context_data(*args, **kwargs)
user_profile = get_object_or_404(Profile, id=self.kwargs['pk'])
context["user_profile"] = user_profile # redundant implementation, you can get this value by `object` context variable in template
context["verbose_model_fields"] = self.verbose_model_fields # or just call self.get_verbose_name(...) method
return context
Also in this solution I have marked redundant implementation that you do not need to re-implement how to get object, because it is already taken care by DetailView.
I am trying to render two differents models using a loop in my templates/dashboard/home.html using Class Based View.
Let's have a look on my code :
views.py
class DashboardListView(ListView):
model = Links
template_name = 'dashboard/home.html'
class ContentListView(ListView):
model = Dashboard
template_name = 'dashboard/home.html'
First I would like to used the same ListView but was unable to do so.
My home.html
{% for dashboard in object_list %} {{dashboard.content}} {% endfor %}
{% for links in object_list %} {{links.content}} {% endfor %}
I would like to render those two models but I can only render one and the other object list will take the content for the preivous one.
Thanks for your help
First you can specify a different Mae for the list of your objects using:
context_object_name =‘links_list’
Then you can add different elements to the context extending this method:
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet
context[‘dashboard_list’]= Dashboard.objects.all()
return context
I am learning following Django 1.7 tutorial in https://docs.djangoproject.com/en/1.7/intro/tutorial04/.
I copied code from web, then run it. I encounter two question:
After adding IndexView which inherits ListView, the 127.0.0.1:8000/polls page just returns "No polls are available." without any database items. I am confused by classed based view, seems the context value is not passed to template.
----------> solved, this is my silly type mistake.
get_queryset(), what is for, when this method is called, how does this map to context? How does this function knows context mapping, what if there are two contexts and two values?
Could someone give me some guide here? thanks a lot.
polls/urls.py
from django.conf.urls import patterns, url
from . import views
urlpatterns = patterns('',
url(r'^$', views.IndexView.as_view(), name='index'),
#url(r'^$', views.index, name='index'), #this url works
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
url(r'^(?P<pk>\d+)/results/$', views.ResultsView.as_view(), name='results'),
url(r'^(?P<question_id>\d+)/vote/$', views.vote, name='vote'),
)
polls/views.py
from django.http import HttpResponse, Http404,HttpResponseRedirect
from django.template import RequestContext, loader
from django.shortcuts import render,get_object_or_404
from .models import Question, Choice
from django.core.urlresolvers import reverse
from django.views import generic
class IndexView(generic.ListView):
template_name ='polls/index.html'
context_object_name = 'last_question_list'
def get_queryset(self):
return Question.objects.order_by('-pub_date')[:5]
#this works
#def index(request):
# latest_question_list = Question.objects.order_by('-pub_date')[:5]
# context = {'latest_question_list': latest_question_list}
# return render(request, 'polls/index.html', context)
polls/index.html
{% if latest_question_list %} <!-- seems here value is not passed by -->
<ul>
{% for question in latest_question_list %}
<li>
{{ question.question_text }}</li>
{% endfor %}
</ul>
{% else %}
<p>{{ latest_question_list }}</p>
<p>No polls are available.</p> <!-- always display this -->
<p>{{ latest_question_list }}</p>
{% endif %}
database
>>> from polls.models import Question
>>> Question.objects.all()
[<Question: What's up?>, <Question: tttt>]
Since Django is Open Source, it is possible to determine, how queryset becomes available in template via context_object_name variable. Let's take a look at django.views.generic.list.py:
class MultipleObjectMixin(ContextMixin):
# Some fields
context_object_name = None
def get_queryset(self):
# get_queryset implementation
def get_context_object_name(self, object_list):
"""
Get the name of the item to be used in the context.
"""
if self.context_object_name:
return self.context_object_name
elif hasattr(object_list, 'model'):
return '%s_list' % object_list.model._meta.model_name
else:
return None
def get_context_data(self, **kwargs):
queryset = kwargs.pop('object_list', self.object_list)
# Some stuff
context_object_name = self.get_context_object_name(queryset)
# Some other stuff
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
return super(MultipleObjectMixin, self).get_context_data(**context)
# And, of course, a lot of other functions
class BaseListView(MultipleObjectMixin, View):
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
# Some magic
context = self.get_context_data()
return self.render_to_response(context)
# Skipped
When you are calling get method, Django initializes self.object_list with get_queryset(). Then, it calls get_context_data() and passes the context data to a template.
MultipleObjectMixin.get_context_data returns a dictionary with name->variable pairs. It creates a pair get_context_object_name() -> object_list, and that is why you can access your list using defined context_object_name name.
Class based views make the task of the developer a lot easier by providing a lot of pre-baked code. ListView is used to return a list of objects. ListView like all other views is extended from View. View implements a get_context_data method which is passes data to be used within the template. So, now if examine the get_context_data method of ListView, we find this:
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
queryset = kwargs.pop('object_list', self.object_list)
page_size = self.get_paginate_by(queryset)
context_object_name = self.get_context_object_name(queryset)
if page_size:
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
context = {
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
}
else:
context = {
'paginator': None,
'page_obj': None,
'is_paginated': False,
'object_list': queryset
}
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
return super(MultipleObjectMixin, self).get_context_data(**context)
As you can see, object_list is set to queryset or if you provide context_object_name, it is set to queryset. And queryset is set by the get_queryset defined by you.
I hope this makes the process clear.
I have created for interface for users to filter the content from database by values.
Class in View
class FilterCommentsUIView(TemplateView,FormView):
template_name = 'allcomments.html'
form_class = CommentFilterForm
def get_context_data(self, **kwargs):
context = super(FilterCommentsUIView, self).get_context_data(**kwargs)
logger.debug("Entered context data")
logger.debug(self.request.method)
if self.request.method == 'POST':
form = CommentFilterForm(self.request.POST)
loggeer.debug("Entered POST")
if form.is_valid():
logger.debug("Entered is valid")
parenttype = self.request.POST['pchoice']
users = self.request.POST.get('users')
tags = self.request.POST.get('tags')
fdate = self.request.POST.get('fdate')
tdate = self.request.POST.get('tdate')
typeuser = self.request.POST.get('typeuser')
query = self.request.POST.get('query')
userid = User.objects.get(username ='pavan')
comments = Item.objects.all().filter(user = userid.id).order_by('-created')
paginator = Paginator(comments,20)
page = self.request.GET.get('page')
try:
comments = paginator.page(page)
except PageNotAnInteger:
comments = paginator.page(1)
except EmptyPage:
comments = paginator.page(paginator.num_pages)
context['comments']=comments
context['form']=CommentFilterForm
else:
logger.debug("Entered isvalid else")
logger.debug(form.errors)
else:
logger.debug("Entered Else of POST")
form = CommentFilterForm()
comments = Item.objects.all().order_by('-created')
paginator = Paginator(comments, 20)
page = self.request.GET.get('page')
try:
comments = paginator.page(page)
except PageNotAnInteger:
comemnts = paginator.page(1)
except EmptyPage:
comments = paginator.page(paginator.num_pages)
context['comments']=comments
context['form']= form
return context
When i click on submit button it does not enter the POST if section. It always goes to else part. What am i doing wrong ?
Thanks in advance for help.
Pavan
As others have said you have issues with the fact all your code is in get_context_data. In all actuality you don't even need to use get_context_data to accomplish what you are trying to do.
From what I can see you are trying to get comments on the page. Then submit a form back to the page and get a filtered out set of comments returned, and rendered. Here is a view solution that better does what you want except using generic class based views more fully.
class FilterCommentsUIView(FormMixin, ListView):
template_name = 'allcomments.html'
form_class = CommentFilterForm
paginate_by = 20
model = Item
context_object_name = 'comments'
qs_kwargs = {}
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
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 get_queryset(self):
qs = super(FilterCommentsUIView, self).get_queryset()
if self.qs_kwargs:
return qs.filter(**self.qs_kwargs)
return qs
def form_valid(self, form):
parenttype = form.cleaned_data['pchoice']
users = form.cleaned_data('users')
tags = form.cleaned_data('tags')
fdate = form.cleaned_data('fdate')
tdate = form.cleaned_data('tdate')
typeuser = form.cleaned_data('typeuser')
query = form.cleaned_data('query')
userid = User.objects.get(username ='pavan')
# put logic here on what to expand into a filter on orm
self.qs_kwargs['user'] = userid.id
self.render_to_response(self.get_context_data())
I haven't run this code, but this is how it should work.
This uses the FormMixin to give us form functionality along with the ListView with your Item class. This takes care of querying for your objects and paginating them, along with a basic get method.
Also the ListView inherits from TemplateResponseMixin which has your template code so you don't need to inherit from TemplateView so in the code above I removed that.
Based on the very nature of the base View class which ListView uses the dispatch method detects whether you are doing a GET, POST, PUT, DELETE, etc... As such it calls the appropriate method on the view. In this case since you are doing a POST it will call the post method in the view. The FormView's implementation of post is this:
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
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)
Since we aren't using FormView, but are using FormMixin We need to include it.
So all it does is get the form class you set in self.form_class instantiates it, then checks if it is valid. If so calls the form_valid method. Thus we overrode the form_valid method above. Since it passes in the form with it and it has cleaned all the data we can use the normal django form of form.cleaned_data. This helps us with security so we should use that to get back information.
In the last part of the form_valid method we are returning render_to_response. Normally this just redirects to self.success_url, but we want to render the page so we just do a render_to_response and pass it in our context data. We call self.get_context_data() because that is what builds all the data for paginating our comments that we need.
Now comes a bit of the magic. if you notice there is self.qs_kwargs. This is something that is not normally in GCBV's, but something I added for this implementation. Here is where you would put logic to build a dictionary of filters to run in your orm call in get_queryset. We pack it into the dictionary, because in the get_queryset method above we unpack it all, if needed, to do our filtering.
So if you have:
qs_kwargs = {'user': user.id, 'created__gt': somedate}
And you do:
qs.filter(**qs_kwargs) or Item.objects.filter(**qs_kwargs)
That is roughly the same as doing:
qs.filter(user=user.id, created__gt=somedate) or Item.objects.filter(user=user.id, created__gt=somedate)
Finally, if you pay attention to the get_queryset method you notice it returns back all results, like you want, unless qs_kwargs is populated which happens from the form_valid method. So it takes in to account both GET and POST on the page.
Also to keep things simple your html should look something like this:
<html>
<head></head>
<body>
<div>
{% for comment in comments %}
{{ comment }}
{% endfor %}
<ul class="pagination">
{% if page_obj.has_previous() %}
<li>Previous</li>
{% endif %}
{% for pg in paginator.page_range %}
{% if page_obj.number == pg %}
<li class="active">{{ pg }}</li>
{% else %}
<li>{{ pg }}</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next() %}
<li>Next</li>
{% endif %}
</ul>
</div>
<div>
<form action="" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="submit" />
</form>
</div>
</body>
</html>
Hope that helps.
This needs to be done in the form_valid() method, not the get_context_data method. get_context_data is just used to provide extra data (aka context) to the template during the request.
The code below paginates the queryset but how can I paginate context['guser1']
class AuthorList(ListView):
template_name = 'authorList.html'
paginate_by = 10
queryset = Author.objects.order_by('date')
def get_context_data(self, **kwargs):
context = super(AuthorList, self).get_context_data(**kwargs)
context['guser1'] = Author.objects.order_by('date')
return context
You'll have to use a Paginator object like you would in a view. Here are the docs.
The paginator for the view can be accessed under the context variable page_obj in your template. The page number is passed as a GET parameter in the url, page. Here's a simple example:
{% if page_obj.has_previous %}
Previous page
{% endif %}
{% if page_obj.has_next %}
Next page
{% endif %}