david / djangorators

A POC about a better use of decorators in Django.

Clone this repository (size: 10.9 KB): HTTPS / SSH
$ hg clone http://code.welldev.org/djangorators

Changed (Δ7.9 KB):

raw changeset »

djangorators/__init__.py (4 lines added, 0 lines removed)

djangorators/models.py (null-size change)

djangorators/permissions.py (16 lines added, 0 lines removed)

djangorators/related.py (18 lines added, 0 lines removed)

djangorators/rendering.py (15 lines added, 0 lines removed)

examples/djangorators_testproject/__init__.py (null-size change)

examples/djangorators_testproject/manage.py (14 lines added, 0 lines removed)

examples/djangorators_testproject/models.py (9 lines added, 0 lines removed)

examples/djangorators_testproject/settings.py (41 lines added, 0 lines removed)

examples/djangorators_testproject/templates/animals/item.html (1 lines added, 0 lines removed)

examples/djangorators_testproject/templates/animals/list.html (3 lines added, 0 lines removed)

examples/djangorators_testproject/tests.py (47 lines added, 0 lines removed)

examples/djangorators_testproject/urls.py (11 lines added, 0 lines removed)

examples/djangorators_testproject/views.py (57 lines added, 0 lines removed)

Up to file-list djangorators/__init__.py:

1
2
from permissions import owner_required
3
from related import from_object, from_queryset
4
from rendering import as_html

Up to file-list djangorators/permissions.py:

1
from django.http import HttpResponseNotFound
2
3
def owner_required(func):
4
    """
5
    Warning: depends on from_object's djangorator for the related_* access.
6
    """
7
    def _dec(request, *args, **kwargs):
8
        if 'related_object' in kwargs:
9
            if kwargs['related_object'].owner == request.user:
10
                return func(request, *args, **kwargs)
11
        elif 'related_queryset' in kwargs:
12
            kwargs['related_queryset'] = kwargs['related_queryset'].filter(owner=request.user)
13
            return func(request, *args, **kwargs)
14
        
15
        return HttpResponseNotFound()
16
    return _dec

Up to file-list djangorators/related.py:

1
from django.shortcuts import get_object_or_404
2
3
def from_object(object_class):
4
    def wrap(func):
5
        def _dec(request, *args, **kwargs):
6
            object_id = kwargs['%s_id' % object_class.__name__.lower()]
7
            kwargs['related_object'] = get_object_or_404(object_class, id=object_id)
8
            return func(request, *args, **kwargs)
9
        return _dec
10
    return wrap
11
12
def from_queryset(queryset):
13
    def wrap(func):
14
        def _dec(request, *args, **kwargs):
15
            kwargs['related_queryset'] = queryset
16
            return func(request, *args, **kwargs)
17
        return _dec
18
    return wrap

Up to file-list djangorators/rendering.py:

1
from django.views.generic.simple import direct_to_template
2
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
3
4
def as_html(template_name):
5
    def wrap(func):
6
        def _dec(request, *args, **kwargs):
7
            response = func(request, *args, **kwargs)
8
            # TODO: add more, or even better find an elegant hack :)
9
            http_responses = (HttpResponse, HttpResponseRedirect, HttpResponseNotFound)
10
            if isinstance(response, http_responses):
11
                return response
12
            # Otherwise, the response is a context which is rendered
13
            return direct_to_template(request, template_name, response)
14
        return _dec
15
    return wrap

Up to file-list examples/djangorators_testproject/manage.py:

1
import sys, os
2
sys.path.insert(0, os.path.split(os.path.split(os.getcwd())[0])[0])
3
4
from django.core.management import execute_manager
5
6
try:
7
    import settings # Assumed to be in the same directory.
8
except ImportError:
9
    import sys
10
    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
11
    sys.exit(1)
12
13
if __name__ == "__main__":
14
    execute_manager(settings)

Up to file-list examples/djangorators_testproject/models.py:

1
from django.db import models
2
from django.contrib.auth.models import User
3
4
class Animal(models.Model):
5
    name = models.CharField(max_length=50)
6
    owner = models.ForeignKey(User)
7
    
8
    def __unicode__(self):
9
        return u'%s owned by %s' % (self.name, self.owner)

Up to file-list examples/djangorators_testproject/settings.py:

1
import os
2
ROOT_PATH = os.path.dirname(__file__)
3
4
TEMPLATE_DEBUG = DEBUG = True
5
MANAGERS = ADMINS = ()
6
DATABASE_ENGINE = 'sqlite3'
7
DATABASE_NAME = os.path.join(ROOT_PATH, 'testdb.sqlite')
8
9
TIME_ZONE = 'America/Chicago'
10
LANGUAGE_CODE = 'en-us'
11
SITE_ID = 1
12
USE_I18N = True
13
MEDIA_ROOT = ''
14
MEDIA_URL = ''
15
ADMIN_MEDIA_PREFIX = '/media/'
16
SECRET_KEY = '2+@4vnr#v8e273^+a)g$8%dre^dwcn#d&n#8+l6jk7r#$p&3zk'
17
TEMPLATE_LOADERS = (
18
    'django.template.loaders.filesystem.load_template_source',
19
    'django.template.loaders.app_directories.load_template_source',
20
)
21
TEMPLATE_CONTEXT_PROCESSORS = (
22
    "django.core.context_processors.auth",
23
    "django.core.context_processors.debug",
24
    "django.core.context_processors.i18n",
25
    "django.core.context_processors.request",
26
)
27
MIDDLEWARE_CLASSES = (
28
    'django.middleware.common.CommonMiddleware',
29
    'django.contrib.sessions.middleware.SessionMiddleware',
30
    'django.contrib.auth.middleware.AuthenticationMiddleware',
31
)
32
ROOT_URLCONF = 'urls'
33
TEMPLATE_DIRS = (os.path.join(ROOT_PATH, 'templates'),)
34
INSTALLED_APPS = (
35
    'djangorators',
36
    'djangorators_testproject',
37
    'django.contrib.auth',
38
    'django.contrib.admin',
39
    'django.contrib.contenttypes',
40
    'django.contrib.sessions',
41
)

Up to file-list examples/djangorators_testproject/templates/animals/item.html:

1
{{ animal }}

Up to file-list examples/djangorators_testproject/templates/animals/list.html:

1
{% for animal in animals %}
2
  {{ animal }}
3
{% endfor %}

Up to file-list examples/djangorators_testproject/tests.py:

1
r"""
2
    >>> from django.test.client import Client
3
    >>> from django.contrib.auth.models import User
4
    >>> from django.core.urlresolvers import reverse
5
    >>> from djangorators_testproject.models import Animal
6
    
7
    >>> alice = User.objects.create_user('alice', 'alice@example.org', 'wonderful')
8
    >>> c = Client()
9
    >>> c.login(username='alice', password='wonderful')
10
    True
11
    
12
    >>> cat = Animal.objects.create(name=u"cat", owner=alice)
13
    >>> response = c.get(reverse('classic_animal_item', args=(cat.id,)))
14
    >>> response.context['animal']
15
    <Animal: cat (updated) owned by alice>
16
    >>> print response.content
17
    cat (updated) owned by alice
18
    <BLANKLINE>
19
    
20
    >>> response = c.get(reverse('decorated_animal_item', args=(cat.id,)))
21
    >>> response.context['animal']
22
    <Animal: cat (updated) (updated) owned by alice>
23
    >>> print response.content
24
    cat (updated) (updated) owned by alice
25
    <BLANKLINE>
26
27
    >>> lion = Animal.objects.create(name=u"lion", owner=alice)
28
29
    >>> response = c.get(reverse('classic_animal_list'))
30
    >>> response.context['animals']
31
    [<Animal: cat (updated) (updated) owned by alice>]
32
    >>> print response.content
33
    <BLANKLINE>
34
      cat (updated) (updated) owned by alice
35
    <BLANKLINE>
36
    <BLANKLINE>
37
38
    >>> response = c.get(reverse('decorated_animal_list'))
39
    >>> response.context['animals']
40
    [<Animal: cat (updated) (updated) owned by alice>]
41
    >>> print response.content
42
    <BLANKLINE>
43
      cat (updated) (updated) owned by alice
44
    <BLANKLINE>
45
    <BLANKLINE>
46
47
"""

Up to file-list examples/djangorators_testproject/urls.py:

1
from django.conf.urls.defaults import *
2
3
from djangorators_testproject.views import classic_animal_item, classic_animal_list, \
4
    decorated_animal_item, decorated_animal_list
5
6
urlpatterns = patterns('',
7
    url(r'^classic/animals/(?P<animal_id>\d+)/$',       classic_animal_item,    name='classic_animal_item'),
8
    url(r'^classic/animals/$',                          classic_animal_list,    name='classic_animal_list'),
9
    url(r'^decorated/animals/(?P<animal_id>\d+)/$',     decorated_animal_item,  name='decorated_animal_item'),
10
    url(r'^decorated/animals/$',                        decorated_animal_list,  name='decorated_animal_list'),
11
)

Up to file-list examples/djangorators_testproject/views.py:

1
from django.http import HttpResponseNotFound
2
from django.shortcuts import get_object_or_404
3
from django.views.generic.simple import direct_to_template
4
5
from djangorators import from_object, from_queryset, owner_required, as_html
6
from djangorators_testproject.models import Animal
7
8
def classic_animal_item(request, animal_id):
9
    # Retrieve item instance
10
    animal = get_object_or_404(Animal, id=animal_id)
11
    
12
    # Check permissions
13
    if animal.owner != request.user:
14
        return HttpResponseNotFound()
15
    
16
    # Do something useful (isn't it?)
17
    animal.name = '%s (updated)' % animal.name
18
    animal.save()
19
    
20
    # Render response
21
    return direct_to_template(request, 'animals/item.html', locals())
22
23
@from_object(Animal)
24
@owner_required
25
@as_html('animals/item.html')
26
def decorated_animal_item(request, *args, **kwargs):
27
    animal = kwargs['related_object']
28
    
29
    # Only do something useful
30
    animal.name = '%s (updated)' % animal.name
31
    animal.save()
32
    
33
    return locals()
34
35
def classic_animal_list(request):
36
    # Retrieve queryset
37
    animals = Animal.objects.all()
38
    
39
    # Check permissions
40
    animals = animals.filter(owner=request.user)
41
    
42
    # Do something useful
43
    animals = animals.exclude(name=u'lion')
44
    
45
    # Render response
46
    return direct_to_template(request, 'animals/list.html', locals())    
47
48
@from_queryset(Animal.objects.all())
49
@owner_required
50
@as_html('animals/list.html')
51
def decorated_animal_list(request, *args, **kwargs):
52
    animals = kwargs['related_queryset']
53
    
54
    # Only do something useful
55
    animals = animals.exclude(name=u'lion')
56
    
57
    return locals()