Commit 704fbd93 authored by Arnaud Fabre's avatar Arnaud Fabre

Cleans core app and adds 3rd parties libs

- Cleans core app by removing useless files
- Organizes utils in a utils directory
- Creates a digg-like pagination class
http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-
django
- Adds constance to installed app to manage settings in django admin
console
- Adds debug_toolbar and django_extensions in dev mode
parent dcc0cd3d
*.sqlite3
celerybeat-*
core/static/libs/*
deploy
# libs
static/libs
......
from django.contrib import admin
# Register your models here.
from django.db import models
# Create your models here.
......@@ -6,17 +6,20 @@
<!-- Set the viewport width to device width for mobile -->
<meta name="viewport" content="width=device-width" />
{% block head %}{% endblock %}
<title>{% block title %}Home{% endblock %} - The Political Memory of {{ organization_name }}</title>
<link rel="stylesheet" href="{{ STATIC_URL }}css/reset.css" type="text/css" />
<link rel="stylesheet" href="{{ STATIC_URL }}css/base.css" type="text/css" />
<title>
{% block title %}Home{% endblock %}
- The Political Memory of {{ config.ORGANIZATION_NAME }}</title>
{% load compress %}
{% load staticfiles %}
{% compress css %}
<link rel="stylesheet" href="{% static 'stylesheets/base.min.css' %}" type="text/css" />
{% endcompress %}
</head>
<body {% block bodyattrs %}{% endblock %}>
{% include "core/blocks/header.html" %}
<div class="row main">
<div class="large-12 main columns">
{% block content %}
{% endblock %}
</div>
<div class="container main-container">
{% block content %}
{% endblock %}
</div>
{% include "core/blocks/footer.html" %}
</body>
......
- load i18n
#footer.row
%ul
%li
- blocktrans
Memopol is free software
<a href="https://gitorious.org/memopol2-0">released</a>
under the terms of the
<a href="http://www.gnu.org/licenses/agpl.html">GNU aGPLV3+</a>
%li
-# %a{href: "{% url about %}"}
-# - trans "About"
%li
%a{href: "https://projets.lqdn.fr/projects/mempol"}
- trans "Bug tracking system"
%li
%a{href: "https://projets.lqdn.fr/projects/mempol/issues/new"}
- trans "Report a bug"
%ul
%li
-# %a{href: "{% url api_doc %}"}
-# API
%li
-# %a{href: "{% url contact_form %}"}
-# Contact
-# - if user.is_staff
-# %li
-# %a{href: "{% url admin:index %}"}
-# - trans "Administration"
-# %li
-# %a{href: "{% url admin:comments_comment_changelist %}"}
-# - trans "Comments moderation"
-# %li
-# %a{href: "{% url admin:logout %}"}
-# - trans "Logout"
-# -if user.is_anonymous
-# %li
-# %a{href: "{% url admin:index %}"}
-# - trans "Login"
%ul
%li
{% trans "Provided by" %} <a href="http://www.laquadrature.net/">La Quadrature du Net</a>
#footer.container
- blocktrans
Memopol is free software
<a href="https://gitorious.org/memopol2-0">released</a>
under the terms of the
<a href="http://www.gnu.org/licenses/agpl.html">GNU aGPLV3+</a>
%a{href: "https://projets.lqdn.fr/projects/mempol"}
- trans "Bug tracking system"
%a{href: "https://projets.lqdn.fr/projects/mempol/issues/new"}
- trans "Report a bug"
%br
{% trans "Provided by" %} <a href="http://www.laquadrature.net/">La Quadrature du Net</a>
/ -load memopol_tags cache
- load i18n
- load cache
- load staticfiles
.row.large-12.head
#header
%a{href: "/"}
%img{src: "https://memopol.lqdn.fr/static/img/logo.png"}/
%h1
%a#header_banner{href: "/"}
-trans "Political Memory"
%p
=organization_name
#header.container
%a{href: "/", id: 'logo'}
%img{src: '{% static "images/logo.png" %}'}
%h1
%a#header_banner{href: "/"}
-trans "Political Memory"
%p.organization
=config.ORGANIZATION_NAME
-include "core/blocks/navigation.html"
#nav.container
-include "core/blocks/navigation.html"
%ul.nav-bar
%ul.nav
%li
%a{href: "{% url 'legislature:representative_index' %}"}
Representatives
%li
%em By :
%li
%a{href: "{% url 'legislature:group_index' kind='country' %}"}
Countries
......@@ -17,7 +15,7 @@
%a{href: "{% url 'legislature:group_index' kind='committee' %}"}
Committees
%ul.nav-bar
%ul.nav
%li
%a{href: "{% url 'votes:dossier_index' %}"}
Votes
-# Pagination block display pagination for the `object_list`
`object_list` could be generated with core.view_utils.render_paginate_list
.pagination
%span.step-links
- if object_list.has_previous
%a{'href': '?={queries.urlencode}&page=={object_list.previous_page_number}'} previous
%span.current
Page ={object_list.number} of ={object_list.paginator.num_pages}
- if object_list.has_next
%a{'href': '?={queries.urlencode}&page=={object_list.next_page_number}'} next
.pagination-block
%nav
%ul.pagination.pagination-sm
- if object_list.has_previous
%li
%a{'href': '?={queries.urlencode}&page=={object_list.previous_page_number}',
'aria-label': 'Previous'}
<i aria-hidden="true" class="fa fa-chevron-left"></i>
- for page_num in object_list.page_range
- if not page_num
%li.disabled
%a{'href': ''}
- elif page_num == object_list.number
%li.active
%a{'href': ''}
{{ page_num }}
- else
%li
%a{'href': '?={queries.urlencode}&page=={page_num}'}
{{ page_num }}
- if object_list.has_next
%li
%a{'href': '?={queries.urlencode}&page=={object_list.next_page_number}',
'aria-label': 'Next'}
<i aria-hidden="true" class="fa fa-chevron-right"></i>
%div.count
Number of results : {{ paginator.count }}
%br
Number of displayed results :
{{ paginator.per_page }}
(
- for limit in pagination_limits
%a{'href': '?limit={{ limit }}'}
{{ limit }}
- if not forloop.last
\/
)
.panel.callout
%h3
What is memopol?
%p
Political Memory is a tool designed by La Quadrature du Net to help
European citizens to reach members of European Parliament (MEPs) and
track their voting records on issues related to fundamental
freedoms online. <em><a href="">More...</a></em>
# coding: utf-8
# This file is part of memopol.
#
# memopol is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or any later version.
#
# memopol is distributed in the hope that it will
# be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU General Affero Public
# License along with django-representatives.
# If not, see <http://www.gnu.org/licenses/>.
#
# Copyright (C) 2015 Arnaud Fabre <af@laquadrature.net>
from create_child_instance_from_parent import create_child_instance_from_parent
from render_paginate_list import render_paginate_list
......@@ -19,32 +19,18 @@
# Copyright (C) 2015 Arnaud Fabre <af@laquadrature.net>
from __future__ import absolute_import
from django.core.exceptions import ObjectDoesNotExist
def create_child_instance_from_parent(child_cls, parent_instance):
"""
Create a child model instance from a parent instance
"""
parent_cls = parent_instance.__class__
field = child_cls._meta.get_ancestor_link(parent_cls).column
# We could specify a parent_identifier which is a unique field in
# the parent class that link child to parent
if hasattr(child_cls, 'parent_identifier'):
try:
child_instance = child_cls.objects.get(
**{child_cls.child_parent_identifier:
getattr(parent_instance, child_cls.parent_identifier)}
)
setattr(child_instance, field, parent_instance.pk)
except ObjectDoesNotExist:
child_instance = child_cls(**{
field: parent_instance.pk,
child_cls.child_parent_identifier:
getattr(parent_instance, child_cls.parent_identifier)
})
else:
child_instance = child_cls(**{
field: parent_instance.pk
})
child_instance = child_cls(**{
field: parent_instance.pk
})
child_instance.__dict__.update(parent_instance.__dict__)
child_instance.save()
return child_instance
import math
from django.core.paginator import \
Paginator, QuerySetPaginator, Page, InvalidPage
__all__ = (
'InvalidPage',
'ExPaginator',
'DiggPaginator',
'QuerySetDiggPaginator',
)
class ExPaginator(Paginator):
"""Adds a ``softlimit`` option to ``page()``. If True, querying a
page number larger than max. will not fail, but instead return the
last available page.
This is useful when the data source can not provide an exact count
at all times (like some search engines), meaning the user could
possibly see links to invalid pages at some point which we wouldn't
want to fail as 404s.
>>> items = range(1, 1000)
>>> paginator = ExPaginator(items, 10)
>>> paginator.page(1000)
Traceback (most recent call last):
InvalidPage: That page contains no results
>>> paginator.page(1000, softlimit=True)
<Page 100 of 100>
# [bug] graceful handling of non-int args
>>> paginator.page("str")
Traceback (most recent call last):
InvalidPage: That page number is not an integer
"""
def _ensure_int(self, num, e):
# see Django #7307
try:
return int(num)
except ValueError:
raise e
def page(self, number, softlimit=False):
try:
return super(ExPaginator, self).page(number)
except InvalidPage, e:
number = self._ensure_int(number, e)
if number > self.num_pages and softlimit:
return self.page(self.num_pages, softlimit=False)
else:
raise e
class DiggPaginator(ExPaginator):
"""
Based on Django's default paginator, it adds "Digg-style" page ranges
with a leading block of pages, an optional middle block, and another
block at the end of the page range. They are available as attributes
on the page:
{# with: page = digg_paginator.page(1) #}
{% for num in page.leading_range %} ...
{% for num in page.main_range %} ...
{% for num in page.trailing_range %} ...
Additionally, ``page_range`` contains a nun-numeric ``False`` element
for every transition between two ranges.
{% for num in page.page_range %}
{% if not num %} ... {# literally output dots #}
{% else %}{{ num }}
{% endif %}
{% endfor %}
Additional arguments passed to the constructor allow customization of
how those bocks are constructed:
body=5, tail=2
[1] 2 3 4 5 ... 91 92
|_________| |___|
body tail
|_____|
margin
body=5, tail=2, padding=2
1 2 ... 6 7 [8] 9 10 ... 91 92
|_| |__|
^padding^
|_| |__________| |___|
tail body tail
``margin`` is the minimum number of pages required between two ranges; if
there are less, they are combined into one.
When ``align_left`` is set to ``True``, the paginator operates in a
special mode that always skips the right tail, e.g. does not display the
end block unless necessary. This is useful for situations in which the
exact number of items/pages is not actually known.
# odd body length
>>> print DiggPaginator(range(1,1000), 10, body=5).page(1)
1 2 3 4 5 ... 99 100
>>> print DiggPaginator(range(1,1000), 10, body=5).page(100)
1 2 ... 96 97 98 99 100
# even body length
>>> print DiggPaginator(range(1,1000), 10, body=6).page(1)
1 2 3 4 5 6 ... 99 100
>>> print DiggPaginator(range(1,1000), 10, body=6).page(100)
1 2 ... 95 96 97 98 99 100
# leading range and main range are combined when close; note how
# we have varying body and padding values, and their effect.
>>> print DiggPaginator(range(1,1000), 10, body=5, padding=2, margin=2).page(3)
1 2 3 4 5 ... 99 100
>>> print DiggPaginator(range(1,1000), 10, body=6, padding=2, margin=2).page(4)
1 2 3 4 5 6 ... 99 100
>>> print DiggPaginator(range(1,1000), 10, body=5, padding=1, margin=2).page(6)
1 2 3 4 5 6 7 ... 99 100
>>> print DiggPaginator(range(1,1000), 10, body=5, padding=2, margin=2).page(7)
1 2 ... 5 6 7 8 9 ... 99 100
>>> print DiggPaginator(range(1,1000), 10, body=5, padding=1, margin=2).page(7)
1 2 ... 5 6 7 8 9 ... 99 100
# the trailing range works the same
>>> print DiggPaginator(range(1,1000), 10, body=5, padding=2, margin=2, ).page(98)
1 2 ... 96 97 98 99 100
>>> print DiggPaginator(range(1,1000), 10, body=6, padding=2, margin=2, ).page(97)
1 2 ... 95 96 97 98 99 100
>>> print DiggPaginator(range(1,1000), 10, body=5, padding=1, margin=2, ).page(95)
1 2 ... 94 95 96 97 98 99 100
>>> print DiggPaginator(range(1,1000), 10, body=5, padding=2, margin=2, ).page(94)
1 2 ... 92 93 94 95 96 ... 99 100
>>> print DiggPaginator(range(1,1000), 10, body=5, padding=1, margin=2, ).page(94)
1 2 ... 92 93 94 95 96 ... 99 100
# all three ranges may be combined as well
>>> print DiggPaginator(range(1,151), 10, body=6, padding=2).page(7)
1 2 3 4 5 6 7 8 9 ... 14 15
>>> print DiggPaginator(range(1,151), 10, body=6, padding=2).page(8)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
>>> print DiggPaginator(range(1,151), 10, body=6, padding=1).page(8)
1 2 3 4 5 6 7 8 9 ... 14 15
# no leading or trailing ranges might be required if there are only
# a very small number of pages
>>> print DiggPaginator(range(1,80), 10, body=10).page(1)
1 2 3 4 5 6 7 8
>>> print DiggPaginator(range(1,80), 10, body=10).page(8)
1 2 3 4 5 6 7 8
>>> print DiggPaginator(range(1,12), 10, body=5).page(1)
1 2
# test left align mode
>>> print DiggPaginator(range(1,1000), 10, body=5, align_left=True).page(1)
1 2 3 4 5
>>> print DiggPaginator(range(1,1000), 10, body=5, align_left=True).page(50)
1 2 ... 48 49 50 51 52
>>> print DiggPaginator(range(1,1000), 10, body=5, align_left=True).page(97)
1 2 ... 95 96 97 98 99
>>> print DiggPaginator(range(1,1000), 10, body=5, align_left=True).page(100)
1 2 ... 96 97 98 99 100
# padding: default value
>>> DiggPaginator(range(1,1000), 10, body=10).padding
4
# padding: automatic reduction
>>> DiggPaginator(range(1,1000), 10, body=5).padding
2
>>> DiggPaginator(range(1,1000), 10, body=6).padding
2
# padding: sanity check
>>> DiggPaginator(range(1,1000), 10, body=5, padding=3)
Traceback (most recent call last):
ValueError: padding too large for body (max 2)
"""
def __init__(self, *args, **kwargs):
self.body = kwargs.pop('body', 10)
self.tail = kwargs.pop('tail', 2)
self.align_left = kwargs.pop('align_left', False)
self.margin = kwargs.pop('margin', 4) # TODO: make the default relative to body?
# validate padding value
max_padding = int(math.ceil(self.body/2.0)-1)
self.padding = kwargs.pop('padding', min(4, max_padding))
if self.padding > max_padding:
raise ValueError('padding too large for body (max %d)'%max_padding)
super(DiggPaginator, self).__init__(*args, **kwargs)
def page(self, number, *args, **kwargs):
"""Return a standard ``Page`` instance with custom, digg-specific
page ranges attached.
"""
page = super(DiggPaginator, self).page(number, *args, **kwargs)
number = int(number) # we know this will work
# easier access
num_pages, body, tail, padding, margin = \
self.num_pages, self.body, self.tail, self.padding, self.margin
# put active page in middle of main range
main_range = map(int, [
math.floor(number-body/2.0)+1, # +1 = shift odd body to right
math.floor(number+body/2.0)])
# adjust bounds
if main_range[0] < 1:
main_range = map(abs(main_range[0]-1).__add__, main_range)
if main_range[1] > num_pages:
main_range = map((num_pages-main_range[1]).__add__, main_range)
# Determine leading and trailing ranges; if possible and appropriate,
# combine them with the main range, in which case the resulting main
# block might end up considerable larger than requested. While we
# can't guarantee the exact size in those cases, we can at least try
# to come as close as possible: we can reduce the other boundary to
# max padding, instead of using half the body size, which would
# otherwise be the case. If the padding is large enough, this will
# of course have no effect.
# Example:
# total pages=100, page=4, body=5, (default padding=2)
# 1 2 3 [4] 5 6 ... 99 100
# total pages=100, page=4, body=5, padding=1
# 1 2 3 [4] 5 ... 99 100
# If it were not for this adjustment, both cases would result in the
# first output, regardless of the padding value.
if main_range[0] <= tail+margin:
leading = []
main_range = [1, max(body, min(number+padding, main_range[1]))]
main_range[0] = 1
else:
leading = range(1, tail+1)
# basically same for trailing range, but not in ``left_align`` mode
if self.align_left:
trailing = []
else:
if main_range[1] >= num_pages-(tail+margin)+1:
trailing = []
if not leading:
# ... but handle the special case of neither leading nor
# trailing ranges; otherwise, we would now modify the
# main range low bound, which we just set in the previous
# section, again.
main_range = [1, num_pages]
else:
main_range = [min(num_pages-body+1, max(number-padding, main_range[0])), num_pages]
else:
trailing = range(num_pages-tail+1, num_pages+1)
# finally, normalize values that are out of bound; this basically
# fixes all the things the above code screwed up in the simple case
# of few enough pages where one range would suffice.
main_range = [max(main_range[0], 1), min(main_range[1], num_pages)]
# make the result of our calculations available as custom ranges
# on the ``Page`` instance.
page.main_range = range(main_range[0], main_range[1]+1)
page.leading_range = leading
page.trailing_range = trailing
page.page_range = reduce(lambda x, y: x+((x and y) and [False])+y,
[page.leading_range, page.main_range, page.trailing_range])
page.__class__ = DiggPage
return page
class DiggPage(Page):
def __str__(self):
return " ... ".join(filter(None, [
" ".join(map(str, self.leading_range)),
" ".join(map(str, self.main_range)),
" ".join(map(str, self.trailing_range))]))
class QuerySetDiggPaginator(DiggPaginator, QuerySetPaginator):
pass
if __name__ == "__main__":
import doctest
doctest.testmod()
\ No newline at end of file
......@@ -20,16 +20,20 @@
from __future__ import absolute_import
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from .digg_paginator import DiggPaginator
from django.shortcuts import render
def render_paginate_list(request, object_list, template_name, num_by_page=30):
def render_paginate_list(request, object_list, template_name):
"""
Render a paginated list of representatives
"""
paginator = Paginator(object_list, num_by_page)
page = request.GET.get('page')
pagination_limits = (10, 20, 50, 100)
num_by_page = request.GET.get('limit', 30)
paginator = DiggPaginator(object_list, num_by_page, body=5)
# paginator = Paginator(object_list, num_by_page)
page = request.GET.get('page', 1)
try:
objects = paginator.page(page)
except PageNotAnInteger:
......@@ -43,8 +47,9 @@ def render_paginate_list(request, object_list, template_name, num_by_page=30):
del queries_without_page['page']
context['queries'] = queries_without_page
context['object_list'] = objects
context['object_count'] = paginator.count
context['paginator'] = paginator
context['pagination_limits'] = pagination_limits
return render(
request,
template_name,
......
from django.shortcuts import render
# coding: utf-8
# This file is part of memopol.
#
# memopol is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or any later version.
#
# memopol is distributed in the hope that it will
# be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero General Public License for more details.