django用户XOR组模型关系

时间:2013-04-20 16:24:44

标签: django properties django-models django-contenttypes

我正在考虑与django的内置UserGroup建立关系的最佳方式。

By'或'我的意思是模型实例必须由UserGroup专有。

我认为通过查看上面的模型,这个问题应该很容易理解。

这是我目前的实施。我一直在看GenericRelations,但他们没有看到  适用于限制在如此少数型号的人。

编辑:使用抽象模型重构。

class OwnedModel(models.Model):

    _owner_user = models.ForeignKey(User, null=True, related_name='%(class)s')

    _owner_group = models.ForeignKey(Group, null=True, related_name='%(class)s')

    class Meta:
        abstract = True

    @property 
    def owner(self):
        return self._owner_user or self._owner_group

    @owner.setter
    def owner(self, value):
        if isinstance(value, User):
            self._owner_user = value
            if self._owner_group:
                self._owner_group = None
        elif isinstance(value, Group):
            self._owner_group = value
            if self._owner_user:
                self._owner_user = None
        else:
            raise ValueError

class RemoteAccess(OwnedModel):
    SSH = 's'
    SUPPORTED_PROTOCOLS = (
        (SSH, "ssh"),
    )

    host = models.CharField(_('host name'), max_length=255, validators=[full_domain_validator])

    protocol = models.CharField(_('protocol'), max_length=1, choices=SUPPORTED_PROTOCOLS, default=SSH)

    credential = models.OneToOneField(RemoteCredential)

我当前实施的主要问题是:

  • 如何在创建实例时强制它设置UserGroup__init__覆盖是否可以成为最佳选择?
  • 是否有更好/更清洁/更有效的实施?

谢谢!

2 个答案:

答案 0 :(得分:1)

我会覆盖save()方法,并定义自定义异常

def save(self, *args, **kwargs):
    if self._owner_user is None and self._owner_group is None:
        raise NoOwner
    super(OwnedModel, self).save(*args, **kwargs)

覆盖 init 可能会在某些情况下导致问题,除非您在保存之前没有所有者(例如在表单等中)。

我认为没有更简洁的方法来坚持使用Django用户/组。

答案 1 :(得分:0)

根据您的程序,有多种选择可以实现这一点。一种方法是使用继承。

class Owner(models.Model):

     def create(self,instance):
         raise NotImplementedError('Method create should be called from subclasses of owner')

class UserOnwer(Owner):
      user = models.ForeignKey(User)
      def create(self,instance):
          self.user = instance


class GroupOnwer(Owner):
      group = modelsForeignKey(Group)
      def create(self,instance):
          self.group = instance

class RemoteAccess(models):
      ....
      owner = models.ForeignKey(Owner)


      def get_onwer():  # shortcut implementation of this method must go to Owner.Manager 
          try:
             return UserOnwner.objects.get(pk = self.owner_id) # or what is more common case
          except UserOnwner.DoesNotExist:
             return GroupOnwer.objects.get(pk = self.owner_id)

我需要说几句关于你在这里支付的交易。在这种情况下,有额外的最多两个查询来获取GroupOwner,并且您将遇到列表问题。要解决一些问题,你需要向所有者提供一些关于他的孩子的额外知识(以第3范式和封装原则为代价)。

class Owner(models.Model):
     owner_type = models.CharField(choices('user','group')) 

     @classmethod # factory here
     def create(cls,instance):
         if is_instance(instance,User):
             user_owner = UserOnwer()
             return user_owner.user = instance
         if is_instance(instance,User):
             group_owner = GroupOwner()
             return group_owner.group = instance

class UserOnwer(Owner):
     user = models.ForeignKey(User)


class GroupOnwer(Owner):
      group = modelsForeignKey(Group)


class RemoteAccess(models):
      ....
      owner = models.ForeignKey(Owner)

      def get_onwer():  # shortcut implementation of this method must go to Owner.Manager 
          if self.owner.owner_type == 'user':
             return UserOnwner.objects.get(pk = self.owner_id)
          elif self.owner.owner_type == 'group':
             return GroupOnwer.objects.get(pk = self.owner_id)

好的,在某些情况下是合适的,但在某些情况下则不合适。还有额外的查询,以避免它 我们可以分离存储级别和行为级别。

 class Owner(models.Model):
     owner_type = models.CharField(choices('user','group')) 
     user = models.ForeignKey(User,null=True,blank=True)
     group = models.ForeignKey(Group,null=True,blank=True)

     @classmethod
     def create(cls,instance):
         owner = Owner()
         owner.set_owner(instance)


     def set_owner(self,instance):
         if is_instance(instance,User):
            self.owner_type = 'user'
         elif is_instance(instance,Group):
            self.owner_type = 'group'
        self.post_init()
        self.owner_behavior.set_instance(instance) 

    def post_init(self): #factory is moved here
        self.owner_behavior = AbstarctOwnerBehavior()
        if self.owner_type == 'user':
            self.owner_behavior = UserOnwerBehaviour(self)
        elif self.owner_type == 'group':
            self.owner_behavior = GroupOnwerBehaviour(self)

     def get_owner(self):
         return self.owner_behaviour.get_owner()

    def title(self):
        self.owner_behavior.printed_title()


class AbstarctOwnerBehavior(object):

    def __init__(self,owner_instance):
        self.owner_instance = owner_instance

    def set_instance(self, instance):
        raise NotImplementedError()

    def get_instance(self):
        raise NotImplementedError()

    def printed_title(self):
        raise NotImplementedError()


class UserOnwerBehaviour(OwnerBehaviour):

    def get_instance(self):
        return self.owner_instance.user

    def set_instance(self, instance):
        self.owner_instance.user = instance

    def printed_title(self):
        return self.owner_instance.user.username # note here



class GroupOnwerBehaviour(OwnerBehaviour):

    def get_instance(self):
        return self.owner_instance.group

    def set_instance(self, instance):
        self.owner_instance.group = group

    def printed_title(self):
        return self.owner_instance.group.name # and here


 #  and finally a sinal if you are afraid of overwriting __init__
from django.db.models.signals import post_init
from models import Owner


def owner_post_init_handler(sender, instance, **kwargs):
    instance.post_init()


post_save.connect(owner_post_init_handler, sender=Owner)
哟可能会美化这一点,但我认为这段代码应该得到这个想法。此解决方案还有一个缺点,您需要绕过所有者模型的调用 行为(这可能会被缩短),你失去3nf形式的数据库结构。从应用程序的其他部分,您需要避免直接调用用户和组模型(在所有权的上下文中),并使用抽象层, 这可能有时会导致AbstarctOwnerBehavior接口增长,从设计的角度来看,我们最好保持接口小。如果没有发送post_init信号,你也会遇到问题。 但是如果你使用abstarction层 - 你会获得利润,如果删除其他情况则不必要,这会简化代码,使其更加健壮,设计良好时,这种行为很容易测试(因为他们没有额外的if-else路径) )和extendable。

所以没有一个'最佳'的解决方案适合所有情况,这取决于你的工程判断。