django复制了来自访问ForeignKey关系的模型方法get_absolute_url的SQL查询

roqulrg3  于 2023-06-07  发布在  Go
关注(0)|答案(1)|浏览(150)

问题汇总

我有一个基于类的列表视图,它只显示每个对象的链接沿着来自一些对象字段的一些数据。链接的href是使用我在模型本身中编写的get_absolute_url方法生成的。问题是每次运行get_absolute_url时都会查询数据库。这会导致许多重复查询(将来,如果有更多的对象,这将是一个问题)。

尝试解决方案

我的get_absolute_url方法从我的模型中访问一些ForeignKey字段,所以我尝试在视图中为我的查询集使用.select_related()。但这并没有改变什么。

提问

如何从运行get_absolute_url中消除重复查询?

代码

models.py

class LanguageLocale(models.Model):
    """model for representing language locale combinations"""

    class LANG_CODES(models.TextChoices):
        EN = 'en', _('English')
        ES = 'es', _('español')
        QC = 'qc', _("K'iche'")

    lang = models.CharField(max_length=2, choices=LANG_CODES.choices, blank=False)

class Scenario(models.Model):
    """model for representing interpreting practice scenarios"""

    scenario_id = models.CharField(max_length=20, primary_key=True)

    lang_power = models.ForeignKey(LanguageLocale)
    lang_primary_non_power = models.ForeignKey(LanguageLocale)

    class SCENARIO_STATUSES(models.TextChoices):
        PROD = 'PROD', _('Production')
        STGE = 'STGE', _('Staged')
        EXPR = 'EXPR', _('Experimental')

    status = models.CharField(
        max_length=4, choices=SCENARIO_STATUSES.choices, default='EXPR')

    def get_absolute_url(self):
        """Returns the URL to access a detail record for this scenario."""
        return reverse('dialogue-detail', kwargs={
            'lang_power': self.lang_power.lang,
            'lang_primary_non_power': self.lang_primary_non_power.lang,
            'pk': self.scenario_id
            }
        )

views.py

class ScenarioListView(generic.ListView):
    """View class for list of Scenarios"""

    queryset = Scenario.objects.select_related(
        'domain_subdomain', 'lang_power', 'lang_primary_non_power'
    )
    
    demo = Scenario.objects.get(scenario_id='demo')
    prod = Scenario.objects.filter(status='PROD')
    staged = Scenario.objects.filter(status='STGE')
    experimental = Scenario.objects.filter(status='EXPR')
    
    extra_context = {
        'demo': demo,
        'prod': prod,
        'staged': staged,
        'experimental': experimental,
    }

scenario_list.html

{% extends "home.html" %}

{% block content %}
  <h1>Practice Dialogues</h1>
  <section>
    {% if prod %}
      <ul>
        {% for scenario in prod %}
          <li>
            <a href="{{ scenario.get_absolute_url }}">{{  scenario.title }}</a> ({{ 
            scenario.domain_subdomain.domain }})
          </li>
        {% endfor %}
      </ul>
    {% else %}
      <p>There are no practice scenarios. Something went wrong.</p>
    {% endif %}
  </section>
  {% if user.is_staff %}
    <section>
      {% if staged %}
        <article>
          <h2>Staged Dialogues</h2>
          <ul>
            {% for scenario in staged %}
              <li>
                <a href="{{ scenario.get_absolute_url }}">{{  scenario.title }}</a> ({{ 
                scenario.domain_subdomain.domain }})
              </li>
            {% endfor %}
          </ul>
        </article>
      {% endif %}
      {% if experimental %}
        <article>
          <h2>Experimental Dialogues</h2>
          <ul>
            {% for scenario in experimental %}
              <li>
                <a href="{{ scenario.get_absolute_url }}">{{  scenario.title }}</a> ({{ 
                scenario.domain_subdomain.domain }})
              </li>
            {% endfor %}
          </ul>
        </article>
      {% endif %}
    </section>
  {% endif %}
{% endblock %}

重复查询读出(从django调试工具栏)

重复查询3次:

SELECT "scenario_languagelocale"."id",
       "scenario_languagelocale"."lang_locale",
       "scenario_languagelocale"."lang"
  FROM "scenario_languagelocale"
 WHERE "scenario_languagelocale"."id" = 1
 LIMIT 21 6 similar queries.  Duplicated 3 times.

重复查询2次:

SELECT "scenario_languagelocale"."id",
       "scenario_languagelocale"."lang_locale",
       "scenario_languagelocale"."lang"
  FROM "scenario_languagelocale"
 WHERE "scenario_languagelocale"."id" = 3
 LIMIT 21 6 similar queries.  Duplicated 2 times.
46scxncf

46scxncf1#

我建议 * 不要 * 使用extra_context。这将防止在两个请求之间重新评估查询,这意味着如果您因此添加了额外的Scenario,并且您没有重新启动服务器,那么当您再次请求场景时,它将不会“更新”列表。demo查询的问题更大,因为它会在您启动服务器时立即运行,因此如果您在带有数据库的服务器上运行它,而没有这样的demo Scenario,可能会引发错误。
但是,您可以使用queryset,它已经在必要的关系上执行了.select_related(…),因此:

from django.shortcuts import get_object_or_404

class ScenarioListView(generic.ListView):
    """View class for list of Scenarios"""
    queryset = Scenario.objects.select_related(
        'domain_subdomain', 'lang_power', 'lang_primary_non_power'
    )

    def get_context_data(self, *args, **kwargs):
        return super().get_context_data(
            *args,
            **kwargs,
            demo=get_object_or_404(self.queryset, scenario_id='demo'),
            prod=self.queryset.filter(status='PROD'),
            staged=self.queryset.filter(status='STGE'),
            experimental=self.queryset.filter(status='EXPR'),
        )

相关问题