ListView invokes the `get_template_names` without calling it - django

I am reading the Django source code of ListView:
django/list.py
class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
template_name_suffix = '_list'
def get_template_names(self):
try:
names = super().get_template_names()
class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
"""
Render some list of objects, set by `self.model` or `self.queryset`.
`self.queryset` can actually be any iterable of items, not just a queryset.
"""
When I define ListView, template_name is assigned automatically
class IndexView(generic.ListView):
pass
I assume there should have a assigning step in MultipleObjectTemplateResponseMixin as
class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
template_name_suffix = '_list'
def get_template_names(self):
try:
names = super().get_template_names()
....
template_name = self.get_template_names()
How it invoke the get_template_names without call it?

get_template_names is called in render_to_response method of TemplateResponseMixin class, which is superclass of MultipleObjectTemplateResponseMixin.

Related

How to call a function with context in django CB list view?

This is my view:
class viewbloglistview(LoginRequiredMixin,ListView):
model = Blog
paginate_by = 6
def get_template_names(self):
if True:
return ['blog/view_blogs.html']
else:
return ['blog/blog_list.html']
def get_queryset(self):
return Blog.objects.all().order_by('-blog_views')[:20]
def get_context_data(self, **kwargs):
context = super(viewbloglistview, self).get_context_data(**kwargs)
context['categories_list'] = categories.objects.all()
return context
This is my function in models.py file:
def categories_count(self):
categories_count = categories.objects.annotate(blog_count=Count('blogs')).values_list('Title','blog_count')
return categories_count
I want call the function in my views with a context name to render the activity in my template..
Can anyone please help me out to solve this problem??
Thank you
This is a python problem, your question is unclear but based on what you said:
Case the function in in your model.py alone:
from . import model.py
// code
categories_count()
Case the function is a method in a class as it is shown on you code with the self parameter in it:
from . import model.py
// code
classname.categories_count()
Assuming that you have named your class as 'categories' (which should have been named as Category in the first place),
categories_count should have been in a manager as you are querying in a class level. Say you don't want a manager and want to keep the code inside the model, then you can use it as a class method.
#classmethod
def categories_count(cls):
return cls.objects.annotate(blog_count=Count('blogs')).values_list('Title','blog_count')
and in the views use it as
categories.categories_count()
Just remember that the regular methods with the 'self' argument like the one you have, should only be used when you are dealing with a single instance, not when you are accessing the class itself.

Django access queryset variabile from a custom method on the same view

I am implementing csv export on django. In particular I have a link on my template to export actual query and for this I am trying to handle all on the same class based view.
Here is my code
# views.py
class MyView(ListView):
template_name = 'my_template.html'
model = Archivio
def get_queryset(self):
if self.request.GET.get('q'):
dateC = '01/01/'+self.request.GET.get('q')
queryset = Archivio.objects.filter(~Q(quoteiscrizione__anno_quota__exact=self.request.GET.get('q'))
return queryset
# my custom method
#staticmethod
def csv_output():
qs = MyView.get_queryset(): # i want to access to the queryset variable from get_queryset() method of the class
# here i have other code to produce csv output
But the method csv_output() is wrong.. I get this TypeError: csv_output() takes 0 positional arguments but 1 was given
I have also tried with a classmethod decorator but without success.
My question is: How can i access queryset variable from another method of the same class?
Note the self in get_queryset(self). It means the first argument of this method is always the instance of the class (in your case, MyView). So when you want to call it in another method, you should provide this instance.
The solution is to replace your #staticmethod decorator with the #classmethod one:
class MyView(ListView):
template_name = 'my_template.html'
model = Archivio
def get_queryset(self):
if self.request.GET.get('q'):
dateC = '01/01/' + self.request.GET.get('q')
queryset = Archivio.objects.filter(~Q(quoteiscrizione__anno_quota__exact=self.request.GET.get('q'))
return queryset
#classmethod
def csv_output(cls):
qs = cls.get_queryset(cls)
# Class methods have one required positional argument
# which is the class that contains them
# They are called like this:
# MyClass.class_method()
# So in your case, you can call
# get_queryset() by doing MyView.get_queryset()
# But as you are in a class method, you do
# cls.get_queryset()
# As get_queryset() needs one positional argument *self*,
# which is the instance of your class, you do
# cls.get_queryset(cls)
# and it will work as expected :-)
I solved my Issue saving my queryset on the request and then using it somewhere on my views like this:
views.py
class SomeClass(ListView):
def get_queryset(self):
.....
queryset = MyModel.objects.filter(*somefilterlist)
self.request.session['search_queryset'] = serialize('json', queryset) # in order to save something on session in must be JSON serialized
class Output(SomeClass):
#classmethod
def cvsOutput(cls):
deserialized = list(deserialize('json', request.session.get('search_queryset')))
pk_list = []
for arch in deserialized:
pk_list.append(arch.object.pk) # List of pk
queryset = Archivio.objects.filter(pk__in=pk_list) # Query the list ok pk's
In this way I was able to make available the list ok pk of the queryset of my ListView, then I make again the same query based on that list of pk's..

How to properly group mixins in Django app?

Initial situation
I have one app, 'devices' with some views:
class DeviceUpdateView(LoginRequiredMixin, UpdateView):
model = Device
form_class = DeviceEditForm
def get_form(self, form_class=None):
form = super(DeviceUpdateView, self).get_form(form_class=None)
form.fields['beacon'].queryset=Beacon.objects.filter(customer_account=self.request.user.default_customer_account)
return form
class DeviceCreateView(LoginRequiredMixin, CreateView):
model = Device
form_class = DeviceEditForm
def get_initial(self):
initial = super(DeviceCreateView, self).get_initial()
initial['customer_account'] = self.request.user.default_customer_account
return initial
def get_form(self, form_class=None):
form = super(DeviceCreateView, self).get_form(form_class=None)
form.fields['beacon'].queryset=Beacon.objects.filter(customer_account=self.request.user.default_customer_account)
return form
I have another app ('buildings') vith this view:
class BuildingCreateView(LoginRequiredMixin, UserFormKwargsMixin, CreateView):
model = Building
form_class = BuildingEditModelForm
def get_initial(self):
initial = super(BuildingCreateView, self).get_initial()
initial["customer_account"] = self.request.user.default_customer_account
return initial
DRY Mixin
Now, I want to factorize my code in order to be more "DRY".
I then create this mixin, in my devices/views.py:
class BeaconFilterMixin(object):
def get_form(self, form_class=None):
form = super(BeaconFilterMixin, self).get_form(form_class=None)
form.fields['beacon'].queryset=Beacon.objects.filter(customer_account=self.request.user.default_customer_account)
return form
And my views become:
class DeviceUpdateView(LoginRequiredMixin, BeaconFilterMixin, UpdateView):
model = Device
form_class = DeviceEditForm
class DeviceCreateView(LoginRequiredMixin, BeaconFilterMixin, CreateView):
model = Device
form_class = DeviceEditForm
def get_initial(self):
initial = super(DeviceCreateView, self).get_initial()
initial['customer_account'] = self.request.user.default_customer_account
return initial
Nice and sweet!
Now the question
I want to do exact same thing and create a "InitialCustomerAccountMixin" that could be used by both 'buildings' and 'devices' apps.
The code will be:
class InitialCustomerAccountMixin(object):
def get_initial(self):
initial = super(InitialCustomerAccountMixin, self).get_initial()
initial['customer_account'] = self.request.user.default_customer_account
return initial
The question is: where do I put my mixins code?
For an 'app-scope' mixin, I get that I should put the code in the views.py or in a mixins.py file in the app.
But for a 'multi-app' mixin, I don't know the design principle to follow.
You should follow functional grouping principle (or whatever you call it).
If you have a UI layer in your app, it should have Mixins module (package, namespace or whatever). This module should have categories for all mixins use: Auth, Filter, View,... etc. Each category should contain respected mixins.
This is for single-app mixins.
Mixins, you have to share between your applications should be encapsulated into their own module (or whatever Python reusable packages are called). This module should be private to you, should be called appropriately and should be split into sensible categories.

Retrieving model class dynamically in CBV

What is the proper way to retrieve the model class dynamically in a CBV?
I realize I have to use apps.get_model, but not sure where to do that.
I would like to make my delete (and other) views more "DRY".
class DeleteParamView(generic.DeleteView):
# The following does not work since kwargs cannot be accessed
#model = apps.get_model('patients', 'param' + self.kwargs['param_name'])
def __init__(self, *args, **kwargs):
from django.apps import apps
self.model = apps.get_model('persons', 'param' + self.kwargs['param_name'])
super(DeleteParamView, self).__init__(*args, **kwargs)
Unfortunately self.kwargs cannot be accessed yet; at least I get 'DeleteParamView' object has no attribute 'kwargs'
I also tried to override def get_model() but that does not exist as part of the CBV.
Override the get_queryset method.
def get_queryset(self):
Model = apps.get_model('persons', 'param' + self.kwargs['param_name'])
return Model.objects.all()

Passing arguments to ModelForm through FormSet

I'm kinda new to Formsets and I'm stuck at a problem.
I use a Modelform to allow the creation of a new object.
class AddUpdateEntryForm(forms.ModelForm):
class Meta:
model = Zeit
exclude = ('mitarbeiter', 'user_updated')
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(AddUpdateEntryForm, self).__init__(*args, **kwargs)
self.fields['projekt'].queryset = Projekt.objects.filter(firma=Mitarbeiter.objects.get(user_id=self.user).firma_id)
That form gets it's arguments from the view:
form = AddUpdateEntryForm(user=entry_user, initial=initial)
Now, I want to display multiple instances of that form on a single page.
I use:
forms.py:
AddEntryFormSet = formset_factory(form=AddUpdateEntryForm)
and
views.py:
formset = AddEntryFormSet(initial=initial)
which works fine, but only when I comment out the "self.user...." and "self.fields...." lines from ModelForm Class.
I tried several ways of passing the argument from the call inside the view to the ModelForm.
Is there a proper way to do this?
Thanks in advance
Conrad
It should be possible to subclass BaseModelFormset so that the user is passed to each form when it is constructed. However, that's quite tricky.
A simpler technique is to define a function that creates a model form for a given user, and dynamically create the model form class in the view.
def create_form(user):
"""Returns a new model form which uses the correct queryset for user"""
class AddUpdateEntryForm(forms.ModelForm):
class Meta:
model = Zeit
exclude = ('mitarbeiter', 'user_updated')
def __init__(self, *args, **kwargs):
super(AddUpdateEntryForm, self).__init__(*args, **kwargs)
self.fields['projekt'].queryset = Projekt.objects.filter(firma=Mitarbeiter.objects.get(user_id=user).firma_id)
return AddUpdateEntryForm
The closure of user in the function means that you can set the queryset correctly. Note that the __init__ method takes the same arguments as its parent class, so we no longer have any problems when we use modelformset_factory in the view.
AddUpdateEntryForm = create_form(user)
AddEntryFormSet = modelformset_factory(model=Zeit, form=AddUpdateEntryForm)