2013-04-20 64 views
1

我正在考虑与django的内置UserGroup建立关系的最佳方法。django用户XOR组模型关系

By'or'我的意思是模型实例必须由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

它看起来像你在设计中缺少所有者模型。 – singer 2013-04-20 18:53:12

+0

@singer谢谢您的评论。我已经将'_owner ...'字段和属性移动到抽象模型中,现在我的'RemoteAccess'模型已经扩展了这个模型。它现在看起来好多了,但仍然没有解决我需要2个领域和一个丑陋的二传手:/ – 2013-04-20 19:09:32

+0

我不知道为什么你认为这很丑陋;过去几年我已经看到了几十个“可以由用户或群组拥有”的实现,这甚至不接近我见过的最丑陋的实现。 – 2013-04-22 12:39:54

回答

1

我会重写保存()方法,并定义自定义异常

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) 

重写初始化可能导致在某些情况下,你实际上并没有所有者,直到保存的问题(例如如在表格等)

我不认为有一个更干净的方式来做到这一点,并坚持与Django的用户/组。

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) 

好的,在某些情况下适用,但在某些情况下不适用。仍然额外的查询,以避免它 我们可以septarate存储级别和行为水平。

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路径)和extedndable。

因此,没有一种适合每种情况的“最佳”解决方案,并且取决于您的工程判断。