custom manager to control access

66 views
Skip to first unread message

Mike Dewhirst

unread,
Apr 3, 2012, 4:43:05 AM4/3/12
to django...@googlegroups.com
I'm trying to make a custom manager for a few models. The objective is
to limit user access to information in the database belonging to the
company of which they are a member.

I think I want to say:

class Something(models.Model):
...
objects = MemberManager()

But when I run manage.py runserver I get:

AttributeError: 'MemberManager' object has no attribute '_inherited'

Here is the code:

from django.db import models
from company.models import *

class MemberManager(models.Manager):
"""
The manager in every model using this manager must return ONLY
rows belonging to the User's company. company.models.Member is
the table which links a member (ie user) to a particular company
in company.models.Company. Each member can be connected to exactly
zero or one company. If zero raise BusinessRuleViolation exception
otherwise return the company with which to filter query sets using
the user's company.
"""
def __init__(self, request=None):
self.user = None
if request and request.user and request.user.is_authenticated():
self.user = request.user

def get_company(self):
if self.user:
user_row = Member.objects.get(user=self.user)
return user_row.company

def get_query_set(self):
coy = self.get_company()
if coy:
return super(MemberManager,
self).get_query_set().filter(company=coy)
raise BusinessRuleViolation('User must be a company Member')


From the research I've done it seems I should not have the __init__()
in this and indeed if I experimentally factor it out it the error goes away.

The doc string says what I'm trying to do but maybe there is a better
way than a custom manager.

Thanks for any pointers

Mike

Tom Evans

unread,
Apr 3, 2012, 5:00:02 AM4/3/12
to django...@googlegroups.com

Two things:

1) You get the exception because you do not call the parent class's
constructor, and thus the manager instance is not set up with the
appropriate values. Your __init__() method on a derived class should
look more like this:

def __init__(self, *args, **kwargs):
super(self, MyClassName).__init__(*args, **kwargs)
# your stuff

2) Managers are not instantiated with the current request object, so
your approach will not work anyway. You could use something like
thread locals, but that is a hack (and won't work if/when you run
outside of the request/response cycle).

Cheers

Tom

Mike Dewhirst

unread,
Apr 3, 2012, 8:15:26 PM4/3/12
to django...@googlegroups.com
I have now discarded the idea :)

I'm not very comfortable with thread locals. I need a bullet-proof
approach which makes data from other companies invisible to members of
this company. I suppose a view decorator is the way to go but I would
have preferred a deeper model-level approach.

Thanks

Mike

>
> Cheers
>
> Tom
>

akaariai

unread,
Apr 4, 2012, 3:55:25 AM4/4/12
to Django users
On Apr 4, 3:15 am, Mike Dewhirst <mi...@dewhirst.com.au> wrote:
> I have now discarded the idea :)
>
> I'm not very comfortable with thread locals. I need a bullet-proof
> approach which makes data from other companies invisible to members of
> this company. I suppose a view decorator is the way to go but I would
> have preferred a deeper model-level approach.

You have two ways to pass this kind of information around:
1. explicitly as parameters
2. using thread locals (or other "global state").

So, you need to use explicit parameters. You should do something like
this:

class MemberManager(models.Manager):
def visible_for_user(self, user):
# Replace the below filter condition with appropriate logic.
return self.get_query_set().filter(company=user.company)

Now, instead of using Member.objects.all() you should use
Member.objects.visible_for_user(request.user) in your code. I think
you can set the manager default for relations, so that you could do
group.membership.visible_for_user(request.user), but I haven't ever
done that. You will need to be careful when coding so that you don't
accidentally show or modify data from other companies. The way I would
do this is have the logic in the Model/Manager classes, and then use
that logic in your view code.

- Anssi

Mike Dewhirst

unread,
Apr 4, 2012, 8:32:44 PM4/4/12
to django...@googlegroups.com
Anssi

Thanks for that - I'll do some testing.

I'm also thinking of middleware to look at the request and discover the
company on the way in so I can make it available to everything which
processes the request and produces a response.

Cheers

Mike

Reply all
Reply to author
Forward
0 new messages