按 child 页面的字段过滤 Wagtail 索引页面(user-initiated 查询)/(FieldError at ... cannot resolve keyword)
Filtering a Wagtail index page by fields of the child pages (user-initiated query) / (FieldError at ... cannot resolve keyword)
我的 Wagtail 项目本质上只是一个非常传统的列表页面,用户可以在其中浏览数据库中的项目,然后单击任何感兴趣的项目进入其详细信息页面。但是我如何允许用户根据 child 页面上的字段内容过滤 and/or 对主页上的列表进行排序?这个最普通、最普通的任务让我望而却步。
假设数据库是事物的集合。假设人们认为每个 Thing 重要的是 (a) 它被发现的年份,以及 (b) 可以找到它的国家。用户可能想要浏览所有事物,但她应该能够将列表缩小到 2019 年在立陶宛发现的那些事物。而且她应该能够按年份或按国家/地区排序。只是你的 super-standard 功能,但我找不到任何指导或自己弄明白。
到目前为止,我的模型借鉴了其他人的工作示例:
class ThingsListingPage(Page):
def things(self):
''' return all child pages, to start with '''
things = self.get_children().specific()
# do I need 'specific' above?
# Is this altogether the wrong way to fetch child
# pages if I need to filter on their fields?
return things
def years(self, things): # Don't need things parameter yet
'''Return a list of years for use in queries'''
years = ['2020', '2019', '2018',]
return years
def countries(self, things):
'''Return a list of countries for use in queries.'''
countries = ['Angola', 'Brazil', 'Cameroon','Dubai', 'Estonia',]
return countries
def get_context(self, request):
context = super(ThingsListingPage, self).get_context(request)
things = self.things()
# this default sort is all you get for now
things_to_display = things.order_by('-first_published_at')
# Filters prn
has_filter = False
for filter_name in ['year', 'country',]:
filter_value = request.GET.get(filter_name)
if filter_value:
if filter_value != "all":
kwargs = {'{0}'.format(filter_name): filter_value}
things_to_display = things_to_display.filter(
**kwargs)
has_filter = True
page = request.GET.get('page') # is this for pagination?
context['some_things'] = things_to_display
context['has_filter'] = has_filter # tested on listings page to select header string
context['page_check'] = page # pagination thing, I guess
# Don't forget the data to populate filter choices on the listings page
context['years'] = self.years(things)
context['countries'] = self.countries(things)
return context
class ThingDetailPage(Page):
year = models.CharField(
max_length=4,
blank=True,
null=True,
)
country = models.CharField(
max_length=50,
blank=True,
null=True,
)
CONTNT_PANELS = Page.content_panels + [
FieldPanel('year'),
FieldPanel('country'),
]
# etc.
列表(索引)页面的模板,仅显示过滤器控件(还需要排序控件,当然还需要列表本身):
{% extends "base.html" %}
{% block content %}
<section class="filter controls">
<form method="get" accept-charset="utf-8" class="filter_form">
<ul>
<li>
<label>Years</label>
<h6 class="expanded">Sub-categories</h6>
<ul class="subfilter">
<li>
<input type="radio" name="year" value="all" id="filter_year_all"
{% if request.GET.year == "all" %}checked="checked" {% endif %} /><label
for="filter_year_all">All Years</label></input>
</li>
{% for year in years %}
<li>
<input type="radio" name="year" value="{{ year }}" id="filter_year_{{ year }}"
{% if request.GET.year == year %}checked="checked" {% endif %} /><label
for="filter_year_{{ year }}">{{ year }}</label></input>
</li>
{% endfor %}
</ul>
</li>
<li>
<label>Countries</label>
<h6 class="expanded">Sub-categories</h6>
<ul class="subfilter">
<li>
<input type="radio" name="country" value="all" id="filter_country_all"
{% if request.GET.country == "all" %}checked="checked" {% endif %} /><label
for="filter_country_all">All Countries</label></input>
</li>
{% for country in countries %}
<li>
<input type="radio" name="country" value="{{ country }}" id="filter_country_{{ country|slugify }}"
{% if request.GET.country == country %}checked="checked" {% endif %} /><label
for="filter_country_{{ country|slugify }}">{{ country }}</label></input>
</li>
{% endfor %}
</ul>
</li>
</ul>
<input type="submit" value="Apply Filters"/>
</form>
</section>
{% endblock %}
以上页面模型似乎在 Wagtail 中运行良好。我创建了一个名为“Things”的 ThingsListingPage 页面和一组 child ThingDetailPage 页面,每个页面都有 'year' 和 'country' 数据。页面显示正常:Things 列表页面上的过滤器显示(当前 hard-coded)来自 ThingsListingPage 模型的年份和国家/地区项目。列表页面还列出了 child 命令页面。服务器无投诉。
但是:在制作我的过滤器 selections 并单击提交/应用过滤器按钮后,我在地址栏 (http://localhost:8000/things/ ?year=2019&country=Lithuania) 但是这个错误:
/things/ 的 FieldError
无法将关键字 'year' 解析为字段。
(如果我没有 select 年过滤器但对国家/地区进行过滤,我会在 'country' 关键字上得到相同的错误。)
所以:
我应该如何更改 ThingsListingPage 模型,以便我可以过滤 child 页面字段(ThingDetailPage 页面的字段)?或者我应该采取完全不同的方法,一种更好的/everybody-knows-that's-how Wagtail 方法来对页面的 children 进行任意 user-initiated 过滤和排序操作字段?
请注意,在实际项目中,TinyThings、WildThings 等可能有不同的页面类型,因此我正在寻找一种解决方案,即使某些 child ren 没有在过滤器中使用的字段。
我也很感激你对如何完成排序操作的任何指导。
Page.get_children
returns 结果作为基本页面实例,其中只有核心字段(如标题)可用 - 这是因为它无法知道 [= 的预期页面类型35=] 在进行查询时(参见 What is the difference between ChildPage.objects.child_of(self) and ParentPage.get_children()?)。添加 .specific()
将 return 结果作为更具体的页面类型,但这对过滤没有帮助,因为它在主查询 [=39= 之后作为 post-processing 步骤工作](包括应用任何过滤器)。
但是,您可以通过重新组织查询表达式来指定页面类型来解决这个问题:
things = ThingDetailPage.objects.child_of(self)
在这里,Django 知道要查询哪个特定的页面模型,因此 ThingDetailPage
的所有字段都可用于过滤。
这确实将您限制为单一页面类型,并且没有完美的解决方法 - 在数据库级别,每个页面类型都由单独的 table 处理,并且不可能有效地查询数据分布在多个 table 上。 (即使 ThingDetailPage 和 TinyThingDetailPage 都定义了 year
字段,它们在数据库中是不同的实体,因此没有一个 'year' 列可以过滤。)但是,您可以使用 multi-table inheritance 重构您的模型以适应这种情况。正如 Wagtail 本身为您提供了一个包含所有页面通用字段的基本页面模型一样,您可以定义 ThingDetailPage 以包含所有事物通用的字段(例如 year
),并从中继承子类型:
class TinyThingDetailPage(ThingDetailPage):
size = models.CharField(...)
您的 TinyThingDetailPages 随后将包含在 ThingDetailPage.objects.child_of(self)
的结果中,尽管它们只会 return 作为 ThingDetailPage 的实例编辑。同样,您可以以最具体的形式将 .specific()
添加到 return,但这不适用于过滤 - 因此您将能够过滤 ThingDetailPage 共有的字段(例如 year
) 但不是 size
.
@gasman 的回答非常成功。但是,@gasman 的一篇链接帖子建议的替代解决方案同样有效。
上面的@gasman 回答要求通过自己的模型访问子页面对象(请注意,您需要附加 child_of(self)
以便查询 returns 只有那些属于当前页面的页面列表页面:
def things(self):
''' return all child pages, to start with '''
things = ThingDetailPage.objects.child_of(self)
return things
通过这种方式查询子页面模型,您就可以在没有任何前缀的情况下访问子页面的字段。因此,我的 models.py (见问题)中的 get_context
方法按原样工作。具体来说,这些行无需任何更改即可工作:
kwargs = {'{0}'.format(filter_name): filter_value}
things_to_display = things_to_display.filter(**kwargs)
但是,从列表页面开始并检索其子页面似乎同样有效,查询在我的问题中是这样写的:
def things(self):
''' return all child pages, to start with '''
things = self.get_children().specific()
return things
但要使其正常工作,您必须在过滤器前加上子页面模型的名称(必须使用两个下划线将模型名称与字段名称连接起来 ).
过滤器按预期运行,并且在我添加后立即没有错误:
kwargs = {'{0}'.format('thingdetailpage__' + filter_name): filter_value}
things_to_display = things_to_display.filter(**kwargs)
根据这些发现,我对@gasman 给出的为什么我的原始代码不起作用的解释表示怀疑:
Adding .specific() will return the results as the more specific page
types, but this won't help for filtering, since it works as a
post-processing step after the main query has run (including applying
any filters).
尽管如此,我添加这个答案只是为了记录一个替代解决方案,即一个似乎也有效的解决方案。我没有理由更喜欢我的问题中使用的方法。子页面字段上的过滤器现在起作用。
我的 Wagtail 项目本质上只是一个非常传统的列表页面,用户可以在其中浏览数据库中的项目,然后单击任何感兴趣的项目进入其详细信息页面。但是我如何允许用户根据 child 页面上的字段内容过滤 and/or 对主页上的列表进行排序?这个最普通、最普通的任务让我望而却步。
假设数据库是事物的集合。假设人们认为每个 Thing 重要的是 (a) 它被发现的年份,以及 (b) 可以找到它的国家。用户可能想要浏览所有事物,但她应该能够将列表缩小到 2019 年在立陶宛发现的那些事物。而且她应该能够按年份或按国家/地区排序。只是你的 super-standard 功能,但我找不到任何指导或自己弄明白。
到目前为止,我的模型借鉴了其他人的工作示例:
class ThingsListingPage(Page):
def things(self):
''' return all child pages, to start with '''
things = self.get_children().specific()
# do I need 'specific' above?
# Is this altogether the wrong way to fetch child
# pages if I need to filter on their fields?
return things
def years(self, things): # Don't need things parameter yet
'''Return a list of years for use in queries'''
years = ['2020', '2019', '2018',]
return years
def countries(self, things):
'''Return a list of countries for use in queries.'''
countries = ['Angola', 'Brazil', 'Cameroon','Dubai', 'Estonia',]
return countries
def get_context(self, request):
context = super(ThingsListingPage, self).get_context(request)
things = self.things()
# this default sort is all you get for now
things_to_display = things.order_by('-first_published_at')
# Filters prn
has_filter = False
for filter_name in ['year', 'country',]:
filter_value = request.GET.get(filter_name)
if filter_value:
if filter_value != "all":
kwargs = {'{0}'.format(filter_name): filter_value}
things_to_display = things_to_display.filter(
**kwargs)
has_filter = True
page = request.GET.get('page') # is this for pagination?
context['some_things'] = things_to_display
context['has_filter'] = has_filter # tested on listings page to select header string
context['page_check'] = page # pagination thing, I guess
# Don't forget the data to populate filter choices on the listings page
context['years'] = self.years(things)
context['countries'] = self.countries(things)
return context
class ThingDetailPage(Page):
year = models.CharField(
max_length=4,
blank=True,
null=True,
)
country = models.CharField(
max_length=50,
blank=True,
null=True,
)
CONTNT_PANELS = Page.content_panels + [
FieldPanel('year'),
FieldPanel('country'),
]
# etc.
列表(索引)页面的模板,仅显示过滤器控件(还需要排序控件,当然还需要列表本身):
{% extends "base.html" %}
{% block content %}
<section class="filter controls">
<form method="get" accept-charset="utf-8" class="filter_form">
<ul>
<li>
<label>Years</label>
<h6 class="expanded">Sub-categories</h6>
<ul class="subfilter">
<li>
<input type="radio" name="year" value="all" id="filter_year_all"
{% if request.GET.year == "all" %}checked="checked" {% endif %} /><label
for="filter_year_all">All Years</label></input>
</li>
{% for year in years %}
<li>
<input type="radio" name="year" value="{{ year }}" id="filter_year_{{ year }}"
{% if request.GET.year == year %}checked="checked" {% endif %} /><label
for="filter_year_{{ year }}">{{ year }}</label></input>
</li>
{% endfor %}
</ul>
</li>
<li>
<label>Countries</label>
<h6 class="expanded">Sub-categories</h6>
<ul class="subfilter">
<li>
<input type="radio" name="country" value="all" id="filter_country_all"
{% if request.GET.country == "all" %}checked="checked" {% endif %} /><label
for="filter_country_all">All Countries</label></input>
</li>
{% for country in countries %}
<li>
<input type="radio" name="country" value="{{ country }}" id="filter_country_{{ country|slugify }}"
{% if request.GET.country == country %}checked="checked" {% endif %} /><label
for="filter_country_{{ country|slugify }}">{{ country }}</label></input>
</li>
{% endfor %}
</ul>
</li>
</ul>
<input type="submit" value="Apply Filters"/>
</form>
</section>
{% endblock %}
以上页面模型似乎在 Wagtail 中运行良好。我创建了一个名为“Things”的 ThingsListingPage 页面和一组 child ThingDetailPage 页面,每个页面都有 'year' 和 'country' 数据。页面显示正常:Things 列表页面上的过滤器显示(当前 hard-coded)来自 ThingsListingPage 模型的年份和国家/地区项目。列表页面还列出了 child 命令页面。服务器无投诉。
但是:在制作我的过滤器 selections 并单击提交/应用过滤器按钮后,我在地址栏 (http://localhost:8000/things/ ?year=2019&country=Lithuania) 但是这个错误: /things/ 的 FieldError 无法将关键字 'year' 解析为字段。 (如果我没有 select 年过滤器但对国家/地区进行过滤,我会在 'country' 关键字上得到相同的错误。)
所以: 我应该如何更改 ThingsListingPage 模型,以便我可以过滤 child 页面字段(ThingDetailPage 页面的字段)?或者我应该采取完全不同的方法,一种更好的/everybody-knows-that's-how Wagtail 方法来对页面的 children 进行任意 user-initiated 过滤和排序操作字段?
请注意,在实际项目中,TinyThings、WildThings 等可能有不同的页面类型,因此我正在寻找一种解决方案,即使某些 child ren 没有在过滤器中使用的字段。
我也很感激你对如何完成排序操作的任何指导。
Page.get_children
returns 结果作为基本页面实例,其中只有核心字段(如标题)可用 - 这是因为它无法知道 [= 的预期页面类型35=] 在进行查询时(参见 What is the difference between ChildPage.objects.child_of(self) and ParentPage.get_children()?)。添加 .specific()
将 return 结果作为更具体的页面类型,但这对过滤没有帮助,因为它在主查询 [=39= 之后作为 post-processing 步骤工作](包括应用任何过滤器)。
但是,您可以通过重新组织查询表达式来指定页面类型来解决这个问题:
things = ThingDetailPage.objects.child_of(self)
在这里,Django 知道要查询哪个特定的页面模型,因此 ThingDetailPage
的所有字段都可用于过滤。
这确实将您限制为单一页面类型,并且没有完美的解决方法 - 在数据库级别,每个页面类型都由单独的 table 处理,并且不可能有效地查询数据分布在多个 table 上。 (即使 ThingDetailPage 和 TinyThingDetailPage 都定义了 year
字段,它们在数据库中是不同的实体,因此没有一个 'year' 列可以过滤。)但是,您可以使用 multi-table inheritance 重构您的模型以适应这种情况。正如 Wagtail 本身为您提供了一个包含所有页面通用字段的基本页面模型一样,您可以定义 ThingDetailPage 以包含所有事物通用的字段(例如 year
),并从中继承子类型:
class TinyThingDetailPage(ThingDetailPage):
size = models.CharField(...)
您的 TinyThingDetailPages 随后将包含在 ThingDetailPage.objects.child_of(self)
的结果中,尽管它们只会 return 作为 ThingDetailPage 的实例编辑。同样,您可以以最具体的形式将 .specific()
添加到 return,但这不适用于过滤 - 因此您将能够过滤 ThingDetailPage 共有的字段(例如 year
) 但不是 size
.
@gasman 的回答非常成功。但是,@gasman 的一篇链接帖子建议的替代解决方案同样有效。
上面的@gasman 回答要求通过自己的模型访问子页面对象(请注意,您需要附加 child_of(self)
以便查询 returns 只有那些属于当前页面的页面列表页面:
def things(self):
''' return all child pages, to start with '''
things = ThingDetailPage.objects.child_of(self)
return things
通过这种方式查询子页面模型,您就可以在没有任何前缀的情况下访问子页面的字段。因此,我的 models.py (见问题)中的 get_context
方法按原样工作。具体来说,这些行无需任何更改即可工作:
kwargs = {'{0}'.format(filter_name): filter_value}
things_to_display = things_to_display.filter(**kwargs)
但是,从列表页面开始并检索其子页面似乎同样有效,查询在我的问题中是这样写的:
def things(self):
''' return all child pages, to start with '''
things = self.get_children().specific()
return things
但要使其正常工作,您必须在过滤器前加上子页面模型的名称(必须使用两个下划线将模型名称与字段名称连接起来 ).
过滤器按预期运行,并且在我添加后立即没有错误:
kwargs = {'{0}'.format('thingdetailpage__' + filter_name): filter_value}
things_to_display = things_to_display.filter(**kwargs)
根据这些发现,我对@gasman 给出的为什么我的原始代码不起作用的解释表示怀疑:
Adding .specific() will return the results as the more specific page types, but this won't help for filtering, since it works as a post-processing step after the main query has run (including applying any filters).
尽管如此,我添加这个答案只是为了记录一个替代解决方案,即一个似乎也有效的解决方案。我没有理由更喜欢我的问题中使用的方法。子页面字段上的过滤器现在起作用。