Django generic ListView: duplicate queryset in the context - django

I'm using the generic ListView of Django (1.9.1). I customized the name of the queryset (I called it content_list) to be put in the context. But surprisingly when I look at the context content, I can see object_list along with content_list. If the list is very big this is not very optimised. How can I get rid of object_list?. Here is my view:
class Home(ListView): #TemplateView
context_object_name = 'content_list'
template_name = 'website/index.html'
paginate_by = CONTENT_PAGINATE_BY
def get_queryset(self):
cc_id = self.kwargs.get('cc_id')
if cc_id != None:
qs = Content.objects.filter(category=cc_id)
else:
qs = Content.objects.all()
return qs.order_by('-created_on')
def get_context_data(self, **kwargs):
context = super(Home, self).get_context_data(**kwargs)
context['content_category_list'] = ContentCategory.objects.all()
print(context)
return context

I'm pretty sure they're both reference to the same list in memory.
From the docs:
Well, if you’re dealing with a model object, this is already done for you. When you are dealing with an object or queryset, Django is able to populate the context using the lower cased version of the model class’ name. This is provided in addition to the default object_list entry, but contains exactly the same data, i.e. publisher_list.
Aside from that, even if they weren't referencing the same data, you're forgetting that querysets are executed lazily so if you never use the other list then it is never executed.

This is by design. It's not another interaction to the database, but a second reference.

Related

adding detail information to django listview object in template

I have a listview in which I'm hoping to insert additional details about the object (activity duration and average power) in the same row as the link to the object detail (the best way to describe it would be that I want some detailview attributes inserted into the listview). At the moment, the best I can achieve is a separate context dictionary listed below the object_list, as shown in this screen shot:
And the following is my listview:
class RideDataListView(LoginRequiredMixin, ListView):
model = RideData
context_object_name='object_list'
template_name='PMC/ridedata_list.html'
def get_queryset(self):
queryset = super(RideDataListView, self).get_queryset()
return queryset
def get_context_data(self, *args, **kwargs):
model = RideData
context = super(RideDataListView, self).get_context_data(*args, **kwargs)
records = list(RideData.objects.all().values())
actdict2={}
id=[]
ap=[]
actdur=[]
for record in records:
actdf=pd.DataFrame.from_dict(record)
id.append(actdf['id'].iloc[0])
ap.append(actdf['watts'].mean())
actdur.append(str(timedelta(seconds=len(actdf['time']))))
actdf2=pd.DataFrame()
actdf2['id']=id
actdf2['ap']=ap
actdf2['actdur']=actdur
actdict2=actdf2.to_dict('records')
context['actdict']=actdict2
context['actdur']=actdur
return context
What I haven't been able to nail down in my research is if there is a way to either a) annotate the queryset with stuff from context or b) loop through the context dictionary 'actdict' within the object_list loop (doesn't seem possible based on some attempts) or c) include individual lists (ap and actdur as additions to to query. Just curious for some additional leads to add some more object detail to the basic listview.
Your context is intended to contain your data, but the way it is displayed rely on the HTML template you will use : https://docs.djangoproject.com/en/3.0/topics/templates/
The actual solution to this was to add to queryset object within def get_queryset
def get_queryset(self):
queryset = super(RideDataListView, self).get_queryset()
for obj in queryset:
record=list(obj.watts)
actdf=pd.DataFrame()
actdf['watts']=record
obj.actdur=str(timedelta(seconds=len(actdf['watts'])))
obj.ap=actdf['watts'].mean()
return queryset
This returned the additional summary information I wanted to include in the listview that is also used in detailview

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 to know what context data returned by any GCBV in django?

Is there any way to know the dictionary returned by any GCBV other than looking at the code.
context_dict
I had to look at the code at django.views.generic.list to know that ListView returns this context dictionary.
Is there any other fast way to know
As per Django Docs, the default is object_list, but can be set using
context_object_name = 'your_context_name'
If you want to add some other context
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['your_other_context_name'] = Model.objects.all() # or whatever you want to query
return context

Django form text field prefilling with data from other form

I'm running into a very strange issue where one form is initializing with the data from another form entirely. Here is the first view:
class UpdateProfileView(FormMixin, DetailView):
form_class = UpdateProfileForm
model = Profile
template_name = 'profile/update.html'
def get_context_data(self, **kwargs):
self.object = self.get_object()
context = super(UpdateProfileView, self).get_context_data(**kwargs)
...
self.initial['description'] = profile.about
context['form'] = self.get_form()
return context
...
This is the form that will return the correct data. As soon as it is loaded, however, the following form will return the initialized data from the previous one, even from different sessions, browsers, and locations:
class BountyUpdateForm(forms.ModelForm):
class Meta:
model = Bounty
fields = ("description", "banner")
class UpdateBountyView(UpdateView):
form_class = BountyUpdateForm
model = Bounty
template_name = 'bounty/update.html'
...
def get_context_data(self, **kwargs):
context = super(UpdateBountyView, self).get_context_data(**kwargs)
description = context['form']['description']
value = description.value()
# Value equals what was initialized by the previous form.
I'm really curious why these two forms are interacting in this way. Both form fields are called 'description', but that doesn't explain why the initial data from one would be crossing over to the other. Restarting the server seems to temporarily get the second form to show the correct values, but as soon as the first one is loaded, the second follows suit.
Any help would be greatly appreciated!
After some more searching, I was able to determine that my second view was having self.initial set to the same values as the first form by the time dispatch was being run. I couldn't determine why, but found these related questions:
Same problem, but no accepted answer:
Django(trunk) and class based generic views: one form's initial data appearing in another one's
Different problem, but good answer:
Setting initial formfield value from context data in Django class based view
My workaround was overriding get_initial() on my first form, instead of setting self.initial['description'] directly.
class UpdateProfileView(FormMixin, DetailView):
form_class = UpdateProfileForm
model = Profile
template_name = 'profile/update.html'
def get_initial(self):
return {
'description': self.object.about
}
def get_context_data(self, **kwargs):
...
# Removed the following line #
# self.initial['description'] = profile.about
...
context['form'] = self.get_form()
return context
Hope this helps anyone else who runs into this same problem. I wish I knew more about Django class-based views to be able to understand why this happens to begin with. However, I was unable to determine where self.initial was being set, beyond the empty dict in FormMixin().

Django How to FormView rename context object?

I have a class view which uses FormView. I need to change the name of the form i.e. this is what it used to be in my old function view:
upload_form = ContactUploadForm(request.user)
context = {'upload': upload_form,}
With my new view I'm assuming I can rename using the get_context_data method but unsure how.
How can I rename this form to upload instead of form as my templates uses {{ upload }} not {{ form }}? Thanks.
Current Class View:
class ImportFromFile(FormView):
template_name = 'contacts/import_file.html'
form_class = ContactUploadForm
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
# Call the base implementation first to get a context.
context = super(ImportFromFile, self).get_context_data(**kwargs)
return context
Try this:
class ImportFromFile(FormView):
template_name = 'contacts/import_file.html'
form_class = ContactUploadForm
def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
kwargs['upload'] = kwargs.pop('form')
return super(ImportFromFile, self).get_context_data(**kwargs)
Django 2.0+ provides support for changing context object names. See: Built-in class-based generic views
Making “friendly” template contexts
You might have noticed that our sample publisher list template stores all the publishers in a variable named object_list. While this works just fine, it isn’t all that “friendly” to template authors: they have to “just know” that they’re dealing with publishers here.
Well, if you’re dealing with a model object, this is already done for you. When you are dealing with an object or queryset, Django is able to populate the context using the lowercased version of the model class’ name. This is provided in addition to the default object_list entry, but contains exactly the same data, i.e. publisher_list.
If this still isn’t a good match, you can manually set the name of the context variable. The context_object_name attribute on a generic view specifies the context variable to use:
class PublisherList(ListView):
model = Publisher
context_object_name = 'choose the name you want here'