use django filters + search filter; still need adapts sub-page list views
This commit is contained in:
parent
e9e09104ad
commit
849a14014c
26
aircox/filters.py
Normal file
26
aircox/filters.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
import django_filters as filters
|
||||||
|
|
||||||
|
from .models import Page, Episode
|
||||||
|
|
||||||
|
|
||||||
|
class PageFilters(filters.FilterSet):
|
||||||
|
q = filters.CharFilter(method='search_filter', label=_('Search'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Page
|
||||||
|
fields = {
|
||||||
|
'category__id': ['in'],
|
||||||
|
'pub_date': ['exact', 'gte', 'lte'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def search_filter(self, queryset, name, value):
|
||||||
|
return queryset.search(value)
|
||||||
|
|
||||||
|
|
||||||
|
class EpisodeFilters(PageFilters):
|
||||||
|
class Meta:
|
||||||
|
model = Episode
|
||||||
|
fields = {
|
||||||
|
}
|
||||||
|
|
|
@ -53,6 +53,11 @@ class PageQuerySet(InheritanceQuerySet):
|
||||||
return self.filter(parent=parent) if id is None else \
|
return self.filter(parent=parent) if id is None else \
|
||||||
self.filter(parent__id=id)
|
self.filter(parent__id=id)
|
||||||
|
|
||||||
|
def search(self, q, search_content=True):
|
||||||
|
if search_content:
|
||||||
|
return self.filter(models.Q(title__icontains=q) | models.Q(content__icontains=q))
|
||||||
|
return self.filter(title__icontains=q)
|
||||||
|
|
||||||
|
|
||||||
class BasePage(models.Model):
|
class BasePage(models.Model):
|
||||||
""" Base class for publishable content """
|
""" Base class for publishable content """
|
||||||
|
|
|
@ -57,6 +57,19 @@ Usefull context:
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
|
{% block top-nav-end %}
|
||||||
|
<div class="navbar-item">
|
||||||
|
<form action="{% url 'page-list' %}" method="GET">
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fa fa-search"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" name="q" class="input"
|
||||||
|
placeholder="{% trans "Search" %}" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
{% block top-nav-tools %}
|
{% block top-nav-tools %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,48 +24,7 @@
|
||||||
|
|
||||||
{% block main %}{{ block.super }}
|
{% block main %}{{ block.super }}
|
||||||
|
|
||||||
{% block before_list %}
|
{% block before_list %}{% endblock %}
|
||||||
{% if filters %}
|
|
||||||
<form method="GET" action="" class="media">
|
|
||||||
<div class="media-content">
|
|
||||||
{% block filters %}
|
|
||||||
{% for label, name, choices in filters %}
|
|
||||||
<div class="field is-horizontal">
|
|
||||||
<div class="field-label">
|
|
||||||
<label class="label">{{ label }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="field-body">
|
|
||||||
<div class="field is-narrow">
|
|
||||||
<div class="control">
|
|
||||||
{% for label, value, checked in choices %}
|
|
||||||
<label class="checkbox">
|
|
||||||
<input type="checkbox" class="checkbox" name="{{ name }}"
|
|
||||||
value="{{ value }}"
|
|
||||||
{% if checked %}checked{% endif %} />
|
|
||||||
{{ label }}
|
|
||||||
</label>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
<div class="media-right">
|
|
||||||
<div class="field is-grouped is-grouped-right">
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-primary"/>{% trans "Apply" %}</button>
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<a href="?" class="button is-secondary">{% trans "Reset" %}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<section role="list">
|
<section role="list">
|
||||||
{% block pages_list %}
|
{% block pages_list %}
|
||||||
|
|
|
@ -2,3 +2,62 @@
|
||||||
{% comment %}Display a list of Pages{% endcomment %}
|
{% comment %}Display a list of Pages{% endcomment %}
|
||||||
{% load i18n aircox %}
|
{% load i18n aircox %}
|
||||||
|
|
||||||
|
{% block before_list %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
{% if view.has_filters %}
|
||||||
|
<form method="GET" action="" class="media">
|
||||||
|
<div class="media-content">
|
||||||
|
{% block filters %}
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label">
|
||||||
|
<label class="label">{% trans "Search" %}</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fa fa-search"></i>
|
||||||
|
</span>
|
||||||
|
<input class="input" type="text" name="q"
|
||||||
|
value="{{ filterset_data.q }}"
|
||||||
|
placeholder="{% trans "Search content" %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field is-horizontal">
|
||||||
|
<div class="field-label">
|
||||||
|
<label class="label">{% trans "Categories" %}</label>
|
||||||
|
</div>
|
||||||
|
<div class="field-body">
|
||||||
|
<div class="field is-narrow">
|
||||||
|
<div class="control">
|
||||||
|
{% for label, value in categories %}
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" class="checkbox" name="category__id__in"
|
||||||
|
value="{{ value }}"
|
||||||
|
{% if value in filterset_data.category__id__in %}checked{% endif %} />
|
||||||
|
{{ label }}
|
||||||
|
</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div class="media-right">
|
||||||
|
<div class="field is-grouped is-grouped-right">
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-primary"/>{% trans "Apply" %}</button>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<a href="?" class="button is-secondary">{% trans "Reset" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -80,3 +80,31 @@ class AttachedToMixin:
|
||||||
.published().first()
|
.published().first()
|
||||||
return super().get_page()
|
return super().get_page()
|
||||||
|
|
||||||
|
|
||||||
|
class FiltersMixin:
|
||||||
|
""" Mixin integrating Django filters' filter set """
|
||||||
|
filterset = None
|
||||||
|
filterset_class = None
|
||||||
|
|
||||||
|
def get_filterset(self, data, query):
|
||||||
|
return self.filterset_class(data, query)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
query = super().get_queryset()
|
||||||
|
if self.filterset_class:
|
||||||
|
self.filterset = self.get_filterset(self.request.GET.copy(), query)
|
||||||
|
return self.filterset.qs
|
||||||
|
return query
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
filterset = kwargs.setdefault('filterset', self.filterset)
|
||||||
|
if filterset.is_valid():
|
||||||
|
kwargs['filterset_data'] = filterset.form.cleaned_data
|
||||||
|
else:
|
||||||
|
kwargs['filterset_data'] = {}
|
||||||
|
|
||||||
|
params = self.request.GET.copy()
|
||||||
|
kwargs['get_params'] = params.pop('page', True) and params
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,12 @@ from django.views.generic import DetailView, ListView
|
||||||
|
|
||||||
from honeypot.decorators import check_honeypot
|
from honeypot.decorators import check_honeypot
|
||||||
|
|
||||||
|
from ..filters import PageFilters
|
||||||
from ..forms import CommentForm
|
from ..forms import CommentForm
|
||||||
from ..models import Category, Comment
|
from ..models import Category, Comment
|
||||||
from ..utils import Redirect
|
from ..utils import Redirect
|
||||||
from .base import BaseView
|
from .base import BaseView
|
||||||
from .mixins import AttachedToMixin, ParentMixin
|
from .mixins import AttachedToMixin, FiltersMixin, ParentMixin
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['BasePageListView', 'BasePageDetailView', 'PageDetailView', 'PageListView']
|
__all__ = ['BasePageListView', 'BasePageDetailView', 'PageDetailView', 'PageListView']
|
||||||
|
@ -71,50 +72,32 @@ class BasePageDetailView(BaseView, DetailView):
|
||||||
return self.object
|
return self.object
|
||||||
|
|
||||||
|
|
||||||
class PageListView(BasePageListView):
|
class PageListView(FiltersMixin, BasePageListView):
|
||||||
""" Page list view. """
|
""" Page list view. """
|
||||||
|
filterset_class = PageFilters
|
||||||
template_name = None
|
template_name = None
|
||||||
has_filters = True
|
has_filters = True
|
||||||
categories = None
|
categories = None
|
||||||
filters = None
|
filters = None
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
self.categories = set(self.request.GET.getlist('categories'))
|
|
||||||
self.filters = set(self.request.GET.getlist('filters'))
|
|
||||||
return super().get(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
return super().get_template_names() + ['aircox/page_list.html']
|
return super().get_template_names() + ['aircox/page_list.html']
|
||||||
|
|
||||||
|
def get_filterset(self, data, query):
|
||||||
|
# FIXME: not the most efficient, cause join then split (in django filters)
|
||||||
|
data['category__id__in'] = ','.join(data.getlist('category__id__in'))
|
||||||
|
return super().get_filterset(data, query)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset().select_related('category') \
|
qs = super().get_queryset().select_related('category') \
|
||||||
.order_by('-pub_date')
|
.order_by('-pub_date')
|
||||||
|
|
||||||
# category can be filtered based on request.GET['categories']
|
|
||||||
# (by id)
|
|
||||||
if self.categories:
|
|
||||||
qs = qs.filter(category__slug__in=self.categories)
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def get_filters(self):
|
|
||||||
categories = self.model.objects.published() \
|
|
||||||
.filter(category__isnull=False) \
|
|
||||||
.values_list('category', flat=True)
|
|
||||||
categories = [ (c.title, c.slug, c.slug in self.categories)
|
|
||||||
for c in Category.objects.filter(id__in=categories) ]
|
|
||||||
return (
|
|
||||||
(_('Categories'), 'categories', categories),
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
if not 'filters' in kwargs:
|
kwargs['categories'] = self.model.objects.published() \
|
||||||
filters = self.get_filters()
|
.filter(category__isnull=False) \
|
||||||
for label, fieldName, choices in filters:
|
.values_list('category__title', 'category__id') \
|
||||||
if choices:
|
.distinct()
|
||||||
kwargs['filters'] = filters
|
|
||||||
break;
|
|
||||||
else:
|
|
||||||
kwargs['filters'] = tuple()
|
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ -151,6 +134,5 @@ class PageDetailView(BasePageDetailView):
|
||||||
comment = form.save(commit=False)
|
comment = form.save(commit=False)
|
||||||
comment.page = self.object
|
comment.page = self.object
|
||||||
comment.save()
|
comment.save()
|
||||||
|
|
||||||
return self.get(request, *args, **kwargs)
|
return self.get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user