Commit c05df265 authored by Arnaud Fabre's avatar Arnaud Fabre

Adds postions application

parent 0838d262
......@@ -5,15 +5,20 @@
<link rel="icon" type="image/png" href="{{ STATIC_URL }}img/favicon.ico" />
<!-- 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 {{ config.ORGANIZATION_NAME }}</title>
{% load compress %}
{% load staticfiles %}
{% 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" />
{% 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>
<body {% block bodyattrs %}{% endblock %}>
{% include "core/blocks/header.html" %}
......
%ul.nav
%li
%a{href: "{% url 'legislature:representative_index' %}"}
%a{href: "{% url 'legislature:representative-index' %}"}
Representatives
%li
%a{href: "{% url 'legislature:group_index' kind='country' %}"}
%a{href: "{% url 'legislature:group-index' kind='country' %}"}
Countries
%li
%a{href: "{% url 'legislature:group_index' kind='group' %}"}
%a{href: "{% url 'legislature:group-index' kind='group' %}"}
Parties
%li
%a{href: "{% url 'legislature:group_index' kind='delegation' %}"}
%a{href: "{% url 'legislature:group-index' kind='delegation' %}"}
Delegations
%li
%a{href: "{% url 'legislature:group_index' kind='committee' %}"}
%a{href: "{% url 'legislature:group-index' kind='committee' %}"}
Committees
%ul.nav
%li
%a{href: "{% url 'votes:dossier_index' %}"}
%a{href: "{% url 'votes:dossier-index' %}"}
Votes
......@@ -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 'legislature:representative_index' %}">the list of MEPs</a>.
<a href="{% url 'legislature:representative-index' %}">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
......@@ -22,4 +22,3 @@
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>
......@@ -278,4 +278,4 @@ class QuerySetDiggPaginator(DiggPaginator, QuerySetPaginator):
if __name__ == "__main__":
import doctest
doctest.testmod()
\ No newline at end of file
doctest.testmod()
......@@ -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 */
gulp.task('less', function() {
......@@ -31,7 +31,7 @@ gulp.task('less', function() {
/* Watch Files For Changes */
gulp.task('watch', function() {
livereload.listen();
gulp.watch(less_src, ['less']);
gulp.watch('static/less/*.less', ['less']);
/* Trigger a live reload on any Django template changes */
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
- extends 'base.html'
- load humanize
- load by_group_url
- load bootstrap3
- block head
{{ position_form.media }}
- block content
.representative
.row
.col-md-8
%h1.name
={representative.full_name}
%h2.score
SCORE : {{ representative.score }}
%p.group
%strong
%a{:href => "{{ representative.main_mandate|by_group_url }}"}
{{ representative.main_mandate.role }} of
{{ representative.main_mandate.group.name }}
%p.personal
Born in {{ representative.birth_place }} the
{{ representative.birth_date }} ({{ representative.get_gender_display }})
%p.country= representative.country.name
.col-md-4
%p.photo
%img{:src => "{{ representative.photo }}"}/
%h2 Votes
%table.table.votes
- for vote in representative.votes_with_proposal.all
%tr
%td= vote.proposal.recommendation.title
%td= vote.position
%td= vote.proposal.recommendation.recommendation
%td= vote.proposal.recommendation.weight
%h2 Mandates
%table.table.mandates
- for mandate in representative.active_mandates
%tr.mandate
%td= mandate.role
- include 'legislature/blocks/representative_header.html' with representative=representative
%h2#votes Votes
%table.table.table-condensed.votes
%tr
%th Title
%th Position
%th Recommendation
%th Score
- for vote in representative.votes_with_proposal.all
%tr
%td= vote.proposal.recommendation.title
%td= vote.position
%td= vote.proposal.recommendation.recommendation
%td= vote.proposal.recommendation.weight
%h2 Mandates
%table.table.table-condensed.mandates
- for mandate in representative.active_mandates
%tr.mandate
%td= mandate.role
%td
%a{:href => "{{ mandate|by_group_url }}"}
{{ mandate.group.name }} ({{ mandate.group.abbreviation }})
%td= mandate.begin_date
%td= mandate.end_date
%td= mandate.constituency.name
.positions
%h2 Public positions
%table.table.table-condensed
- for position in representative.positions.published.all
%tr.position
%td= position.datetime|naturalday:"d/m/Y"
%td
%a{:href => '{% url "positions:position-detail" position.pk %}'}
=position.text|truncatewords:8
%td
= position.dossier
%td
%a{:href => "{{ mandate|by_group_url }}"}
{{ mandate.group.name }} ({{ mandate.group.abbreviation }})
%td= mandate.begin_date
%td= mandate.end_date
%td= mandate.constituency.name
%a{:href => '{{position.link}}'}
= position.link
%form{:action => '{% url "positions:position-create" %}',
: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 @@
- include 'legislature/search.html'
- include "core/blocks/pagination.html"
%table.table
%tr
%th
......@@ -22,21 +22,21 @@
- for representative in object_list
%tr
%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'}/
%td
%a{'href': "{% url 'legislature:representative_detail' name=representative.slug %}"}
%a{'href': "{% url 'legislature:representative-detail' name=representative.slug %}"}
={representative.full_name}
%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}
%td
%a{'href': "{{ representative.main_mandate|by_group_url }}"}
={representative.main_mandate.group.abbreviation}
%td
={representative.score}
={representative.score}
- include "core/blocks/pagination.html"
......@@ -13,7 +13,7 @@ def by_group_url(group):
if not isinstance(group, Group):
return ''
kwargs = {'group_kind': group.kind}
if group.abbreviation:
......@@ -22,8 +22,8 @@ def by_group_url(group):
kwargs['group'] = group.name
# kwargs['group_id'] = group.id
return reverse(
'legislature:representative_index',
'legislature:representative-index',
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 views import representative
......@@ -8,30 +27,30 @@ urlpatterns = [
url(
r'^groups/(?P<kind>\w+)?$',
group.index,
name='group_index'
name='group-index'
),
# Representative detail by representative name
url(
r'^(?P<name>[-\w]+)$',
representative.detail,
name='representative_detail'
name='representative-detail'
),
# Representative detail by representative pk
url(
r'^(?P<pk>\d+)$',
representative.detail,
name='representative_detail'
name='representative-detail'
),
# List of representatives by group kind and group name or pk
url(
r'^(?P<group_kind>\w+)/(?P<group>.+)$',
representative.index,
name='representative_index'
name='representative-index'
),
# List all representatives by default
url(
r'',
representative.index,
name='representative_index'
name='representative-index'
),
]
......@@ -29,14 +29,11 @@ def index(request, kind=None):
groups = Group.objects.filter(
mandates__end_date__gte=datetime.now()
)
if kind:
groups = groups.filter(
kind=kind
kind=kind
)
print(groups)
groups = groups.distinct().order_by('name')
return render(
......
......@@ -27,6 +27,7 @@ from django.http import Http404
from ..models import MemopolRepresentative
from core.utils import render_paginate_list
from positions.forms import PositionForm
def index(request, group_kind=None, group=None):
......@@ -86,10 +87,14 @@ def detail(request, pk=None, name=None):
except MemopolRepresentative.DoesNotExist:
return Http404()
position_form = PositionForm()
return render(
request,
'legislature/representative_detail.html',
{'representative': representative}
{
'representative': representative,
'position_form': position_form
}
)
......@@ -105,4 +110,3 @@ def _filter_by_search(request, representative_list):
)
else:
return representative_list
......@@ -24,7 +24,6 @@ config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.j
with open(config_file) as f:
config = json.loads(f.read())
def get_param(setting, config=config, default=None):
"""Get the secret variable or return explicit exception."""
try:
......@@ -53,27 +52,32 @@ COMPOTISTA_SERVER = get_param('compotista_server')
TOUTATIS_SERVER = get_param('toutatis_server')
REDIS_DB = get_param('redis_db')
ORGANIZATION_NAME = get_param('organization')
# Application definition
INSTALLED_APPS = (
# '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.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
# 3rd party app
'compressor',
'adminplus',
'constance',
'bootstrap3',
'datetimewidget',
# ---
'core',
'representatives',
'representatives_votes',
'legislature',
'votes',
# 'positions'
'positions'
)
if DEBUG:
......@@ -84,7 +88,7 @@ if DEBUG:
if get_param('local'):
INSTALLED_APPS += (
'debug_toolbar',
)
)
MIDDLEWARE_CLASSES = (
......@@ -180,7 +184,7 @@ STATICFILES_FINDERS = (
)
# Use compressor even in debug
COMPRESS_ENABLED = True
COMPRESS_ENABLED = False
COMPRESS_PRECOMPILERS = (
# ('text/coffeescript', 'coffee --compile --stdio'),
......@@ -241,5 +245,6 @@ CONSTANCE_REDIS_CONNECTION = {
CONSTANCE_CONFIG = {
'USE_COUNTRY': (True, 'Use country for representative'),
'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('',
url(r'^$', core.views.HomeView.as_view(), name='index'),
url(r'^legislature/', include('legislature.urls', namespace='legislature')),
url(r'^votes/', include('votes.urls', namespace='votes')),
url(r'^positions/', include('positions.urls', namespace='positions')),
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
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('votes', '0003_auto_20150709_1601'),
('representatives', '0004_auto_20150709_1601'),
]
operations = [
migrations.CreateModel(
name='Position',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('datetime', models.DateTimeField()),
('text', models.TextField()),
('link', models.URLField()),
('published', models.BooleanField(default=False)),
('dossier', models.ForeignKey(to='votes.MemopolDossier', null=True)),
('representative', models.ForeignKey(related_name='positions', to='representatives.Representative')),
],
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('positions', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='position',
name='datetime',
field=models.DateField(),
),
migrations.AlterField(
model_name='position',
name='representative',
field=models.ForeignKey(related_name='positions', to='legislature.MemopolRepresentative'),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('positions', '0002_auto_20150720_1217'),
]
operations = [
migrations.AlterField(
model_name='position',
name='dossier',
field=models.ForeignKey(blank=True, to='votes.MemopolDossier', null=True),
),
]
# 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.db import models
from django.template.defaultfilters import truncatewords
from constance import config
from legislature.models import MemopolRepresentative
from votes.models import MemopolDossier
class PositionManager(models.Manager):
"""A simple model manager for querying published Positions"""
# https://docs.djangoproject.com/en/1.8/topics/db/managers/#using-managers-for-related-object-access
use_for_related_fields = True
def published(self, **kwargs):
return self.filter(published=True, **kwargs)
class Position(models.Model):
representative = models.ForeignKey(MemopolRepresentative, related_name='positions')
dossier = models.ForeignKey(MemopolDossier, null=True, blank=True)
datetime = models.DateField()
text = models.TextField()
link = models.URLField()
published = models.BooleanField(default=False)
# Adds our custom manager
objects = PositionManager()
def save(self, *args, **kwargs):
""" Set published to default value and save the model"""
self.published = config.POSITION_PUBLISHED
super(Position, self).save(*args, **kwargs)
@property
def short_text(self):
return truncatewords(self.text, 5)
def publish(self):
self.published = True
def unpublish(self):
self.published = False