`self.response_class` in `TemplateResponseMixin` don't call super() - django

Referencece to response_class in Django's code:
django/base.py
class TemplateResponseMixin:
"""A mixin that can be used to render a template."""
response_class = TemplateResponse
def render_to_response(self, context, **response_kwargs):
response_kwargs.setdefault('content_type', self.content_type)
#here
return self.response_class(
#here
request=self.request,
template=self.get_template_names(),
context=context,
using=self.template_engine,
**response_kwargs
)
The class attribute setting response_class = TemplateResponse,
while call it through instance's attribute self.response_class,
I guess it might be super().response_class
How to understand it?

You need to use super() when calling superclass's method. But in case of response_class it's just attribute defined inside TemplateResponseMixin so you can simple accessed it through self.response_class. Since response_class is Class to instancinate you need to add () like this: self.response_class(*args, **kwargs).
You can check this question to get more details about super().
Example:
class A:
def method_a(self):
pass
class B(A):
some_class = SomeClass
def method_a(self):
super().method_a() # This code will find A's method_a and call it
self.some_class() # This will only instancinate some_class attribute of current B's instance

Related

Django DRF unit tests added with dynamic mixins via metaclass not being executed

I am trying to test DRF endpoints, and trying to add mixings dynamically to a test in order to execute tests again each method allowed in the endpoint (get, post, put, patch, delete)
So, my idea is to make a base test class that will automatically add some mixings to test endpoints if they are allowed. And I can create the actual test that will inherit from this base class.
The code:
from rest_framework.test import APITestCase
class GetTestMixin:
def test_get_all(self):
response = self.client.get(self.uri)
self.assertEqual(response.status_code,status.HTTP_200_OK)
class AutoMixinMeta(type):
def __call__(cls, *args, **kwargs):
allowed_methods = ['get', 'post']
# cls would be the Test class, for example TestExample
# cls.__bases__ is a tuple with the classes inherited in the Test class, for example:
# (<class 'unit_tests.endpoints.base_test.RESTTestCase'>, <class 'rest_framework.test.APITestCase'>)
bases = cls.__bases__
for method in allowed_methods:
bases += (cls.method_mixins[method.lower()],)
# Create a new instance with all the mixins
cls = type(cls.__name__, bases, dict(cls.__dict__))
return type.__call__(cls, *args, **kwargs)
class RESTTestCase(metaclass=AutoMixinMeta):
uri = None
method_mixins = {
'post': PostTestMixin,
'get': GetTestMixin,
}
class TestExample(RESTTestCase, APITestCase):
uri = reverse('somemodel-list')
I was expecting test_get_all to be executed, but it is not.
Mixings are in place. I made a dummy method inside TestExample and put a debugger in place, and checked it, like this:
(Pdb) self.__class__
<class 'TestExample'>
(Pdb) self.__class__.__bases__
(<class 'RESTTestCase'>, <class 'rest_framework.test.APITestCase'>, <class 'GetTestMixin'>)
The problem there is that the code that collects the classes to be tested will never "see" the class as an instance of the Test classes, or as a subclass of: the class inheriting from the test cases only exist when an instance is created.
The only way for this to work is to create the derived classes at import time, and to bind the desired dynamic classes as top-level names on the module.
To do that, you can do away with the metaclass, and just place the statements in the module body, assigning the new class or classes to names using globals(). Or, if you want just the subclasses, rather than at module top level, the code can be placed in the __init_subclass__ method. This methd is called when the class is created, not when it is instantiated, and it should work.
from rest_framework.test import APITestCase
class GetTestMixin:
def test_get_all(self):
response = self.client.get(self.uri)
self.assertEqual(response.status_code,status.HTTP_200_OK)
class RESTTestCase():
uri = None
method_mixins = {
'post': PostTestMixin,
'get': GetTestMixin,
}
def __init_subclass__(cls, *args, **kw):
super.__init_subclass__(*args, **kw)
if "Dynamic" in cls.__name__:
return
allowed_methods = ['get', 'post']
bases = list(cls.__bases__)
for method in allowed_methods:
bases.append(cls.method_mixins[method.lower()])
# Create a new instance with all the mixins
new_cls = type(cls.__name__ + "Dynamic", bases, dict(cls.__dict__))
globals()[new_cls.__name__] = new_cls
class TestExample(RESTTestCase, APITestCase):
uri = reverse('somemodel-list')
# class TestExampleDynamic is created automatically when the `class` statement above resolves

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.

Wrap all class methods using a meta class

I'm trying to wrap all of the methods inside a class I wrote with a specific wrapper method.
My class inherits from the python dict class, and I want to wrap all of the methods of this parent class, such as __setitem__, __getitem__, etc.
In my attempts to achieve that I have written a meta class that wraps all the methods inside it's child class, using the __init__ method in the meta class, so I can access the child class's object (and not it's class definition which does not include the parent methods).
However, after running the code, I see the wrapper method is never called. Meaning the wrapping didn't succeed.
Can you help with figuring out what went wrong?
My code:
def wrapper(func):
def wrapped(self, *args, **kwargs):
print 'wrapper.__call__()'
res = func(self, *args, **kwargs)
return res
return wrapped
class MyMeta(type):
def __init__(cls, classname, bases, class_dict):
print 'meta.__init__()'
new_class_dict = {}
for attr_name in dir(cls):
attr = getattr(cls, attr_name)
if hasattr(attr, '__call__'):
attr = wrapper(attr)
new_class_dict[attr_name] = attr
return super(MyMeta, cls).__init__(classname, bases, new_class_dict)
class MyDict(dict):
__metaclass__ = MyMeta
def __init__(self, *args):
print 'child.__init__()'
super(MyDict, self).__init__(*args)
d = MyDict({'key': 'value'})
d['new_key'] = 'new_value'
The printout I get is:
meta.__init__()
child.__init__()
without any reference to the wrapper.__call__() print I placed inside the wrapped method...
When the metaclass __init__ gets called, the class object has already been built so modifying the attributes dict (class_dict in your code) at this stage is totally useless indeed. You want to use setattr instead:
class MyMeta(type):
def __init__(cls, classname, bases, class_dict):
for attr_name in dir(cls):
if attr_name == "__class__":
# the metaclass is a callable attribute too,
# but we want to leave this one alone
continue
attr = getattr(cls, attr_name)
if hasattr(attr, '__call__'):
attr = wrapper(attr)
setattr(cls, attr_name, attr)
# not need for the `return` here
super(MyMeta, cls).__init__(classname, bases, class_dict)

ListView invokes the `get_template_names` without calling it

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.

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