1

I'm writing an application in Django (which I'm very new to) where the admin area will be exposed to 'customers' of the application, not just staff/superusers, because of the nature of the application and the way Django automatically generates forms in the admin area with such little code..

As such I need to robust and manageable way to maintain authentication and separating data, so only data created by a user is seen by that user.

At the moment I'm just using the default admin package and changing permissions for 'client users' to filter what data they can see (I only want them to see data they've created) using code like the below:

class MyModelAdmin(admin.ModelAdmin):

    def get_queryset(self, request): 
        qs = super(MyModelAdmin, self).get_queryset(request) 
        return qs.filter(user=request.user)

    def save_model(self, request, obj, form, change):
        # field not editable in admin area so handle it here...
        obj.user = request.user
        obj.save()

However as the application scales, I can see ensuring this type of data filtering becoming difficult to manage, for example if there are chains of foreign keys on certain tables(A->B B->C C->D), and to filter the table at the end of the chain I need to do various JOINs to get the rows which relate to the current user.

A couple of solutions I'm pondering are creating a separate admin app per user, but this feels like overkill and even more unmanageable.

Or just adding the user column to every Model where data filtering by user is required to make it easier to filter.

Any thoughts on the best approach to take?

Sebastian Wozny
  • 16,943
  • 7
  • 52
  • 69
dpdenton
  • 300
  • 1
  • 9

1 Answers1

0

First off, from experience, you're better off offering editing and creating functionality to your users in an actual django app, using Views. Generic views make this very easy. Once you let your users into the admin, they will get used to it and it's hard to get them to leave.

Additionally you should use contrib.auth.Group together with django-guardian to keep track of object-level permissions instead of implementing it yourself. It pays off in the long run.

If you want to make this experience on your own however, you have more than one sensible choice:

  • owner on root objects in the ForeignKey pyramid
  • owner on every model

To realize the first option, you should implement two methods on every model down the ForeignKey chain:

def get_parent(self):
    """Should return the object that should be queried for ownership information"""
    pass
def owned_by(self, user):
    """Should return a boolean indicating whether `user` owns the object, by querying `self.get_parent().owned_by(user)`"""  
    pass

However, as you stated, this incurrs many JOINS if your schema is sufficiently complex.

I would advise you to store the information about the owner in every model, everything else is a maintanence nightmare in my experience.

Instead of adding the field manually to every model manually, you should use inheritance. However django provides bad built-in support for inheritance with relations: An abstract base model cannot define a models.ForeignKey, so you're stuck with table based inheritance.

Table based inheritance brings another problem with itself: Consider these models:

from django.db import models
from app.settings import AUTH_USER_MODEL
class Base(models.Model):
    owner = models.ForeignKey(AUTH_USER_MODEL)
class ChildA(Base):
    name = models.CharField(max_length=5)
class ChildB(Base):
    location = models.CharField(max_length=5)

It is easy to find the owner of a given instance of ChildA or ChildB:

>>> obj = ChildA.objects.create(owner=Peter, name="alex")    
>>> obj.owner

Peter

However it is non trivial to find all objects owned by a particular user:

>>> Base.objects.filter(owner=Peter)
<Base-object at 0xffffff>

The default manager returns a Base object, and doesn't contain information about whether it is a ChildA or ChildB instance, which can be troublesome.

To circumvent this, I recommend a polymorphic approach with django-polymorphic or django-model-utils, which is more lightweight. They both provide means to retrieve the child classes for a given Base model in the queries. See my answer here for more information on polymorphism in django.

These also incur JOINs, but at least the complexity is manageable.

Sebastian Wozny
  • 16,943
  • 7
  • 52
  • 69
  • Thanks for this - will have a think.. Out of interest what do you mean by 'Once you let your users into the admin, they will get used to it and it's hard to get them to leave.'? I'm seriously pondering whether I should be using the admin area for end users or not, but that's another question.. – dpdenton Oct 28 '15 at 17:57
  • I maintain a students association's website with 2000 users, which I developed when I was new to django. I made the mistake and let them into the admin, initially. Now I have a front end administration, that works, but they still try to go to the broken admin panel. :) – Sebastian Wozny Oct 28 '15 at 17:59
  • Ah ok! Why did you switch to a front end admin? Been reading up on using the admin panel for end users and the it seems (to me) the main reason to not use it is if you start needing functionality that is only possible outside the admin... – dpdenton Oct 28 '15 at 18:03
  • also it's difficulty to security harden it. when you need stuff thats complex, you start rewriting admin templates, which is not something i'd do every day. I switched to front end because it was more integrated with my application, and the users liked it more. – Sebastian Wozny Oct 28 '15 at 18:07