So what does the code look like:
from django.db.models import ForeignKey, Manager
from django.contrib.auth.models import User
class LazyUserForeignKey(ForeignKey):
def __init__(self, **kwargs):
kwargs['to'] = User
self.manager_name = kwargs.pop('manager_name', 'for_user')
super(ForeignKey, self).__init__(**kwargs)
def contribute_to_class(self, cls, name):
super(ForeignKey, self).contribute_to_class(cls, name)
class MyManager(Manager):
def __call__(self2, user):
return cls._default_manager.filter(**{self.name: user})
cls.add_to_class(self.manager_name, MyManager())
So now, what does this do?
We are subclassing ForeignKey. In __init__ we make sure to is set to User and we also set self.manager_name equal to either the manager_name kwarg, if provided or 'for_user'. contribute_to_class get called by the ModelMetaclass to add each item to the Model itself. So here we call the parent method, to get the ForeignKey itself set on the model, and then we create a new subclass of Manager. And we define an __call__ method on it, this lets us call an instance as if it were a function. And we make __call__ return the QuerySet that would be returned by filtering the default manager for the class where the user field is equal to the given user. And then we add it to the class with the name provided earlier.
And that's all. Now we can do things like:
MyModel.for_user(request.user)
Next post we'll probably look at making this more generic.
Nice first post and glad to see you posting Alex!
ReplyDeleteVery cool idea. I haven't seen this approach before. I really like this sort of abstraction. I look forward to more posts from you.
ReplyDeleteWhy so many magic for what can be accomplished with simple reusable Manager class?
ReplyDeleteclass MyManager(models.Manager):
def get_for_user(self, user):
return self.get_query_set().filter(user=user)
class MyModel(models.Model)
user = models.ForeignKey(User)
objects = MyManager()
MyModel.objects.get_for_user(user)
Andy, the main advantage of this is that it doesn't require the user foreign key to be named "user", and also this same technique can be abstracted, as I discuss in the next post.
ReplyDelete