7

I'm using a middleware to get the currently logged in user in my views and models. This helps me to for example return only the objects created or assigned to the logged-in user. Please follow this link to see which middleware that I use.

I call this middleware with:

get_current_user()

This worked fine till now. But now I experienced some strange behaviour and only for one special use-case.

I'm using this get_current_user() in a custom manager to return only projects for which the currently logged in user is a member. Membership is defined through the model "ProjectMembership". This model looks like this:

class ProjectMembership(models.Model):
    project = models.ForeignKey(Project)
    member = models.ForeignKey(User, related_name='project_membership_member_set')
    day_rate = models.PositiveIntegerField(max_length=11)

In the project model I have set a custom manager called user_objects. The project model looks like this (simplified):

class Project(models.Model):
    name = models.CharField(max_length=100)

    #Managers
    objects = models.Manager()
    user_objects=UserProjectManager()

The UserProjectManager() is now my point of concern. The manager looks like this:

class UserProjectManager(models.Manager):
    def get_query_set(self):
        print "current user is" + str(get_current_user())
        return super(UserProjectManager, self).get_query_set().filter(projectmembership__member=get_current_user())

I added print "current user is" + str(get_current_user()) in order to debug it. This print statement always! prints out the currently logged in user. When I created this function the server (manage.py runserver) was running and I did not restarted the server and the method runs as I would have expected.

But if I restart the server with manage.py runserver the UserProjectManager() crashes with this error:

caught an exception while rendering: Incorrect integer value: 'AnonymousUser' for column 'member_id' at row 1

I uploaded the error page: link

Interestingly enough is that when I let the server running (after the error was thrown) and then change something in my source-code (add a sign and remove it) and save it (somewhere in my project, it does not matter where!!), click again on the link that has thrown the error, it works! More interesting is that the

print "current user is" + str(get_current_user())

in front of the line that throws the error, always returns the logged-in user correctly!

This does not make a lot of sense to me. Especially since it works if I just resave ( which leads to an automatic restart of the server!) my source.

I'm 100% sure that the error is created in the above outlined source line, since I changed this:

return super(UserProjectManager, self).get_query_set().filter(projectmembership__member=get_current_user())

to this:

return super(UserProjectManager, self).get_query_set())

and then it works perfectly fine. I just say this since the above posted error is maybe a bid misleading.

Probably tough to help me out here. Would appreciate any help!

Edit:

The first answer below from "whrde" stated that the middleware approach is probably a bad idea, whereas the people in the other thread link said that the approach is fine.

Therefore I wanted to state another example where such a middleware is really convenient to use. I use it all over my application. I would just be interested if I really should remove this middleware from my app. since probably I will get more errors than the one that I posted or that the approach is fine. For example overwriting the save method for a model and setting the current_user is really easy in using this middleware. It saves me to write the same three lines in each view afer save().

class ProjectMembership(models.Model):
    project = models.ForeignKey(Project)
    member = models.ForeignKey(User, related_name='project_membership_member_set')
    day_rate = models.PositiveIntegerField(max_length=11)

    created_by = models.ForeignKey(User, editable=False, related_name='project_membership_creator')
    created = models.DateTimeField(auto_now_add=True, editable=False, verbose_name='creation date')
    modified_by = models.ForeignKey(User, editable=False, related_name='project_membership_modifier')
    modified = models.DateTimeField(auto_now=True, editable=False)

    #Managers
    objects = models.Manager()
    user_objects=UserProjectMembershipManager()

    class Meta:
        unique_together = (("project", "member"),)

    def __unicode__(self):
        return u'%s in project: %s' % (self.member, self.project)

    def save(self):
        if not self.id:
            self.created_by = get_current_user()
        self.modified_by = get_current_user()
        super(ProjectMembership, self).save()

Edit: Conclusio: Do not use the get_current_user() middleware since there is absolutely no need to use it. Pass the request object to your forms, object managers, overwritten object save methods etc.. and everything will be fine ;-)

Community
  • 1
  • 1
Thomas Kremmel
  • 14,575
  • 26
  • 108
  • 177
  • 1
    It seems unclear to me why you need to resort to middleware for this. You could simply pass request.user to some query to get the desired queryset. – Brian Neal Jan 18 '10 at 17:01
  • In order to get the current user in a model manager you have to use some kind of middleware, since you can not pass a request object to your models.py. – Thomas Kremmel Jan 18 '10 at 17:06
  • 1
    What's wrong with MyModel.objects.some_custom_method(request.user)? – Brian Neal Jan 18 '10 at 17:13

2 Answers2

10

This looks like a bad approach: you need to pass the request object around to provide a function/class/method with access to the current user. Don't mess with a global state.

Create a method on your manager that takes the user as an argument, and call this from your views:

# models.py
class ProjectMembership(models.Model):
    project = models.ForeignKey(Project)
    member = models.ForeignKey(User, related_name='project_membership_member_set')
    day_rate = models.PositiveIntegerField(max_length=11)

class ProjectManager(models.Manager):
    def for_user(self, user):
        return self.get_query_set().filter(projectmembership__member=user)

class Project(models.Model):
    name    = models.CharField(max_length=100)
    objects = ProjectManager()

# somewhere deep in views.py
if request.user.is_authenticated():
    Project.objects.for_user(request.user)
Will Hardy
  • 14,588
  • 5
  • 44
  • 43
  • 4
    I do stuff like this all time. It is a much simpler and more explicit approach than messing around with middleware and thread local storage. – Brian Neal Jan 18 '10 at 17:20
  • Ok, that looks like a neat approach. Thank you. I will implement it this way. It would have been great if somebody told me this a few weeks before ;-) I will leave the question as unanswered for some days, since I really want to know why this behaviour occurs. – Thomas Kremmel Jan 18 '10 at 18:36
  • Actually please see the edit of my question. Do you think using this kind of middleware in an overwritten model save() method is also not a good idea? – Thomas Kremmel Jan 18 '10 at 18:46
  • If you're only using django's admin, you can overload the `ModelAdmin.save_model(self, request, obj, form, change)` method, setting the `obj.user = request.user` before saving. Note that Django's approach here is not to use thread local (see eg http://www.b-list.org/weblog/2008/dec/24/admin/) You're free to use the threadlocal hack if you like, but there's a strong argument against it. It's not necessary. Passing the user object is more flexible, explicit, readable and less complicated than a threadlocal hack, and you're free to write methods and functions to make it easier/convenient. – Will Hardy Jan 18 '10 at 20:28
  • Yes probably you are right. I already begun to remove the get_current_user() from all objects managers. Now I just check how to remove them from the object save() method. I'm searching for a solution outside the admin. I will try def save(self,request): .. – Thomas Kremmel Jan 18 '10 at 21:34
  • Ok that approach seems to do not work. Any suggestions how to pass the logged-in user to a object save method outside the admin? – Thomas Kremmel Jan 18 '10 at 22:02
  • 1
    @Tom Tom it does work. You'll need to post your code or ask another question to get more help. – Brian Neal Jan 18 '10 at 22:07
  • Ok probably it's time for another question. ;-) Thanks for your help! – Thomas Kremmel Jan 18 '10 at 22:26
  • Ok my fault. I changed the save() method of the model to receive the request object. That was ok. But in the view I changed the save method call of the form instead of the object. This way it can not work. ;-) Ok you both were absolutely right, that there is no need for the get_current_user() middleware. I'm just wondering why nobody in the other thread told me this. I will remove now all occurences of this middleware. Thank you guys for your help! – Thomas Kremmel Jan 18 '10 at 22:34
2

Ignoring the thread local sideshow, your problem is probably related to the fact that the AnonymousUser object is not really a User model instance. Querying the database with this object may not get you very far. You will want to check for authenticated users at some point, either in the view:

if request.user.is_authenticated():
    Project.objects.for_user(request.user)

Or in your manager method:

def for_user(self, user):
    if user.is_authenticated():
        return self.get_query_set().filter(projectmembership__member=user)
    else:
        return self.get_query_set().none()
Will Hardy
  • 14,588
  • 5
  • 44
  • 43
  • That's not the problem, since I use the @login_required decorator and the view where the problem occurs can only be called if the user is logged in. And as I said, the print "current user is" + str(get_current_user()) does return the logged in user, which is really strange. – Thomas Kremmel Jan 18 '10 at 21:26