Commit c05df265 authored by Arnaud Fabre's avatar Arnaud Fabre

Adds postions application

parent 0838d262
...@@ -5,15 +5,20 @@ ...@@ -5,15 +5,20 @@
<link rel="icon" type="image/png" href="{{ STATIC_URL }}img/favicon.ico" /> <link rel="icon" type="image/png" href="{{ STATIC_URL }}img/favicon.ico" />
<!-- Set the viewport width to device width for mobile --> <!-- Set the viewport width to device width for mobile -->
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
{% block head %}{% endblock %}
<title> <title>
{% block title %}Home{% endblock %} {% block title %}Home{% endblock %}
- The Political Memory of {{ config.ORGANIZATION_NAME }}</title> - The Political Memory of {{ config.ORGANIZATION_NAME }}</title>
{% load compress %} {% load compress %}
{% load staticfiles %} {% load staticfiles %}
{% compress css %} {% compress css %}
<link rel="stylesheet" href="{% static 'stylesheets/libs.min.css' %}" type="text/css" />
<link rel="stylesheet" href="{% static 'stylesheets/base.min.css' %}" type="text/css" /> <link rel="stylesheet" href="{% static 'stylesheets/base.min.css' %}" type="text/css" />
{% endcompress %} {% endcompress %}
{% compress js %}
<script type="text/javascript" src="{% static 'libs/jquery/dist/jquery.js' %}"></script>
<script type="text/javascript" src="{% static 'libs/bootstrap/dist/js/bootstrap.js' %}"></script>
{% endcompress %}
{% block head %}{% endblock %}
</head> </head>
<body {% block bodyattrs %}{% endblock %}> <body {% block bodyattrs %}{% endblock %}>
{% include "core/blocks/header.html" %} {% include "core/blocks/header.html" %}
......
%ul.nav %ul.nav
%li %li
%a{href: "{% url 'legislature:representative_index' %}"} %a{href: "{% url 'legislature:representative-index' %}"}
Representatives Representatives
%li %li
%a{href: "{% url 'legislature:group_index' kind='country' %}"} %a{href: "{% url 'legislature:group-index' kind='country' %}"}
Countries Countries
%li %li
%a{href: "{% url 'legislature:group_index' kind='group' %}"} %a{href: "{% url 'legislature:group-index' kind='group' %}"}
Parties Parties
%li %li
%a{href: "{% url 'legislature:group_index' kind='delegation' %}"} %a{href: "{% url 'legislature:group-index' kind='delegation' %}"}
Delegations Delegations
%li %li
%a{href: "{% url 'legislature:group_index' kind='committee' %}"} %a{href: "{% url 'legislature:group-index' kind='committee' %}"}
Committees Committees
%ul.nav %ul.nav
%li %li
%a{href: "{% url 'votes:dossier_index' %}"} %a{href: "{% url 'votes:dossier-index' %}"}
Votes Votes
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
%p %p
Memopol is reachable only in <b>reduced functionality mode</b>. Memopol is reachable only in <b>reduced functionality mode</b>.
By the way, you could access to By the way, you could access to
<a href="{% url 'legislature:representative_index' %}">the list of MEPs</a>. <a href="{% url 'legislature:representative-index' %}">the list of MEPs</a>.
%p %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>. 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 .col-md-4
...@@ -22,4 +22,3 @@ ...@@ -22,4 +22,3 @@
European citizens to reach members of European Parliament (MEPs) and European citizens to reach members of European Parliament (MEPs) and
track their voting records on issues related to fundamental track their voting records on issues related to fundamental
freedoms online. <em><a href="">More...</a></em> freedoms online. <em><a href="">More...</a></em>
...@@ -278,4 +278,4 @@ class QuerySetDiggPaginator(DiggPaginator, QuerySetPaginator): ...@@ -278,4 +278,4 @@ class QuerySetDiggPaginator(DiggPaginator, QuerySetPaginator):
if __name__ == "__main__": if __name__ == "__main__":
import doctest import doctest
doctest.testmod() doctest.testmod()
\ No newline at end of file
...@@ -13,7 +13,7 @@ var gzip_options = { ...@@ -13,7 +13,7 @@ var gzip_options = {
} }
}; };
var less_src = 'static/less/*.less'; var less_src = ['static/less/base.less', 'static/less/libs.less'];
/* Compile Our Sass */ /* Compile Our Sass */
gulp.task('less', function() { gulp.task('less', function() {
...@@ -31,7 +31,7 @@ gulp.task('less', function() { ...@@ -31,7 +31,7 @@ gulp.task('less', function() {
/* Watch Files For Changes */ /* Watch Files For Changes */
gulp.task('watch', function() { gulp.task('watch', function() {
livereload.listen(); livereload.listen();
gulp.watch(less_src, ['less']); gulp.watch('static/less/*.less', ['less']);
/* Trigger a live reload on any Django template changes */ /* Trigger a live reload on any Django template changes */
gulp.watch('**/templates/*').on('change', livereload.changed); gulp.watch('**/templates/*').on('change', livereload.changed);
......
- load by_group_url
.representative
%h1.name
={representative.full_name}
.row
.col-md-4
%p.photo
%img{:src => "{{ representative.photo }}"}/
.col-md-8
%table.table.table-condensed.detail-view
%tr.score
%th Score
%td
%span.label.label-success
%a{:href => '#votes'}
= representative.score
%tr
%th Country
%td= representative.country.name
%tr
%th Party
%td
%a{:href => "{{ representative.main_mandate|by_group_url }}"}
{{ representative.main_mandate.role }} of
{{ representative.main_mandate.group.name }}
%tr
%th Biography
%td
Born in {{ representative.birth_place }} the
{{ representative.birth_date }} ({{ representative.get_gender_display }})
-# List representatives -# List representatives
- extends 'base.html' - extends 'base.html'
- load humanize
- load by_group_url - load by_group_url
- load bootstrap3
- block head
{{ position_form.media }}
- block content - block content
.representative - include 'legislature/blocks/representative_header.html' with representative=representative
.row %h2#votes Votes
.col-md-8
%h1.name %table.table.table-condensed.votes
={representative.full_name} %tr
%th Title
%h2.score %th Position
SCORE : {{ representative.score }} %th Recommendation
%th Score
%p.group
%strong - for vote in representative.votes_with_proposal.all
%a{:href => "{{ representative.main_mandate|by_group_url }}"} %tr
{{ representative.main_mandate.role }} of %td= vote.proposal.recommendation.title
{{ representative.main_mandate.group.name }} %td= vote.position
%td= vote.proposal.recommendation.recommendation
%p.personal %td= vote.proposal.recommendation.weight
Born in {{ representative.birth_place }} the
{{ representative.birth_date }} ({{ representative.get_gender_display }})
%h2 Mandates
%p.country= representative.country.name
.col-md-4 %table.table.table-condensed.mandates
%p.photo - for mandate in representative.active_mandates
%img{:src => "{{ representative.photo }}"}/ %tr.mandate
%td= mandate.role
%td
%h2 Votes %a{:href => "{{ mandate|by_group_url }}"}
{{ mandate.group.name }} ({{ mandate.group.abbreviation }})
%table.table.votes %td= mandate.begin_date
- for vote in representative.votes_with_proposal.all %td= mandate.end_date
%tr %td= mandate.constituency.name
%td= vote.proposal.recommendation.title
%td= vote.position .positions
%td= vote.proposal.recommendation.recommendation %h2 Public positions
%td= vote.proposal.recommendation.weight
%table.table.table-condensed
- for position in representative.positions.published.all
%h2 Mandates %tr.position
%td= position.datetime|naturalday:"d/m/Y"
%table.table.mandates %td
- for mandate in representative.active_mandates %a{:href => '{% url "positions:position-detail" position.pk %}'}
%tr.mandate =position.text|truncatewords:8
%td= mandate.role %td
= position.dossier
%td %td
%a{:href => "{{ mandate|by_group_url }}"} %a{:href => '{{position.link}}'}
{{ mandate.group.name }} ({{ mandate.group.abbreviation }}) = position.link
%td= mandate.begin_date
%td= mandate.end_date %form{:action => '{% url "positions:position-create" %}',
%td= mandate.constituency.name :method => 'post'}
- csrf_token
%input{:type => 'hidden',
:name => 'representative',
:value => '{{ representative.id }}'}
- bootstrap_form position_form
- buttons
%button{'type': 'submit', 'class': 'btn btn-primary'}
{% bootstrap_icon "star" %} Submit
- endbuttons
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- include 'legislature/search.html' - include 'legislature/search.html'
- include "core/blocks/pagination.html" - include "core/blocks/pagination.html"
%table.table %table.table
%tr %tr
%th %th
...@@ -22,21 +22,21 @@ ...@@ -22,21 +22,21 @@
- for representative in object_list - for representative in object_list
%tr %tr
%td %td
%a{'href': "{% url 'legislature:representative_detail' name=representative.slug %}"} %a{'href': "{% url 'legislature:representative-detail' name=representative.slug %}"}
%img{'src': '={representative.photo}', 'width': '80'}/ %img{'src': '={representative.photo}', 'width': '80'}/
%td %td
%a{'href': "{% url 'legislature:representative_detail' name=representative.slug %}"} %a{'href': "{% url 'legislature:representative-detail' name=representative.slug %}"}
={representative.full_name} ={representative.full_name}
%td %td
%a{'href': "{% url 'legislature:representative_index' group_kind='country' group=representative.country.code %}"} %a{'href': "{% url 'legislature:representative-index' group_kind='country' group=representative.country.code %}"}
={representative.country.name} ={representative.country.name}
%td %td
%a{'href': "{{ representative.main_mandate|by_group_url }}"} %a{'href': "{{ representative.main_mandate|by_group_url }}"}
={representative.main_mandate.group.abbreviation} ={representative.main_mandate.group.abbreviation}
%td %td
={representative.score} ={representative.score}
- include "core/blocks/pagination.html" - include "core/blocks/pagination.html"
...@@ -13,7 +13,7 @@ def by_group_url(group): ...@@ -13,7 +13,7 @@ def by_group_url(group):
if not isinstance(group, Group): if not isinstance(group, Group):
return '' return ''
kwargs = {'group_kind': group.kind} kwargs = {'group_kind': group.kind}
if group.abbreviation: if group.abbreviation:
...@@ -22,8 +22,8 @@ def by_group_url(group): ...@@ -22,8 +22,8 @@ def by_group_url(group):
kwargs['group'] = group.name kwargs['group'] = group.name
# kwargs['group_id'] = group.id # kwargs['group_id'] = group.id
return reverse( return reverse(
'legislature:representative_index', 'legislature:representative-index',
kwargs=kwargs kwargs=kwargs
) )
# 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 django.conf.urls import url from django.conf.urls import url
from views import representative from views import representative
...@@ -8,30 +27,30 @@ urlpatterns = [ ...@@ -8,30 +27,30 @@ urlpatterns = [
url( url(
r'^groups/(?P<kind>\w+)?$', r'^groups/(?P<kind>\w+)?$',
group.index, group.index,
name='group_index' name='group-index'
), ),
# Representative detail by representative name # Representative detail by representative name
url( url(
r'^(?P<name>[-\w]+)$', r'^(?P<name>[-\w]+)$',
representative.detail, representative.detail,
name='representative_detail' name='representative-detail'
), ),
# Representative detail by representative pk # Representative detail by representative pk
url( url(
r'^(?P<pk>\d+)$', r'^(?P<pk>\d+)$',
representative.detail, representative.detail,
name='representative_detail' name='representative-detail'
), ),
# List of representatives by group kind and group name or pk # List of representatives by group kind and group name or pk
url( url(
r'^(?P<group_kind>\w+)/(?P<group>.+)$', r'^(?P<group_kind>\w+)/(?P<group>.+)$',
representative.index, representative.index,
name='representative_index' name='representative-index'
), ),
# List all representatives by default # List all representatives by default
url( url(
r'', r'',
representative.index, representative.index,
name='representative_index' name='representative-index'
), ),
] ]
...@@ -29,14 +29,11 @@ def index(request, kind=None): ...@@ -29,14 +29,11 @@ def index(request, kind=None):
groups = Group.objects.filter( groups = Group.objects.filter(
mandates__end_date__gte=datetime.now() mandates__end_date__gte=datetime.now()
) )
if kind: if kind:
groups = groups.filter( groups = groups.filter(
kind=kind kind=kind
) )
print(groups)
groups = groups.distinct().order_by('name') groups = groups.distinct().order_by('name')
return render( return render(
......
...@@ -27,6 +27,7 @@ from django.http import Http404 ...@@ -27,6 +27,7 @@ from django.http import Http404
from ..models import MemopolRepresentative from ..models import MemopolRepresentative
from core.utils import render_paginate_list from core.utils import render_paginate_list
from positions.forms import PositionForm
def index(request, group_kind=None, group=None): def index(request, group_kind=None, group=None):
...@@ -86,10 +87,14 @@ def detail(request, pk=None, name=None): ...@@ -86,10 +87,14 @@ def detail(request, pk=None, name=None):
except MemopolRepresentative.DoesNotExist: except MemopolRepresentative.DoesNotExist:
return Http404() return Http404()
position_form = PositionForm()
return render( return render(
request, request,
'legislature/representative_detail.html', 'legislature/representative_detail.html',
{'representative': representative} {
'representative': representative,
'position_form': position_form
}
) )
...@@ -105,4 +110,3 @@ def _filter_by_search(request, representative_list): ...@@ -105,4 +110,3 @@ def _filter_by_search(request, representative_list):
) )
else: else:
return representative_list return representative_list
...@@ -24,7 +24,6 @@ config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.j ...@@ -24,7 +24,6 @@ config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.j
with open(config_file) as f: with open(config_file) as f:
config = json.loads(f.read()) config = json.loads(f.read())
def get_param(setting, config=config, default=None): def get_param(setting, config=config, default=None):
"""Get the secret variable or return explicit exception.""" """Get the secret variable or return explicit exception."""
try: try:
...@@ -53,27 +52,32 @@ COMPOTISTA_SERVER = get_param('compotista_server') ...@@ -53,27 +52,32 @@ COMPOTISTA_SERVER = get_param('compotista_server')
TOUTATIS_SERVER = get_param('toutatis_server') TOUTATIS_SERVER = get_param('toutatis_server')
REDIS_DB = get_param('redis_db') REDIS_DB = get_param('redis_db')
ORGANIZATION_NAME = get_param('organization') ORGANIZATION_NAME = get_param('organization')
# Application definition # Application definition
INSTALLED_APPS = ( INSTALLED_APPS = (
# 'django.contrib.admin', # 'django.contrib.admin',
'django.contrib.admin.apps.SimpleAdminConfig', # Instead of contrib.admin to use Django-Admin-Plus # Instead of contrib.admin to use Django-Admin-Plus
'django.contrib.admin.apps.SimpleAdminConfig',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.humanize',
# 3rd party app # 3rd party app
'compressor', 'compressor',
'adminplus', 'adminplus',
'constance', 'constance',
'bootstrap3',
'datetimewidget',
# --- # ---
'core', 'core',
'representatives', 'representatives',
'representatives_votes', 'representatives_votes',
'legislature', 'legislature',
'votes', 'votes',
# 'positions' 'positions'
) )
if DEBUG: if DEBUG:
...@@ -84,7 +88,7 @@ if DEBUG: ...@@ -84,7 +88,7 @@ if DEBUG:
if get_param('local'): if get_param('local'):
INSTALLED_APPS += ( INSTALLED_APPS += (
'debug_toolbar', 'debug_toolbar',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
...@@ -180,7 +184,7 @@ STATICFILES_FINDERS = ( ...@@ -180,7 +184,7 @@ STATICFILES_FINDERS = (
) )
# Use compressor even in debug # Use compressor even in debug
COMPRESS_ENABLED = True COMPRESS_ENABLED = False
COMPRESS_PRECOMPILERS = ( COMPRESS_PRECOMPILERS = (
# ('text/coffeescript', 'coffee --compile --stdio'), # ('text/coffeescript', 'coffee --compile --stdio'),
...@@ -241,5 +245,6 @@ CONSTANCE_REDIS_CONNECTION = { ...@@ -241,5 +245,6 @@ CONSTANCE_REDIS_CONNECTION = {
CONSTANCE_CONFIG = { CONSTANCE_CONFIG = {
'USE_COUNTRY': (True, 'Use country for representative'), 'USE_COUNTRY': (True, 'Use country for representative'),
'MAIN_GROUP_KIND': ('group', 'Main group kind'), 'MAIN_GROUP_KIND': ('group', 'Main group kind'),
'ORGANIZATION_NAME': ('La Quadrature du Net', 'Organization name') 'ORGANIZATION_NAME': ('La Quadrature du Net', 'Organization name'),
'POSITION_PUBLISHED': (False, 'Default position published status')
} }
...@@ -33,5 +33,6 @@ urlpatterns = patterns('', ...@@ -33,5 +33,6 @@ urlpatterns = patterns('',
url(r'^$', core.views.HomeView.as_view(), name='index'), url(r'^$', core.views.HomeView.as_view(), name='index'),
url(r'^legislature/', include('legislature.urls', namespace='legislature')), url(r'^legislature/', include('legislature.urls', namespace='legislature')),
url(r'^votes/', include('votes.urls', namespace='votes')), url(r'^votes/', include('votes.urls', namespace='votes')),
url(r'^positions/', include('positions.urls', namespace='positions')),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
) )
# 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 __future__ import absolute_import
from django.contrib import admin
from .models import Position
def publish_positions(modeladmin, request, queryset):
"""Set published to True for the queryset
doc : https://docs.djangoproject.com/en/1.8/ref/contrib/admin/actions/#adding-actions-to-the-modeladmin"""
queryset.update(published=True)
publish_positions.short_description = 'Publish selected positions'
def unpublish_positions(modeladmin, request, queryset):
"""Set published to False for the queryset
doc : https://docs.djangoproject.com/en/1.8/ref/contrib/admin/actions/#adding-actions-to-the-modeladmin"""
queryset.update(published=False)
unpublish_positions.short_description = 'Unpublish selected positions'
class PositionAdmin(admin.ModelAdmin):
list_display = ('representative', 'short_text', 'dossier', 'datetime', 'link', 'published')
list_display_links = ('short_text',)
list_editable = ('published',)
list_filter = ('published',)
actions = (publish_positions, unpublish_positions)
admin.site.register(Position, PositionAdmin)
# 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 django import forms
from datetimewidget.widgets import DateWidget
from votes.models import MemopolDossier
from .models import Position
class PositionForm(forms.ModelForm):
class Meta:
model = Position
fields = ['dossier', 'datetime', 'text', 'link']
widgets = {
#Use localization and bootstrap 3
'datetime': DateWidget(
attrs={
'id':'yourdatetimeid'
},
usel10n = True,
bootstrap_version=3,
)
}
dossier = forms.ModelChoiceField(
queryset=MemopolDossier.objects.all(),
required=False
)
# -*- coding: utf-8 -*-
from __future__ import unicode_literals