django-annoying
django-annoying copied to clipboard
Using AutoOneToOneField with proxy model
When using AutoOneToOneField with Proxy model, I've a hard time to get the related model. The similar setup with OneToOneField doesn't have problem. While I check the code I found a difference here:
class AutoOneToOneField(OneToOneField):
def contribute_to_related_class(self, cls, related):
setattr(
cls,
related.get_accessor_name(),
AutoSingleRelatedObjectDescriptor(related)
)
Studying Django's code:
class ForeignObject(RelatedField):
def contribute_to_related_class(self, cls, related):
if not self.remote_field.is_hidden() and not related.related_model._meta.swapped:
setattr(cls._meta.concrete_model, related.get_accessor_name(), self.related_accessor_class(related))
if self.remote_field.limit_choices_to:
cls._meta.related_fkey_lookups.append(self.remote_field.limit_choices_to)
When calling setattr(...)
, should AutoOneToOneField also pass in cls._meta.concrete_model
instead?
Just verified that cls
should really be cls._meta.concrete_model
.
Here is my project which use a proxy model of Auth.User to filter out staff and superuser, also to display by email instead of username. Since I use/mix with Firebase SDK, username is Firebase's UID and hidden from end user. My admin stuff can only recognize end users by their email addresses.
from django.contrib.auth.models import UserManager, User
class EndUserManager(UserManager):
def get_queryset(self):
return super().get_queryset().filter(is_staff=False, is_superuser=False)
class EndUser(User):
objects = EndUserManager()
class Meta:
proxy = True
def __str__(self):
return "%s" % (self.email or self.username)
Now I've two models which both are One-To-One relation with EndUser
, and display with email obtained from EndUser.__str__()
:
class Profile(models.Model):
owner = AutoOneToOneField(EndUser, on_delete=models.CASCADE, related_name='profile')
def __str__(self):
return "%s" % self.owner
class Tutor(models.Model):
owner = OneToOneField(EndUser, on_delete=models.CASCADE, related_name='tutor')
def __str__(self):
return "%s" % self.owner
When I obtain an instance of User
and try to access .profile
and .tutor
, I got error for profile
but no error for tutor
. Here is my test done in python shell:
>>> user = User.objects.get(pk=100)
>>> user.profile
AttributeError: 'User' object has no attribute 'profile'
>>> user.tutor
<Tutor: [email protected]>
I know I can obtain instance of EndUser
by a database call. Since I want to avoid that, I force User
to become EndUser
(such as self.request.user.__class__ = EndUser
). At first it seems working, at least I didn't see any errors, but with AutoOneToOneField
, it doesn't.
Using a patched version AutoOneToOneField
does resolve the problem.
class FixedAutoOneToOneField(AutoOneToOneField):
def contribute_to_related_class(self, cls, related):
setattr(
cls._meta.concrete_model,
related.get_accessor_name(),
AutoSingleRelatedObjectDescriptor(related)
)
Thanks for this research! Unfortunately, I've never used the AutoOneToOne
model myself, so you know more than me at this point. Would you like to submit a PR with the fix?