Login

Row-Level, URL-based permissions for FlatPages

Author:
bradmontgomery
Posted:
June 17, 2009
Language:
Python
Version:
1.0
Score:
2 (after 2 ratings)

I'm using Django's FlatPages, but I want to be able to restrict admin access to Users based on a FlatPage url. For example, User John Doe should be able to edit any FlatPage objects whose URL begins with /johndoe/ (such as /johndoe/about/ or /johndoe/projects/whatever/).

For this to work, John Doe would already need the appropriate admin permissions for FlatPage (such as can_add and can_change).

I have set this up as a separate flatpage_addons app. It consists of the Permission model, which maps a starting URL to one or more django Users. It consists of the minimal necessary admin code so Permissions can be created using the admin.

The bulk of this code consists of the ifhasflatpagepermission template tag as well as the flatpage_result_list inclusion tag. The former works much like django's existing if, else, endif tags while the latter is modified from the django admin's result_list inclusion tag.

This may not be the most elegant solution to my problem, but so far it works for me. Any comments or suggestions are welcome!

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#########################
# models.py
#########################
from django.db import models
from django.contrib.flatpages.models import FlatPage
from django.contrib.auth.models import User

class Permission(models.Model):
    '''
    A mapping of Flatpage URLs to Users. 
    NOTE: for this to work, the user must already have 
    permissions to edit FlatPages.
    '''
    url = models.CharField(max_length=255, \
        help_text='The selected Users will be able to edit all FlatPages \
        that begin with this URL. For example: "/johndoe/"')
    users = models.ManyToManyField(User, related_name="flatpagepermission")

    def __unicode__(self):
        return self.url

    class Meta:
        ordering = ['url', ]

def user_has_flatpage_permission(user, url):
    '''
    A helper function to check to see if the given user has 
    permission to edit the flatpage associated with the given URL.

    user = a django.contrib.auth.models.User object
    url = a URL fragment for a FlatPage. E.g. /about/something/else
    '''
    if user.is_superuser:
        return True

    perms = Permission.objects.filter(users=user)
    for p in perms:
        if url.startswith(p.url):
            return True
    return False

#########################
# admin.py
#########################
from flatpage_addons.models import Permission
from django.contrib import admin

class PermissionAdmin(admin.ModelAdmin):
    list_display = ('url', )

admin.site.register(Permission, PermissionAdmin)

##################################
# templatetags/flatpage_addons_extras.py
##################################
from django import template
from django.template import Variable, VariableDoesNotExist
from django.contrib.auth.models import User
from de_concierge.flatpage_addons.models import user_has_flatpage_permission
from django.contrib.admin.templatetags import admin_list

register = template.Library()

def results(user, cl):
    '''
    Modified from django.contrib.admin.templatetags.admin_list
    Generates a list of items for the Admin's change_list page.
    This function filters out those FlatPage objects that do 
    for which the current user does not have Permission.
    '''
    for res in cl.result_list:
        if user_has_flatpage_permission(user, res.url):
            yield list(admin_list.items_for_result(cl,res))
            
@register.inclusion_tag('admin/change_list_results.html')
def flatpage_result_list(user, cl):
    ''' 
    Based on django.contrib.admin.templatetag.admin_list.result_list;
    An inclusion tag that omits flatpages that the user 
    does not have permission to edit.
    '''
    return {'cl':cl,
            'result_headers':list(admin_list.result_headers(cl)),
            'results': list(results(user, cl))}

class IfHasFlatPagePermissionNode(template.Node):
    ''' 
    Execute code in this block if the given User has permission to
    the given FlatPage URL. 
    '''
    def __init__(self, user, url, nodelist_true, nodelist_false):
        self.user = Variable(user)
        self.url = Variable(url)
        self.nodelist_true = nodelist_true
        self.nodelist_false = nodelist_false

    def __repr__(self):
        return "<IfHasFlatPagePermissionNode>"

    def render(self, context):
        try:
            user_obj = self.user.resolve(context)
            flatpage_url = self.url.resolve(context)

            if user_has_flatpage_permission(user_obj, flatpage_url):
                return self.nodelist_true.render(context)
        except VariableDoesNotExist:
            pass

        return self.nodelist_false.render(context)

def do_ifhasflatpagepermission(parser, token):
    ''' 
    Parser for Template:
        {% ifhasflatpagepermission <user> <url> %} 
            {# execute contents #}
        {% endifhasflatpagepermission %}
    '''
    bits = list(token.split_contents())
    if len(bits) != 3:
        raise template.TemplateSyntaxError, "%r takes 2 arguments: a User object and a URL."%bits[0]
    end_tag = 'end' + bits[0]
    nodelist_true = parser.parse(('else', end_tag))
    token = parser.next_token()
    if token.contents == 'else':
        nodelist_false = parser.parse((end_tag, ))
        parser.delete_first_token()
    else:
        nodelist_false = template.NodeList()
    return IfHasFlatPagePermissionNode(bits[1], bits[2], nodelist_true, nodelist_false)

# Register the new tag
register.tag('ifhasflatpagepermission', do_ifhasflatpagepermission)


##########################################
# Lastly, we override the change_form.html and the
# change_list.html admin templates for the flatpage app:
# /my_project/templates/admin/flatpages/flatpage/
##########################################

##########################################
# change_form.html
##########################################
{% extends "admin/change_form.html" %}
{% load flatpage_addons_extras %}

{% block content %}
    {% ifhasflatpagepermission user original.url %}
        {{ block.super }}
    {% else %}
        <h1> You do not have permission to Edit this page </h1>
    {% endifhasflatpagepermission %}
{% endblock %}

##########################################
# change_list.html
##########################################
{% extends "admin/change_list.html" %}
{% load adminmedia admin_list i18n %}
{% load flatpage_addons_extras %}

{% block result_list %}
    {# {% result_list cl %} #}
    {% flatpage_result_list user cl %}
{% endblock %}

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

bartTC (on June 17, 2009):

The admin part looks complicated to me. I guess it's enough to just overwrite the ModelAdmin.queryset method to filter the rows that the user has permission to.

#

bradmontgomery (on July 15, 2009):

bartTC, I'm assuming you meant the admin template part? I really didn't want to touch the flatpage code, so that's why I just override the template (which I'm doing for flatpages anyway). I'm sure this is not the best solution, but it solves my problems :)

#

Please login first before commenting.