How to mix in class based views from pluggable apps?

15 views
Skip to first unread message

jrief

unread,
Jan 9, 2012, 4:36:36 PM1/9/12
to django...@googlegroups.com
Hi,
currently I am writing a Django applications built up from loosely coupled plug-ins. Each of these plug-ins shall offer a class based view to handle get and post requests. For get requests the context shall be populated with plug-in specific data. For post requests, the plug-in specific posted data shall be handled by the corresponding view class. 

This of course is not difficult to achieve. The view class of the final app, which combines all these plugins, can overload the methods get_context_data() and post() and dispatch the requests to functions offered by these plug-ins. But I do not like this approach because it does not separate concerns and the author of the final app has to remember, how to dispatch these requests manually to the plug-ins. 

My question is, if there is there a more elegant solution, say a pattern, which does not require to duplicate the dispatching code for the mixin classes?

Let me explain using some sample code:

class MainAppDetailView(SomeBaseDetailView, PluginAMixin, PluginBMixin):
    model = MyModel
    template_name = "my_detail.html"

    def get_context_data(self, **kwargs):
        context = super(FinalAppDetailView, self).get_context_data(**kwargs)
        PluginAMixin(self).update_context(context)
        PluginBMixin(self).update_context(context)
        return context

    def post(self, *args, **kwargs):
        post_request = self.request.POST
        response = PluginAMixin(self).handle_post(post_request)
        if issubclass(response, HTTPResponse):
            return response
        response = PluginBMixin(self).handle_post(post_request)
        if issubclass(response, HTTPResponse):
            return response
        # handle post request for the main app
        ...
        return response

For my point of view this example contains too much code duplication. Is there a pattern, so that I only have to modify the class declaration of my FinalAppDetailView or even better, only in my settings.py?

Cheers, Jacob

Roland van Laar

unread,
Jan 10, 2012, 1:55:22 AM1/10/12
to django...@googlegroups.com
On 01/09/2012 10:36 PM, jrief wrote:
Hi,
currently I am writing a Django applications built up from loosely coupled plug-ins. Each of these plug-ins shall offer a class based view to handle get and post requests. For get requests the context shall be populated with plug-in specific data. For post requests, the plug-in specific posted data shall be handled by the corresponding view class. 

This of course is not difficult to achieve. The view class of the final app, which combines all these plugins, can overload the methods get_context_data() and post() and dispatch the requests to functions offered by these plug-ins. But I do not like this approach because it does not separate concerns and the author of the final app has to remember, how to dispatch these requests manually to the plug-ins.

Is there a way to know which plugin is needed?


My question is, if there is there a more elegant solution, say a pattern, which does not require to duplicate the dispatching code for the mixin classes?

Let me explain using some sample code:

class MainAppDetailView(SomeBaseDetailView, PluginAMixin, PluginBMixin):
    model = MyModel
    template_name = "my_detail.html"

    def get_context_data(self, **kwargs):
        context = super(FinalAppDetailView, self).get_context_data(**kwargs)
        PluginAMixin(self).update_context(context)
        PluginBMixin(self).update_context(context)
        return context

    def post(self, *args, **kwargs):
        post_request = self.request.POST
Instead of:

        response = PluginAMixin(self).handle_post(post_request)
        if issubclass(response, HTTPResponse):
            return response
        response = PluginBMixin(self).handle_post(post_request)
        if issubclass(response, HTTPResponse):
            return response
you could use:

for plugin in (PluginAMixin, PluginBMixin):
    response = super(plugin, self).handle_post(post_request)

    if issubclass(response, HTTPResponse):
        return response



        # handle post request for the main app
        ...
        return response

For my point of view this example contains too much code duplication. Is there a pattern, so that I only have to modify the class declaration of my FinalAppDetailView or even better, only in my settings.py?

Cheers, Jacob
--
Roland

Andre Terra

unread,
Jan 10, 2012, 8:16:35 AM1/10/12
to django...@googlegroups.com
Each plugin should call super() on their own definition of the get_context_data method -- which will return a dictionary --, then update and return a modified dictionary.

There's no need to explicitly call each plugin by the name in subclasses. It works the other way around.


Cheers,
AT

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to django...@googlegroups.com.
To unsubscribe from this group, send email to django-users...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.

jrief

unread,
Jan 10, 2012, 9:05:37 AM1/10/12
to django...@googlegroups.com
But the mixin plugins are not derived from django.views.generic.DetailView, otherwise the main app's DetailView would obtain a diamond shaped inheritance.

And django.views.generic.detail.BaseDetailView.get calls get_context_data only once, so I don't see how the plugins shall "deliver" their contexts.

BTW, I found another solution.

Cheers, J.

Message has been deleted

Andre Terra

unread,
Jan 10, 2012, 9:21:28 AM1/10/12
to django...@googlegroups.com
Python will use solve the diamond problem through MRO[1], so it all depends on the order you in which you mix your classes.


class MyBaseView(BaseDetailView):
    def get_context_data(self, *args, **kwargs):
        context = super(MyBaseView, self).get_context_data(*args, **kwargs)
        context['foo'] = 'MyView'
       
        return context
       
class MixinA(object):
    def get_context_data(self, *args, **kwargs):
        context = super(MixinA, self).get_context_data(*args, **kwargs)
        context['foo'] = 'MixinA'
       
        return context
       
class MixinB(object):
    def get_context_data(self, *args, **kwargs):
        context = super(MixinB, self).get_context_data(*args, **kwargs)
        context['foo'] = 'MixinB'
       
        return context       
       
       
class SomeView(MixinA, MixinB, MyBaseView):
    pass

# ----

This is the correct solution. Try changing the order of the mixins in SomeView to see the difference.


Cheers,
AT

[1] http://en.wikipedia.org/wiki/C3_linearization

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To view this discussion on the web visit https://groups.google.com/d/msg/django-users/-/FpplXSO5pBYJ.

Tom Evans

unread,
Jan 10, 2012, 9:29:14 AM1/10/12
to django...@googlegroups.com
On Tue, Jan 10, 2012 at 2:05 PM, jrief <jacob...@gmail.com> wrote:
> But the mixin plugins are not derived from django.views.generic.DetailView,
> otherwise the main app's DetailView would obtain a diamond shaped
> inheritance.

Well, that is evident isn't it - they are mixins. If they were derived
from DetailView, they would be subclasses.

Besides which, with python multiple inheritance, you don't get a
diamond, you get a chain, see below.

>
> And django.views.generic.detail.BaseDetailView.get calls get_context_data
> only once, so I don't see how the plugins shall "deliver" their contexts.

Welcome to the world of the MRO - Method Resolution Order. This SO
post gives a good example of how MRO works:

http://stackoverflow.com/questions/2010692/what-does-mro-do-in-python

This page*, particularly the diagram in the "Argument passing, argh!"
section should make it clearer how a diamond inheritance is handled
(viz. it's not a diamond):

http://fuhm.net/super-harmful/

Got that? OK, so when you have a class like this:

class MyView(Mixin1, Mixin2, SomeSuperView):

then the MRO for this class will look something like this:

(MyView, Mixin1, Mixin2, SomeSuperView, object)

When you call MyView.get_context_data and call super(MyView,
self).get_context_data, it will first try Mixin1.get_context_data,
which if exists should call super(Mixin1, self).get_context_data, and
so on up the MRO stack.


Cheers

Tom

* super is not harmful, no matter what that page says. It is just that
you must be consistent when using it.

jrief

unread,
Jan 10, 2012, 4:21:32 PM1/10/12
to django...@googlegroups.com
OK, now I got it.
Coming from C++ I was stuck too much in static inheritance thinking. The diagram in http://fuhm.net/super-harmful/ helped me to understand this issue.
Thank you very much for your help!
Reply all
Reply to author
Forward
0 new messages