I've been experimenting with Django's Class Based Views and am trying to write a simple class based view that processes certain information in request so that the processed information can be used by the "handler" method.
I don't seem to have fully understood what the docs say and am unsure of whether this should be a Mixin, a generic view or something else. I'm thinking of making a class like this:
class MyNewGenericView(View):
redirect_on_error = 'home'
error_message = 'There was an error doing XYZ'
def dispatch(self, request, *args, **kwargs):
try:
self.process_information(request)
# self.process_information2(request)
# self.process_information3(request)
# etc...
except ValueError:
messages.error(request, self.error_message)
return redirect(self.redirect_on_error)
return super(MyNewGenericView, self).dispatch(request, *args, **kwargs)
def process_information(self, request):
# Use get/post information and process it using
# different models, APIs, etc.
self.useful_information1 = 'abc'
self.useful_information2 = 'xyz'
def get_extra_info(self):
# Get some extra information on something
return {'foo':'bar'}
This will allow someone to write a view like:
class MyViewDoesRealWork(MyNewGenericView):
def get(self, request, some_info):
return render(request, 'some_template.html',
{'info':self.useful_information1})
def post(self, request, some_info):
# Store some information, maybe using get_extra_info
return render(request, 'some_template.html',
{'info':self.useful_information1})
Is the above code the right way to go? Is there any simpler/better way of doing this? Will this prevent the above functionalities from being used in another generic view (e.g. a built-in generic view)?
Have a look at this. great example code. http://www.stereoplex.com/blog/get-and-post-handling-in-django-views
It seems I just asked a stupid question.
This can easily be achieved by making a class that processes that information:
class ProcessFooInformation(object):
def __init__(self, request):
self.request = request
#property
def bar(self):
baz = self.request.GET.get('baz', '')
# do something cool to baz and store it in foobar
return foobar
# etc...
Then using old style function views or new class-based views:
def my_view(request):
foo = ProcessFooInformation(request)
# use foo in whatever way and return a response
return render(request, 'foobar.html', {'foo':foo})
I also made this more efficient by using lazy evaluation of properties.
I adapted ideas from the lazy property evaluation recipe and the comments to write a wrapper:
def lazy_prop(func):
def wrap(self, *args, **kwargs):
if not func.__name__ in self.__dict__:
self.__dict__[func.__name__] = func(self, *args, **kwargs)
return self.__dict__[func.__name__]
return property(wrap)
This evaluates the value of the wrapped method only once per instance and uses a stored value on subsequent calls. This is useful if the property evaluates slowly.
Related
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.
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.
I can't understand the class based views, so I am trying to figure it out with an example. Here is what I have so far:
#urls.py
url(r'^(?P<langcode>[a-zA-Z-]+/about/$', about, name='about')
#views.py
def about(request, langcode):
languages = Language.objects.values_list('code', flat=True)
language = get_object_or_404(Language, pk=langcode)
return render(request, 'about.html', {
'languages': languages,
'language': language
})
I also have some other functional views which contain the first 2 lines of about:
languages = Language.objects.values_list('code', flat=True)
language = get_object_or_404(Language, pk=langcode)
So, what I want to do now is:
create a class BaseView (or however you want to call it) which extends
something from django.generic.views and which will determine the language and languages parameters for the context based on the langcode input parameter
Create the class AboutView(BaseView) (so extending the BaseView) which will somehow define the template name about.html to be used for rendering.
I will further have another class based view, also to extend BaseView and which is simillar to AboutView, but which sets one more context parameter called region depending as well on the langcode input parameter
Can someone show me exactly how to code this stuff? thank you
Here's a simple way to achieve what you want:
You first define the common logic, using the TemplateView generic view:
class MyBaseView(TemplateView):
def dispatch(self, request, *args, **kwargs):
# dispatch takes care of "reading" the parameters from the url
self.language = get_object_or_404(Language, pk=kwargs.pop("langcode")) # I would use some kind of default value to prevent exception, but its up to your logic
return TemplateView.dispatch(self, request, *args, **kwargs)
def get_context_data(self, **kwargs):
# get_context_data creates the context
context = TemplateView.get_context_data(self, **kwargs)
context.update({"language": self.language,
"languages": Language.objects.values_list('code', flat=True)})
return context
Then, you don't even need an AboutView because all you want is to control the template_name, so in your urls.py:
# in urls,py you can just use this, instead of defining an AboutView, you pass the template_name you want to use, this is sufficient because TemplateView expects a template_name attribute
url('^(?P<langcode>[a-zA-Z]+)/about/$', MyBaseView.as_view(template_name="about.html"), name='about')
and finally, for the other view that uses region you can just inherit from the MyBaseView, and add the context you want:
class AboutViewWithRegion(MyBaseView):
def get_context_data(self, **kwargs):
context = MyBaseView.get_context_data(self, **kwargs)
context.update({"region": <<your logic here>>})
return context
Hope that helps!
I'd like to write an except clause that redirects the user if there isn't something in a queryset. Any suggestions welcome. I'm a Python noob, which I get is the issue here.
Here is my current code:
def get_queryset(self):
try:
var = Model.objects.filter(user=self.request.user, done=False)
except:
pass
return var
I want to do something like this:
def get_queryset(self):
try:
var = Model.objects.filter(user=self.request.user, done=False)
except:
redirect('add_view')
return var
A try except block in the get_queryset method isn't really appropriate. Firstly, Model.objects.filter() won't raise an exception if the queryset is empty - it just returns an empty queryset. Secondly, the get_queryset method is meant to return a queryset, not an HttpResponse, so if you try to redirect inside that method, you'll run into problems.
I think you might find it easier to write a function based view. A first attempt might look like this:
from django.shortcuts import render
def my_view(request):
"""
Display all the objects belonging to the user
that are not done, or redirect if there are not any,
"""
objects = Model.objects.filter(user=self.request.user, done=False)
if not objects:
return HttpResponseRedirect("/empty-queryset-url/")
return render(request, 'myapp/template.html', {"objects": objects})
The advantage is that the flow of your function is pretty straight forward. This doesn't have as many features as the ListView generic class based view (it's missing pagination for example), but it is pretty clear to anyone reading your code what the view is doing.
If you really want to use the class based view, you have to dig into the CBV documentation for multiple object mixins and the source code, and find a suitable method to override.
In this case, you'll find that the ListView behaviour is quite different to what you want, because it never redirects. It displays an empty page by default, or a 404 page if you set allow_empty = False. I think you would have to override the get method to look something like this (untested).
class MyView(ListView):
def get_queryset(self):
return Model.objects.filter(user=self.request.user, done=False)
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
if len(self.object_list == 0):
return HttpResponseRedirect("/empty-queryset-url/")
context = self.get_context_data(object_list=self.object_list)
return self.render_to_response(context)
This is purely supplemental to #Alasdair's answer. It should really be a comment, but couldn't be formatted properly that way. Instead of actually redefining get on the ListView, you could override simply with:
class MyView(ListView):
allow_empty = False # Causes 404 to be raised if queryset is empty
def get(self, request, *args, **kwargs):
try:
return super(MyView, self).get(request, *args, **kwargs)
except Http404:
return HttpResponseRedirect("/empty-queryset-url/")
That way, you're not responsible for the entire implementation of get. If Django changes it in the future, you're still good to go.
With function based Django view it was simple to switch between several different views based on a condition, e.g. something like:
def base_view(request):
if some_condition():
return foo_view(request)
else:
return bar_view(request)
I can't find a simple way to do the same with the new class-based generic views. The only way I can think of is to redisrect, which I would like to avoid for various reasons:
def base_view(request):
if some_condition():
return redirect(reverse("name_of_url_to_class-based_view_foo"))
else:
return redirect("/url_to_class-based_view_bar/")
Any suggestions?
This is equivalent to your example with class based views.
class FooView(View):
pass
class BarView(View):
pass
class BaseView(View):
# staticmethod to avoid adding 'self' to the arguments
foo_view = staticmethod(FooView.as_view())
bar_view = staticmethod(BarView.as_view())
def dispatch(self, request, *args, **kwargs):
if some_condition():
return self.foo_view(request, *args, **kwargs)
else:
return self.bar_view(request, *args, **kwargs)
Even though the Django docs do say that the function based generic views are now deprecated I think the only reason to switch would be if you're writing less code.
If you're still set on switching, you'll want to first identify which class based views or mixins are most appropriate (single object, multiple objects, date based, forms, etc.). If the conditional was used to select a function that returns different context data / template to provide to the view, you can push the conditional down into an overridden get_queryset|get_context_data|get_object|get_template_names depending on your use case.
For example,
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(BaseView, self).get_context_data(**kwargs)
# Add in the publisher
if some_condition():
context['some_data'] = ...
else:
context['other_data'] = ...
return context
If all else fails and you're still determined to have class based views, you could probably also override get(self, request, *args, **kwargs) and do the switching there to the appropriate method. The detailed docs are getting better but I've still found myself poking through the source code to figure out how to accomplish what I want.