Decorator aware Django

34 views
Skip to first unread message

Bernardo

unread,
Dec 12, 2011, 7:49:06 PM12/12/11
to Django users
I've been working lately (not my choice!) with Pylons and the terrible
Genshi as template system.
The thing is: for the next project I wanted to use something better
and the first thing I could think of was Django.

I spent the last weaks reading documentation (FAQs, installation,
templates, models and so on) and It feels like
I'm in the right path.

One thing the project I was working had and I liked was lots of
decorators on top of controllers (what you people call views - and
makes sense)
Something like that:

#-----------------------------

@require_permission('login')
@expose_xhr('admin/data/index.html', 'admin/data/table.html')
@paginate('media', items=20)
@observable(events.Admin.DataController.index)
def index(self, page=1, search=None, filter=None):
"""
Home page for ... bla bla
"""
filter = Person.query.options(orm.undefer('comment_count'))
tag = ....

return {
'base': self.info,
'search': search,
'search_form': search_form,
'media_filter': filter,
'tag': tag,
}

#-----------------------------

It feels right to make the controller returns only the data to be
rendered/serialized instead of a HTTP response. Not doing that makes
it a lot aware of things it shouldn't be and make it easy to repeat
code.

As I said, I'm new to Django and couldn't find out if it had something
like that by default (Pylons dindn't had much of that though).
The things I found was decorators to check request methods (and that's
also great).

So I started writing myself those decorators, because I was already
used to work with them.

As I was teaching myself to use the framework, I realized that using
decorators that way fits in Django philosophies pretty well:
- The framework should be consistent at all levels -> Using
decorators it's easy do make it work
- Django apps should use as little code as possible -> Views only
return data. Decorators render the data to the user
- Don’t repeat yourself:
- The framework, within reason, should deduce as much as
possible from as little as possible.
That last phrase tells everything I'm trying to explain.

A decorator e.g. `@render('page.html')` could get the output of the
view and render to a template using the request information as
"RequestContext" without the view programmer bothering with calling
something like `render_to_response`.

Another decorator could receive 'json' or 'xml' as argument and grab
an serializer to return HTTP data to a Ajax request using the correct
mimetype to avoid making the programmer remember this kind of
information (or even how to instantiate the serializer class).

Well, these were my thoughts and feel free to think otherwise. I would
appreciate to know if something is being done in this direction.


Sorry for the long post!
Bernardo

Russell Keith-Magee

unread,
Dec 13, 2011, 1:29:05 AM12/13/11
to django...@googlegroups.com

Hi Bernardo,

You're not the first person I've seen suggest this approach, and it's
certainly legal Python -- but to me, it feels completely wrong.

Django's contract for a view is simple, and very closely aligned to
it's end purpose -- serving HTTP content. A Django view:

* Accepts a HTTP Request, along with any pre-parsed URL arguments
* Returns a HTTP Response

And that's it. While I can see the appeal of trying to do a classical
MVC "Controller" separation, it just doesn't seem appropriate when
you're dealing with a framework that is 100% focussed on delivering a
very specific type of output.

Having views that return HttpResponse doesn't mean you *can't* perform
good separation of concerns. A well designed view *will* separate the
rendering from the data. It just does it with normal, well-decoupled
functions.

It also doesn't mean that you must return a fully serialized HTTP
Response. Consider the case of TemplateResponse. This is a data
structure that will *eventually* be a fully rendered HTTP response,
but until it is baked, it's just a collection of data. This means your
decorator and middleware stack has ample opportunity to modify the
response before it is served to the client.

Returning HTTPResponse also provides a data formatting constraint that
your approach doesn't have. At each level of Django view, you know
you're going to get a HTTP Response. That means there are certain
predictable fields, structures and so on. If you have a view that
returns "Data" -- what format is that data in? Will that format change
if it's decorated? If so, what will it change into? If I have some
extra functionality that I want to add to your view, how do I build a
decorator so that I know it will always work with your view?

So -- in summary: Nobody will can stop you from taking this sort of
"return data then decorate" approach. You certainly *can* build a
fully functional Django stack that uses this approach. However, I
would personally advise against it.

Yours,
Russ Magee %-)

Javier Guerra Giraldez

unread,
Dec 13, 2011, 9:14:28 AM12/13/11
to django...@googlegroups.com
hi,


Russel explained the real reasons of why your proposal doesn't fit so
good in Django.

still, this snippet:

On Mon, Dec 12, 2011 at 7:49 PM, Bernardo <jbv...@gmail.com> wrote:
>        - The framework, within reason, should deduce as much as
> possible from as little as possible.
>    That last phrase tells everything I'm trying to explain.


for me, is opposed to the "explicit is better than implicit" in the
Zen of Python. not a bad thing on itself, but something to be careful
about. (and, personally, it's a big part of why i like Django so
much better than RoR)

now, thinking a little more on what seems to be good: separating the
'data gathering' part of a view from the presentation part, I think it
should be handled at a slightly different level.

for example, make a (generic) view that simply calls a data gathering
function (which is not a view) and hands the result to a HTML
template, and another (generic) view that calls the same function but
returns a different (XML? JSON? Pickle?) representation.

paired with a middleware and some shortcuts in the urls.py, you can
have nicely separated concerns and DRYness.

--
Javier

Bernardo

unread,
Dec 13, 2011, 5:16:55 PM12/13/11
to Django users
Hi Russel,

> Returning HTTPResponse also provides a data formatting constraint that
> your approach doesn't have. At each level of Django view, you know
> you're going to get a HTTP Response.

Yes, now it makes sense the view working only with HTTP request and
responses and the decorators will only work with some kind of specific
information that will not be the same for each decorator.

Being strict, the view (the decorated function) will always return an
HTTP response, but in another (more abstract) layer... I think I'll
need to find some balance in this technique.

Hi Javier,

> for example, make a (generic) view that simply calls a data gathering
> function (which is not a view) and hands the result to a HTML
> template, and another (generic) view that calls the same function but
> returns a different (XML? JSON? Pickle?) representation.

I don't know if I understand what you're saying but the decorator
approach is working exactly like that but instead of calling these
generic views, I decorate my view with it.
Could you explain me that with a simple snippet?

Regards,
Bernardo

On Dec 13, 12:14 pm, Javier Guerra Giraldez <jav...@guerrag.com>
wrote:

Matej Cepl

unread,
Dec 14, 2011, 4:57:04 PM12/14/11
to django...@googlegroups.com
On 13.12.2011 15:14, Javier Guerra Giraldez wrote:
> for me, is opposed to the "explicit is better than implicit" in the
> Zen of Python. not a bad thing on itself, but something to be careful
> about. (and, personally, it's a big part of why i like Django so
> much better than RoR)

I am a complete newbie in the world of webapps, but exactly this was the
reason why I left Flask. Nothing against them, but

@app.route("/")
def hello():
return "Hello World!"

looks to me like a wrong to do things and yes “explicit is better than
implicit” (or “No magic allowed!”) was something I was thinking about.

What I liked about the first look at Django was that it looks just like
a Python app not playing any tricks on me.

Now I have to overcome this feeling with ORMs (be it the Django one or
SQLAlchemy) ... but there is probably not a better way how to do this.

Just my €0.02

Matěj

Derek

unread,
Dec 15, 2011, 1:51:35 AM12/15/11
to django...@googlegroups.com
On 14 December 2011 23:57, Matej Cepl <mc...@redhat.com> wrote:
> On 13.12.2011 15:14, Javier Guerra Giraldez wrote:
>>
>> for me, is opposed to the "explicit is better than implicit" in the
>> Zen of Python.  not a bad thing on itself, but something to be careful
>> about.   (and, personally, it's a big part of why i like Django so
>> much better than RoR)
>
>
> What I liked about the first look at Django was that it looks just like a
> Python app not playing any tricks on me.
>
> Now I have to overcome this feeling with ORMs (be it the Django one or
> SQLAlchemy) ... but there is probably not a better way how to do this.
>
> Just my €0.02
>
> Matěj

And the other advantage of Django is that you are not forced to use
the ORM for queries; you could just write everything as plain old
SQL... if you were sure that was the best way to do it!

Bernardo

unread,
Dec 15, 2011, 8:51:15 AM12/15/11
to Django users
> @app.route("/")
> def hello():
> return "Hello World!"

That's not what I want to do. I like how views are handled, but I
wanted a easier way to render data from multiple return points in a
same function without calling another thing multiple times...

> Now I have to overcome this feeling with ORMs

ORMs are great. I've already spent too many years writing SQL by hand
and I regret the time I lost.


Regards
Bernardo

Andre Terra

unread,
Dec 15, 2011, 12:48:01 PM12/15/11
to django...@googlegroups.com
On Thu, Dec 15, 2011 at 11:51 AM, Bernardo <jbv...@gmail.com> wrote:
but I
wanted a easier way to render data from multiple return points in a
same function without calling another thing multiple times...

Use Class-based views[1] and subclass them as needed?


Cheers,
AT

[1] http://django.me/cbv

Bernardo

unread,
Dec 15, 2011, 5:17:35 PM12/15/11
to Django users
> Use Class-based views[1] and subclass them as needed?

Earlier today I saw a blog talking about that. I haven't read
everything, but seems that it is what I want.
I still need to see how to combine multiple Class based views to treat
my data.
Thanks!

Bernardo

Andre Terra

unread,
Dec 16, 2011, 8:12:53 AM12/16/11
to django...@googlegroups.com
On Thu, Dec 15, 2011 at 8:17 PM, Bernardo <jbv...@gmail.com> wrote:
I still need to see how to combine multiple Class based views to treat
my data.

The idea here is to use Mixins, i.e., classes that implement a few methods and can be mixed with one of your main classes.


# ------------------------------------------------------------------------
# example.py
# ------------------------------------------------------------------------
from django.core.serializers.json import simplejson, DjangoJSONEncoder

class JSONMixin(object):
    def get(self, context, **kwargs):
        """
        On ajax requests, return a JSON object with
        information provided by subclasses.

        """

        if self.request.is_ajax():
            self.get_data(context, **kwargs)
            return HttpResponse(self.dump_data(self.data))

        return super(JSONMixin, self).get(context)

    def dump_data(self, data):
        return simplejson.dumps(data, cls=DjangoJSONEncoder)

    def get_data(self, *args, **kwargs):
        """
        Subclasses must implement this, setting the 'data' attribute on the
        instance which will then be dumped on the HttpResponse.

        """
        raise NotImplementedError

    def fail(self, msg=""):
        return HttpResponseBadRequest(msg)


# ------------------------------------------------------------------------

Assuming the code above (not sure how good it is), slap this onto another generic CBV and, as long as that view implements a get_data which sets a value for the 'data' instance attribute, you should be able to fetch some json through an ajax request, e.g.


# ------------------------------------------------------------------------
# views.py
# ------------------------------------------------------------------------
from example import JSONMixin
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.views.generic import ListView

class UsersWithCount(JSONMixin, ListView): # note how we mix the functionality
    """
    My Simple View
    """"
    foo = 'bar'

    def get(self, *args, **kwargs):
        # the usual get() logic goes here,
        # this is a very silly example
        if self.foo in self.request.GET:
                context = self.get_context_data(*args, **kwargs)
                return self.render_to_response(context)
               
        else:
                return HttpResponseRedirect("/")

    def get_data(self, *args, **kwargs):
        # a different logic that creates a json-dumpable object
        # and saves it in self.data
        users = User.objects.all()
        self.data = {'users': users, 'user_count': len(users)}


# ------------------------------------------------------------------------


Let us know if you have any more questions.


Cheers,
AT
Reply all
Reply to author
Forward
0 new messages