Django外键查询的最佳实践

4

models.py

class Category(models.Model):
    name = models.CharField(max_length=50)

class SubCatergory(models.Model):
    parent_category = models.ForeignKey(Category)
    name = models.CharField(max_length=100)

views.py

def all_products(request):
c = Category.objects.all()
s = SubCatergory.objects.all()

return render_to_response('all_products.html',
                          {'c':c, 's':s})

all_products.html

{% for category in c %}
    <h1>{{ category.name }}</h1>
    <ul>
        {% for sub in s  %}
        {% if category.id == sub.parent_category.id %}
            <li>{{ sub.name }}</li>
        {% endif %}
        {% endfor %}
    </ul>
{% endfor %}

想知道上述查询外键的最佳实践是否正确。我是在模板级别上过滤的(如果category.id == sub...),我应该将其移至模型或视图级别吗?

3个回答

5
如果只有一层子类别,以下代码不应该有问题:
{% for category in c %}
    <h1>{{ category.name }}</h1>
    <ul>
        {% for sub in category.subcatergory_set.all %}
            <li>{{ sub.name }}</li>
        {% endfor %}
    </ul>
{% endfor %}

但有一些优化技巧可以减少查询次数,因为您将在每个循环中执行一次查询。我正在尝试想出一个。

实际上,我开始觉得这是一个有趣的问题:最佳实践?

您的方法使用了2个查询。我的方法使用了django实践,但会执行多个查询。

为了防止多个查询,您需要在视图中执行与模板中相同的操作,即遍历SubCatergory,在python中提取其ID,并将每个ID集合分组到Category的属性上。

我不知道这个问题的答案。


谢谢,只有一个子类别的深度。 - Rob B

2
我认为在这里一个好的做法是创建一个模板标签来完成这个工作。这样你就可以缓存渲染后的模板,并且只在第一次渲染时访问数据库。
首先,在你的应用程序中创建模板标签。

templatetags/show_categories_list.py

from django.core.cache import cache

@register.simple_tag
def show_categories_list():
    cached = cache.get('CATEGORIES_LIST_CACHE_KEY', None)
    if cached is None:
        categories = Category.objects.all()
        rendered = render_to_string('all_categories.html', {'categories': categories})
        cache.set('CATEGORIES_LIST_CACHE_KEY', rendered)
        return rendered
    return cached

然后,创建要使用的模板。

all_categories.html

{% for category in categories %}
    <h1>{{ category.name }}</h1>
    <ul>
        {% for sub in category.subcategory_set.all %}
            <li>{{ sub.name }}</li>
        {% endfor %}
    </ul>
{% endfor %}

在您的模型中覆盖保存方法,以便删除类别列表缓存条目(强制在下一次请求时呈现,这也可以放置在(pre|post)_save信号处):

models.py

class Category(models.Model):
    name = models.CharField(max_length=50)

    def save(self, *args, **kwargs):
        cache.delete('CATEGORIES_LIST_CACHE_KEY')
        return super(Category, self).save(*args, **kwargs)

class SubCatergory(models.Model):
    parent_category = models.ForeignKey(Category)
    name = models.CharField(max_length=100)

    def save(self, *args, **kwargs):
        cache.delete('CATEGORIES_LIST_CACHE_KEY')
        return super(Category, self).save(*args, **kwargs)

最后,像这样使用它:

base.html

{% load show_categories_list %}
{% show_categories_list %}

您还可以为缓存条目添加超时时间,这样就不必在模型中覆盖保存方法,但是您需要等待超时时间才能再次渲染它。

一些有用的参考:

http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/#shortcut-for-simple-tags http://docs.djangoproject.com/en/1.2/topics/cache/#the-low-level-cache-api http://docs.djangoproject.com/en/1.2/topics/signals/


0
为什么不同时添加反向引用,使得每个类别都可以引用其子类别列表呢?这样你就可以编写两个嵌套循环:一个外部循环用于类别,一个内部循环用于迭代每个类别中的子类别。

2
这是正确的想法,但请注意Django会自动为您执行此操作。它会自动为指向模型的每个外键创建一个“反向关系”,这是(如果您没有明确更改)相关模型的名称加上“_set”。 有关更多信息,请参见以下链接: http://docs.djangoproject.com/en/dev/topics/db/queries/#backwards-related-objects - vitorbal
Django确实提供了一个API来查询反向关系。然而,使用反向关系来检索相关模型仍会导致额外的SQL查询,如果多次执行(例如在循环中),这将对性能造成巨大影响。为了避免这种情况,可以考虑对关系进行去规范化处理,以便在上述情况下,在每个类别上保存对子类别的引用。 - heyman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接