Таблицы Django - Фильтрация столбцов

Я начал использовать django-tables2 (который я могу очень порекомендовать с первого впечатления), и я задаю себе вопрос, как реализовать фильтрацию столбцов. Я не могу найти соответствующую документацию, но я уверен, что это где-то там.

Ответ 1

Немного поздний ответ, но в любом случае... Я также не смог найти подходящую документацию для фильтрации столбцов. Для этого есть много способов:

а. Рукой: я добавляю форму, содержащую поля, которые я хотел бы фильтровать, а затем я делаю что-то вроде этого на мой взгляд:

  data = models.MyClass.all()
  form = forms.MyFilterForm(request.GET)
  if request.GET.get('field1'):
    data = data.filter(field1=request.GET.get('field1') )
  if request.GET.get('field2'):
    data = data.filter(field2=request.GET.get('field2') )   
  ...
  table = tables.MyTable(data)

Это работает очень хорошо, однако оно не так сухо, потому что оно жестко закодировано в представлении.

В. Использование SingleTableView. Еще один способ - добавить SingleTableView, который содержит форму:

from django_tables2 import SingleTableView
class FilteredSingleTableView(SingleTableView):
  def get_table_data(self):
    data= models.MyClass.objects.all
    if self.request.GET.get('field1'):
      data = data.filter(field1=self.request.GET.get('field1') )
    if self.request.GET.get('field1'):
      data = data.filter(field1=self.request.GET.get('field1') )
    return data

    def get_context_data(self, **kwargs):
      context = super(FilteredSingleTableView, self).get_context_data(**kwargs)
      context['form'] = forms.MyFilterForm(self.request.user, self.request.GET)
      return context

Это более DRY:)

С. Использование SingleTableView и django_filters. Это, вероятно, самый DRY-способ:) Вот как это сделать:

Сначала определите фильтр:

class MyFilter(django_filters.FilterSet):
  field1 = django_filters.CharFilter()
  field2 = django_filters.CharFilter()
...

(или вы можете добавить фильтр модели в Meta (model = MyModel)

Теперь создайте SingleTableView следующим образом

class FilteredSingleTableView(SingleTableView):
  def get_table_data(self):
    f = filters.MyFilter(self.request.GET, queryset =models.MyClass.objects.all() , request=self.request )
    return f

  def get_context_data(self, **kwargs):
    context = super(FilteredSingleTableView, self).get_context_data(**kwargs)
    f = filters.MyFilter(self.request.GET, queryset =models.MyClass.objects.all() , request=self.request )
    context['form'] = f.form
    return context

(возможно, существует проблема с линией f =... но я не мог заставить ее работать иначе.

Наконец, вы можете вызвать SingleTableView с вашего urls.py, как это

url(r'^$', views.FilteredSingleTableView.as_view(
    table_class = tables.MyTable, 
    model=models.MyClass, 
    template_name ='mytemplate.html', 
    table_pagination={ "per_page":50 } )) , 
    name='filtered_single_table_view'
),

Д. Использование универсального класса: Это еще более DRY и django-generic-class-views, как путь! Это на самом деле следующий шаг от C: просто объявите свой FilteredSingleTableView следующим образом:

class FilteredSingleTableView(django_tables2.SingleTableView):
  filter_class = None

  def get_table_data(self):
    self.filter = self.filter_class(self.request.GET, queryset =super(FilteredSingleTableView, self).get_table_data() )
    return self.filter.qs

  def get_context_data(self, **kwargs):
    context = super(FilteredSingleTableView, self).get_context_data(**kwargs)
    context['filter'] = self.filter
    return context

Теперь FilteredSingleTableView имеет параметр для класса фильтра, поэтому вы можете передать его в urls.py среди других параметров:

    url(r'^$', ships.views.FilteredSingleTableView.as_view(
        model=models.MyModel,
        table_class=tables.MyTable, 
        template_name='mytemplate.html' , 
        filter_class = filters.MyFilter, 
    ) , name='myview'),

Таким образом, вы можете использовать FilteredSingleTableView без изменений для фильтрации любой из ваших моделей!

Также обратите внимание, что теперь я сохранил фильтр как переменную экземпляра и удалил повторяющийся код f=filters.MyFilter(...), который у меня был в C (get_table_data вызывается до get_context_data), если это не всегда case, то мы могли бы добавить метод экземпляра get_filter, который бы сделал трюк)!

Обновление 23/04/2016. После популярного спроса я создал простой проект Django, который использует общий класс FilteredSingleTableView для фильтрации таблицы книг. Вы можете узнать это: https://github.com/spapas/django_table_filtering

Обновление 05/07/2016. Обратите внимание, что вы должны использовать return self.filter.qs для возврата get_table_data в D (я обновил ответ с помощью это), иначе вид займет слишком много времени для больших таблиц. Более подробную информацию можно найти на https://github.com/spapas/django_table_filtering/issues/1

Ответ 2

Существует более простой способ DRYer для создания общего представления:

from django_filters.views import FilterView
from django_tables2 import SingleTableView


class FilterTableView(FilterView, SingleTableView):   
    def get_table_data(self):
        return self.object_list

Итак, вы можете сделать это:

class MyTableView(FilterTableView):
    model = MyModel
    table_class = MyTable
    filterset_class = MyFilter

Ответ 3

Если вы предпочитаете использовать django_tables2.views.SingleTableMixin совместно с Django ListView или его подклассом (а не SingleTableView), я предлагаю следующее:

class FilteredListViewMixin(object):
    """ Uses django-filter to filter a ListView. """

    filter_class = None

    def get_queryset(self):
        qs = super(FilteredListViewMixin, self).get_queryset()

        self.filter = self.filter_class(self.request.GET,
                                        queryset=qs)
        return self.filter.qs

    def get_context_data(self, **kwargs):
        context = super(FilteredListViewMixin, self).get_context_data(**kwargs)
        context['filter'] = self.filter
        return context

Он имеет дополнительное преимущество: он не связан с django-tables2 (DRY FTW), что означает, что он также может использоваться с общим ListViews.

Ответ 4

Это полный рабочий пример приложения, которое я пишу с использованием Django 2, Crispy Forms и Bootstrap 4:

urls.py:

from django.urls import path
from .views import ASTodasView


urlpatterns = [
    path("asignatura/todas", ASTodasView.as_view(), name="as-todas"),
]

views.py:

from .filters import AsignaturaListFilter
from .forms import AsignaturaFilterFormHelper
from .models import Asignatura, Calendario
from .tables import AsignaturasTable
from .utils import PagedFilteredTableView


class ASTodasView(PagedFilteredTableView):
    filter_class = AsignaturaListFilter
    model = Asignatura
    table_class = AsignaturasTable
    template_name = "asignatura/todas.html"
    formhelper_class = AsignaturaFilterFormHelper

    def get_queryset(self):
        anyo_academico = Calendario.get_anyo_academico_actual()
        return Asignatura.objects.filter(anyo_academico=anyo_academico)

filters.py:

import django_filters
from .models import Asignatura


class AsignaturaListFilter(django_filters.FilterSet):
    class Meta:
        model = Asignatura
        fields = {
            "nombre_estudio": ["icontains"],
            "nombre_centro": ["icontains"],
            "asignatura_id": ["exact"],
            "nombre_asignatura": ["icontains"],
            "cod_grupo_asignatura": ["exact"],
        }
        order_by = ["asignatura_id"]

forms.py:

from django import forms
from django.utils.translation import gettext_lazy as _

from crispy_forms.bootstrap import FormActions, InlineField
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Div, Fieldset, Layout, Submit


class AsignaturaFilterFormHelper(FormHelper):
    # See https://django-crispy-forms.readthedocs.io/en/latest/form_helper.html

    form_class = "form form-inline"
    form_id = "asignatura-search-form"
    form_method = "GET"
    form_tag = True
    html5_required = True
    layout = Layout(
        Div(
            Fieldset(
                "<span class='fa fa-search'></span> " + str(_("Buscar asignatura")),
                Div(
                    InlineField("nombre_estudio__icontains", wrapper_class="col-4"),
                    InlineField("nombre_centro__icontains", wrapper_class="col-4"),
                    InlineField("asignatura_id", wrapper_class="col-4"),
                    InlineField("nombre_asignatura__icontains", wrapper_class="col-4"),
                    InlineField("cod_grupo_asignatura", wrapper_class="col-4"),
                    css_class="row",
                ),
                css_class="col-10 border p-3",
            ),
            FormActions(
                Submit("submit", _("Filtrar")),
                css_class="col-2 text-right align-self-center",
            ),
            css_class="row",
        )
    )

tables.py:

import django_tables2 as tables
from django.utils.translation import gettext_lazy as _

from .models import Asignatura


class AsignaturasTable(tables.Table):
    class Meta:
        attrs = {"class": "table table-striped table-hover cabecera-azul"}
        model = Asignatura
        fields = (
            "nombre_estudio",
            "nombre_centro",
            "asignatura_id",
            "nombre_asignatura",
            "cod_grupo_asignatura",
        )
        empty_text = _(
            "No hay ninguna asignatura que satisfaga los criterios de búsqueda."
        )
        template_name = "django_tables2/bootstrap4.html"
        per_page = 20

utils.py:

from django_tables2 import SingleTableView


class PagedFilteredTableView(SingleTableView):
    filter_class = None
    formhelper_class = None
    context_filter_name = "filter"

    def get_table_data(self):
        self.filter = self.filter_class(
            self.request.GET, queryset=super().get_table_data()
        )
        self.filter.form.helper = self.formhelper_class()
        return self.filter.qs

    def get_context_data(self, **kwargs):
        context = super(PagedFilteredTableView, self).get_context_data(**kwargs)
        context[self.context_filter_name] = self.filter
        return context

todas.html:

{% extends 'base.html' %}
{% load crispy_forms_tags i18n %}
{% load render_table from django_tables2 %}

{% block title %}{% trans "Todas las asignaturas" %}{% endblock title %}

{% block content %}
<div class="container-blanco">
  <h1>{% trans "Todas las asignaturas" %}</h1>
  <hr />
  <br />

  {% crispy filter.form filter.form.helper %}
  <br />
  {% render_table table %}
</div>
{% endblock content %}

Надеюсь, поможет. Улучшения приветствуются.