Send context the template in a class based view - django

How can I send context to my template in a class-based view with the get_object function?
This is my class:
class DetailMessages(LoginRequiredMixin, DetailView, ChannelFormMixin):
template_name = 'DM/chat.html'
def get_object(self, *args, **kwargs):
my_username = self.request.user.username
username = self.kwargs.get("username")
channel, _ = Channel.objects.get_dm_channel(my_username, username)
if channel == None:
raise Http404
context = {"example1" : "I want this on the template", "example2" : "I want this on the template too"}
return channel

It's usually not good idea to mix methods in class based views. You can pass context in two ways: in get() or get_context_data(). Examples:
# or
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["example1"] = "I want this on the template"
context["example2"] = "I want this on the template too"
return context
# or
def get(self, request, *args, **kwargs):
context = {}
context["example1"] = "I want this on the template"
context["example2"] = "I want this on the template too"
return render(..., context=context)
If you don't actually need to operate with get() (or post()) method, then much better way is to leave context managing to get_context_data() method.

Related

Can I make a Django mixin that adds context variables just before template render?

I have a Django application that uses a JSON API as its data source.
Here's a simplified example of use in one of my views.py:
class GroupsList(LoginRequiredMixin):
def get(self, request, **kwargs):
# Get file list and totals
try:
group_list = group_adapter.list() # makes an API call and ALSO populates a meta info class
except APIAccessForbidden:
return HttpResponseRedirect(reverse('logout'))
return render(request, 'groups/index.html', {
# can I make a mixin to add data here gained from the API call?
'group_list': group_list,
})
This line:
The group_adapter.list() call populates some meta information into another class, that's not related to the group_list itself. I'd like to pass that data to the template. Ordinarily I'd use a context_processor, but when the context processor is called, the API call hasn't been made yet. I could manually check the information and add it to the render() method, but then I'd need to do that in dozens of different views.
Potential Solution #1: Create a Mixin For It
Can I use a mixin here that adds this information to context AFTER the view code runs but BEFORE render passes information to the template?
In other words is there a way to do this:
class GroupsList(LoginRequiredMixin, AddMetaInfoToContextMixin):
and then create a mixin something like this?
class AddMetaInfoToContextMixin(ContextMixin):
def get_context_data(self, **kwargs):
# self.request
context = super().get_context_data(**kwargs)
context['global_meta_information'] = get_global_meta_information()
return context
Potential Solution #2: Make an overridden templateview
Commenter Melvyn pointed out that I can potentially subclass TemplateView and override get_context_data(), so would something like this work?
class TemplateViewWithMeta(TemplateView):
def get_context_data(self, *args, **kwargs):
context = super(Home. self).get_context_data(*args, **kwargs)
context['global_meta_information'] = get_global_meta_information()
return context
class GroupsList(LoginRequiredMixin, TemplateViewWithMeta):
[...]
The typical workflow for a Django generic TemplateView is:
get()
get_context_data()
render_to_response()
So in your case keeping with the spirit of generic views, you could do it like this:
from django.views import generic
class BaseRemoteApiView(generic.TemplateView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.group_list = None
def get(self, request, *args, **kwargs):
try:
self.group_list = group_adapter.list() # makes an API call and ALSO populates a meta info class
except APIAccessForbidden:
return HttpResponseRedirect(reverse('logout'))
return super().get(request, *args, **kwargs)
class RemoteApiContextMixin(generic.base.ContextMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["group_list"] = self.group_list
context["meta_information"] = get_global_meta_information()
return context
class ConcreteRemoteApiView(RemoteApiContextMixin, BaseRemoteApiView):
pass
Of course, you don't have to make 3 classes and can just combine the 3 into one - depends on how mixable you want to be.

Django CBV : get() and get_context_data()

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?

Cleanest way to decorate Django's dispatch method

I stumbled upon a code that is used to provide some args to the request method. Problem is that I'm not that sure if it is the cleanest way to handle this case.
def check_permissions(check_mixins):
"""
:param check_mixins: is given to the inner decorator
Decorator that will automatically populate some parameters when
using dispatch() toward the right method (get(), post())
"""
def _decorator(_dispatch):
def wrapper(request, *args, **kwargs):
Is it a problem if "self" isn't passed in the method definition in here...
for mixin in check_mixins:
kwargs = mixin.check(request, *args, **kwargs)
if isinstance(kwargs, HttpResponseRedirect):
return kwargs
return _dispatch(request, *args, **kwargs)
return wrapper
return _decorator
class UserLoginMixin(object):
def check(request, *args, **kwargs):
... and here ? It seems so ugly in my IDE
user = request.user
if user.is_authenticated() and not user.is_anonymous():
kwargs['user'] = user
return kwargs
return redirect('user_login')
class AppoExistMixin(object):
def check(request, *args, **kwargs):
Here too...
appo_id = kwargs['appo_id']
try:
appoff = IdAppoff.objects.get(id=appo_id)
kwargs['appoff'] = appoff
del kwargs['appo_id']
return kwargs
except IdAppoff.DoesNotExist:
pass
messages.add_message(request, messages.ERROR,
"Item doesn't exist!")
return redirect('home')
class SecurityMixin(View):
"""
Mixin that dispatch() to the right method with augmented kwargs.
kwargs are added if they match to specific treatment.
"""
data = []
def __init__(self, authenticators):
super(SecurityMixin, self).__init__()
# Clearing data in order to not add useless param to kwargs
self.data.clear()
# Build the list that contain each authenticator providing
# context increase
for auth in authenticators:
self.data.append(auth)
#method_decorator(check_permissions(data))
Why data and not self.data ? How is it possible ?
def dispatch(self, request, *args, **kwargs):
return super(SecurityMixin, self).dispatch(request, *args, **kwargs)
Each view then inherits from SecurityMixin and got authenticators = [UserLoginMixin, ...] as class attribute.
The problem I have sometimes (I can't reproduce the bug...) is that I got KeyError on augmented kwargs while URL definition is properly set. eg:
appo_id = kwargs['appo_id']
KeyError: 'appo_id'Exception
I've been looking for hours and it seems that I will never have the solution... It's a bit frustrating.
If someone could help It'll be greatly appreciated.
I have a hunch that improper handling of class attributes is at fault.
CLASS VS INSTANCE
The class attribute data is overwritten every time SecurityMixin.__init__ is called:
class A:
data = []
def __init__(self, *args):
self.data.clear() # self.data references the class attribute
for x in args:
self.data.append(x)
x = A('foo')
# A.data = ['foo']
# x.data = ['foo']
y = A('bar')
# A.data = ['bar']
# y.data = ['bar']
# x.data = ['bar'] !!
HOWEVER:
class A:
data = ['I am empty']
def __init__(self, *args):
self.data = [] # redeclaring data as an instance attribute
for x in args:
self.data.append(x)
x = A('foo')
# A.data = ['I am empty']
# x.data = ['foo']
y = A('bar')
# A.data = ['I am empty']
# y.data = ['bar']
# x.data = ['foo']
This class attribute data is passed to the decorator (you cannot pass an instance attribute to a method decorator, i.e. self.data, because the instance does not yet exist during decorator declaration).
The wrapped function, however, does have access to the instance if it is passed in ('self' argument).
Django's method_decorator removes this self argument; that decorator is used to transform a function decorator (which does not get a self argument implicitly) into a method decorator (which gets a self parameter implicitly). That's why you do not have to include self in the list of parameters for the various mixin check methods as it was removed by method_decorator. To put it simply: use method_decorator to decorate a method with a function decorator. Read up on it here decorating CBVs.
Knowing that, I am not really sure why check_permissions should be a function decorator as it is now when you only use it to decorate methods.
You could just decorate dispatch with check_permissions itself:
def check_permissions(_dispatch):
def _decorator(self, request, *args, **kwargs): # adding self
for mixin in self.data: # referencing the INSTANCE data
kwargs = mixin.check(request, *args, **kwargs)
if isinstance(kwargs, HttpResponseRedirect):
return kwargs
return _dispatch(self, request, *args, **kwargs) # don't forget self here
return _decorator
#check_permissions
def dispatch(self, request, *args, **kwargs):
...
Maybe some view is trying to check AppoExistMixin because it is in that view's data list, although it should not be - and the view's kwargs do not include 'appo_id'. You could also try being explicit by passing the wanted check mixins directly to the decorator: #method_decorator(check_permissions([UserLoginMixin, ...])). This way you you don't have to mess with class vs instance attributes.
Also... you should rename data to something that you are unlikely to overwrite with your own variable.
If you want to be super-lazy you could just do:
appo_id = kwargs.get('appo_id',False)
if not appo_id: return kwargs
But this would only fix that particular error in that one view. It's ignoring a symptom instead of curing the disease.
Some more explanation:
function vs method. check_permissions is a function, while dispatch() is a method. You cannot simply use a function decorator on a method: for one, because the implicit argument self (the instance the method belongs to) is passed to the decorator as well, although it may not expect it.
That is where django's method_decorator comes in by removing and storing self within the decorator. Compare the two signatures: wrapper(request, *args, **kwargs) vs _decorator(self, request, *args, **kwargs). In the former, method_decorator 'absorbed' self before the function decorator is called.
Think of it as an adapter, a decorator for the decorator, that 'bridges the gap' between function and method. Use it if you don't want to/cannot alter the decorator.
In your case, however, you can change the decorator to make it work with a method - thus you don't need django's method_decorator.

Writing a test for a Django View get_context_data() method

I am writing a test for a View where I update context to pass additional information to the template.
Problem
In writing the test, I'm having trouble accessing context from the RequestFactory.
Code
View
class PlanListView(HasBillingRightsMixin, ListView):
"""Show the Plans for user to select."""
headline = "Select a Plan"
model = Plan
template_name = "billing/plan_list.html"
def get_context_data(self, *args, **kwargs):
context = super(PlanListView, self).get_context_data(**kwargs)
context.update({
"customer": self.get_customer()
})
return context
Test
class TestPlanListView(BaseTestBilling):
def setUp(self):
super(TestPlanListView, self).setUp()
request = self.factory.get('billing:plan_list')
request.user = self.user
request.company_uuid = self.user.company_uuid
self.view = PlanListView()
self.view.request = request
self.response = PlanListView.as_view()(request)
def test_get_context_data(self, **kwargs):
context = super(self.view, self).get_context_data(**kwargs)
context.update({"customer": self.view.get_customer()})
self.assertEqual(
self.view.get_context_data(),
context
)
Question
How can I test the view's get_context_data() method?
Using a test client gives you access to your context.
def test_context(self):
# GET response using the test client.
response = self.client.get('/list/ofitems/')
# response.context['your_context']
self.assertIsNone(response.context['page_obj'])
self.assertIsNone(response.context['customer']) # or whatever assertion.
.....
If you don't want to test the full browser behavior you could use the RequestFactory instead. This factory provides a request instance that you can pass to your view. The benefit in my case was that I can test a single view function as a black box, with exactly known inputs, testing for specific outputs. Just as described in the docs.
class TestView(TemplateView):
template_name = 'base.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context = {'1': 11337}
return context
# ...
def test_context(self):
factory = RequestFactory()
request = factory.get('/customer/details')
response = TestView.as_view()(request)
self.assertIsInstance(response.context_data, dict)
self.assertEqual(response.context_data['1'], 1337)

Subclassing Django class based views?

I'm trying to warp my head around Django's new class-based views from 1.3.
I've done a bit of reading:
http://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html
http://www.caktusgroup.com/blog/2011/12/29/class-based-views-django-13/
But the one thing I haven't seen an example of, or how to do is, can several views subclass a common 'parent' class, reusing the data from there? (Pardon my thrashing of the common nomenclature)
An example of what I am trying to do:
class MyParentClass(TemplateView):
def get(self, request, *args, **kwargs):
session_data = request.session
other_variables = foovars
return self.render_to_response(context)
class MyChildClassOne(TemplateView):
template_name = "template_one.htm"
def get(self,request, *args, **kwargs):
resultant_data = foodata
return {'data' : resultant_data }
class MyChildClassTwo(TemplateView):
template_name = "template_two.htm"
def get(self,request, *args, **kwargs):
other_data = foootherdata
return {'data' : other_data }
So that the only difference between the two child classes is the templates they use and the 'data' they return. Both views would also return session_data and other_variables from the parent class, and therefore not repeating "return session_data, other_variables" in every child class.
First, don't override your view's get method. That's dangerous for several reasons I won't go into here.
Anmyway, what you need is the get_context_data method, which returns the context dict which is being passed to the template.
So, both your child views should look something like:
class ChildView(ParentView):
template_name = "foo"
def get_context_data(self, **kwargs):
context = super(ChildView, self).get_context_data(**kwargs)
context.update({
'foodata': 'bardata',
})
return context
But this is pretty much how views work out of the box; why do you think you need to subclass from an additional custom view class?
Here's a possible way: Your parent class will return a variable called data in the context which will be set by the child class.
Example:
class MyParentClass(TemplateView):
def get(self, request, *args, **kwargs):
session_data = request.session
other_variables = foovars
context['data'] = data
return self.render_to_response(context)
class MyChildClassOne(MyParentClass):
template_name = "template_one.htm"
def get(self,request, *args, **kwargs):
data = foodata
return super(MyChildClassOne, self).get(request, args, kwargs)
class MyChildClassTwo(MyParentClass):
template_name = "template_two.htm"
def get(self,request, *args, **kwargs):
data = foootherdata
return super(MyChildClassTwo, self).get(request, args, kwargs)
Both your child classes inherit from MyParentClass, whose get method automatically sets a variable named data into the context. The data values are provided by the child classes. Once done they call the parent's get method to perform the common operations, including rendering.