Merge pull request '#11: champ de recherche dans la partie publique du site' (#30) from fix-1.0-11 into develop-1.0
Reviewed-on: #30
This commit is contained in:
		
							
								
								
									
										32
									
								
								aircox/filters.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								aircox/filters.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					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):
 | 
				
			||||||
 | 
					    podcast = filters.BooleanFilter(method='podcast_filter', label=_('Podcast'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = Episode
 | 
				
			||||||
 | 
					        fields = PageFilters.Meta.fields.copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def podcast_filter(self, queryset, name, value):
 | 
				
			||||||
 | 
					        if value:
 | 
				
			||||||
 | 
					            return queryset.filter(sound__is_public=True).distinct()
 | 
				
			||||||
 | 
					        return queryset.filter(sound__isnull=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -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 """
 | 
				
			||||||
 | 
				
			|||||||
@ -59,6 +59,19 @@ Usefull context:
 | 
				
			|||||||
                        <div class="navbar-end">
 | 
					                        <div class="navbar-end">
 | 
				
			||||||
                            {% block top-nav-tools %}
 | 
					                            {% block top-nav-tools %}
 | 
				
			||||||
                            {% endblock %}
 | 
					                            {% endblock %}
 | 
				
			||||||
 | 
					                            {% 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 %}
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </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 and object_list %}
 | 
				
			||||||
 | 
					<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 %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ import datetime
 | 
				
			|||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
from django.views.generic import ListView
 | 
					from django.views.generic import ListView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..filters import EpisodeFilters
 | 
				
			||||||
from ..models import Diffusion, Episode, Program, StaticPage, Sound
 | 
					from ..models import Diffusion, Episode, Program, StaticPage, Sound
 | 
				
			||||||
from .base import BaseView
 | 
					from .base import BaseView
 | 
				
			||||||
from .program import ProgramPageDetailView
 | 
					from .program import ProgramPageDetailView
 | 
				
			||||||
@ -25,22 +26,12 @@ class EpisodeDetailView(ProgramPageDetailView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class EpisodeListView(PageListView):
 | 
					class EpisodeListView(PageListView):
 | 
				
			||||||
    model = Episode
 | 
					    model = Episode
 | 
				
			||||||
 | 
					    filterset_class = EpisodeFilters
 | 
				
			||||||
    item_template_name = 'aircox/widgets/episode_item.html'
 | 
					    item_template_name = 'aircox/widgets/episode_item.html'
 | 
				
			||||||
    has_headline = True
 | 
					    has_headline = True
 | 
				
			||||||
    parent_model = Program
 | 
					    parent_model = Program
 | 
				
			||||||
    attach_to_value = StaticPage.ATTACH_TO_EPISODES
 | 
					    attach_to_value = StaticPage.ATTACH_TO_EPISODES
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_queryset(self):
 | 
					 | 
				
			||||||
        qs = super().get_queryset()
 | 
					 | 
				
			||||||
        if self.filters and 'podcasts' in self.filters:
 | 
					 | 
				
			||||||
            qs = qs.filter(sound__is_public=True)
 | 
					 | 
				
			||||||
        return qs
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_filters(self):
 | 
					 | 
				
			||||||
        return super().get_filters() + (
 | 
					 | 
				
			||||||
            (_('Podcasts'), 'podcasts', tuple()),
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DiffusionListView(GetDateMixin, AttachedToMixin, BaseView, ListView):
 | 
					class DiffusionListView(GetDateMixin, AttachedToMixin, BaseView, ListView):
 | 
				
			||||||
    """ View for timetables """
 | 
					    """ View for timetables """
 | 
				
			||||||
 | 
				
			|||||||
@ -80,3 +80,30 @@ 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user