I would like to get some indicators about get() and get_context_data() classes because I get an issue and I'm trying to understand why.
I have a Django DetailView which let to display some statistics with multiple querysets. In the same class, I have a query string which shows the result from a get queryset.
My code looks like this :
class StatsView(DetailView):
""" Create statistics pageview """
template_name = 'app/stats.html'
def get(self, request):
return render(request, self.template_name, context)
def set_if_not_none(self, mapping, key, value):
if value is not None:
if len(value) != 0:
mapping[key] = value
def get_context_data(self, **kwargs):
return context_data
Like this, get_context_data() function doesn't work, but when I set get() in comment, it works fine. I think there is a small misunderstanding from myself.
Maybe I don't use the good django generic display view or maybe it's not possible to use get() and get_context_data() together in the same class ?
Thank you
I read the Django documentation but I would like to get explanations from you
EDIT:
I'm trying to pass querysets from get() method to get_context_data(). Then I removed get() method, I changed DetailView by TemplateView and it works with just get_context_data(). But how I can add a "skeleton" without get() method ?
I'm trying to pass querysets from get() method to get_context_data()
class StatsView(DetailView):
""" Create statistics pageview """
template_name = 'app/stats.html'
def get(self, request, *args, **kwargs):
queryset = SampleModel.objects.all()
return render(request, self.template_name, context=self.get_context_data(queryset=queryset))
def set_if_not_none(self, mapping, key, value):
if value is not None:
if len(value) != 0:
mapping[key] = value
def get_context_data(self, **kwargs):
qs = kwargs.get('queryset')
# do something
If your overriding get_context_data() method, it's advaisable to call the super() method as
class StatsView(DetailView):
# your code
def get_context_data(self, **kwargs):
data = super(StatsView, self).get_context_data(**kwargs)
data.update({"foo": "bar"})
return data
I would like to get some indicators about get() and get_context_data()
I think it's nicely answered here already , When to use get, get_queryset, get_context_data in Django?
Related
In my application i made a ListView, in this ListView I have to make a post so I'm defining the post method, in this post method i need the filtered get_queryset method, but it comes unfiltered as if it is default.
I made some research and I've noticed method post() is ran before get_queryset(), my question is: Is there any way I can get the filtered get_queryset() in the post() method?
Ex:
def get_queryset(self):
qs = super(FilteredListView, self).get_queryset()
q = Q()
if self.request.session.get('make_id'):
q.add(Q(owner__make=self.request.session['auto_id']), Q.AND)
if self.request.user.is_superuser:
return qs.filter(q).distinct('id').order_by('-id')
return qs.filter(q, owner=self.request.user.company).distinct('id').order_by('-id')
def post(self, request, *args, **kwargs):
if 'action' in self.request.POST:
action = self.request.POST.get('action')
if action == 'batch_approve':
queryset = self.get_queryset() # i need the filtered queryset here, but it comes unfiltered
return redirect('{}?{}'.format(reverse('flow:filtered_list'), request.GET.urlencode()))
return redirect('flow:filtered_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.
I created the FormView below that will dynamically return a form class based on what step in the process that the user is in. I'm having trouble with the get_form method. It returns the correct form class in a get request, but the post request isn't working.
tournament_form_dict = {
'1':TournamentCreationForm,
'2':TournamentDateForm,
'3':TournamentTimeForm,
'4':TournamentLocationForm,
'5':TournamentRestrictionForm,
'6':TournamentSectionForm,
'7':TournamentSectionRestrictionForm,
'8':TournamentSectionRoundForm,}
class CreateTournament(FormView):
template_name = 'events/create_tournament_step.html'
def __init__(self, *args, **kwargs):
form_class = self.get_form()
success_url = self.get_success_url()
super(CreateTournament, self).__init__(*args, **kwargs)
def get_form(self, **kwargs):
if 'step' not in kwargs:
step = '1'
else:
step = kwargs['step']
return tournament_form_dict[step]
def get_success_url(self, **kwargs):
if 'step' not in kwargs:
step = 1
else:
step = int(kwargs['step'])
step += 1
if 'record_id' not in kwargs:
record_id = 0
else:
record_id = int(kwargs['record_id'])
return 'events/tournaments/create/%d/%d/' % (record_id, step)
The post request fails at the django\views\generic\edit.py at the get_form line, which I realize is because I've overwritten it in my FormView:
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid(): …
return self.form_valid(form)
else:
return self.form_invalid(form)
However, when I change the name of my custom get_form method to say gen_form, like so:
def __init__(self, *args, **kwargs):
form_class = self.gen_form()
success_url = self.get_success_url()
super(CreateTournament, self).__init__(*args, **kwargs)
def gen_form(self, **kwargs):
if 'step' not in kwargs:
step = '1'
else:
step = kwargs['step']
return tournament_form_dict[step]
my form class doesn't get processed in the get request and evaluates to None. I'm scratching my head as to why when I override the get_form method, it works, but my own named method doesn't? Does anyone know what the flaw might be?
Django's FormMixin [Django-doc] defines a get_form function [Django-doc]. You here thus basically subclassed the FormView and "patched" the get_form method.
Your attempt with the gen_form does not work, since you only defined local variables, and thus do not make much difference anyway, only the super(..) call will have some side effects. The other commands will keep the CPU busy for some time, but at the end, will only assign a reference to a Form calls to the form_class variable, but since it is local, you will throw it away.
That being said, your function contains some errors. For example the **kwargs will usually contain at most one parameter: form_class. So the steps will not do much. You can access the URL parameters through self.args and self.kwargs, and the querystring parameters through self.request.GET. Furthermore you probably want to patch the get_form_class function anyway, since you return a reference to a class, not, as far as I understand it, a reference to an initilized form.
Constructing URLs through string processing is probably not a good idea either, since if you would (slightly) change the URL pattern, then it is likely you will forget to replace the success_url, and hence you will refer to a path that no longer exists. Using the reverse function is a safer way, since you pass the name of the view, and parameters, and then this function will "calculate" the correct URL. This is basically the mechanism behind the {% url ... %} template tag in Django templates.
A better approach is thus:
from django.urls import reverse
class CreateTournament(FormView):
template_name = 'events/create_tournament_step.html'
def get_form_class(self):
return tournament_form_dict[self.kwargs.get('step', '1')]
def get_success_url(self):
new_step = int(self.kwargs.get('step', 1)) + 1
# use a reverse
return reverse('name_of_view', kwargs={'step': new_step})
I've been working with Django for about 3 months now and feel I'm getting a bit better, working my way up to class based views. On the surface they seem cleaner and easier to understand and in some cases they are. In others, not so much. I am trying to use a simple drop down view via ModelChoiceField and a form. I can get it to work with a function based view as shown below in my views.py file:
def book_by_name(request):
form = BookByName(request.POST or None)
if request.method == 'POST':
if form.is_valid():
book_byname = form.cleaned_data['dropdown']
return HttpResponseRedirect(book_byname.get_absolute_url1())
return render(request,'library/book_list.html',{'form':form})
Here is my form in forms.py:
class BookByName(forms.Form):
dropdown = forms.ModelChoiceField(queryset=Book.objects.none())
def __init__(self, *args, **kwargs):
super(BookByName, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
self.fields['dropdown'].queryset = Book.objects.order_by('publisher')
This code works. When I have tried to convert to a Class Based View, that's when the trouble begins. I tried to do something like this in views.py:
class BookByNameView(FormView, View):
form_class = BookByName
initial = { 'Book' : Book }
template_name = 'library/book_list.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def get_success_url(self, *args):
return reverse_lazy('library:book_detail', args = (self.object.id,))
When using this with the same form, I receive an attribute error,
'BookByNameView' object has no attribute 'object'.
I've tried ListView as well and received several other errors along the way. The get_success_url also needs to take in a primary key and I can't figure out how to get that passed in as well. Again, I'm a 3 month Django newbie so please be gentle and thanks in advance for your thoughts and suggestions! I feel like I'm in the ballpark...just can't find my seat! I'm very open to doing this differently, if there's a cleaner/better way to do this!
Based on the latest feedback, it would appear the Class Based View should look like:
class BookNameView(FormView):
form_class = BookName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
Is this correct? I ran a test version of this and in response to your question as to why I am using self.object.id at all, I am trying to get the pk from the modelchoicefield that I am using to return the view I am trying to get. This may be where I am getting a bit lost. I am trying to get the detail view from the modelchoicefield dropdown, and return the book that is selected. However, I can't seem to pass the pk to this view successfully.
I updated my code to...
class BookByNameView(FormView, ListView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
But now it says error...Reverse for 'book_detail' with no arguments not found.
Why are you using self.object there at all? You used form.cleaned_data in the original view, that's what you should use in the class based version too. Note that the form is passed to form_valid.
Note that you've done lots of other weird things too. Your getmethod is pointless, as is your definition of the initial dict; you should delete them both. Also, FormView already inherits from View, there's no need to have View in your declaration explicitly.
You can override the form_valid() function in FormView to achieve what you want. If the form is valid then it is passed to the form_valid() function.
Try this:
class BookByNameView(FormView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def form_valid(self, form):
bookbyname = form.cleaned_data['dropdown']
return HttpResponseRedirect(bookbyname.get_absolute_url())
views.py
class PaginatorView(_LanguageMixin, ListView):
context_object_name = 'concepts'
#some custom functions like _filter_by_first_letter
def get_queryset(self):
# some logic here ...
all_concepts = self._filter_by_letter(self.concepts, letters, startswith)
#letters and startswith are obtained from the logic above
print all_concepts
return all_concepts
def get_context_data(self, **kwargs):
context = super(PaginatorView, self).get_context_data(**kwargs)
print context[self.context_object_name]
context.update({
'letters': [(l[0], self._letter_exists(context[self.context_object_name], l)) for l in self.all_letters],
'letter': self.letter_index,
'get_params': self.request.GET.urlencode(),
})
return context
The print all_concepts statement prints all my concepts correctly. So everything until here works just fine. Then, I return all_concepts.
Shouldn't at this point, all_concepts being added to the context, under the key specified by context_object_name? i.e., context['concepts'] should be populated with all_concepts?
If so,the print statement inside get_context_data prints nothing. Which suggests me that the context was not updated.
When I previously used a DetailView, the get_object function was updating the context referenced by context_object_name correctly.(i.e. context[context_object_name] was populated with the object returned by get_object) Shouldn't get_queryset do the same for the ListView?
_LanguageMixin is also defined in views.py, but it is not so relevant for my problem. Just included it here for you to see
class _LanguageMixin(object):
def dispatch(self, request, *args, **kwargs):
self.langcode = kwargs.pop("langcode")
self.language = get_object_or_404(Language, pk=self.langcode)
return super(_LanguageMixin, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(_LanguageMixin, self).get_context_data(**kwargs)
context.update({"language": self.language,
"languages": Language.objects.values_list('code',
flat=True)})
return context
[EDIT1]
if instead I do save all_concepts i.e. self.all_concepts=... and then I use self.all_concepts instead of context[self.contex_object_name], everything works fine.
[EDIT2]
I never instantiate the PaginatorView. It's only for extending purposes. Down here you can see how I extend it. self.concepts helps me to find all_concepts in the get_queryset of the parent class(PaginatorView)
class AlphabeticView(PaginatorView):
template_name = "alphabetic_listings.html"
model = Property
def get_queryset(self):
self.concepts = (
self.model.objects.filter(
name='prefLabel',
language__code=self.langcode,
)
.extra(select={'name': 'value',
'id': 'concept_id'},
order_by=['name'])
.values('id', 'name')
)
super(AlphabeticView, self).get_queryset()
The print statement in get_context_data is printing empty because the variable context_object_name is empty. You should try print context[self.context_object_name]
EDIT: In response to your correction, try
print context[self.get_context_object_name(self.get_queryset())]
get_context_object_name docs
EDIT 2: In response to your second edit, the reason its is printing 'None' is because you aren't returning from the get_queryset method of AlphabeticView. Change the last line in that method to
return super(AlphabeticView, self).get_queryset()