views:

398

answers:

3

I want to overwrite the custom objects model manager to only return objects a specific user created. Admin users should still return all objects using the objects model manager.

Now I have found an approach that could work. They propose to create your own middleware looking like this:

#### myproject/middleware/threadlocals.py

try:
    from threading import local
except ImportError:
    # Python 2.3 compatibility
    from django.utils._threading_local import local

_thread_locals = local()

def get_current_user():
    return getattr(_thread_locals, 'user', None)

class ThreadLocals(object):
    """Middleware that gets various objects from the
    request object and saves them in thread local storage."""
    def process_request(self, request):
        _thread_locals.user = getattr(request, 'user', None)

#### end

And in the Custom manager you could call the get_current_user() method to return only objects a specific user created.

class UserContactManager(models.Manager):
    def get_query_set(self):
        return super(UserContactManager, self).get_query_set().filter(creator=get_current_user())

Is this a good approach to this use-case? Will this work? Or is this like "using a sledgehammer to crack a nut" ? ;-)

Just using:

Contact.objects.filter(created_by= user)

in each view doesn`t look very neat to me.

EDIT Do not use this middleware approach !!!

use the approach stated by Jack M. below

After a while of testing this approach behaved pretty strange and with this approach you mix up a global-state with a current request.

Use the approach presented below. It is really easy and no need to hack around with the middleware.

create a custom manager in your model with a function that expects the current user or any other user as an input.

#in your models.py
class HourRecordManager(models.Manager):
    def for_user(self, user):
        return self.get_query_set().filter(created_by=user)

class HourRecord(models.Model):
    #Managers
    objects = HourRecordManager()

#in vour view you can call the manager like this and get returned only the objects from the currently logged-in user.

hr_set = HourRecord.objects.for_user(request.user)

See also this discusstion about the middelware approach.

+3  A: 

It seems necessary to use the middleware to store the user information.

However, I'd rather not modify the default ModelManager objects, but hook it upto a different manager, that I will use in the code, say in your case user_objects instead of objects.

Since you will use this only within views that are @login_required you dont need all the complex error handling in the Middleware.

Just my 2¢.

Lakshman Prasad
+1  A: 

One way to handle this would be to create a new method instead of redefining get_query_set. Something along the lines of:

class UserContactManager(models.Manager):
    def for_user(self, user):
        return super(UserContactManager, self).get_query_set().filter(creator=user)

This allows your view to look like this:

contacts = Contact.objects.for_user(request.user)

This should help keep your view simple, and because you would be using Django's built in features, it isn't likely to break in the future.

Jack M.
A: 

Thank you for sharing the code. Not so good solution in terms of testability but I did not find another way to customize model managers by request object data. It would be better to have control over manager creation but Django does not allow this.

Yaroslav
@Yaroslav I've run into the same road block with Django. When developing an SAAS, it's not very DRY to have to repeat even the simplest of `for_user()` or `for_account()` type methods mentioned above in every view you use. My way of making it a bit more DRY is to use: `request.account.some_model_set.all()`, etc in my view code. Following the relationship backwards like this is more implicit but much more DRY than filtering every time or having a mix-in model class with a custom manager for each model.
orokusaki
Yes, that is a good point.
Yaroslav