Django ListView - context not getting updated - django

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()

Related

Printing kwargs.pop displays the right value, using it in a method takes None

I ant to pass a PK in kwargs to a form :
views.py
def create_mapping_form(request, pk):
context = {
'form': MappingForm(pk=pk)
}
return render(request, 'flows/partials/mapping_form.html', context)
In the form i retrieve the PK using :
forms.py
class MappingForm(forms.ModelForm):
class Meta:
model = MappingField
fields = (
'fl_col_number',
'fl_col_header',
'fl_cross_field_name',
'fl_cross_position',
'fl_replace_list'
)
def __init__(self, *args, **kwargs):
pk = kwargs.pop('pk', 'Rien')
super(MappingForm, self).__init__(*args, **kwargs)
#print(pk)
self.helper = FormHelper(self)
self.fields['fl_replace_list'].widget.attrs[
'placeholder'] = "Liste de tuples eg. : [('reman','ES'), ('Gasoline','Diesel')] "
headers = GetCsvHeadersAndSamples(pk)['headers']
[...]
For populating some fields' CHOICES, I use a method that returns a dic (last line above)
headers = GetCsvHeadersAndSamples(pk)['headers']
But something I can't explain sends Rien to GetCsvHeadersAndSamples while when I print(pk) the right value is shown. (GetCsvHeadersAndSamples is not useful, I don't show it).
Note: I display the form in template using HTMX. The issue seems not coming from HTMX because when I hard-code the PK, everything is ok.
For the moment, I have found nothing else but storing the PK value in a "temp" file but this slows down my script.
Thanks
I moved GetCsvHeadersAndSamples from forms.py to views.py and passed the return of GetCsvHeadersAndSamples in form kwargs.
[...]
headers_samples = GetCsvHeadersAndSamples(pk)
fiche_headers = fetch_fiche_headers()
form = MappingForm(request.POST or None,
headers_samples=headers_samples,
fiche_headers=fiche_headers)
[...]
Then I retrieve them in the form's init
def __init__(self, *args, **kwargs):
self.headers_samples = kwargs.pop('headers_samples', None)
self.fiche_headers = kwargs.pop('fiche_headers', None)
Issue solved with a workaround ... but still not explained

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.

Django FormView with dynamic forms

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})

Switch case for form_class and template_name

I have a session var with the name 'foo'.
Now I would like, depending of the value of 'foo' load a specific form and template in my cbv. So I need to put form_class and template_name into a switch case.
Which function is the right place for this? get? get_form? looks like nothing is really the right place for this.
Anyone a suggestion or knows another way? :)
CBV explorer is your friend: http://ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/CreateView/
You need to override
def get_form_class(self):
and
def get_template_names(self):
after asking my FormView CBV change ModelForm per request of very much like this question just without the template changing I found this one and your grate answers
so basically I am going to sum it all up
at first I tested this answer by Denny Crane
class Foo(FormView):
def dispatch(self, request, *args, **kwargs):
self.var = request.session['sessionvar']['var']
if self.var == some_value:
form_class = form1
template_name = template1
elif self.var == another_value:
form_class = form2
template_name = template2
[...]
return super(Foo, self).dispatch(request, *args, **kwargs)
I did needed to override
def get_form_class(self):
and
def get_template_names(self):
for this to work and exactly what I needed JUST WITHOUT THE TEMPLATE part becouse in my situation I would like to keep the same templae
so the combination of that two did work!
however then I sew #Serafeim comment
Be careful - this is not good usage of django! If you don't want to repeat your
conditions in both functions just define a new function that would contain > your
conditions and would return True/False. Now this function could be used from > > both get_form_class and get_template_names :)
i changed everything to
this code only
def get_form_class(self):
self.step = self.request.GET.get('step')
# for now I am getting this with request.get, till I will get the
# grip on session :) my first week on django and web-dev in general
if not self.step:
self.step = 'step1'
self.form_class = FORM[self.step] #FORM = dict{'step#': ModelForm}
return self.form_class
and this is working answer
thanks all
If I would use get_form_class() and get_template_names I would have to repeat in both functions my conditions. That would be redundant. Which is not prefered.
I figured out another solution which is not repeating code fragments.
class Foo(FormView):
def dispatch(self, request, *args, **kwargs):
self.var = request.session['sessionvar']['var']
if self.var == some_value:
form_class = form1
template_name = template1
elif self.var == another_value:
form_class = form2
template_name = template2
[...]
return super(Foo, self).dispatch(request, *args, **kwargs)

How to use RequestContext for render_to_response inside Django FormView?

I need to return a render_to_response in case an exception happens inside my FormView derived class.
I'm doing the following (code excerpt, but the rest of the code doesn't cause problems):
class ProjectCreateView(FormView):
"""Create view."""
form_class = ProjectCreateForm
template_name = 'projects/project_form.html'
group = None
def dispatch(self, request, *args, **kwargs):
"""Populate group attribute."""
self.group = get_object_or_404(Group, slug=self.kwargs['slug'])
return super(ProjectCreateView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""Add current group to context."""
context = super(ProjectCreateView, self).get_context_data(**kwargs)
context['group'] = self.group
return context
def form_valid(self, form):
"""Form and logic behind project creation."""
project_file = form.cleaned_data['project_file']
thumbnail = form.cleaned_data['thumbnail']
description = form.cleaned_data['description']
try:
<something to try>
except IntegrityError as e:
data = {'error':{'msg':_('project_overwrite_error')}}
return render_to_response('generic_error.html',data,context_instance=RequestContext(self.request))
except Exception as e:
return HttpResponse(e)
return HttpResponseRedirect(self.group.get_absolute_url())
If I debug with a breakpoint inside the dispatch() method, everything works fine, and my error template is loaded correctly, and context processors work fine.
When I run it outside debug I get a DatabaseError, which seems to be caused by the access to the django DB session data.
What am I doing wrong? Should I use render_to_response differently inside a generic view?
I don't know what is happening but I've solved with a workaround:
dummy = self.request.user.is_authenticated()
calling is_authenticated before returning render_to_response solves the problem, because request.user is "casted" to the actual user instance, instead of remaining a SimpleLazyObject.
I hope to understand why it happens one day...