如何将 Django 模板转换为使用 bootstrap-table 服务器端分页
How to convert a django template to use bootstrap-table server-side pagination
我有许多由 django 模板呈现的页面,我已应用 bootstrap-table 来实现列切换、客户端分页和多列排序。这是在创建了一个功能齐全的 django 模板之后。
我的table非常大,每列都有多次操作,比如:
- 指向网站上其他页面的链接
- 数字格式
- 水平对齐(例如右对齐数字)
- 连接来自相关 table 的值,由各种字符串分隔(例如,逗号分隔)
- 工具提示
- 用“None”填充空值
- 将时间增量转换为天或周
...
许多操作使用 simple_tags 和 python 编写的过滤器。甚至有一个模板使用 javascript 使用 bootstrap table 事件(例如 $("#advsrchres").bootstrapTable({onAll: ...
)对某些 colspans 进行一些自定义操作(例如 $("#advsrchres").bootstrapTable({onAll: ...
)。
我看到的每个例子都使用了bootstrap-table的服务器端分页,没有模板,所有数据都是使用“data-url
”获取的returns JSON.
我希望我在这方面是错的,但我的评估是我将不得不重写 javascript 或其他模板中的所有那些单元格装饰。我还没有开始研究如何去做,所以在谷歌搜索了很多无果之后,我来这里是想看看是否有人知道一种不必完全重写那些巨大的 django 模板来实现服务器端分页的方法?有没有办法告诉 bootstrap-table 将 JSON 中的数据插入到 django 模板中?
这是模板之一的示例...
<table class="table table-hover table-striped table-bordered"
id="advsrchres"
data-toggle="table"
data-buttons-toolbar=".buttons-toolbar"
data-buttons-class="primary"
data-buttons-align="right"
data-filter-control="false"
data-search="false"
data-show-search-clear-button="false"
data-show-multi-sort="true"
data-show-columns="true"
data-show-columns-toggle-all="true"
data-show-fullscreen="false"
data-show-export="false"
data-pagination="true">
<colgroup span="8" class="identdata"></colgroup>
<colgroup span="4" class="datadata"></colgroup>
<colgroup span="12" class="metadata"></colgroup>
<thead>
<tr>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Animal" class="idgrp">Animal</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Sample" class="idgrp" data-switchable="false">Sample</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Tissue" class="idgrp">Tissue</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Peak_Group" class="idgrp">Peak Group</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Compound_Name" class="idgrp">Measured<br>Compound</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Compound_Synonym" class="idgrp">Measured<br>Compound<br>Synonym(s)</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Labeled_Element" class="idgrp">Labeled<br>Element</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Peak_Group_Set_Filename" class="idgrp">Peak Group Set Filename</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="numericOnly" data-field="Total_Abundance" class="datagrp" data-switchable="false">Total<br>Abundance</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="numericOnly" data-field="Enrichment_Fraction" class="datagrp">Enrichment<br>Fraction</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="numericOnly" data-field="Enrichment_Abundance" class="datagrp">Enrichment<br>Abundance</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="numericOnly" data-field="Normalized_Labeling" class="datagrp">Normalized<br>Labeling</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Formula" class="metagrp">Formula</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Genotype" class="metagrp">Genotype</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Sex" class="metagrp">Sex</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Feeding_Status" class="metagrp">Feeding<br>Status</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Diet" class="metagrp">Diet</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Treatment" class="metagrp">Treatment</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="numericOnly" data-field="Body_Weight" class="metagrp">Body<br>Weight<br>(g)</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Age" class="metagrp">Age<br>(weeks)</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Tracer_Compound" class="metagrp" data-switchable="false">Tracer<br>Compound</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="numericOnly" data-field="Tracer_Infusion_Rate" class="metagrp">Tracer<br>Infusion<br>Rate<br>(ul/min/g)</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="numericOnly" data-field="Tracer_Infusion_Concentration" class="metagrp">Tracer<br>Infusion<br>Concentration<br>(mM)</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Study" class="metagrp">Studies</th>
</tr>
</thead>
<tbody>
{% for pg in res.all %}
... SNIP ... below shows a sample of 6 of the 24 columns in this particular template
<!-- Body Weight (g) -->
<td class="text-end">
{{ pg.msrun.sample.animal.body_weight }}
</td>
<!-- Age (weeks) -->
<td class="text-end">
<p title="{{ pg.msrun.sample.animal.age }} (d-hh:mm:ss)">{{ pg.msrun.sample.animal.age|durationToWeeks|decimalPlaces:2 }}</p>
</td>
<!-- Tracer Compound -->
<td>
{% if pg.msrun.sample.animal.tracer_compound is None %}
<!-- Put displayed link text first for sorting -->
<div style="display:none;">None</div>
<p title="Animal has no tracer.">None</p>
{% else %}
<!-- Put displayed link text first for sorting -->
<div style="display:none;">{{ pg.msrun.sample.animal.tracer_compound.name }}</div>
<a href="{% url 'compound_detail' pg.msrun.sample.animal.tracer_compound.id %}">
{{ pg.msrun.sample.animal.tracer_compound.name }}
</a>
{% endif %}
</td>
<!-- Tracer Infusion Rate (ul/min/g) -->
<td class="text-end">
{{ pg.msrun.sample.animal.tracer_infusion_rate }}
</td>
<!-- Tracer Infusion Concentration (mM) -->
<td class="text-end">
{{ pg.msrun.sample.animal.tracer_infusion_concentration }}
</td>
<!-- Studies -->
<td>
<!-- Put displayed link text first for sorting -->
<div style="display:none;">
{% define True as first %}
{% for study in pg.msrun.sample.animal.studies.all %}{% if not first %},<br>{% endif%}{{ study.name }}{% define False as first %}{% endfor %}
</div>
{% define True as first %}
{% for study in pg.msrun.sample.animal.studies.all %}{% if not first %},<br>{% endif%}<a href="{% url 'study_detail' study.id %}">{{ study.name }}</a>{% define False as first %}{% endfor %}
</td>
{% endfor %}
</tbody>
这里有几点需要注意。
- 我没有指出(因为我没有想到它会相关)我显示的数据是搜索结果。 Bootstrap-table 的 server-side 分页仅支持静态数据,因此弄清楚如何使用模板是没有实际意义的。
- 虽然您不能使用任何 bootstrap-table 内置 server-side 分页功能,但它确实提供了各种分页控件的装饰,因此当您实现自己的 server-side 分页时,它会在至少利用 BST 的 CSS.
- Django 的内置分页器工具仅适用于个别模型,不能应用于结果不等于 1 条记录 = 1 行的搜索。
所以我最终实现了自己的分页器 class 并利用了 BST 的分页器控件装饰。我不会详细介绍实现的细节,因为有很多选项,但是为了在您拥有以下内容时滚动您自己的分页器:
- 搜索结果表
- 行 ≠ 记录(即它们不是 1:1)
- 已加入记录
这些基本上是您需要的:
- 用于搜索
- 定义搜索参数的隐藏表单字段(我使用了 JSONField)
- 可选控件的任何其他字段
- 每页行的表单域
- 要导航到的页码的表单域
- 并且可选:
- 表示排序依据的字段的字段
- 排序方向字段
当您使用 Django ORM 执行搜索时,您只需根据页码和每页 rows/records 对结果进行切片。
在这方面 Django 有一些怪癖需要注意:
虽然您可以使用 .distinct(fields)
(与 .order_by(field)
)来模仿真正的 SQL 左连接(您可以获得重复的“root table”记录),你必须处理一些限制:
- 如果您想计算列中唯一值的计数以提供一些统计信息,则不能使用
.annotate(Count(...))
,因为您最终会遇到 NotImplemented
异常。
- 在视图代码中,您可以通过
M:M
关系访问“真正的左连接”,但在模板中,您不能,并且您必须为每个重复项遍历所有可能的相关记录根 table 记录(例如 {% for rootTable.MMrecs.all }
)。但是您可以在视图中使用 .annotate()
解决此限制,例如:.annotate("myMMtablerec"=F("MMrecs__pk"))
。然后在模板中,我只是使用模板标签从属于该行的 rootTable.MMrecs.all
中检索特定记录。
我有许多由 django 模板呈现的页面,我已应用 bootstrap-table 来实现列切换、客户端分页和多列排序。这是在创建了一个功能齐全的 django 模板之后。
我的table非常大,每列都有多次操作,比如:
- 指向网站上其他页面的链接
- 数字格式
- 水平对齐(例如右对齐数字)
- 连接来自相关 table 的值,由各种字符串分隔(例如,逗号分隔)
- 工具提示
- 用“None”填充空值
- 将时间增量转换为天或周 ...
许多操作使用 simple_tags 和 python 编写的过滤器。甚至有一个模板使用 javascript 使用 bootstrap table 事件(例如 $("#advsrchres").bootstrapTable({onAll: ...
)对某些 colspans 进行一些自定义操作(例如 $("#advsrchres").bootstrapTable({onAll: ...
)。
我看到的每个例子都使用了bootstrap-table的服务器端分页,没有模板,所有数据都是使用“data-url
”获取的returns JSON.
我希望我在这方面是错的,但我的评估是我将不得不重写 javascript 或其他模板中的所有那些单元格装饰。我还没有开始研究如何去做,所以在谷歌搜索了很多无果之后,我来这里是想看看是否有人知道一种不必完全重写那些巨大的 django 模板来实现服务器端分页的方法?有没有办法告诉 bootstrap-table 将 JSON 中的数据插入到 django 模板中?
这是模板之一的示例...
<table class="table table-hover table-striped table-bordered"
id="advsrchres"
data-toggle="table"
data-buttons-toolbar=".buttons-toolbar"
data-buttons-class="primary"
data-buttons-align="right"
data-filter-control="false"
data-search="false"
data-show-search-clear-button="false"
data-show-multi-sort="true"
data-show-columns="true"
data-show-columns-toggle-all="true"
data-show-fullscreen="false"
data-show-export="false"
data-pagination="true">
<colgroup span="8" class="identdata"></colgroup>
<colgroup span="4" class="datadata"></colgroup>
<colgroup span="12" class="metadata"></colgroup>
<thead>
<tr>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Animal" class="idgrp">Animal</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Sample" class="idgrp" data-switchable="false">Sample</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Tissue" class="idgrp">Tissue</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Peak_Group" class="idgrp">Peak Group</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Compound_Name" class="idgrp">Measured<br>Compound</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Compound_Synonym" class="idgrp">Measured<br>Compound<br>Synonym(s)</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Labeled_Element" class="idgrp">Labeled<br>Element</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Peak_Group_Set_Filename" class="idgrp">Peak Group Set Filename</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="numericOnly" data-field="Total_Abundance" class="datagrp" data-switchable="false">Total<br>Abundance</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="numericOnly" data-field="Enrichment_Fraction" class="datagrp">Enrichment<br>Fraction</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="numericOnly" data-field="Enrichment_Abundance" class="datagrp">Enrichment<br>Abundance</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="numericOnly" data-field="Normalized_Labeling" class="datagrp">Normalized<br>Labeling</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Formula" class="metagrp">Formula</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Genotype" class="metagrp">Genotype</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Sex" class="metagrp">Sex</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Feeding_Status" class="metagrp">Feeding<br>Status</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Diet" class="metagrp">Diet</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Treatment" class="metagrp">Treatment</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="numericOnly" data-field="Body_Weight" class="metagrp">Body<br>Weight<br>(g)</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="alphanum" data-field="Age" class="metagrp">Age<br>(weeks)</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Tracer_Compound" class="metagrp" data-switchable="false">Tracer<br>Compound</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="numericOnly" data-field="Tracer_Infusion_Rate" class="metagrp">Tracer<br>Infusion<br>Rate<br>(ul/min/g)</th>
<th data-valign="top" data-sortable="true" data-visible="false" data-sorter="numericOnly" data-field="Tracer_Infusion_Concentration" class="metagrp">Tracer<br>Infusion<br>Concentration<br>(mM)</th>
<th data-valign="top" data-sortable="true" data-visible="true" data-sorter="alphanum" data-field="Study" class="metagrp">Studies</th>
</tr>
</thead>
<tbody>
{% for pg in res.all %}
... SNIP ... below shows a sample of 6 of the 24 columns in this particular template
<!-- Body Weight (g) -->
<td class="text-end">
{{ pg.msrun.sample.animal.body_weight }}
</td>
<!-- Age (weeks) -->
<td class="text-end">
<p title="{{ pg.msrun.sample.animal.age }} (d-hh:mm:ss)">{{ pg.msrun.sample.animal.age|durationToWeeks|decimalPlaces:2 }}</p>
</td>
<!-- Tracer Compound -->
<td>
{% if pg.msrun.sample.animal.tracer_compound is None %}
<!-- Put displayed link text first for sorting -->
<div style="display:none;">None</div>
<p title="Animal has no tracer.">None</p>
{% else %}
<!-- Put displayed link text first for sorting -->
<div style="display:none;">{{ pg.msrun.sample.animal.tracer_compound.name }}</div>
<a href="{% url 'compound_detail' pg.msrun.sample.animal.tracer_compound.id %}">
{{ pg.msrun.sample.animal.tracer_compound.name }}
</a>
{% endif %}
</td>
<!-- Tracer Infusion Rate (ul/min/g) -->
<td class="text-end">
{{ pg.msrun.sample.animal.tracer_infusion_rate }}
</td>
<!-- Tracer Infusion Concentration (mM) -->
<td class="text-end">
{{ pg.msrun.sample.animal.tracer_infusion_concentration }}
</td>
<!-- Studies -->
<td>
<!-- Put displayed link text first for sorting -->
<div style="display:none;">
{% define True as first %}
{% for study in pg.msrun.sample.animal.studies.all %}{% if not first %},<br>{% endif%}{{ study.name }}{% define False as first %}{% endfor %}
</div>
{% define True as first %}
{% for study in pg.msrun.sample.animal.studies.all %}{% if not first %},<br>{% endif%}<a href="{% url 'study_detail' study.id %}">{{ study.name }}</a>{% define False as first %}{% endfor %}
</td>
{% endfor %}
</tbody>
这里有几点需要注意。
- 我没有指出(因为我没有想到它会相关)我显示的数据是搜索结果。 Bootstrap-table 的 server-side 分页仅支持静态数据,因此弄清楚如何使用模板是没有实际意义的。
- 虽然您不能使用任何 bootstrap-table 内置 server-side 分页功能,但它确实提供了各种分页控件的装饰,因此当您实现自己的 server-side 分页时,它会在至少利用 BST 的 CSS.
- Django 的内置分页器工具仅适用于个别模型,不能应用于结果不等于 1 条记录 = 1 行的搜索。
所以我最终实现了自己的分页器 class 并利用了 BST 的分页器控件装饰。我不会详细介绍实现的细节,因为有很多选项,但是为了在您拥有以下内容时滚动您自己的分页器:
- 搜索结果表
- 行 ≠ 记录(即它们不是 1:1)
- 已加入记录
这些基本上是您需要的:
- 用于搜索
- 定义搜索参数的隐藏表单字段(我使用了 JSONField)
- 可选控件的任何其他字段
- 每页行的表单域
- 要导航到的页码的表单域
- 并且可选:
- 表示排序依据的字段的字段
- 排序方向字段
当您使用 Django ORM 执行搜索时,您只需根据页码和每页 rows/records 对结果进行切片。
在这方面 Django 有一些怪癖需要注意:
虽然您可以使用 .distinct(fields)
(与 .order_by(field)
)来模仿真正的 SQL 左连接(您可以获得重复的“root table”记录),你必须处理一些限制:
- 如果您想计算列中唯一值的计数以提供一些统计信息,则不能使用
.annotate(Count(...))
,因为您最终会遇到NotImplemented
异常。 - 在视图代码中,您可以通过
M:M
关系访问“真正的左连接”,但在模板中,您不能,并且您必须为每个重复项遍历所有可能的相关记录根 table 记录(例如{% for rootTable.MMrecs.all }
)。但是您可以在视图中使用.annotate()
解决此限制,例如:.annotate("myMMtablerec"=F("MMrecs__pk"))
。然后在模板中,我只是使用模板标签从属于该行的rootTable.MMrecs.all
中检索特定记录。