Question:
I have a number of pages rendered by django templates to which I have applied bootstrap-table to implement column switching, client-side pagination, and multi-column sorting. This was after having created a fully functioning django template.My tables are very large and each column has multiple manipulations, such as:
- links to other pages on the site
- number formatting
- horizontal alignment (e.g. right-justify numbers)
- concatenating values from related tables, delimited by various strings (e.g. comma-delimiting)
- tooltips
- filling in empty values with “None”
- converting timedeltas to days or weeks …
A number of the manipulations utilize simple_tags and filters written in python. There’s even one template that uses javascript to do some custom stuff with some colspans using bootstrap table events (e.g.
$("#advsrchres").bootstrapTable({onAll: ...
).And every example I look at that uses bootstrap-table’s server-side pagination, there is no template and all the data is obtained using a “
data-url
” that returns JSON.I’m hoping I’m wrong about this, but my assessment is that I would have to rewrite all those cell decorations in the template in javascript or something. I haven’t started looking into how to do it yet, so after much fruitless googling, I’m here to see if anyone knows a way to not have to entirely rewrite those huge django templates in order to implement server-side pagination? Is there a way to tell bootstrap-table to insert the data from the JSON into the django template?
Here’s a sample of one of the templates…
<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>
Answer:
There are a few things to note here.- I did not point out (because it didn’t occur to me that it would be relevant) that the data I’m displaying is the result of a search. Bootstrap-table’s server-side pagination only supports static data, so figuring out how to utilize a template is moot.
- While you cannot use any bootstrap-table builtin server-side pagination feature, it does provide various decorations of paging controls, so that when you implement your own server-side pagination, it would at least take advantage of BST’s CSS.
- Django’s builtin paginator tools are only for individual models and cannot be applied to searches whose results do not equate to 1 record = 1 row.
So I ended up implementing my own paginator class and took advantage of BST’s paginator control decorations. I won’t go into the details of the implementation, since there are a number of options, but in order to roll your own paginator when you have:
- Tables resulting from a search
- Rows ≠ records (i.e. they’re not 1:1)
- Joined records
These are essentially what you would need:
- For the search
- A hidden form field that defines the search parameters (I used a JSONField)
- Any other field for optional controls
- A form field for rows per page
- A form field for the page number to navigate to
- And optionally:
- A field that indicates a field by which to sort
- A field for sort direction
And when you perform the search using the Django ORM, you just have the slice the results based on the page number and rows/records per page.
There are a few quirks to note about Django in this regard:
While you can use
.distinct(fields)
(with .order_by(field)
) to mimmic a true SQL left join (where you can get duplicate “root table” records), you have to deal with a few limitations:- If you want to compute counts of unique values in a column to provide some stats, you cannot use
.annotate(Count(...))
because you’ll end up with aNotImplemented
exception. - In the view code, you have access to the “true left join” through
M:M
relationships, but in the template, you do not, and you have to loop through all possible related record for each duplicate root table record (e.g.{% for rootTable.MMrecs.all }
). But you can work around this limitation using.annotate()
in the view, like:.annotate("myMMtablerec"=F("MMrecs__pk"))
. Then in the template, I just used a template tag to retrieve the specific record from therootTable.MMrecs.all
that belongs to that row.
If you have better answer, please add a comment about this, thank you!
Source: Stackoverflow.com
Leave a Review