views:

90

answers:

2

Hi all,

I need to show a user only the objects that he owns. Since I need to do this on more then 80% of my views, hardcoding this kills DRY. More so, it is absolutely imperative that a user never sees records owned by others. And doing it by hand (in all the views) also seems error prone.

I've been looking at decorators (like login_required) but that seems to early in the request handling process. Is it possible to get the request.user value into a custom written manager and do something like this:

class CustomerManager(models.Manager):
    def get_query_set(self):
        return super(CustomerManager, self).get_query_set().filter(created_by=request.user)

Or is the manager object just as 'no go!' as the model definition as far as request info is concerned?

Thanx a lot.


This is the saving objects part, but purely here as elaboration and not a necessary read.

The saving part is relatively secure. I changed the object.save() function on the model so it takes the userid as a parm.

def save(self, userid):  
    self.created_by = userid    
    super(Customer, self).save(userid)

In the view:

if form.is_valid():
    customer = form.save(commit=False)
    customer.save(request.user)

This way I dont need to have the line below in my view before customer.save...

    customer.created_by = request.user

And thus making it less error prone.

A: 

I think the best place for this may be in your views - though I could be wrong. Adding the self.created_by = userid in the save() method doesn't do anything to protect people from viewing the information. Just from saving it.

If you use a custom manager for a default queryset, then the 20% of your views will be out of luck.

I think you need a view - why not create your own generic view once and then reuse it ?

def object_detail_user_only(request. queryset, **kwargs):
    queryset = queryset.filter(created_by=request.user)

    return list_detail.object_detail(request,queryset,**kwargs)

Something like that (untested)?

thornomad
I disagree that views are the best place. If he places all the authorization in the views, then a decision is made to switch the client technology, all that must be re-written. I do my authorization in the model.
Sailing Judo
Thanx for the feedback thornomad. So what your saying is have an extra method in views.py that is sort of in between my views and database. Thus from an actual view always call "object_detail_user_only" and therewith always doing a prefined query?
GerardJP
@Sailing Judo: It's not so much the authorization, I fully lean on Django auth for that. Whether or not the issue should be handled in views is a good point though .. Hence my models.manager question :)
GerardJP
@GerardJP - you can stack as many views as you need, I sometimes override/replace the default `list_detail.object_detail` because its so easy to do. @Sailing_Judo - if he puts all the authorization in the model, though, doesn't it make it more challenging if he (as an admin) wants to edit another user's views?
thornomad
@thornomad: Been looking at `list_detail.object_detail` within generic views but not sure how to use it. Could you please give me an example on how I'd call `object_detail_user_only` from my view? Thanx!
GerardJP
You can do it the same way, actually, that I am returning the `list_detail.object_detail` method - a view, I think, just needs to return a response object. So, we are daisy-chaining them together: `your_view` returns `object_detail_user_only` view which returns `list_detail.object_detail` which returns the response object - you can make changes to the query all along the way ...
thornomad
+4  A: 

Why not define a method that fetches records for the user in the CustomerManager:

class CustomerManager(models.Manager):
    def get_user_records(self, user):
        return self.filter(created_by=user)

I would leave the get_query_set unoverridden as I see no benefits to override it in your case.

shanyu
@Shanyu: This seems a pretty sweet implementation (just tried it :). Would it be easy though to get additional sorting/filtering through there. Something like ".order_by('company_name')" ?
GerardJP
@Shanyu: Tried it .. this works: list = Customer.objects.get_user_records(request.user).order_by('company_name').reverse()
GerardJP
@GerardJP: You can chain methods as you like: Foo.objects.filter(..).exclude(..).order_by(..)[x:y]
shanyu