Creating Template Links for Pagination in Generic CBV - django

I'm just starting with Django so I have a lot of questions. I've built a Mixin to create the paginator template links for "next page" and previous page in a GCBV (ListView):
class PageLinksMixin:
page_kwarg = 'page'
def _page_urls(self, page_number):
return "?{pkw}={n}".format(pkw=self.page_kwarg, n=page_number)
def previous_page(self, page):
if page.has_previous():
return self._page_urls(page.previous_page_number())
return None
def next_page(self, page):
if page.has_next():
return self._page_urls(page.next_page_number())
return None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
page = context.get('page_obj')
if page is not None:
context.update({'previous_page_url': self.previous_page(page),
'next_page_url': self.next_page(page)})
return context
Everything seems to work. But I can simply not see where some of the inputs for functions were created. Especially "page", "page_number".
With a CBV it would be necessary to declare:
page = paginator.page(page_number)
But in the GCBV this is apparently unnecessary which means the attribute is created somewhere and later inherited.
For the ListView there is a function def paginate_queryset but in there page is only declared in a local scope not a global scope. Can somebody explain this to me? I'm really confused and trying to figure this out for a while now.
This is the View:
class TagList(PageLinksMixin, ListView):
template_name = 'organizer/tag_list.html'
paginate_by = 5
model = Tag

The Django generic ListView has a paginator object, which is added to the context through self.get_context_data(). Here's the Django source code:
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)
In your mixin, you first call super().get_context_data(), which populates the context with the paginator and a page object, which you set to page:
page = context.get('page_obj')
Finally you call self.previous_page(page) to fetch the actual URL and add it to the context. Nothing magic.
You could also have fetched the page_number from the kwargs and the page from the paginator instance but since it's already done for you, the page_obj in the context is the easiest way.

Related

How Do I Override GET in Django Detailview?

I have a FORMVIEW that is redirecting to a DETAILVIEW and that is working perfectly. The issue I'm having is when I try to combine Pagination with DetailView. When I try to leverage the pagination, the GET is essentially redirecting me to the FORMVIEW. I get why it's doing this...I'm telling it to. What I'm trying to work out is how I can put in some logic in my overridden GET. I tried to do a self.GET_OBJECT.pk...to see if I could determine if I'm on the current page and not the FORMVIEW but that didn't work...
Here's my DetailView....
def get_context_data(self, **kwargs):
context = super(SuggestionByNameDetailView, self).get_context_data(**kwargs)
attachments = SuggestionFiles.objects.filter(suggestion=self.object.pk).all()
comment_form = SuggestionCommentForm()
response_form = SuggestionCommentReplyForm()
activities= self.get_related_activities()
context['suggestion_comments'] = activities
context['page_obj'] = activities
context['attachments'] = attachments
context['comment_form'] = comment_form
context['response_form'] = response_form
return context
def get_related_activities(self):
queryset = self.object.suggestion_comments.all()
paginator = Paginator(queryset,5) #paginate_by
page = self.request.GET.get('page')
activities = paginator.get_page(page)
return activities
def get_object(self, queryset=None):
return get_object_or_404(Suggestion, id=self.request.GET.get("dropdown"))
def get(self, request, *args, **kwargs):
dropdown=self.request.GET.get("dropdown")
if dropdown is not None:
if Suggestion.objects.filter(Q(id=self.request.GET.get("dropdown"))).distinct():
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
else:
raise Http404
else:
messages.add_message(self.request, messages.INFO, 'Suggestion is required.')
return HttpResponseRedirect(reverse('Suggestions:suggestion_by_name'))
As mentioned I did try to do something like...if DROPDOWN is NONE and pk = self.get_object().pk...to essentially try and determine if I can just bypass the DROPDOWN logic in GET...but the PK is always present...I also tried to do something like self.request.GET.get('pk') but that didn't work either.
When I click to do the pagination I get redirected back to the FORMVIEW. I'm trying to avoid that once I'm on the DETAILVIEW. Thanks in advance for any thoughts on what I can do to resolve this.
So....after a couple of hours....turns out....GET may be an issue...but here's a way around it.....The "canned" django code...for pagination looks something like this...
Next
In my case...I simply needed to get more specific with my URL...so it now looks more like....
Next
Case closed. This one anyway. :).

Django tutorial. Generic views. context_object_name = 'latest_question_list'

I'm a bit confused with Django generic views. As shown in here we are converting custom views into generic views. And while I understand what happens in DetailView and ResultsView, I don't entirely grasp how this:
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {
'latest_question_list': latest_question_list,
}
return render(request, 'polls/index.html', context)
converts into this:
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""Return the last five published questions."""
return Question.objects.order_by('-pub_date')[:5]
In the first example, latest_question_list = Question.objects.order_by('-pub_date')[:5]
But in the second example, what latest_question_list variable is equal to in here? We haven't even defined it..
Can anyone please shed some light into this?
A ListView behind the curtains performs a lot of operations to create a context and pass that to a render engine. We can take a look at the implementation through Classy Class-Based Views.
In essence when you trigger such class-based view you will, depending on the HTTP method, trigger the get(..), post(..), etc. method.
The get(..) method is defined by the BaseListView class, and defined as:
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if not allow_empty:
# When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
is_empty = not self.object_list.exists()
else:
is_empty = not self.object_list
if is_empty:
raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
'class_name': self.__class__.__name__,
})
context = self.get_context_data()
return self.render_to_response(context)
The import part is that we first the result of get_queryset() to self.objects_list, and later construct a context with self.get_context_data(). We then make a call to self.render_to_response(..) which basically will use the specified template, and render it with the given context.
The get_context data has two parents with an implementation. The most basic (highest in the inheritance hierarchy) is that of the ContextMixin, but this function does not do much:
def get_context_data(self, **kwargs):
kwargs.setdefault('view', self)
if self.extra_context is not None:
kwargs.update(self.extra_context)
return kwargs
It simply takes the dictionary constructed by the keyword arguments (empty if there are no keyword arguments, which is the case here), and it adds an extra key 'view' that is associated with self. It also can add extra key-value pairs that can be defined in self.extra_context, but we can ignore that here.
The most interesting logic is implemented in the MultipleObjectMixin:
def get_context_data(self, *, object_list=None, **kwargs):
"""Get the context for this view."""
queryset = object_list if object_list is not None else 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().get_context_data(**context)
What happens here is that we first assign self.object_list, the variable that we first have set with the outcome of self.get_queryset to a local variable named queryset. We then will paginate that queryset, but that is not very relevant for your question.
We then obtain the name by calling self.get_context_object_name(queryset). By default this is implemented as:
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
So if you have set the context_object_name attribute, like you did, then it will simply return that name. So we can conclude that in the get_context_data(..), method, the context_object_name will have the name you privided, here 'latest_question_list'.
We then keep processing the code in get_context_data(..): we construct a dictionary, and at the bottom we check if context_object_name is not None. If that is the case, we associate the queryset with that key (so here with 'latest_question_list'). Eventually when the correct context dictionary is constructed, we make a super() call with the constructed context as **kwargs, and as we discussed before, the ContextMixin, will simply return that dictionary with very small changes.
So at the end the context will have the name of your list (here 'latest_question_list') associated with the queryset, and it will render the template with that context data.
In class based view you used context_object_name = 'latest_question_list'
That why it's similar to latest_question_list, you used in function based view.
In class based view, if you don't add context_object_name then it's value automatically object_list.
Something like context_object_name='object_list'.
TL;DR version of the accepted answer.
Under the hood, Django does a lot of things in the ListView generic view.
For such a view:
class IndexView(generic.ListView):
model=Question
The automatically generated context variable is question_list.
If you want to override it, you must use context_object_name variable to set a name for your custom context variable. That's all it is, just setting a name.
Then you must use the get_queryset predefined function, which will relate its output to the context_object_name variable.
Thus, it is important to use these particular names for the variable and the function.

How do I get django url parameters from a view mixin?

Exactly what the title says. I have a mixin that needs to pull in the id of a model field in order to be useful. I assume the easy way to do that would be to pull it from the URL.
class StatsMixin(ContextMixin):
def get_stats_list(self, **kwargs):
# the ??? is the problem.
return Stats.objects.filter(id=???).select_related('url')
def get_context_data(self, **kwargs):
kwargs['stats'] = self.get_stats_list()[0]
print kwargs
return super(StatsMixin, self).get_context_data(**kwargs)
Here's the view implementation for reference.
class ResourceDetail(generic.DetailView, StatsMixin):
model = Submissions
template_name = 'url_list.html'
queryset = Rating.objects.all()
queryset = queryset.select_related('url')
You can access URL parameters in Django by using, self.args and self.kwargs.

django migrating to 1.6 of an old app: ListView

I am migrating our old app to django 1.6.
Now, some views have been programmed this way:
from django.views.generic.list_detail import object_list
#render_to("items/index.html")
def index(request):
profile = request.user.get_profile()
args = clean_url_encode(request.GET.copy().urlencode())
context = {
'is_dashboard': True,
'body_id': 'dashboard',
'object_list': None,
'args':args,
'show_in_process':False
}
return context
I know that I need to use the new ListView now, but the examples and docs don't seem to tell me this particular case I have: the object_list being passed in the context.
How can I adapt this code to use the new class based generic view? Can I also just use ListView.asView() instead of 'object_list':None ?
Why are you using ListView if you don't have a object list? I think TemplateView should do the job. You just need to override get_context_data and provide custom context
class IndexView(TemplateView):
template_name = 'items/index.html'
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
profile = self.request.user.get_profile()
args = clean_url_encode(self.request.GET.copy().urlencode())
context.update({
'is_dashboard': True,
'body_id': 'dashboard',
'object_list': None,
'args':args,
'show_in_process':False
})
return context

Django: Search form in Class Based ListView

I am trying to realize a Class Based ListView which displays a selection of a table set. If the site is requested the first time, the dataset should be displayed. I would prefer a POST submission, but GET is also fine.
That is a problem, which was easy to handle with function based views, however with class based views I have a hard time to get my head around.
My problem is that I get a various number of error, which are caused by my limited understanding of the classed based views. I have read various documentations and I understand views for direct query requests, but as soon as I would like to add a form to the query statement, I run into different error. For the code below, I receive an ValueError: Cannot use None as a query value.
What would be the best practise work flow for a class based ListView depending on form entries (otherwise selecting the whole database)?
This is my sample code:
models.py
class Profile(models.Model):
name = models.CharField(_('Name'), max_length=255)
def __unicode__(self):
return '%name' % {'name': self.name}
#staticmethod
def get_queryset(params):
date_created = params.get('date_created')
keyword = params.get('keyword')
qset = Q(pk__gt = 0)
if keyword:
qset &= Q(title__icontains = keyword)
if date_created:
qset &= Q(date_created__gte = date_created)
return qset
forms.py
class ProfileSearchForm(forms.Form):
name = forms.CharField(required=False)
views.py
class ProfileList(ListView):
model = Profile
form_class = ProfileSearchForm
context_object_name = 'profiles'
template_name = 'pages/profile/list_profiles.html'
profiles = []
def post(self, request, *args, **kwargs):
self.show_results = False
self.object_list = self.get_queryset()
form = form_class(self.request.POST or None)
if form.is_valid():
self.show_results = True
self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])
else:
self.profiles = Profile.objects.all()
return self.render_to_response(self.get_context_data(object_list=self.object_list, form=form))
def get_context_data(self, **kwargs):
context = super(ProfileList, self).get_context_data(**kwargs)
if not self.profiles:
self.profiles = Profile.objects.all()
context.update({
'profiles': self.profiles
})
return context
Below I added the FBV which does the job. How can I translate this functionality into a CBV?
It seems to be so simple in function based views, but not in class based views.
def list_profiles(request):
form_class = ProfileSearchForm
model = Profile
template_name = 'pages/profile/list_profiles.html'
paginate_by = 10
form = form_class(request.POST or None)
if form.is_valid():
profile_list = model.objects.filter(name__icontains=form.cleaned_data['name'])
else:
profile_list = model.objects.all()
paginator = Paginator(profile_list, 10) # Show 10 contacts per page
page = request.GET.get('page')
try:
profiles = paginator.page(page)
except PageNotAnInteger:
profiles = paginator.page(1)
except EmptyPage:
profiles = paginator.page(paginator.num_pages)
return render_to_response(template_name,
{'form': form, 'profiles': suppliers,},
context_instance=RequestContext(request))
I think your goal is trying to filter queryset based on form submission, if so, by using GET :
class ProfileSearchView(ListView)
template_name = '/your/template.html'
model = Person
def get_queryset(self):
name = self.kwargs.get('name', '')
object_list = self.model.objects.all()
if name:
object_list = object_list.filter(name__icontains=name)
return object_list
Then all you need to do is write a get method to render template and context.
Maybe not the best approach. By using the code above, you no need define a Django form.
Here's how it works : Class based views separates its way to render template, to process form and so on. Like, get handles GET response, post handles POST response, get_queryset and get_object is self explanatory, and so on. The easy way to know what's method available, fire up a shell and type :
from django.views.generic import ListView if you want to know about ListView
and then type dir(ListView). There you can see all the method defined and go visit the source code to understand it. The get_queryset method used to get a queryset. Why not just define it like this, it works too :
class FooView(ListView):
template_name = 'foo.html'
queryset = Photo.objects.all() # or anything
We can do it like above, but we can't do dynamic filtering by using that approach. By using get_queryset we can do dynamic filtering, using any data/value/information we have, it means we also can use name parameter that is sent by GET, and it's available on kwargs, or in this case, on self.kwargs["some_key"] where some_key is any parameter you specified
Well, I think that leaving validation to form is nice idea. Maybe not worth it in this particular case, because it is very simple form - but for sure with more complicated one (and maybe yours will grow also), so I would do something like:
class ProfileList(ListView):
model = Profile
form_class = ProfileSearchForm
context_object_name = 'profiles'
template_name = 'pages/profile/list_profiles.html'
profiles = []
def get_queryset(self):
form = self.form_class(self.request.GET)
if form.is_valid():
return Profile.objects.filter(name__icontains=form.cleaned_data['name'])
return Profile.objects.all()
This is similar to #jasisz 's approach, but simpler.
class ProfileList(ListView):
template_name = 'your_template.html'
model = Profile
def get_queryset(self):
query = self.request.GET.get('q')
if query:
object_list = self.model.objects.filter(name__icontains=query)
else:
object_list = self.model.objects.none()
return object_list
Then all you have to do on the html template is:
<form method='GET'>
<input type='text' name='q' value='{{ request.GET.q }}'>
<input class="button" type='submit' value="Search Profile">
</form>
This has been explained nicely on the generic views topic here Dynamic filtering.
You can do filtering through GET, I don't think you can use POST method for this as ListView is not inherited from edit mixings.
What you can do is:
urls.py
urlpatterns = patterns('',
(r'^search/(\w+)/$', ProfileSearchListView.as_view()),
)
views.py
class ProfileSearchListView(ListView):
model = Profile
context_object_name = 'profiles'
template_name = 'pages/profile/list_profiles.html'
profiles = []
def get_queryset(self):
if len(self.args) > 0:
return Profile.objects.filter(name__icontains=self.args[0])
else:
return Profile.objects.filter()
I think that the error you are getting is because your form doesn't require the name field. So, although the form is valid, the cleaned_data for your name field is empty.
These could be the problematic lines:
if form.is_valid():
self.show_results = True
self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])
If I were you, I would try changing the line:
self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])
to this:
self.profiles = Profile.objects.none()
If you stop receiving errors (and your template receives an empty object_list), the problem you have is what I said before: name field not required.
Let us know if this doesn't work!
Search on all fields in model
class SearchListView(ItemsListView):
# Display a Model List page filtered by the search query.
def get_queryset(self):
fields = [m.name for m in super(SearchListView, self).model._meta.fields]
result = super(SearchListView, self).get_queryset()
query = self.request.GET.get('q')
if query:
result = result.filter(
reduce(lambda x, y: x | Q(**{"{}__icontains".format(y): query}), fields, Q())
)
return result
def get_queryset(self):
query_name = self.request.GET.get('query', '')
object_list = Product.objects.filter(
Q(title__icontains=query_name)
)
return object_list
<form action="" method="GET">
{% csrf_token %}
<input type="text" name="query" placeholder="Search keyword">
<i class="ti-search"></i>
</form>