如何创建作为多个查询联合的 Django 模型字段,以实现覆盖字段?

Posted

技术标签:

【中文标题】如何创建作为多个查询联合的 Django 模型字段,以实现覆盖字段?【英文标题】:How do I create a Django model field that is the union of several queries, in order to implement overriding fields? 【发布时间】:2017-04-12 19:17:53 【问题描述】:

我正在开发一个用户/组 API(对大多数事情使用 django-rest-framework),并且正忙于作为每个对象一部分的设置字典。

我的基本模型如下所示:

class Group(models.Model):
    name = models.CharField(null=False, unique=True)
    is_active = models.BooleanField(null=False, default=True)
    description = models.CharField(max_length=512)

class User(models.Model):
    name = models.CharField(null=False, unique=True, db_index=True)
    group = models.ForeignKey('Group', related_name='users')
    is_active = models.BooleanField(null=False, default=True)

返回的 JSON 很简单(省略了视图,唯一值得注意的是我没有为 users 字段使用 PrimaryKeyRelatedField)。

组:


   "id": 1,
   "name": "Basic Group",
   "is_active": true,
   "description": "Just a simple group",
   "users": [
      "Test User 1",
      "Test User 2"
   ]

用户:


   "id": 1,
   "name": "Test User 1",
   "group": 1,
   "is_active": true
, 
   "id": 2,
   "name": "Test User 2",
   "group": 1,
   "is_active": true

我想通过为 Group 和 User 对象类型设置一个“设置”字典来增强这一点,它们是覆盖的。也就是说,组的设置字典将包括所有全局设置,除非在组上设置了具有相同名称的设置。同样,用户的设置字典将具有所有全局设置,被组设置覆盖,除非已为用户设置了同名的设置。

想象一个大致如下的结构:

name      | value     | group | user
---       | ---       | ---   | ---
setting 1 | value 1   |       | 
setting 1 | value 2   | 1     | 
setting 2 | SKFJDL    | 1     | 
setting 2 | ABCD      | 1     | 2

这将导致为第 1 组返回以下 JSON:


    "id": 1,
    "name": "Basic Group",
    "is_active": true,
    "description": "Just a simple group",
    "users": [
        "Test User 1",
        "Test User 2"
    ],
    "settings": 
        "setting 1": "value 2",
        "setting 2": "SKFJDL"
    

对于用户 1,设置字典如下所示:

"settings": 
    "setting 1": "value 2",
    "setting 2": "SKFJDL"

对于用户 2,它看起来像:

"settings": 
    "setting 1": "value 2",
    "setting 2": "ABCD"

这给我留下了两个问题。

问题 1:让我获得所需输出的 ​​SQL 相对复杂。基本上:

SELECT DISTINCT(name) * 
FROM (
    (SELECT * FROM setting WHERE group IS NULL AND user IS NULL) 
  UNION ALL 
    (SELECT * FROM setting WHERE group = 1 AND user IS NULL) 
  UNION ALL 
    (SELECT * FROM setting WHERE group = 1 AND user = 2)) 
  AS temp

我想我可能不得不接受它并编写一些自定义查询,而不是依赖 Django ORM 开箱即用提供给我的任何东西......但我很高兴能成为错了。

问题 2(更大的问题):我到底是怎么这个的? ForeignKey 关系似乎没有给我一种方法来自定义在进行反向查找时拉入的字段,至少不是我在一天左右的跌跌撞撞中能够找到的任何方式。我有一种感觉,custom reverse manager 几乎肯定是我正在寻找的东西,但我实际上还没有弄清楚如何在模型的上下文中使用它,特别是一个模型,它的下游视图由自动生成DRF。

任何帮助表示赞赏!

【问题讨论】:

【参考方案1】:

我对此进行了快速尝试。在 github 上抛出一个非常基本的 Django 项目(使用 testuser|testpass 登录管理员): https://github.com/junctionapps/so40854954

基本上,三个过滤器使用的 sql 几乎完全一样。然后用数据填充字典(或其他),根据优先级进行替换。所以输入默认值(null/null),然后是组,然后是用户。我重命名了模型 CustomUser、CustomGroup 和 CustomSettings,以免碰到保留的 Django 对象。

存储在模型中的设置(我想这就是你的意思,我可能误解了这一点)。

class CustomSettings(models.Model):
    name = models.CharField(max_length=64)
    value = models.CharField(max_length=64)
    group = models.ForeignKey(CustomGroup, null=True, blank=True)
    user = models.ForeignKey(CustomUser, null=True, blank=True)

并且在类似以下的视图中(对具有三个循环的部分并不完全满意;可能有一种更巧妙的方式)。

if user_id:
    # a place to store the end values
    custom_settings = 

    # get the user's group
    custom_user = get_object_or_404(CustomUser, id=user_id)

    # get the default values
    # see some tips at http://***.com/a/844572/4872140
    default_settings = CustomSettings.objects.filter(group__isnull=True,
                                                     user__isnull=True)
    # get the group values
    group_settings = CustomSettings.objects.filter(group=custom_user.group,
                                                   user__isnull=True)
    # get the user values
    user_settings = CustomSettings.objects.filter(group=custom_user.group,
                                                  user=custom_user)
    # probably a more 'pythonic' way to conflate, however:
    # set the values:
    for s in default_settings:
        custom_settings[s.name] = s.value
    for s in group_settings:
        custom_settings[s.name] = s.value
    for s in user_settings:
        custom_settings[s.name] = s.value

那么 custom_settings 应该有你想在某些序列化程序中在 DRF 中使用的值。

【讨论】:

【参考方案2】:
class Hop(models.Model):
    migration = models.ForeignKey('Migration')
    host = models.ForeignKey(User, related_name='host_set')

    class Meta:
        unique_together = (("migration", "host"),)

https://docs.djangoproject.com/en/2.1/ref/models/options/#unique-together

【讨论】:

以上是关于如何创建作为多个查询联合的 Django 模型字段,以实现覆盖字段?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 django 模型中从一个字段添加多个输入

如何给mysql表建立联合索引

Django怎么多表联合查询

两个相关模型的 Django 查询

Django 测试开发3

在 Graphene Django 中查询多个模型