Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Political Memory
memopol
Commits
24f83ba8
Commit
24f83ba8
authored
Sep 03, 2016
by
Nicolas Joyard
Browse files
Merge branch 'v3'
parents
107b5321
52cf504a
Changes
355
Expand all
Hide whitespace changes
Inline
Side-by-side
.gitignore
View file @
24f83ba8
...
...
@@ -22,7 +22,12 @@ memopol_env
pip-log.txt
pip-delete-this-directory.txt
.dpl
db.sqlite
*.sqlite
*.json.xz
log/
/data
data/
# editors
*~
.travis.yml
View file @
24f83ba8
...
...
@@ -15,6 +15,8 @@ python:
services
:
-
postgresql
install
:
-
pip install -U setuptools
-
pip install git+https://github.com/njoyard/django-responsediff.git@fix-selectors-encoding
-
pip install -e .[testing]
before_script
:
-
bin/install_client_deps.sh
...
...
@@ -39,12 +41,3 @@ deploy:
repo
:
political-memory/political_memory
branch
:
master
-
provider
:
openshift
user
:
memopol@laquadrature.net
password
:
secure
:
atDq1NEkHXOsV2gZKeXAIn+PvbL3jduz3WK1qIs7BSHyNbrZMT1OUmvoXXrM8+i5eqW3TNsvp23w0RuD06wxSjHkPl+ZCEXP1Ao98p85UZNCgixxiwZHEhL6Amz5vqueGhv+47VOIKNgNFb9NAtRrWyIdA9xDUiK2oWkMSDmHas=
app
:
v3
domain
:
memopol
deployment_branch
:
v3
on
:
repo
:
political-memory/political_memory
branch
:
v3
bin/install_client_deps.sh
View file @
24f83ba8
...
...
@@ -8,7 +8,7 @@
# starting with 'downloadFromGithub' at the bottom.
# The last argument is expected to be a git ref (ie
# a branch name, tag or commit-ish).
#
#
set
-e
...
...
@@ -37,8 +37,6 @@ set -e
mkdir
-p
${
DEST
}
downloadFromGithub jquery jquery/jquery 2.1.4
downloadFromGithub fontawesome FortAwesome/Font-Awesome v4.3.0
downloadFromGithub flag-icon-css lipis/flag-icon-css 0.7.1
downloadFromGithub bootstrap twbs/bootstrap v3.3.5
echo
"* Done."
bin/quickstart.sh
View file @
24f83ba8
...
...
@@ -39,7 +39,7 @@ export DJANGO_SETTINGS_MODULE=memopol.settings
./manage.py migrate
# Import sample data
./manage.py loaddata memopol/fixtures/
smaller
_sample.json
./manage.py loaddata memopol/fixtures/
data
_sample.json
echo
echo
"You're all set!"
...
...
core/templates/core/blocks/active-filter.haml
deleted
100644 → 0
View file @
107b5321
.active-block
Display :
-
if
active_only
current legislature only /
%a
{
'href'
:
'
?
=
{
queries
.
urlencode
}
&
active_only
=
0
'
}
all data
-
else
%a
{
'href'
:
'
?
=
{
queries
.
urlencode
}
&
active_only
=
1
'
}
current legislature only
=
"/ all data"
\ No newline at end of file
core/templates/core/blocks/active-filter.html
0 → 100644
View file @
24f83ba8
{% block active %}
Display :
{% if active_only %}
current legislature only /
<a
href=
"{queries.urlencode}&active_only=0"
>
all data
</a>
{% else %}
<a
href=
"{queries.urlencode}&active_only=1"
>
current legislature only
</a>
/ all data
{% endif %}
{% endblock %}
core/templates/core/blocks/pagination.haml
deleted
100644 → 0
View file @
107b5321
.pagination-block
%nav
%ul
.pagination.pagination-sm
-
if
page_obj
.
has_previous
%li
%a
{
'href'
:
'
?
=
{
queries
.
urlencode
}
&
page
=
1
',
'
aria
-
label
': '
First
'
}
<i
aria-hidden=
"true"
class=
"fa fa-chevron-circle-left"
></i>
%li
%a
{
'href'
:
'
?
=
{
queries
.
urlencode
}
&
page
==
{
page_obj
.
previous_page_number
}
',
'
aria
-
label
': '
Previous
'
}
<i
aria-hidden=
"true"
class=
"fa fa-chevron-left"
></i>
-
for
p
in
page_range
-
if
p
-
if
p
==
page_obj
.
number
%li
.active
%a
{
'href'
:
''
}
{{ p }}
-
else
%li
%a
{
'href'
:
'
?
=
{
queries
.
urlencode
}
&
page
==
{
p
}
'
}
{{ p }}
-
if
page_obj
.
has_next
%li
%a
{
'href'
:
'
?
=
{
queries
.
urlencode
}
&
page
==
{
page_obj
.
next_page_number
}
',
'
aria
-
label
': '
Next
'
}
<i
aria-hidden=
"true"
class=
"fa fa-chevron-right"
></i>
%li
%a
{
'href'
:
'
?
=
{
queries
.
urlencode
}
&
page
==
{
paginator
.
num_pages
}
',
'
aria
-
label
': '
Last
'
}
<i
aria-hidden=
"true"
class=
"fa fa-chevron-circle-right"
></i>
%div
.count
.count-block
Number of results : {{ paginator.count }}
-
if
active_only
!=
None
-
include
'core/blocks/active-filter.html'
-
if
sort
!=
None
-
include
'core/blocks/sorting.html'
.page-size-block
Number of displayed results :
{{ paginator.per_page }}
(
-
for
limit
in
pagination_limits
%a
{
'href'
:
'
?
=
{
queries
.
urlencode
}
&
paginate_by
=
{{
limit
}}
'
}
<
{{
limit
}}
-
if
not
forloop
.
last
\/
)
-
if
grid_list
-
include
'core/blocks/grid-list.html'
core/templates/core/blocks/pagination.html
0 → 100644
View file @
24f83ba8
{% block pagination %}
<nav>
<ul
class=
"pagination pagination-sm"
>
{% if page_obj.has_previous %}
<li>
<a
href=
"{{queries.urlencode}}&page=1"
aria-label=
"First"
>
<i
aria-hidden=
"true"
class=
"fa fa-chevron-circle-left"
></i>
</a>
</li>
<li>
<a
href=
"{queries.urlencode}&page=={page_obj.previous_page_number}"
aria-label=
"Previous"
>
<i
aria-hidden=
"true"
class=
"fa fa-chevron-left"
></i>
</a>
</li>
{% endif %}
{% for p in page_range %}
{% if p %}
{% if p == page_obj.number %}
<li
class=
"active"
>
<a
href=
''
>
{{ p }}
</a>
</li>
{% else %}
<li>
<a
href=
"{queries.urlencode}&page=={p}"
>
{{ p }}
</a>
</li>
{% endif %}
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li>
<a
href=
""
{
queries.urlencode
}&
page=
={page_obj.next_page_number}"
aria-label=
"Next"
>
<i
aria-hidden=
"true"
class=
"fa fa-chevron-right"
></i>
</a>
</li>
<li>
<a
href=
"{queries.urlencode}&page=={paginator.num_pages}"
aria-label=
"Last"
>
<i
aria-hidden=
"true"
class=
"fa fa-chevron-circle-right"
></i>
</a>
</li>
{% endif %}
</ul>
</nav>
<div
class=
".count"
>
{% block count %}
Number of results : {{ paginator.count }}
{% if active_only != None %}
{% include 'core/blocks/active-filter.html' %}
{% endif %}
{% if sort != None %}
{% include 'core/blocks/sorting.html' %}
{% endif %}
{% endblock %}
{% block page-size %}
Number of displayed results :
{{ paginator.per_page }}
(
{% for limit in pagination_limits %}
<a
href=
"{queries.urlencode}&paginate_by={{ limit }}"
>
{{ limit }}
</a>
{% if not forloop.last %}
/
{% endif %}
{% endfor %}
)
{% if grid_list %}
{% include 'core/blocks/grid-list.html' %}
{% endif %}
{% endblock %}
</div>
{% endblock %}
core/views.py
View file @
24f83ba8
...
...
@@ -53,55 +53,66 @@ class ActiveLegislatureMixin(object):
class
SortMixin
(
object
):
"""
Mixin for views that allow sorting.
The sort_fields attribute should be defined to a {field: label} dict
containing all fields usable for sorting.
The sort_default and sort_default_dir attributes should contain the default
sorting settings.
The sort_modes attribute should be defined to a dict as such:
{
'mode1': {
'order': 42,
'label': 'mode label',
'fields': ['-field1', 'field2', ...]
},
...
}
The sort_default attribute should contain the default sorting mode.
"""
sort_
field
s
=
{}
sort_default
_field
=
None
sort_
default_dir
=
'
asc
'
sort_
mode
s
=
{}
sort_default
=
None
sort_
session_prefix
=
''
def
get
(
self
,
*
args
,
**
kwargs
):
self
.
set_sorting
()
return
super
(
SortMixin
,
self
).
get
(
*
args
,
**
kwargs
)
def
set_sorting
(
self
):
if
'sort_by'
in
self
.
request
.
GET
:
self
.
request
.
session
[
'sort_by'
]
=
self
.
request
.
GET
[
'sort_by'
]
elif
'sort_by'
not
in
self
.
request
.
session
:
self
.
request
.
session
[
'sort_by'
]
=
self
.
sort_default_field
def
_session_get_sort
(
self
):
k
=
'%s_sort'
%
self
.
sort_session_prefix
return
self
.
request
.
session
[
k
]
def
_session_set_sort
(
self
,
value
):
k
=
'%s_sort'
%
self
.
sort_session_prefix
self
.
request
.
session
[
k
]
=
value
if
self
.
request
.
session
[
'sort_by'
]
not
in
self
.
sort_fields
:
self
.
request
.
session
[
'sort_by'
]
=
self
.
sort_default_field
def
_session_sort_exists
(
self
):
k
=
'%s_sort'
%
self
.
sort_session_prefix
return
k
in
self
.
request
.
session
def
set_sorting
(
self
):
if
'sort'
in
self
.
request
.
GET
:
self
.
_session_set_sort
(
self
.
request
.
GET
[
'sort'
])
elif
not
self
.
_session_sort_exists
():
self
.
_session_set_sort
(
self
.
sort_default
)
if
'sort_dir'
in
self
.
request
.
GET
:
self
.
request
.
session
[
'sort_dir'
]
=
self
.
request
.
GET
[
'sort_dir'
]
elif
'sort_dir'
not
in
self
.
request
.
session
:
self
.
request
.
session
[
'sort_dir'
]
=
self
.
sort_default_dir
if
self
.
_session_get_sort
()
not
in
self
.
sort_modes
:
self
.
_session_set_sort
(
self
.
sort_default
)
def
get_context_data
(
self
,
**
kwargs
):
c
=
super
(
SortMixin
,
self
).
get_context_data
(
**
kwargs
)
c
[
'queries'
]
=
copy
(
self
.
request
.
GET
)
if
'sort_by'
in
c
[
'queries'
]:
del
c
[
'queries'
][
'sort_by'
]
if
'sort_dir'
in
c
[
'queries'
]:
del
c
[
'queries'
][
'sort_dir'
]
c
[
'sort_querystring'
]
=
copy
(
self
.
request
.
GET
)
if
'sort'
in
c
[
'sort_querystring'
]:
del
c
[
'sort_querystring'
][
'sort'
]
c
[
'sort'
]
=
{
'
fields'
:
self
.
sort_fields
,
'field'
:
self
.
request
.
session
[
'sort_by'
],
'
dir
'
:
self
.
request
.
session
[
'sort_dir'
],
'
modes'
:
[{
'id'
:
k
,
'label'
:
v
[
'label'
],
'order'
:
v
[
'order'
]}
for
k
,
v
in
self
.
sort_modes
.
iteritems
()
],
'
mode
'
:
self
.
_session_get_sort
()
}
return
c
def
get_queryset
(
self
):
qs
=
super
(
SortMixin
,
self
).
get_queryset
()
if
self
.
request
.
session
[
'sort_by'
]:
qs
=
qs
.
order_by
(
'%s%s'
%
(
'-'
if
self
.
request
.
session
[
'sort_dir'
]
==
'desc'
else
''
,
self
.
request
.
session
[
'sort_by'
]))
if
self
.
_session_get_sort
()
in
self
.
sort_modes
:
mode
=
self
.
sort_modes
[
self
.
_session_get_sort
()]
qs
=
qs
.
order_by
(
*
mode
[
'fields'
])
return
qs
...
...
@@ -138,9 +149,9 @@ class PaginationMixin(object):
c
[
'pagination_limits'
]
=
self
.
pagination_limits
c
[
'paginate_by'
]
=
self
.
request
.
session
[
'paginate_by'
]
c
[
'page_range'
]
=
self
.
get_page_range
(
c
[
'page_obj'
])
c
[
'
queries
'
]
=
copy
(
self
.
request
.
GET
)
if
'page'
in
c
[
'
queries
'
]:
del
c
[
'
queries
'
][
'page'
]
c
[
'
pagination_querystring
'
]
=
copy
(
self
.
request
.
GET
)
if
'page'
in
c
[
'
pagination_querystring
'
]:
del
c
[
'
pagination_querystring
'
][
'page'
]
return
c
...
...
@@ -167,6 +178,12 @@ class GridListMixin(object):
class
CSVDownloadMixin
(
object
):
def
get_context_data
(
self
,
**
kwargs
):
c
=
super
(
CSVDownloadMixin
,
self
).
get_context_data
(
**
kwargs
)
c
[
'csv'
]
=
True
c
[
'csv_querystring'
]
=
copy
(
self
.
request
.
GET
)
return
c
def
get_paginate_by
(
self
,
queryset
):
if
self
.
request
.
GET
.
get
(
'csv'
,
None
)
is
None
:
return
super
(
CSVDownloadMixin
,
self
).
get_paginate_by
(
queryset
)
...
...
memopol/context_processors.py
0 → 100644
View file @
24f83ba8
from
representatives.models
import
Chamber
,
Group
def
search_form_options
(
request
):
d
=
{}
# Note: Those queries needs to be eval in the template so that we can cache
# it efficiently
d
[
'chambers'
]
=
Chamber
.
objects
.
all
()
d
[
'countries'
]
=
Group
.
objects
.
filter
(
kind
=
'country'
)
d
[
'parties'
]
=
Group
.
objects
.
filter
(
kind
=
'group'
)
d
[
'delegations'
]
=
Group
.
objects
.
filter
(
kind
=
'delegation'
)
d
[
'committees'
]
=
Group
.
objects
.
filter
(
kind
=
'committee'
)
return
d
memopol/filters.py
View file @
24f83ba8
# coding: utf-8
from
dal.autocomplete
import
ModelSelect2
import
datetime
from
django.db.models
import
Q
from
django.utils.text
import
slugify
from
django_filters
import
FilterSet
,
MethodFilter
,
ModelChoiceFilter
from
django_filters
import
FilterSet
,
MethodFilter
from
representatives.models
import
Chamber
,
Group
,
Representative
from
representatives.models
import
Representative
from
representatives_votes.models
import
Dossier
from
memopol_themes.models
import
Theme
def
rep_chamber_filter
(
qs
,
value
):
today
=
datetime
.
date
.
today
()
return
qs
.
filter
(
Q
(
mandates__end_date__gte
=
today
)
|
Q
(
mandates__end_date__isnull
=
True
),
mandates__group__chamber
=
value
)
class
RepresentativeFilter
(
FilterSet
):
def
dossier_chamber_filter
(
qs
,
value
):
return
qs
.
filter
(
documents__chamber
=
value
)
search
=
MethodFilter
(
action
=
'search_filter'
)
scoremin
=
MethodFilter
(
action
=
'score_min_filter'
)
scoremax
=
MethodFilter
(
action
=
'score_max_filter'
)
chamber
=
MethodFilter
(
action
=
'chamber_filter'
)
country
=
MethodFilter
(
action
=
'group_filter'
)
party
=
MethodFilter
(
action
=
'group_filter'
)
delegation
=
MethodFilter
(
action
=
'group_filter'
)
committee
=
MethodFilter
(
action
=
'group_filter'
)
class
Meta
:
model
=
Representative
fields
=
[
'search'
,
'chamber'
,
'country'
,
'party'
,
'delegation'
,
'committee'
]
def
group_filter
(
qs
,
value
):
today
=
datetime
.
date
.
today
()
return
qs
.
filter
(
Q
(
mandates__end_date__gte
=
today
)
|
Q
(
mandates__end_date__isnull
=
True
),
mandates__group
=
value
)
def
search_filter
(
self
,
qs
,
value
):
if
len
(
value
)
==
0
:
return
qs
return
qs
.
filter
(
slug__icontains
=
slugify
(
value
))
class
RepresentativeFilter
(
FilterSet
):
def
chamber_filter
(
self
,
qs
,
value
):
if
len
(
value
)
==
0
:
return
qs
search
=
MethodFilter
(
action
=
'search_filter'
)
today
=
datetime
.
date
.
today
()
return
qs
.
filter
(
Q
(
mandates__end_date__gte
=
today
)
|
Q
(
mandates__end_date__isnull
=
True
),
mandates__group__chamber
=
value
)
chamber
=
ModelChoiceFilter
(
queryset
=
Chamber
.
objects
.
all
(),
action
=
rep_chamber_filter
)
def
group_filter
(
self
,
qs
,
value
):
if
len
(
value
)
==
0
:
return
qs
country
=
ModelChoiceFilter
(
queryset
=
Group
.
objects
.
filter
(
kind
=
'country'
),
action
=
group_filter
)
today
=
datetime
.
date
.
today
()
return
qs
.
filter
(
Q
(
mandates__end_date__gte
=
today
)
|
Q
(
mandates__end_date__isnull
=
True
),
mandates__group
=
value
)
group
=
ModelChoiceFilter
(
queryset
=
Group
.
objects
.
exclude
(
kind__in
=
[
'chamber'
,
'country'
]),
action
=
group_filter
,
widget
=
ModelSelect2
(
url
=
'group-autocomplete'
),
label
=
'Party, committee or delegation'
)
def
score_min_filter
(
self
,
qs
,
value
):
if
len
(
value
)
==
0
:
return
qs
class
Meta
:
model
=
Representative
fields
=
[
'search'
,
'chamber'
,
'country'
]
try
:
return
qs
.
filter
(
score__score__gte
=
int
(
value
))
except
ValueError
:
return
qs
def
s
earch
_filter
(
self
,
qs
,
value
):
def
s
core_max
_filter
(
self
,
qs
,
value
):
if
len
(
value
)
==
0
:
return
qs
return
qs
.
filter
(
slug__icontains
=
slugify
(
value
))
try
:
return
qs
.
filter
(
score__score__lte
=
int
(
value
))
except
ValueError
:
return
qs
class
DossierFilter
(
FilterSet
):
search
=
MethodFilter
(
action
=
'search_filter'
)
chamber
=
ModelChoiceFilter
(
queryset
=
Chamber
.
objects
.
all
(),
action
=
dossier_chamber_filter
)
chamber
=
MethodFilter
(
action
=
'chamber_filter'
)
class
Meta
:
model
=
Dossier
...
...
@@ -77,7 +89,14 @@ class DossierFilter(FilterSet):
return
qs
return
qs
.
filter
(
Q
(
title__icontains
=
value
)
|
Q
(
reference__icontains
=
value
))
Q
(
reference__icontains
=
value
)
|
Q
(
documents__link__icontains
=
value
))
def
chamber_filter
(
self
,
qs
,
value
):
if
len
(
value
)
==
0
:
return
qs
return
qs
.
filter
(
documents__chamber
=
value
)
class
ThemeFilter
(
FilterSet
):
...
...
memopol/fixtures/data_sample.json
0 → 100644
View file @
24f83ba8
This diff is collapsed.
Click to expand it.
memopol/fixtures/one_representative.json
deleted
100644 → 0
View file @
107b5321
This diff is collapsed.
Click to expand it.
memopol/fixtures/smaller_sample.json
deleted
100644 → 0
View file @
107b5321
This diff is collapsed.
Click to expand it.
memopol/settings.py
View file @
24f83ba8
...
...
@@ -85,6 +85,7 @@ INSTALLED_APPS = (
'bootstrap3'
,
'datetimewidget'
,
'django_filters'
,
'fontawesome'
,
'rest_framework'
,
'taggit'
,
# ---
...
...
@@ -106,6 +107,20 @@ if DEBUG:
else
:
INSTALLED_APPS
+=
(
'debug_toolbar'
,)
try
:
import
django_extensions
# noqa
except
:
pass
else
:
INSTALLED_APPS
+=
(
'django_extensions'
,)
CACHES
=
{
'default'
:
{
'BACKEND'
:
'django.core.cache.backends.dummy.DummyCache'
,
}
}
MIDDLEWARE_CLASSES
=
(
'django.contrib.sessions.middleware.SessionMiddleware'
,
'django.middleware.common.CommonMiddleware'
,
...
...
@@ -186,8 +201,6 @@ TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')]
TEMPLATE_LOADERS
=
(
'django.template.loaders.filesystem.Loader'
,
'django.template.loaders.app_directories.Loader'
,
'hamlpy.template.loaders.HamlPyFilesystemLoader'
,
'hamlpy.template.loaders.HamlPyAppDirectoriesLoader'
,
)
"""
...
...
@@ -201,6 +214,7 @@ TEMPLATE_LOADERS = (
TEMPLATE_CONTEXT_PROCESSORS
=
global_settings
.
TEMPLATE_CONTEXT_PROCESSORS
+
(
'django.template.context_processors.request'
,
'memopol.context_processors.search_form_options'
,
)
# Static files finders
...
...
@@ -225,7 +239,7 @@ if os.environ.get('OPENSHIFT_LOG_DIR', None):
COMPRESS_PRECOMPILERS
=
(
# ('text/coffeescript', 'coffee --compile --stdio'),
# ('text/less', 'lesscpy {infile}'),
(
'text/x-scss'
,
'django_libsass.SassCompiler'
),
#
('text/x-scss', 'django_libsass.SassCompiler'),
# ('text/x-sass', 'sass {infile} {outfile}'),
# ('text/x-scss', 'sass --scss {infile} {outfile}'),
# ('text/stylus', 'stylus < {infile} > {outfile}'),
...
...
@@ -233,9 +247,6 @@ COMPRESS_PRECOMPILERS = (
)
LIBSASS_SOURCE_COMMENTS
=
False
LOGGING
=
{
'version'
:
1
,
'disable_existing_loggers'
:
False
,
...
...
memopol/templatetags/memopol_tags.py
View file @
24f83ba8
...
...
@@ -88,25 +88,22 @@ def country_flag(country):
# Enable using groups instead of countries
code
=
country
.
code
if
hasattr
(
country
,
'code'
)
else
country
.
abbreviation
return
mark_safe
(
'<span class="flag-icon flag-icon-{code}"></span> {name}'
.
format
(
name
=
country
.
name
,
'<span class="flag-icon flag-icon-{code}"></span>'
.
format
(
code
=
code
.
lower
()))
@
register
.
filter
def
chamber_icon
(
chamber
):
def
chamber_icon
(
chamber
,
tplace
=
'bottom'
):
url
=
static
(
'images/chamber-%s.png'
%
cssify
(
chamber
.
abbreviation
))
return
mark_safe
(
u
'<span class="chamber-icon" style="background-image: url({url})">'
u
'</span> {name}'
.
format
(
name
=
chamber
.
name
,
url
=
url
))
@
register
.
filter
def
chamber_small_icon
(
chamber
):
url
=
static
(
'images/chamber-%s.png'
%
cssify
(
chamber
.
abbreviation
))
return
mark_safe
(
u
'<span class="chamber-icon" style="background-image: url({url})" '
u
'title="{name}"></span>'
.
format
(
name
=
chamber
.
name
,
url
=
url
))
u
'<span class="chamber-icon" style="background-image: url({url})"'
u
' data-toggle="tooltip" data-placement="{place}"'
u
' title="{name}"></span>'
.
format
(
name
=
chamber
.
name
,
url
=
url
,
place
=
tplace
)
)