Cleanest way to decorate Django's dispatch method - django

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.

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

How to support static and class methods when passing positional arguments from a decorator?

I want to write a decorator that passes an additional positional argument at first position, but after self or cls, to the wrapped function.
def handle_route(self, url, headers):
pass
#require_login
def handle_route(self, user, url, headers):
pass
Since there are staticmethods, classmethods and normal methods, how do I know at what position to pass the additional argument?
import functools
def require_login(function):
#functools.wraps(function)
def wrapper(*args, **kwargs):
user = obtain_user(kwargs.get('headers', {}))
return function(user, *args, **kwargs)
return wrapper
This example only words for staticmethods. In the other cases, user gets passed as self or cls argument causing an error.
What you wish to do is best done using a decorator class, not a decorator function. You can then implement the full descriptor protocol, which is otherwise inherited from the function.
Specifically, you need to create a __get__ method for your object. You can then defer the resolution of class-/static-/regular-method to the wrapped method.
class RequireLogin(object):
def __init__(self, wrapee):
self.wrapee = wrapee
self.user = 'foo' # changeme
def __get__(self, instance, owner):
def wrapped(*args, **kwargs):
return self.wrapee.__get__(instance, owner)(self.user, *args, **kwargs)
# ^ use class/static/regular method's __get__
return wrapped

Using class based views to process information?

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.

How to pass previous form data to the constructor of a DynamicForm in FormWizard

I have a FormWizard where I need data from the first form to pass to the constructor of the second form so I can build a dynamic form.
I can get the first form's data via the process_step of the FormWizard.
I create the fields of the second form with a database call of the list of fields.
class ConditionWizardDynamicQuestions(forms.Form):
def __init__(self, DynamicQuestions=None, *args, **kwargs):
super(ConditionWizardDynamicQuestions, self).__init__(*args, **kwargs)
questions = Question.objects.filter(MYDATA = DATA_FROM_1STFORM)
for q in questions:
dynField = FieldFactory(q)
self.fields[q.label] = dynField
How can I pass over the DATA_FROM_1STFORM ?
my resultant code:
I abandoned the init of the form, and switched it to the CreateQuestions def. Then used the wizard's get_form override to alter the form after creation.
class ConditionWizard(SessionFormWizard):
def get_form(self, request, storage, step=None, data=None, files=None):
form = super(ConditionWizard, self).get_form(request, storage, step, data, files)
stepIndex = self.get_step_index(request, storage, step)
if stepIndex == 1:
form.CreateQuestions(request.session["WizardConditionId"])
if stepIndex == 3:
form.fields['hiddenConditionId'].initial = request.session["WizardConditionId"]
form.fields['medicationName'].queryset = Medication.objects.filter(condition = request.session["WizardConditionId"])
return form
I solved this by overriding get_form_kwargs for the WizardView. It normally just returns an empty dictionary that get_form populates, so by overriding it to return a dictionary with the data you need prepopulated, you can pass kwargs to your form init.
def get_form_kwargs(self, step=None):
kwargs = {}
if step == '1':
your_data = self.get_cleaned_data_for_step('0')['your_data']
kwargs.update({'your_data': your_data,})
return kwargs
Then, in your form init method you can just pop the kwarg off before calling super:
self.your_data = kwargs.pop('client', None)
FormWizard already passes the data from each previous form to the next form. If you want to get that data in order to instantiate a class (for example, if a form has special keyword arguments that it requires), one way of doing it is to grab the querydict by overriding get_form in your form wizard class. For example:
class SomeFormWizard(FormWizard):
def get_form(self, step, data=None):
if step == 1 and data: # change this to whatever step requires
# the extra data
extra_data = data.get('key_from_querydict')
if extra_data:
return self.form_list[step](data,
keyword_argument=extra_data,
prefix=self.prefix_for_step(step),
initial=self.initial.get(step, None))
# Fallback for the other forms.
return self.form_list[step](data,
prefix=self.prefix_for_step(step),
initial=self.initial.get(step, None))
Note that you can also override parse_params(self, request, *args, **kwargs) in FormWizard to access the url/request data, just like you would in a view, so if you have request data (request.user, for instance) that is going to be needed for all of the forms, it might be better to get the data from there.
Hope this helps.
Override the get_form_kwargs method of your form wizard in views
view.py
class FormWizard(SessionWizardView):
def get_form_kwargs(self, step=None):
kwargs = {}
if step == '1':
step0_form_field = self.get_cleaned_data_for_step('0')['previous_form_field_data']
kwargs.update({'step0_form_field': step0_form_field})
return kwargs
Override the init of your form by popping up the data you got from the previous field to create a dynamic field.
forms.py
class MyForm(forms.Form):
#some fields
class MyForm1(forms.Form):
def __init__(self, *args, **kwargs):
extra = kwargs.pop('step0_form_field')
super(MyForm1, self).__init__(*args, **kwargs)
for i in range(extra):
self.fields['name_%s' % i] = forms.CharField()
I was recently working with django form wizard, and i was solving the similar issue. I don't think you can pass data to init, however, what you can do, is override the init constructor:
next_form = self.form_list[1]
# let's change the __init__
# function which will set the choices :P
def __init__(self, *args, **kw):
super(next_form, self).__init__(*args, **kw)
self.fields['availability'].choices = ...
next_form.__init__ = __init__
It's quite annoying that in python you can't declare and assign a function in one go and have to put it in the namespace (unless you use lambdas), but oh well.