代码之家  ›  专栏  ›  技术社区  ›  shacker kravietz

Django:来自非嵌套模型的嵌套查询

  •  2
  • shacker kravietz  · 技术社区  · 16 年前

    class Beverage(models.Model):
       country = models.ForeignKey(Country,null=True,blank=True)    
       region = models.ForeignKey(Region,null=True,blank=True)
       subregion = models.ForeignKey(SubRegion,null=True,blank=True) 
       in_stock = models.BooleanField()
       ...
    

    生成的菜单类似于:

    France
        Region 1
            Subregion 1
            Subregion 2
        Region 2
            Subregion 3
            Subregion 4
    Spain
        ....
    

    如果菜单中没有库存的饮料,则菜单中不应出现任何国家、地区或次区域。因为一个次区域总是属于一个区域,而一个区域总是属于一个国家,所以我最初的方法是将模型本身嵌套起来,只将次区域放在饮料上。然后,饮料分区域将始终知道区域和国家。不幸的是,现实世界中有太多的例外情况使这一点变得可行——葡萄酒有一个区域,但没有一个分区,等等。所以我将布局展平,如上所述。

    现在的问题是如何从该模型布局导出菜单。它看起来像是一个深度嵌套的查询集列表,但这似乎在计算上很昂贵,而且代码复杂。有没有更干净的方法?

    3 回复  |  直到 16 年前
        1
  •  1
  •   Adam    16 年前

    我过去解决类似问题时使用的一个过程是,通过一个查询选择所有项目,然后根据国家、地区、次区域进行排序。然后循环查询结果,并维护指向您看到的国家和地区的最后一个id的变量。如果饮料上的下一个国家/地区id与上一个id不匹配,则保存旧列表并开始新列表。下面是一些非常粗糙/混乱的pythoncode来解释这个想法:

    beverages = Beverage.objects.order_by('country', 'region', 'subregion')
    last_country = -1
    menu = []
    country_obj = None
    for beverage in beverages:
        if beverage.country_id != last_country:
            if country_obj is not None:
                if region_obj is not None:
                    if subregion_obj is not None:
                        region_obj['children'].append(subregion_obj)
                    country_obj['children'].append(region_obj)
                menu.append(country_obj)
            country_obj = {'name': beverage.country.name, 'children': []}
            last_country = beverage.country_id
            last_region = -1
            region_obj = None
            last_subregion = -1
            subregion_obj = None
        if beverage.region is None:
            country_obj['children'].append(beverage)    
        else:
            if beverage.region_id != last_region:
                if region_obj is not None:
                    if subregion_obj is not None:
                        region_obj['children'].append(subregion_obj)
                    country_obj['children'].append(region_obj)
                region_obj = {'name': beverage.region.name, 'children': []}
                last_region = beverage.region_id
                last_subregion = -1
                subregion_obj = None
            if beverage.subregion is None:
                region_obj['children'].append(beverage)
            else:
                if beverage.subregion_id != last_subregion:
                    if subregion_obj is not None:
                        region_obj['children'].append(subregion_obj)
                    subregion_obj = {'name': beverage.subregion.name, 'children': []}
                    last_subregion = beverage.subregion_id
                subregion_obj['children'].append(beverage)
    if beverage.subregion is not None:
        region_obj['children'].append(subregion_obj)
    if beverage.region is not None:
        country_obj['children'].append(region_obj)
    menu.append(country_obj)
    

    正如您可能知道的,每个级别都有相同的逻辑:检查id是否已更改,是否已附加旧的x_obj并启动一个新的。最后五行用于处理最后一种饮料,因为在当前迭代过程中,您总是保存前一项(并且最后一项没有下一次迭代)。这是一个非常粗糙的过程,但我一直使用这个过程,只需要一个查询。

    当我最终开始运行它时,我编辑了一些程序来修复我发现的错误。它似乎适用于我的简单测试用例。

        2
  •  1
  •   shacker kravietz    16 年前

    {
        Country1:{
            region1:[subregion1,subregion2],
            region2:[subregion3,subregion4]
            },
        Country2: {
            region3:[subregion5,subregion6],
            region4:[subregion7,subregion8]    
        },
    }
    

    每个国家、地区和次区域都是真实的对象,而不是字符串。这是业务端(位于templatetag中)。注意,我们在每次迭代中检查可用库存,只有在有库存的情况下才设置字典或列表项。

    regionmenu = {}
    for c in Country.objects.all() :
        if Wine.objects.filter(country=c,inventory__gt=0).count() > 0 :
            regionmenu[c] = {}
    
        for r in c.region_set.all(): 
            if Wine.objects.filter(country=c,region=r,inventory__gt=0).count() > 0 :
                regionmenu[c][r] = []           
    
            for s in r.subregion_set.all():
                if Wine.objects.filter(country=c,region=r,subregion=s,inventory__gt=0).count() > 0 :
                    regionmenu[c][r].append(s)
    

    字典非常适合你的需要,只是你失去了排序的能力,所以我以后得想办法按字母顺序排序。

    要遍历模板中的dict,请执行以下操作:

    <ul>
    {% for country, regions in regionmenu.items  %}
        <li>{{ country }} 
            <ul>
            {% for region, subregions in regions.items %}
            <li>{{ region }}
                <ul>
                {% for subregion in subregions %}
                    <li>{{ subregion }}</li>
                {% endfor %}
                </ul>
            </li>
            {% endfor %}
            </ul>
        </li>
    {% endfor %}
    </ul>   
    

        3
  •  1
  •   Zoe - Save the data dump 张群峰    7 年前

    两个想法

    • limit_choices_to 具有 Q objects 要控制,可以添加哪些类型。

    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    origin = generic.GenericForeignKey('content_type', 'object_id', 
                                       limit_choices_to = \
                                  Q(name='contry', app_label='what ever is the name of the app')| \
                                  Q(name='region', app_label='what ever is the name of the app')| \
                                  Q(name='subregion', app_label='what ever is the name of the app')')))
    
    • 或者我的第二个想法:首先不要优化db查询,使用一些缓存。

      signaling

      但要注意:信号和全局变量只在过程范围内起作用。但Web服务器可能跨越多个进程。在这里,您可以将菜单写入数据库或文件,并保留时间戳以进行检查(如果需要重新加载)。

    当然,这些想法可以结合起来

    推荐文章