From e5dad7d7731e2a280f78fb43c50e1b61f8e0244e Mon Sep 17 00:00:00 2001
From: Nicolas Joyard <joyard.nicolas@gmail.com>
Date: Mon, 6 Jun 2016 08:13:25 +0200
Subject: [PATCH] Integrate views removed from django-representatives[-votes]

---
 memopol/api.py                                | 24 +++---
 memopol/templatetags/memopol_tags.py          | 37 +++++++-
 .../content                                   | 10 +--
 .../content                                   | 10 +--
 .../content                                   | 10 +--
 .../content                                   | 10 +--
 .../content                                   | 10 +--
 memopol/tests/test_representatives_list.py    | 21 +++++
 memopol/urls.py                               | 62 +++++++++----
 memopol/views/__init__.py                     |  0
 memopol/views/dossier_ac.py                   | 34 ++++++++
 memopol/views/dossier_detail.py               | 10 +++
 memopol/views/dossier_list.py                 | 18 ++++
 memopol/views/group_list.py                   | 23 +++++
 .../representative_detail.py}                 | 64 +++++---------
 memopol/views/representative_list.py          | 86 +++++++++++++++++++
 memopol/views/representative_mixin.py         | 59 +++++++++++++
 .../tests/test_functional.py                  |  3 +-
 representatives_positions/views.py            |  6 +-
 templates/_header.haml                        | 14 +--
 templates/home.haml                           |  2 +-
 .../_representative_block.haml                |  6 +-
 templates/representatives/group_list.haml     |  8 +-
 .../representative_detail.haml                |  5 +-
 .../representatives/representative_grid.haml  | 11 +--
 .../representatives/representative_list.haml  | 11 +--
 .../representatives_votes/dossier_list.haml   |  2 +-
 27 files changed, 431 insertions(+), 125 deletions(-)
 create mode 100644 memopol/views/__init__.py
 create mode 100644 memopol/views/dossier_ac.py
 create mode 100644 memopol/views/dossier_detail.py
 create mode 100644 memopol/views/dossier_list.py
 create mode 100644 memopol/views/group_list.py
 rename memopol/{views.py => views/representative_detail.py} (50%)
 create mode 100644 memopol/views/representative_list.py
 create mode 100644 memopol/views/representative_mixin.py

diff --git a/memopol/api.py b/memopol/api.py
index 40ae7fc6..cf4c0702 100644
--- a/memopol/api.py
+++ b/memopol/api.py
@@ -23,14 +23,16 @@ from representatives_recommendations.api import (
 
 router = routers.DefaultRouter()
 
-router.register(r'constituencies', ConstituencyViewSet)
-router.register(r'dossiers', DossierViewSet)
-router.register(r'dossier_scores', DossierScoreViewSet)
-router.register(r'groups', GroupViewSet)
-router.register(r'mandates', MandateViewSet)
-router.register(r'proposals', ProposalViewSet)
-router.register(r'recommendations', RecommendationViewSet)
-router.register(r'representatives', RepresentativeViewSet)
-router.register(r'scores', RepresentativeScoreViewSet)
-router.register(r'vote_scores', VoteScoreViewSet)
-router.register(r'votes', VoteViewSet)
+router.register(r'constituencies', ConstituencyViewSet, 'api-constituency')
+router.register(r'dossiers', DossierViewSet, 'api-dossier')
+router.register(r'dossier_scores', DossierScoreViewSet, 'api-dossierscore')
+router.register(r'groups', GroupViewSet, 'api-group')
+router.register(r'mandates', MandateViewSet, 'api-mandate')
+router.register(r'proposals', ProposalViewSet, 'api-proposal')
+router.register(r'recommendations', RecommendationViewSet,
+    'api-recommendation')
+router.register(r'representatives', RepresentativeViewSet,
+    'api-representative')
+router.register(r'scores', RepresentativeScoreViewSet, 'api-score')
+router.register(r'vote_scores', VoteScoreViewSet, 'api-votescore')
+router.register(r'votes', VoteViewSet, 'api-vote')
diff --git a/memopol/templatetags/memopol_tags.py b/memopol/templatetags/memopol_tags.py
index 6c26fea6..840124ba 100644
--- a/memopol/templatetags/memopol_tags.py
+++ b/memopol/templatetags/memopol_tags.py
@@ -1,7 +1,11 @@
 # coding: utf-8
+
+import re
+
 from django import template
+from django.core.urlresolvers import reverse
 from django.utils.safestring import mark_safe
-import re
+from django.utils.html import escape
 
 register = template.Library()
 link = '<a class="{network}-link" href="{url}" target="_blank">{label}</a>'
@@ -30,3 +34,34 @@ def website_link(url):
 def email_link(address):
     return mark_safe(link.format(network='email', url='mailto:%s' % address,
         label=address))
+
+
+@register.simple_tag
+def group_url(group):
+    if group.kind == 'chamber' or group.chamber is None:
+        return escape(reverse('representative-list', kwargs={
+            'group_kind': group.kind,
+            'group': group.name
+        }))
+    else:
+        return escape(reverse('representative-list', kwargs={
+            'group_kind': group.kind,
+            'chamber': group.chamber.name,
+            'group': group.name
+        }))
+
+
+@register.simple_tag
+def chamber_url(chamber):
+    return escape(reverse('representative-list', kwargs={
+        'group_kind': 'chamber',
+        'group': chamber.name
+    }))
+
+
+@register.simple_tag
+def country_url(country):
+    return escape(reverse('representative-list', kwargs={
+        'group_kind': 'country',
+        'group': country.name
+    }))
diff --git a/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby12_displaylist/content b/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby12_displaylist/content
index 3506bfbb..f2b7e7dd 100644
--- a/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby12_displaylist/content
+++ b/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby12_displaylist/content
@@ -43,27 +43,27 @@
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/country/'>
+      <a href='/legislature/group/country/'>
         Countries
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/chamber/'>
+      <a href='/legislature/group/chamber/'>
         Chambers
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/group/'>
+      <a href='/legislature/group/group/'>
         Parties
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/delegation/'>
+      <a href='/legislature/group/delegation/'>
         Delegations
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/committee/'>
+      <a href='/legislature/group/committee/'>
         Committees
       </a>
     </li>
diff --git a/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby12_displaylist_searchjoly/content b/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby12_displaylist_searchjoly/content
index 9daa5bb9..9348085d 100644
--- a/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby12_displaylist_searchjoly/content
+++ b/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby12_displaylist_searchjoly/content
@@ -43,27 +43,27 @@
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/country/'>
+      <a href='/legislature/group/country/'>
         Countries
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/chamber/'>
+      <a href='/legislature/group/chamber/'>
         Chambers
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/group/'>
+      <a href='/legislature/group/group/'>
         Parties
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/delegation/'>
+      <a href='/legislature/group/delegation/'>
         Delegations
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/committee/'>
+      <a href='/legislature/group/committee/'>
         Committees
       </a>
     </li>
diff --git a/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby24_displaygrid/content b/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby24_displaygrid/content
index e9bc5161..9e22cc9f 100644
--- a/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby24_displaygrid/content
+++ b/memopol/tests/response_fixtures/RepresentativeListTest.test_page1_paginateby24_displaygrid/content
@@ -43,27 +43,27 @@
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/country/'>
+      <a href='/legislature/group/country/'>
         Countries
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/chamber/'>
+      <a href='/legislature/group/chamber/'>
         Chambers
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/group/'>
+      <a href='/legislature/group/group/'>
         Parties
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/delegation/'>
+      <a href='/legislature/group/delegation/'>
         Delegations
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/committee/'>
+      <a href='/legislature/group/committee/'>
         Committees
       </a>
     </li>
diff --git a/memopol/tests/response_fixtures/RepresentativeListTest.test_page2_paginateby12_displaylist/content b/memopol/tests/response_fixtures/RepresentativeListTest.test_page2_paginateby12_displaylist/content
index 211f4a3c..e9f36f93 100644
--- a/memopol/tests/response_fixtures/RepresentativeListTest.test_page2_paginateby12_displaylist/content
+++ b/memopol/tests/response_fixtures/RepresentativeListTest.test_page2_paginateby12_displaylist/content
@@ -43,27 +43,27 @@
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/country/'>
+      <a href='/legislature/group/country/'>
         Countries
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/chamber/'>
+      <a href='/legislature/group/chamber/'>
         Chambers
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/group/'>
+      <a href='/legislature/group/group/'>
         Parties
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/delegation/'>
+      <a href='/legislature/group/delegation/'>
         Delegations
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/committee/'>
+      <a href='/legislature/group/committee/'>
         Committees
       </a>
     </li>
diff --git a/memopol/tests/response_fixtures/RepresentativeListTest.test_page2_paginateby24_displaylist/content b/memopol/tests/response_fixtures/RepresentativeListTest.test_page2_paginateby24_displaylist/content
index 303c9580..9de3976c 100644
--- a/memopol/tests/response_fixtures/RepresentativeListTest.test_page2_paginateby24_displaylist/content
+++ b/memopol/tests/response_fixtures/RepresentativeListTest.test_page2_paginateby24_displaylist/content
@@ -43,27 +43,27 @@
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/country/'>
+      <a href='/legislature/group/country/'>
         Countries
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/chamber/'>
+      <a href='/legislature/group/chamber/'>
         Chambers
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/group/'>
+      <a href='/legislature/group/group/'>
         Parties
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/delegation/'>
+      <a href='/legislature/group/delegation/'>
         Delegations
       </a>
     </li>
     <li>
-      <a href='/legislature/groups/committee/'>
+      <a href='/legislature/group/committee/'>
         Committees
       </a>
     </li>
diff --git a/memopol/tests/test_representatives_list.py b/memopol/tests/test_representatives_list.py
index f4f3cbb9..04f28f38 100644
--- a/memopol/tests/test_representatives_list.py
+++ b/memopol/tests/test_representatives_list.py
@@ -1,15 +1,36 @@
 # -*- coding: utf8 -*-
+
 from django.test import TestCase
 
 from responsediff.response import Response
 
+
 from .base import UrlGetTestMixin
+from representatives.models import Representative
+from ..views.representative_mixin import RepresentativeViewMixin
 
 
 class RepresentativeListTest(UrlGetTestMixin, TestCase):
     fixtures = ['smaller_sample.json']
     url = '/legislature/representative/'
 
+    def test_prefetch_profile(self):
+        test = RepresentativeViewMixin()
+        reps = test.prefetch_for_representative_country_and_main_mandate(
+            Representative.objects.order_by('pk'))
+
+        with self.assertNumQueries(2):
+            # Cast to list to avoid [index] to cast a select with an offset
+            # below !
+            reps = [test.add_representative_country_and_main_mandate(r)
+                    for r in reps]
+
+            assert reps[0].country.code == 'GB'
+            assert reps[0].main_mandate.pk == 3318
+
+            assert reps[1].country.code == 'FI'
+            assert reps[1].main_mandate.pk == 5545
+
     def functional_test(self, page, paginate_by, display, search=''):
         url = '%s?page=%s&search=%s' % (self.url, page, search)
 
diff --git a/memopol/urls.py b/memopol/urls.py
index 1f5931bc..6175d54b 100644
--- a/memopol/urls.py
+++ b/memopol/urls.py
@@ -3,7 +3,13 @@ from django.conf.urls import include, url
 from django.contrib import admin
 from django.views import generic
 
-import views
+from views.dossier_ac import DossierAutocomplete, ProposalAutocomplete
+from views.dossier_detail import DossierDetail
+from views.dossier_list import DossierList
+from views.group_list import GroupList
+from views.representative_detail import RepresentativeDetail
+from views.representative_list import RepresentativeList
+
 import api
 
 admin.autodiscover()
@@ -11,32 +17,58 @@ admin.autodiscover()
 urlpatterns = [
     # Project-specific overrides
     url(
-        r'^legislature/representative/(?P<group_kind>\w+)/(?P<chamber>.+)/' +
+        r'^legislature/representatives?/(?P<group_kind>\w+)/(?P<chamber>.+)/' +
         r'(?P<group>.+)/$',
-        views.RepresentativeList.as_view(),
+        RepresentativeList.as_view(),
+        name='representative-list'
+    ),
+    url(
+        r'^legislature/representatives?/(?P<group_kind>\w+)/(?P<group>.+)/$',
+        RepresentativeList.as_view(),
+        name='representative-list'
+    ),
+    url(
+        r'^legislature/representatives?/(?P<slug>[-\w]+)/$',
+        RepresentativeDetail.as_view(),
+        name='representative-detail'
+    ),
+    url(
+        r'^legislature/groups?/$',
+        GroupList.as_view(),
+        name='group-list'
+    ),
+    url(
+        r'^legislature/groups?/(?P<kind>\w+)/$',
+        GroupList.as_view(),
+        name='group-list'
+    ),
+    url(
+        r'^legislature/representatives?/$',
+        RepresentativeList.as_view(),
+        name='representative-list'
     ),
     url(
-        r'^legislature/representative/(?P<group_kind>\w+)/(?P<group>.+)/$',
-        views.RepresentativeList.as_view(),
+        r'^votes/dossiers?/$',
+        DossierList.as_view(),
+        name='dossier-list'
     ),
     url(
-        r'^legislature/representative/(?P<slug>[-\w]+)/$',
-        views.RepresentativeDetail.as_view(),
+        r'^votes/dossiers?/(?P<pk>\d+)/$',
+        DossierDetail.as_view(),
+        name='dossier-detail'
     ),
     url(
-        r'^legislature/representative/$',
-        views.RepresentativeList.as_view(),
+        r'^votes/autocomplete/dossier/$',
+        DossierAutocomplete.as_view(),
+        name='dossier-autocomplete',
     ),
     url(
-        r'^votes/dossier/$',
-        views.DossierList.as_view(),
+        r'^votes/autocomplete/proposal/$',
+        ProposalAutocomplete.as_view(),
+        name='proposal-autocomplete',
     ),
 
     url(r'^admin/', include(admin.site.urls)),
-    url(r'^legislature/', include('representatives.urls',
-        namespace='representatives')),
-    url(r'^votes/', include('representatives_votes.urls',
-        namespace='representatives_votes')),
     url(r'^positions/', include('representatives_positions.urls',
         namespace='representatives_positions')),
     url(r'^api/', include(api.router.urls)),
diff --git a/memopol/views/__init__.py b/memopol/views/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/memopol/views/dossier_ac.py b/memopol/views/dossier_ac.py
new file mode 100644
index 00000000..03c8525b
--- /dev/null
+++ b/memopol/views/dossier_ac.py
@@ -0,0 +1,34 @@
+# coding: utf-8
+
+from dal import autocomplete
+
+from django.db.models import Q
+
+from representatives_votes.models import Dossier, Proposal
+
+
+class DossierAutocomplete(autocomplete.Select2QuerySetView):
+    def get_queryset(self):
+        qs = Dossier.objects.all()
+
+        if self.q:
+            qs = qs.filter(
+                Q(title__icontains=self.q) |
+                Q(reference__icontains=self.q)
+            )
+
+        return qs
+
+
+class ProposalAutocomplete(autocomplete.Select2QuerySetView):
+    def get_queryset(self):
+        qs = Proposal.objects.all()
+
+        if self.q:
+            qs = qs.filter(
+                Q(dossier__title__icontains=self.q) |
+                Q(title__icontains=self.q) |
+                Q(reference__icontains=self.q)
+            )
+
+        return qs
diff --git a/memopol/views/dossier_detail.py b/memopol/views/dossier_detail.py
new file mode 100644
index 00000000..64f43195
--- /dev/null
+++ b/memopol/views/dossier_detail.py
@@ -0,0 +1,10 @@
+# coding: utf-8
+
+from django.views import generic
+
+from representatives_votes.models import Dossier
+
+
+class DossierDetail(generic.DetailView):
+
+    model = Dossier
diff --git a/memopol/views/dossier_list.py b/memopol/views/dossier_list.py
new file mode 100644
index 00000000..63335b99
--- /dev/null
+++ b/memopol/views/dossier_list.py
@@ -0,0 +1,18 @@
+# coding: utf-8
+
+from core.views import PaginationMixin
+
+from django.db.models import Count
+from django.views import generic
+
+from representatives_votes.models import Dossier
+
+
+class DossierList(PaginationMixin, generic.ListView):
+
+    queryset = Dossier.objects.prefetch_related(
+        'proposals',
+        'proposals__recommendation'
+    ).annotate(
+        nb_recomm=Count('proposals__recommendation')
+    ).order_by('-nb_recomm', '-reference')
diff --git a/memopol/views/group_list.py b/memopol/views/group_list.py
new file mode 100644
index 00000000..eb500f71
--- /dev/null
+++ b/memopol/views/group_list.py
@@ -0,0 +1,23 @@
+# coding: utf-8
+
+import datetime
+
+from django.db import models
+from django.views import generic
+
+from representatives.models import Group
+
+
+class GroupList(generic.ListView):
+
+    def get_queryset(self):
+        qs = Group.objects.filter(
+            models.Q(mandates__end_date__gte=datetime.date.today()) |
+            models.Q(mandates__end_date__isnull=True)
+        )
+
+        kind = self.kwargs.get('kind', None)
+        if kind:
+            qs = qs.filter(kind=kind).distinct()
+
+        return qs.select_related('chamber').order_by('chamber__name', 'name')
diff --git a/memopol/views.py b/memopol/views/representative_detail.py
similarity index 50%
rename from memopol/views.py
rename to memopol/views/representative_detail.py
index a3f1e8da..7b484e34 100644
--- a/memopol/views.py
+++ b/memopol/views/representative_detail.py
@@ -1,49 +1,27 @@
-# Project specific "glue" coupling of all apps
+# coding: utf-8
+
 from django.db import models
-from django.db.models import Count
+from django.views import generic
 
-from core.views import GridListMixin, PaginationMixin, CSVDownloadMixin
-from representatives import views as representatives_views
-from representatives.models import (Representative, Address, Phone, WebSite)
-from representatives_votes import views as representatives_votes_views
-from representatives_votes.models import Dossier, Proposal
+from representatives.models import Representative, Address, Phone, WebSite
 from representatives_positions.forms import PositionForm
 from representatives_recommendations.models import VoteScore
+from representatives_votes.models import Proposal
 
+from .representative_mixin import RepresentativeViewMixin
 
-class RepresentativeList(
-    CSVDownloadMixin,
-    GridListMixin,
-    PaginationMixin,
-    representatives_views.RepresentativeList
-):
-
-    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,
-        )
-
-    queryset = Representative.objects.filter(
-        active=True).select_related('score')
 
+class RepresentativeDetail(RepresentativeViewMixin, generic.DetailView):
 
-class RepresentativeDetail(representatives_views.RepresentativeDetail):
     queryset = Representative.objects.select_related('score')
 
     def get_queryset(self):
+        qs = super(RepresentativeDetail, self).get_queryset()
+
+        qs = self.prefetch_for_representative_country_and_main_mandate(qs)
+
         social = ['twitter', 'facebook']
-        qs = super(RepresentativeDetail, self).get_queryset().prefetch_related(
+        qs = qs.prefetch_related(
             'email_set',
             models.Prefetch(
                 'website_set',
@@ -71,21 +49,21 @@ class RepresentativeDetail(representatives_views.RepresentativeDetail):
                     '-proposal__datetime')
             )
         )
+
         return qs
 
     def get_context_data(self, **kwargs):
         c = super(RepresentativeDetail, self).get_context_data(**kwargs)
+
+        self.add_representative_country_and_main_mandate(c['object'])
+
+        c['votes'] = c['object'].votes.all()
+        c['mandates'] = c['object'].mandates.all()
+        c['positions'] = c['object'].positions.filter(
+            published=True).prefetch_related('tags')
+
         c['position_form'] = PositionForm(
             initial={'representative': self.object.pk})
         self.add_representative_country_and_main_mandate(c['object'])
 
         return c
-
-
-class DossierList(PaginationMixin, representatives_votes_views.DossierList):
-    queryset = Dossier.objects.prefetch_related(
-        'proposals',
-        'proposals__recommendation'
-    ).annotate(
-        nb_recomm=Count('proposals__recommendation')
-    ).order_by('-nb_recomm', '-reference')
diff --git a/memopol/views/representative_list.py b/memopol/views/representative_list.py
new file mode 100644
index 00000000..6b034abb
--- /dev/null
+++ b/memopol/views/representative_list.py
@@ -0,0 +1,86 @@
+# coding: utf-8
+
+from core.views import GridListMixin, PaginationMixin, CSVDownloadMixin
+
+import datetime
+
+from django.db import models
+from django.utils.text import slugify
+from django.views import generic
+
+from representatives.models import Group, Representative
+
+from .representative_mixin import RepresentativeViewMixin
+
+
+class RepresentativeList(CSVDownloadMixin, GridListMixin, PaginationMixin,
+                         RepresentativeViewMixin, generic.ListView):
+
+    csv_name = 'meps.csv'
+    queryset = Representative.objects.filter(
+        active=True).select_related('score')
+
+    def get_context_data(self, **kwargs):
+        c = super(RepresentativeList, self).get_context_data(**kwargs)
+
+        c['object_list'] = [
+            self.add_representative_country_and_main_mandate(r)
+            for r in c['object_list']
+        ]
+
+        return c
+
+    def search_filter(self, qs):
+        search = self.request.GET.get('search', None)
+        if search:
+            qs = qs.filter(slug__icontains=slugify(search))
+        return qs
+
+    def group_filter(self, qs):
+        group_kind = self.kwargs.get('group_kind', None)
+        chamber = self.kwargs.get('chamber', None)
+        group = self.kwargs.get('group', None)
+        today = datetime.date.today()
+
+        if group_kind and group:
+            if group.isnumeric():
+                group_qs = Group.objects.filter(
+                    id=int(group)
+                )
+            else:
+                group_qs = Group.objects.filter(
+                    name=group,
+                    kind=group_kind
+                )
+
+            if chamber:
+                group_qs = group_qs.filter(chamber__name=chamber)
+
+            qs = qs.filter(
+                models.Q(mandates__end_date__gte=today) |
+                models.Q(mandates__end_date__isnull=True),
+                mandates__group__in=group_qs
+            )
+
+        return qs
+
+    def get_queryset(self):
+        qs = super(RepresentativeList, self).get_queryset()
+        qs = self.group_filter(qs)
+        qs = self.search_filter(qs)
+        qs = self.prefetch_for_representative_country_and_main_mandate(qs)
+        return qs
+
+    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,
+        )
diff --git a/memopol/views/representative_mixin.py b/memopol/views/representative_mixin.py
new file mode 100644
index 00000000..98b27d40
--- /dev/null
+++ b/memopol/views/representative_mixin.py
@@ -0,0 +1,59 @@
+# coding: utf-8
+
+import datetime
+
+from django.db import models
+
+from representatives.models import Mandate
+
+
+class RepresentativeViewMixin(object):
+    """
+    A view mixin to add pre-fetched main_mandate and country to Representative
+
+    If a Representative was fetched from a QuerySet that have been through
+    prefetch_for_representative_country_and_main_mandate(), then
+    add_representative_country_and_main_mandate(representative) adds the
+    ``.country`` and ``.main_mandate`` properties "for free" - the prefetch
+    methods adds an extra query, but gets all.
+    """
+
+    def prefetch_for_representative_country_and_main_mandate(self, queryset):
+        """
+        Prefetch Mandates with their Group and Constituency with Country.
+        """
+        mandates = Mandate.objects.order_by(
+            '-end_date').select_related('constituency__country', 'group',
+            'group__chamber')
+        return queryset.prefetch_related(
+            models.Prefetch('mandates', queryset=mandates))
+
+    def add_representative_country_and_main_mandate(self, representative):
+        """
+        Set representative country, main_mandate and chamber.
+
+        Note that this will butcher your database if you don't use
+        self.prefetch_related.
+        """
+        today = datetime.date.today()
+
+        representative.country = None
+        representative.main_mandate = None
+
+        for m in representative.mandates.all():
+            if m.constituency.country_id and not representative.country:
+                representative.country = m.constituency.country
+
+            if ((m.end_date is None or m.end_date > today) and
+                    m.group.kind == 'group' and
+                    not representative.main_mandate):
+
+                representative.main_mandate = m
+
+            if representative.country and representative.main_mandate:
+                break
+
+        if representative.main_mandate:
+            representative.chamber = representative.main_mandate.group.chamber
+
+        return representative
diff --git a/representatives_positions/tests/test_functional.py b/representatives_positions/tests/test_functional.py
index 2925229f..f134e41c 100644
--- a/representatives_positions/tests/test_functional.py
+++ b/representatives_positions/tests/test_functional.py
@@ -29,7 +29,8 @@ class PositionTest(TestCase):
     def test_create_position(self):
         response = self.client.post(self.create_url, self.fixture)
         assert response.status_code == 302
-        expected = 'http://testserver%s' % self.mep.get_absolute_url()
+        mep_url = reverse('representative-detail', args=(self.mep.slug,))
+        expected = 'http://testserver%s' % mep_url
         assert response['Location'] == expected
 
         result = Position.objects.get(text='%stext' % self.id())
diff --git a/representatives_positions/views.py b/representatives_positions/views.py
index 3b24ef11..4d7d2df1 100644
--- a/representatives_positions/views.py
+++ b/representatives_positions/views.py
@@ -1,7 +1,8 @@
 from django.views import generic
 from django.db import models
+from django.core.urlresolvers import reverse
 
-from representatives.views import RepresentativeViewMixin
+from memopol.views.representative_mixin import RepresentativeViewMixin
 from representatives.models import Mandate
 
 from .models import Position
@@ -13,7 +14,8 @@ class PositionCreate(generic.CreateView):
     form_class = PositionForm
 
     def get_success_url(self):
-        return self.object.representative.get_absolute_url()
+        return reverse('representative-detail',
+            args=(self.object.representative.slug,))
 
 
 class PositionDetail(RepresentativeViewMixin, generic.DetailView):
diff --git a/templates/_header.haml b/templates/_header.haml
index b0dd05f5..ae7cb83a 100644
--- a/templates/_header.haml
+++ b/templates/_header.haml
@@ -15,23 +15,23 @@
 #nav.container-fluid
   %ul.nav
     %li
-      %a{href: "{% url 'representatives:representative-list' %}"}
+      %a{href: "{% url 'representative-list' %}"}
         - trans 'Representatives'
     %li
-      %a{href: "{% url 'representatives:group-list' kind='country' %}"}
+      %a{href: "{% url 'group-list' kind='country' %}"}
         - trans 'Countries'
     %li
-      %a{href: "{% url 'representatives:group-list' kind='chamber' %}"}
+      %a{href: "{% url 'group-list' kind='chamber' %}"}
         - trans 'Chambers'
     %li
-      %a{href: "{% url 'representatives:group-list' kind='group' %}"}
+      %a{href: "{% url 'group-list' kind='group' %}"}
         - trans 'Parties'
     %li
-      %a{href: "{% url 'representatives:group-list' kind='delegation' %}"}
+      %a{href: "{% url 'group-list' kind='delegation' %}"}
         - trans 'Delegations'
     %li
-      %a{href: "{% url 'representatives:group-list' kind='committee' %}"}
+      %a{href: "{% url 'group-list' kind='committee' %}"}
         - trans 'Committees'
     %li
-      %a{href: "{% url 'representatives_votes:dossier-list' %}"}
+      %a{href: "{% url 'dossier-list' %}"}
         - trans 'Dossiers'
diff --git a/templates/home.haml b/templates/home.haml
index 9a9ad928..bd263ed2 100644
--- a/templates/home.haml
+++ b/templates/home.haml
@@ -5,7 +5,7 @@
       %p
         Memopol is reachable only in <b>reduced functionality mode</b>.
         By the way, you could access to
-        <a href="{% url 'representatives:representative-list' %}">the list of MEPs</a>.
+        <a href="{% url 'representative-list' %}">the list of MEPs</a>.
       %p
         You can help on building the new Memopol by <a href="https://wiki.laquadrature.net/Projects/Memopol/Roadmap">coding, translating, de    signing, funding, etc...</a>.
     .col-md-4
diff --git a/templates/representatives/_representative_block.haml b/templates/representatives/_representative_block.haml
index ca5797bf..429e2834 100644
--- a/templates/representatives/_representative_block.haml
+++ b/templates/representatives/_representative_block.haml
@@ -22,17 +22,17 @@
         %tr
           %th Chamber
           %td<
-            %a{'href': "{% url 'representatives:representative-list' group_kind='chamber' group=representative.chamber.name %}"}
+            %a{'href': "{% chamber_url representative.chamber %}"}
               = representative.chamber|chamber_icon
         %tr
           %th Country
           %td<
-            %a{:href => "{{ representative.country.get_absolute_url }}"}
+            %a{:href => "{% country_url representative.country %}"}
               = representative.country|country_flag
         %tr
           %th Party
           %td<
-            %a{:href => "{{ representative.main_mandate.group.get_absolute_url }}"}
+            %a{:href => "{% group_url representative.main_mandate.group %}"}
               = representative.main_mandate|mandate_icon
         %tr
           %th Biography
diff --git a/templates/representatives/group_list.haml b/templates/representatives/group_list.haml
index 0e41d42f..b009e463 100644
--- a/templates/representatives/group_list.haml
+++ b/templates/representatives/group_list.haml
@@ -1,22 +1,24 @@
 - extends 'base.html'
 
+- load memopol_tags
 - load representatives_tags
 
+
 - block content
   %table.table
     - for group in object_list
       %tr
         - if group.kind != 'country' and group.kind != 'chamber'
           %td
-            %a{'href': "{% url 'representatives:representative-list' group_kind='chamber' group=group.chamber.name %}"}
+            %a{'href': "{% chamber_url group.chamber %}"}
               = group.chamber|chamber_icon
 
         %td
-          %a{'href': '{{ group.get_absolute_url }}'}
+          %a{'href': "{% group_url group %}"}
             - if group.abbreviation
               ={group.abbreviation}
         %td
-          %a{'href': '{{ group.get_absolute_url }}'}=
+          %a{'href': "{% group_url group %}"}=
             - if group.kind == 'chamber'
               = group|chamber_icon
             - elif group.kind == 'group'
diff --git a/templates/representatives/representative_detail.haml b/templates/representatives/representative_detail.haml
index 8d3214cd..d2818765 100644
--- a/templates/representatives/representative_detail.haml
+++ b/templates/representatives/representative_detail.haml
@@ -1,6 +1,7 @@
 - extends 'base.html'
 
 - load humanize
+- load memopol_tags
 - load representatives_tags
 - load representatives_votes_tags
 - load representatives_recommendations_tags
@@ -48,7 +49,7 @@
       %tr.mandate
         %td= mandate.role
         %td
-          %a{:href => "{{ mandate.group.get_absolute_url }}"}
+          %a{:href => "{% group_url mandate.group %}"}
             {{ mandate.group.name }}
             - if mandate.group.abbreviation
               {{ mandate.group.abbreviation }}
@@ -75,7 +76,7 @@
               %span.label.label-default
                 = tag
           %td
-            %a{:href => '{{position.link}}'}
+            %a{:href => '{{ position.link }}'}
               = position.link
 
     - include 'representatives_positions/_form.html' with form=position_form
diff --git a/templates/representatives/representative_grid.haml b/templates/representatives/representative_grid.haml
index f9270e0e..bc50ad49 100644
--- a/templates/representatives/representative_grid.haml
+++ b/templates/representatives/representative_grid.haml
@@ -1,5 +1,6 @@
 - extends 'representatives/representative_list.html'
 
+- load memopol_tags
 - load representatives_tags
 - load representatives_recommendations_tags
 
@@ -9,20 +10,20 @@
     - for representative in object_list
       .representative_item
         %p.photo
-          %a{'href': "{{ representative.get_absolute_url }}"}
+          %a{'href': "{% url 'representative-detail' representative.slug %}"}
             %img{'src': '={representative.photo}', 'width': '80'}/
         %ul
           %li.name
-            %a{'href': "{{ representative.get_absolute_url }}"}
+            %a{'href': "{% url 'representative-detail' representative.slug %}"}
               = representative.full_name
           %li.chamber
-            %a{'href': "{% url 'representatives:representative-list' group_kind='chamber' group=representative.chamber.name %}"}
+            %a{'href': "{% chamber_url representative.chamber %}"}
               = representative.chamber|chamber_icon
           %li.country
-            %a{'href': "{{ representative.country.get_absolute_url }}"}
+            %a{'href': "{% country_url representative.country %}"}
               = representative.country|country_flag
           %li.mandate
-            %a{'href': "{{ representative.main_mandate.group.get_absolute_url }}"}
+            %a{'href': "{% group_url representative.main_mandate.group %}"}
               = representative.main_mandate.group|group_icon
           %li.score
             = representative.score.score|score_label
diff --git a/templates/representatives/representative_list.haml b/templates/representatives/representative_list.haml
index 5a6c2993..af5c9621 100644
--- a/templates/representatives/representative_list.haml
+++ b/templates/representatives/representative_list.haml
@@ -1,6 +1,7 @@
 - extends 'base.html'
 
 - load i18n
+- load memopol_tags
 - load representatives_tags
 - load representatives_recommendations_tags
 
@@ -39,23 +40,23 @@
       - for representative in object_list
         %tr
           %td
-            %a{'href': "{{ representative.get_absolute_url }}"}
+            %a{'href': "{% url 'representative-detail' representative.slug %}"}
               %img{'src': '={representative.photo}', 'width': '80'}/
 
           %td
-            %a{'href': "{{ representative.get_absolute_url }}"}
+            %a{'href': "{% url 'representative-detail' representative.slug %}"}
               = representative.full_name
 
           %td
-            %a{'href': "{% url 'representatives:representative-list' group_kind='chamber' group=representative.chamber.name %}"}
+            %a{'href': "{% chamber_url representative.chamber %}"}
               = representative.chamber
 
           %td
-            %a{'href': "{{ representative.country.get_absolute_url }}"}
+            %a{'href': "{% country_url representative.country %}"}
               = representative.country
 
           %td
-            %a{'href': "{{ representative.main_mandate.group.get_absolute_url }}"}
+            %a{'href': "{% group_url representative.main_mandate.group %}"}
               = representative.main_mandate.group|group_icon
           %td
             = representative.score.score|score_label
diff --git a/templates/representatives_votes/dossier_list.haml b/templates/representatives_votes/dossier_list.haml
index 15d1bab1..4bc5df7e 100644
--- a/templates/representatives_votes/dossier_list.haml
+++ b/templates/representatives_votes/dossier_list.haml
@@ -17,7 +17,7 @@
     - for dossier in object_list
       %tr
         %td
-          %a{'href': "{{ dossier.get_absolute_url }}"}
+          %a{'href': "{% url 'dossier-detail' dossier.pk %}"}
             {{ dossier.title }}
         %td= dossier.nb_recomm
         %td= dossier.reference
-- 
GitLab