Django过滤器遍历多个外键和通用外键

Posted

技术标签:

【中文标题】Django过滤器遍历多个外键和通用外键【英文标题】:Django filter traversing multiple foreign keys and generic foreign keys 【发布时间】:2021-07-27 03:13:21 【问题描述】:

我尝试了很多不同的方法,但我在逻辑上碰壁了,希望这里的人能够轻松提出。

我有几个不同的连接相关模型(为了便于查看,我已尝试去除所有不相关的字段和方法):

class InventoryAddress:
    def __init__(self, warehouse, location, sublocation):
        self.warehouse = warehouse
        self.location = location
        self.sublocation = sublocation


# Product Models
class ProductMaster(models.Model):
    internal_id = models.CharField(max_length=20, unique=True, primary_key=True, null=False, blank=False,
                                   verbose_name="Internal ID")
    
class ShoeAttributes(models.Model):
    style_id = models.CharField(max_length=20, unique=True, primary_key=True, null=False, blank=False,
                                verbose_name="Style ID")
    product_master = models.OneToOneField(ProductMaster, related_name='ShoeAttributes', on_delete=models.CASCADE)
    brand = models.CharField(max_length=30, choices=shoe_brand_choices, default=None, blank=True, null=True,
                             verbose_name="Brand")


class Shoe(models.Model):
    sku = models.CharField(max_length=20, unique=True, primary_key=True, null=False, blank=False,
                           verbose_name="SKU")
    product_master = models.ForeignKey(ProductMaster, blank=False, null=False, on_delete=models.CASCADE, verbose_name="Brands")
    size = models.CharField(max_length=20, null=False, blank=False, verbose_name="Size")
    condition = models.CharField(max_length=25, choices=shoe_condition_choices, blank=False, null=False,
                                 verbose_name='Condition')


class InventoryItem(models.Model):
    inventory_sku = models.CharField(max_length=20, unique=True, primary_key=True, null=False, blank=False,
                                     verbose_name="Inventory SKU")
    authenticated = models.CharField(max_length=2, choices=yes_no_choices, verbose_name='Authentication Status')

    # GenericForeignKey for Specific Product
    content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT, null=True)
    object_id = models.CharField(max_length=50)
    content_object = GenericForeignKey('content_type', 'object_id')

    def movements(self):
        movements = InventoryMovement.objects.filter(inventory_item=self)
        return sorted(movements, key=lambda x: x.datetime_entered, reverse=True)

    def completed_movements(self):
        movements = InventoryMovement.objects.filter(inventory_item=self, datetime_completed__isnull=False)
        return sorted(movements, key=lambda x: x.datetime_completed, reverse=True)

    def current_location(self):
        movements = self.completed_movements()
        if movements:
            last_movement = movements[0]
            loc = InventoryAddress(
                warehouse=last_movement.warehouse,
                location=last_movement.location,
                sublocation=last_movement.sublocation
            )
        else:
            loc = InventoryAddress(
                warehouse='EXT',
                location=None,
                sublocation=None,
            )
        return loc

    def current_location_display(self):
        loc = self.current_location()
        setattr(loc, 'warehouse', reverse_warehouse_location_vars[self.current_location().warehouse].title())
        return loc


class InventoryMovement(models.Model):
    movement_id = models.CharField(max_length=20, unique=True, blank=False, null=False, verbose_name='Movement ID')
    warehouse = models.CharField(max_length=40, null=False, blank=False, choices=warehouse_location_choices,
                                 verbose_name='Warehouse')
    location = models.CharField(max_length=40, null=False, blank=False, verbose_name='Target Bin')
    sublocation = models.CharField(max_length=40, null=False, blank=False, verbose_name='Target Position')
    inventory_item = models.ForeignKey(InventoryItem, null=True, on_delete=models.PROTECT,
                                       verbose_name="Inventory Item")

我正在尝试根据来自 html 表单的用户输入过滤 InventoryItem(示例如下)

post_dict = 'style_id': 'CQ4227-030', 'size': '8', 'condition': 'NWB', 'warehouse': 'A',
             'location': 'G', 'sublocation': '2'

似乎有两组问题。第一个是如何反转其属性的通用关系和外键(在查找 style_id、大小、条件时),第二个是如何根据方法结果进行过滤(针对仓库、位置、子位置的 InventoryItem.current_location() 方法)

我尝试了很多不同的方法(查询集、管理器、列表理解),但就是无法找到一种有效的工作方法。如果有人能想出一种方法来轻松过滤上述所有内容,将不胜感激(过滤器将是 AND,而不是 OR)

【问题讨论】:

【参考方案1】:

您的关系有点难以理解,但下面是一个简单的示例,说明如何将查询集与GenericForeignKeysGenericRelations 一起使用:

models.py

from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType


class Order(models.Model):
    
    ''' generic order class '''

    # fields:
    price = models.DecimcalField(...)

    # generic foreign key to a product:
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True)
    object_id = models.PositiveIntegerField(null=True)
    product = GenericForeignKey('content_type', 'object_id')


class Shirt(models.Model):

    ''' shirt class representing shirts available for sale '''

    # fields:
    color = models.CharField(...)
    
    # generic relation:
    order = GenericRelation('Order', related_query_name='shirt')


class Shoe(models.Model):

    ''' shoe class representing shoes available for sale '''

    # fields:
    size = models.CharField(...)
    
    # generic relation:
    order = GenericRelation('Order', related_query_name='shoe')

那么我们可以这样做:

views.py

# create a new order with a shirt:
blue_shirt = Shirt.objects.get(color='blue')
order = Order.objects.create(price=10.00, product=blue_shirt)

# lookup orders with a given shirt:
orders_with_blue_shirt = Order.objects.filter(shirt=blue_shirt)

# lookup orders with a given shoe:
orders_with_large_shoe = Order.objects.filter(shoe__size='large')

# find all shirts that were on orders with a price larger than 10.00:
shirts_over_10 = Shirt.objects.filter(order__price__gt = 10.00)

【讨论】:

以上是关于Django过滤器遍历多个外键和通用外键的主要内容,如果未能解决你的问题,请参考以下文章

Django REST框架外键和过滤

Django 通用外键和 select_related

Django过滤多个外键关系

Django:遍历模板中的过滤列表

如何通过Django中的prefetch_related过滤具有多个条件的反向外键

Django,从反向外键查询添加数据(外键加入过滤器)