Commit 377706f9 authored by Nicolas Joyard's avatar Nicolas Joyard

Implement theme list

parent a831f374
......@@ -23213,11 +23213,26 @@
28147
],
"slug": "acta",
"name": "acta"
"name": "ACTA"
},
"model": "memopol_themes.theme",
"pk": 1
},
{
"fields": {
"description": "None yet.",
"positions": [
],
"proposals": [
],
"dossiers": [
],
"slug": "etat-durgence",
"name": "État d'urgence"
},
"model": "memopol_themes.theme",
"pk": 2
},
{
"fields": {
"theme": 1,
......
<div class="col-xs-12 col-md-4 theme-card">
<div class="thumbnail">
<a class="custom-thumbnail custom-invisible" href="/theme/acta/">
<div class="row">
<div class="col-xs-12">
<h4 class="text-center">ACTA</h4>
<p class="text-center lead">
 
<span class="label label-default" data-placement="bottom" data-toggle="tooltip" title="Links">
<span class="glyphicon glyphicon-link"></span>
<span class="badge">1</span>
</span>
<span class="label label-default" data-placement="bottom" data-toggle="tooltip" title="Dossiers">
<span class="glyphicon glyphicon-book"></span>
<span class="badge">1</span>
</span>
<span class="label label-default" data-placement="bottom" data-toggle="tooltip" title="Proposals">
<span class="glyphicon glyphicon-file"></span>
<span class="badge">3</span>
</span>
 
</p>
</div>
</div>
</a>
</div>
</div>
---
<div class="col-xs-12 col-md-4 theme-card">
<div class="thumbnail">
<a class="custom-thumbnail custom-invisible" href="/theme/etat-durgence/">
<div class="row">
<div class="col-xs-12">
<h4 class="text-center">État d'urgence</h4>
<p class="text-center lead">
 
 
</p>
</div>
</div>
</a>
</div>
</div>
\ No newline at end of file
{
"status_code": 200
}
\ No newline at end of file
<h4 class="text-center">ACTA</h4>
---
<h4 class="text-center">État d'urgence</h4>
\ No newline at end of file
<h4 class="text-center">État d'urgence</h4>
---
<h4 class="text-center">ACTA</h4>
\ No newline at end of file
<li class="disabled">
<a href="?&amp;sort_by=name">name</a>
</li>
---
<li class="disabled">
<a href="?&amp;sort_dir=asc">ascending</a>
</li>
---
<li>
<a href="?&amp;sort_dir=desc">descending</a>
</li>
\ No newline at end of file
from .base import BaseTest
class ThemeListTest(BaseTest):
url = '/theme/'
def test_queries(self):
# First query to set session variables
self.client.get(self.url)
with self.assertNumQueries(self.left_pane_queries + 3):
"""
Left pane queries plus:
- 1 for session key
- 1 for theme count (pagination)
- 1 for themes
"""
self.client.get(self.url)
def test_cards(self):
self.selector_test('.theme-card')
def test_navbar_order_options(self):
self.selector_test('#listheader #orderby li, #listheader #orderdir li')
def test_navbar_order_name_asc(self):
self.selector_test('.theme-card h4',
'%s?sort_by=name&sort_dir=asc' % self.url)
def test_navbar_order_name_desc(self):
self.selector_test('.theme-card h4',
'%s?sort_by=name&sort_dir=desc' % self.url)
# Project specific "glue" coupling of all apps
from django.db import models
from django.db.models import Count
from django.utils.http import urlencode
from core.views import GridListMixin, PaginationMixin, CSVDownloadMixin
from representatives import views as representatives_views
from representatives.models import Representative
from representatives_votes import views as votes_views
from representatives_votes.models import Dossier, Proposal
from representatives_positions.forms import PositionForm
from representatives_recommendations.models import ScoredVote
class PaginationFormMixin(PaginationMixin):
"""
Only add an searchparameters to the context to make it easy to paginate
without duplicating the 'page' parameter and keeping the form's GET
parameters.
"""
def get_context_data(self, **kwargs):
c = super(PaginationFormMixin, self).get_context_data(**kwargs)
params = [(k, v) for k, v in self.request.GET.iteritems()
if k != 'page']
c['searchparameters'] = urlencode(dict(params))
return c
class RepresentativeList(CSVDownloadMixin,
GridListMixin,
PaginationFormMixin,
representatives_views.RepresentativeList):
queryset = Representative.objects.filter(active=True).select_related(
'score')
csv_name = 'meps.csv'
def get_csv_results(self, context, **kwargs):
qs = super(RepresentativeList, self).get_queryset()
qs = qs.prefetch_related('email_set')
return [self.add_representative_country_and_main_mandate(r)
for r in qs]
def get_csv_row(self, obj):
return (
obj.full_name,
u', '.join([e.email for e in obj.email_set.all()]),
obj.main_mandate.group.abbreviation,
obj.country,
)
def get_context_data(self, **kwargs):
c = super(RepresentativeList, self).get_context_data(**kwargs)
group = self.kwargs.get('group', None)
group_kind = self.kwargs.get('group_kind', None)
c['search'] = {
'search': self.request.GET.get('search', None),
group_kind: group,
}
return c
class RepresentativeDetail(representatives_views.RepresentativeDetail):
queryset = Representative.objects.select_related('score')
def get_queryset(self):
qs = super(RepresentativeDetail, self).get_queryset()
votes = (ScoredVote.objects.filter(
proposal__in=Proposal.objects.exclude(recommendation=None))
.select_related('proposal__recommendation')
.select_related('proposal__dossier'))
qs = qs.prefetch_related(models.Prefetch('votes', queryset=votes))
return qs
def get_context_data(self, **kwargs):
c = super(RepresentativeDetail, self).get_context_data(**kwargs)
c['position_form'] = PositionForm(
initial={'representative': self.object.pk})
self.add_representative_country_and_main_mandate(c['object'])
return c
class DossierList(PaginationFormMixin, votes_views.DossierList):
queryset = Dossier.objects.filter(proposals__recommendation__isnull=False)
def get_queryset(self):
qs = super(DossierList, self).get_queryset()
return qs.annotate(votes_count=Count('proposals__votes'))
class DossierDetail(votes_views.DossierDetail):
def get_context_data(self, **kwargs):
c = super(DossierDetail, self).get_context_data(**kwargs)
c['proposals'] = c['dossier'].proposals.filter(
recommendation__isnull=False).select_related('recommendation')
# Note: this is a bit of a hack, we feed the RelatedManager with
# the prefetch_related with a clause, so that representative.votes.all
# doesn't query the db but returns what he has in store.
votes = (ScoredVote.objects
.filter(proposal__in=c['proposals'])
.select_related('proposal__recommendation'))
c['representatives'] = (Representative.objects
.filter(votes__proposal__in=c['proposals'])
.distinct()
.prefetch_related(models.Prefetch('votes',
queryset=votes)))
return c
# coding: utf-8
from core.views import PaginationMixin
from core.views import PaginationMixin, SortMixin
from django.db.models import Count
from django.views import generic
from memopol_themes.models import Theme
......@@ -9,10 +10,20 @@ from memopol_themes.models import Theme
from ..filters import ThemeFilter
class ThemeList(PaginationMixin, generic.ListView):
class ThemeList(PaginationMixin, SortMixin, generic.ListView):
current_filter = None
queryset = Theme.objects.all()
queryset = Theme.objects.all().annotate(
nb_links=Count('links', distinct=True),
nb_dossiers=Count('dossiers', distinct=True),
nb_proposals=Count('proposals', distinct=True),
nb_positions=Count('positions', distinct=True)
)
sort_fields = {
'name': 'name',
}
sort_default_field = 'name'
def theme_filter(self, qs):
f = ThemeFilter(self.request.GET, queryset=qs)
......@@ -27,4 +38,5 @@ class ThemeList(PaginationMixin, generic.ListView):
def get_context_data(self, **kwargs):
c = super(ThemeList, self).get_context_data(**kwargs)
c['filter'] = self.current_filter
c['view'] = 'theme_list'
return c
......@@ -14,6 +14,13 @@ body {
padding: 2em;
}
.pagination {
margin: 0;
}
.card-list {
margin-top: 1em;
}
/***************************************************************
Typographie
......@@ -49,6 +56,10 @@ h3 {
font-weight: 400;
}
.label + .label {
margin-left: .25em;
}
a.custom-invisible, a.custom-invisible:hover, a.custom-invisible:focus {
color: #222;
text-decoration: none;
......
{% extends "base.html" %}
{% load bootstrap3 %}
{% load i18n %}
{% load memopol_tags %}
{% load humanize %}
{% block head %}
{{ filter.form.media }}
{% endblock %}
{% block title %}{% trans "Themes" %}{% endblock %}
{% block content %}
<h1 class="text-center">{% trans "Themes" %}</h1>
<p class="lead text-center">
{% blocktrans count counter=paginator.count %}{{ counter }} theme{% plural %}{{ counter }} themes{% endblocktrans %}.
</p>
{% include "blocks/listheader.html" %}
<div class="row card-list">
{% for theme in object_list %}
<div class="col-xs-12 col-md-4 theme-card">
<div class="thumbnail">
<a href="{% url 'theme-detail' slug=theme.slug %}" class="custom-thumbnail custom-invisible">
<div class="row">
<div class="col-xs-12">
<h4 class="text-center">{{ theme.name }}</h4>
{% block search %}
{% url 'theme-list' as action_url %}
{% include '_filter_form.html' with action=action_url form=filter.form qs=request.GET.urlencode %}
{% endblock %}
<p class="text-center lead">
&nbsp;
{% if object_list|length == 0 %}
{% if theme.nb_links > 0 %}
<span class="label label-default" data-toggle="tooltip" data-placement="bottom" title="{% trans 'Links' %}">
{% bootstrap_icon "link" %}
<span class="badge">{{ theme.nb_links }}</span>
</span>
{% endif %}
<div class="no-results">
No matching themes found :(
{% if theme.nb_dossiers > 0 %}
<span class="label label-default" data-toggle="tooltip" data-placement="bottom" title="{% trans 'Dossiers' %}">
{% bootstrap_icon "book" %}
<span class="badge">{{ theme.nb_dossiers }}</span>
</span>
{% endif %}
{% if theme.nb_proposals > 0 %}
<span class="label label-default" data-toggle="tooltip" data-placement="bottom" title="{% trans 'Proposals' %}">
{% bootstrap_icon "file" %}
<span class="badge">{{ theme.nb_proposals }}</span>
</span>
{% endif %}
{% if theme.nb_positions > 0 %}
<span class="label label-default" data-toggle="tooltip" data-placement="bottom" title="{% trans 'Public positions' %}">
{% bootstrap_icon "comment" %}
<span class="badge">{{ theme.nb_positions }}</span>
</span>
{% endif %}
&nbsp;
</p>
</div>
</div>
</a>
</div>
</div>
{% endfor %}
</div>
{% include "blocks/listfooter.html" %}
{% else %}
{% include 'core/blocks/pagination.html' %}
<h1>
{% trans "Themes" %}
</h1>
<table>
<tr>
<th>
{% trans "Name" %}
</th>
</tr>
{% for theme in object_list %}
<tr>
<td>
<a href="{% url 'theme-detail' theme.slug %}">
{{ theme.name }}
</a>
</td>
</tr>
{% endfor %}
</table>
{% include "core/blocks/pagination.html" %}
{% endif %}
{% endblock %}
......@@ -14,7 +14,7 @@
{% include "blocks/listheader.html" %}
<div class="row">
<div class="row card-list">
{% for representative in object_list %}
<div class="col-xs-12 col-md-4 representative-card">
<div class="thumbnail">
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment