1

我正在考虑与 django 的内置UserGroup.

我所说的“或”是指模型实例必须由Usera 或 a独占拥有Group

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

这是我目前的实现。我一直在研究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)

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

  • 创建实例时如何强制它设置User或?Group覆盖可能__init__是要走的路吗?
  • 是否有更好/更清洁/更高效的实施?

谢谢!

4

2 回答 2

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 用户/组。

于 2013-04-20T20:32:10.877 回答
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,并且您将遇到列表问题。为了解决一些问题,您需要向所有者提供一些关于他的孩子的额外知识(以第三范式和封装原则为代价)。

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)

哟可能会美化一点,但我认为这段代码足以理解这个想法。这个解决方案也有它的缺点,你需要绕过从 Owner 模型到行为的调用(这可能是快捷方式)并且你丢失了 3nf 形式的数据库结构。从应用程序的其他部分,您需要避免直接调用 User 和 Group 模型(在所有权上下文中),并使用抽象层,这有时可能会导致 AbstarctOwnerBehavior 接口增长,从设计的角度来看,我们最好保持接口小. 如果未发送 post_init 信号,您也会遇到问题。但是如果你使用抽象层——你会获得利润,不需要的 if-else 案例被删除,这会简化代码,使其更健壮,如果设计得当,这种行为很容易测试(因为它们没有额外的 if-else 路径) 和可扩展的。

因此,没有适合所有情况的“最佳”解决方案,这取决于您的工程判断。

于 2013-04-21T00:38:14.770 回答