Skip to content
Extraits de code Groupes Projets

Comparer les révisions

Les modifications sont affichées comme si la révision source était fusionnée avec la révision cible. En savoir plus sur la comparaison des révisions.

Source

Sélectionner le projet cible
No results found

Cible

Sélectionner le projet cible
  • la-quadrature-du-net/memopol/memopol
  • lnclt/political_memory
  • arthur/political_memory
  • agrausem/political_memory
  • periode/memopol
  • Anthony/memopol
  • Porkepix/memopol
  • jaster/memopol
  • luxcem/memopol
  • TAlone/memopol
10 résultats
Afficher les modifications
Affichage de
avec 340 ajouts et 697 suppressions
Deployment on OpenShift
~~~~~~~~~~~~~~~~~~~~~~~
OpenShift is an Open-Source Platform-as-a-Service software by Red Hat. It is
also available in its hosted version known as "OpenShift Online" and the first
three websites ("gears") are free.
Clone the repository
====================
You should fork the project on github and use the fork's clone url. For the
sake of the demo, we'll use the main repository URL::
$ git clone https://github.com/political-memory/political_memory.git
Cloning into 'political_memory'...
remote: Counting objects: 2516, done.
remote: Compressing objects: 100% (109/109), done.
remote: Total 2516 (delta 44), reused 0 (delta 0), pack-reused 2402
Receiving objects: 100% (2516/2516), 4.40 MiB | 79.00 KiB/s, done.
Resolving deltas: 100% (1103/1103), done.
Checking connectivity... done.
$ cd political_memory/
Create your own branch, ie::
$ git checkout -b yourbranch origin/pr
Branch yourbranch set up to track remote branch pr from origin.
Switched to a new branch 'yourbranch'
Create an app on OpenShift
==========================
To deploy the website, use a command like::
$ rhc app-create \
python-2.7 \
cron-1.4 \
postgresql-9.2 \
-a yourappname \
-e OPENSHIFT_PYTHON_WSGI_APPLICATION=memopol/wsgi.py \
--from-code https://github.com/political-memory/political_memory.git \
--no-git
This should create an app on openshift. Other commands would deploy it at once
but in this tutorial we're going to see how to manage it partly manually for
development.
Add the git remote created by OpenShift
=======================================
Add the git remote openshift created for you, you can see it with
``rhc app-show``, ie.::
$ rhc app-show -a yourappname
[snip]
Git URL: ssh://569f5cf500045f6a1839a0a4@yourappname-yourdomain.rhcloud.com/~/git/yourappname.git/
Initial Git URL: https://github.com/political-memory/political_memory.git
SSH: 569f5cf500045f6a1839a0a4@yourappname-yourdomain.rhcloud.com
[snip]
$ git remote add oo_yourappname ssh://569f5cf500045f6a1839a0a4@yourappname-yourdomain.rhcloud.com/~/git/yourappname.git/
Activate OpenShift's git post-recieve hook
==========================================
Activate OpenShift's post-receive hook on your branch::
$ rhc app-configure -a yourappname --deployment-branch yourbranch
Deploy your branch
==================
OpenShift will deploy when it receives commits on the deployment branch, to
deploy just do::
$ git push oo_yourappname yourbranch
If something goes wrong and you want to retry, use the ``rhc app-deploy``
command, ie::
$ rhc app-deploy yourbranch -a yourappname
Data provisionning
==================
To fill up the representatives database table, either wait for the cron script
to be executed, either do it manually::
$ rhc ssh -a yourappname 'cd app-root/repo/ && bin/update_all'
OpenShift is fun, login with ssh and look around if you're curious, you'll be
able to recreate your app without much effort if you break it anyway.
Continue to :doc:`administration`.
Local development tutorial
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. warn:: I reverse-engineered this from the source code I inherited, I might
not be doing the right way nor be able to defend all of technical
decisions.
This tutorial drives through a local installation of the project for
development on Linux. It requires git, a fairly recent version of nodejs (see
:file:`.openshift/action_hooks/deploy` for a way to install it), python2 and
virtualenv.
development on Linux. It requires git, a fairly recent version of python2,
virtualenv and PostgreSQL.
Setup the database
==================
Memopol requires PostgreSQL 9.1 or higher. It used to run with SQLite, too, but
that is no longer the case. It is better to install and configure you're local
PostgreSQL server before starting to install Memopol.
On Debian
---------
To setup you're PostgreSQL database on a debian stable distribution, you can use
the package manager apt::
$ apt install postgresql postgresql-server-dev-9.X
Then you need to create the 'memopol' user and the 'memopol' database::
# To have a root access on Postgres, you need to connect as user 'postgres'
$ su - postgres
$ psql -c "create user memopol with password 'memopol';"
$ psql -c "alter role memopol with createdb;"
$ psql -c "create database memopol with owner memopol;"
$ exit
You're database is now setup for Memopol. You can now launch the 'quickstart.sh'
script to automatically install all the components or do it manually.
In General
----------
Make sure the corresponding user and database exist on your system; the user
will need the 'createdb' permission in order to be able to run tests. To create
them, you may use the following commands::
$ psql -c "create user memopol with password 'memopol';" -U postgres
$ psql -c "alter role memopol with createdb;" -U postgres
$ psql -c "create database memopol with owner memopol;" -U postgres
Automatic Install
=================
There is a quickstart script used and tested manually by some of the
developers. Feel free to try it, but don't worry if it doesn't work for you
then you can do each install step manually, which is recommended because well
that's how you will learn most.
Here's how to try it::
$ git clone gitlab@git.laquadrature.net:memopol/memopol.git
$ cd memopol
$ source bin/quickstart.sh
At this point, you should now run the development server and access to Memopol::
$ memopol runserver
If you want more control or if it doesn't work for you, then follow the steps
below or have a look at what the quickstart script does.
Development helper
===================
You can run the script 'bin/dev.sh' to automaticaly setup some aliases. It works
only with Bash and Zsh.
The script build a custom file named '.memopol.alias' at the root of the project
containing all the aliases for memopol. All the path to the project are build
automatically. A single line is added to your '$HOME/.bashrc' or '$HOME/.zshrc'
to source the aliases.
After execute 'bin/dev.sh' you should close the current terminal and open
another one to have access to the aliases.
There is a quick list of available aliases::
memopol-code : Go into the repository and activate virtualenv and
set Django in debug mode
memopol-launch : Run the development server echo
memopol-update-all : Get all the production data
memopol-refresh-scores : Refresh all scores
.. warning:: If you are using multiple setup of Memopol, it is not recommended to
use this script.
If you need to change the location of the project, you should remove this line
from your .bashrc or .zshrc::
source $PATH_TO_THE_PROJECT/.memopol.alias
Make a virtual environment
==========================
......@@ -28,26 +113,32 @@ Create a python virtual environment and activate it::
$ source memopol_env/bin/activate
Alternatively, use the tox command::
$ tox -e py27
$ source .tox/py27/bin/activate
Clone the repository
====================
You should fork the project on github and use the fork's clone url. For the
sake of the demo, we'll use the main repository URL::
The project is hosted on https://git.laquadrature.net/memopol/memopol
$ git clone https://github.com/political-memory/political_memory.git
Cloning into 'political_memory'...
remote: Counting objects: 2516, done.
remote: Compressing objects: 100% (109/109), done.
remote: Total 2516 (delta 44), reused 0 (delta 0), pack-reused 2402
Receiving objects: 100% (2516/2516), 4.40 MiB | 79.00 KiB/s, done.
Resolving deltas: 100% (1103/1103), done.
Checking connectivity... done.
You can get the code with git ::
$ cd political_memory/
$ git clone https://git.laquadrature.net/memopol/memopol
Clonage dans 'memopol'...
remote: Counting objects: 7972, done.
remote: Compressing objects: 100% (2668/2668), done.
remote: Total 7972 (delta 5203), reused 7830 (delta 5099)
Réception d'objets: 100% (7972/7972), 4.88 MiB | 4.73 MiB/s, fait.
Résolution des deltas: 100% (5203/5203), fait.
Vérification de la connectivité... fait.
$ cd memopol/
Create your own branch, ie::
$ git checkout -b yourbranch origin/pr
$ git checkout -b yourbranch
Branch yourbranch set up to track remote branch pr from origin.
Switched to a new branch 'yourbranch'
......@@ -57,76 +148,46 @@ Install Python dependencies
Then, install the package for development::
$ pip install -e .
Obtaining file:///tmp/political_memory
Collecting django (from political-memory==0.0.1)
Obtaining file:///tmp/memopol
Collecting django (from memopol==0.0.1)
Using cached Django-1.9-py2.py3-none-any.whl
[output snipped for readability]
Installing collected packages: django, sqlparse, django-debug-toolbar, django-pdb, six, django-extensions, werkzeug, south, pygments, markdown, hamlpy, django-coffeescript, ijson, python-dateutil, pytz, political-memory
Running setup.py develop for political-memory
Successfully installed django-1.9 django-coffeescript-0.7.2 django-debug-toolbar-1.4 django-extensions-1.5.9 django-pdb-0.4.2 hamlpy-0.82.2 ijson-2.2 markdown-2.6.5 political-memory pygments-2.0.2 python-dateutil-2.4.2 pytz-2015.7 six-1.10.0 south-1.0.2 sqlparse-0.1.18 werkzeug-0.11.2
Installing collected packages: django, sqlparse, django-debug-toolbar, django-pdb, six, django-extensions, werkzeug, south, pygments, markdown, hamlpy, django-coffeescript, ijson, python-dateutil, pytz, memopol
Running setup.py develop for memopol
Successfully installed django-1.9 django-coffeescript-0.7.2 django-debug-toolbar-1.4 django-extensions-1.5.9 django-pdb-0.4.2 hamlpy-0.82.2 ijson-2.2 markdown-2.6.5 memopol pygments-2.0.2 python-dateutil-2.4.2 pytz-2015.7 six-1.10.0 south-1.0.2 sqlparse-0.1.18 werkzeug-0.11.2
Install NodeJS dependencies
Install client dependencies
===========================
We'll also need to install bower for the staticfiles::
$ npm install bower
memopol@3.0.0 /tmp/political_memory
└── bower@1.7.0 extraneous
As well as all the requirements from :file:`package.json`::
$ npm install
memopol@3.0.0 /tmp/political_memory
├── bower@1.7.0 extraneous
├─┬ gulp@3.9.0
[output snipped for readability]
npm WARN In bower@1.7.0 replacing bundled version of configstore with configstore@0.3.2
npm WARN In bower@1.7.0 replacing bundled version of latest-version with latest-version@1.0.1
npm WARN In bower@1.7.0 replacing bundled version of update-notifier with update-notifier@0.3.2
Don't worry about the warnings, for they are non-critical (as all warnings).
Then, install the bower packages::
We'll also need to download client libraries::
$ node_modules/.bin/bower install
bower bootstrap#~3.3.5 cached git://github.com/twbs/bootstrap.git#3.3.6
bootstrap#3.3.6 static/libs/bootstrap
└── jquery#2.1.4
[output snipped for readability]
jquery#2.1.4 static/libs/jquery
Build the static files with gulp::
$ node_modules/gulp/bin/gulp.js less
[22:26:42] Using gulpfile /tmp/political_memory/gulpfile.js
[22:26:42] Starting 'less'...
[22:26:44] Finished 'less' after 1.54 s
.. note:: The ``node_modules/gulp/bin/gulp.js watch`` command may be used to
have gulp watching for changes and rebuilding static files
automatically.
$ src/memopol/bin/install_client_deps.sh
* Downloading jquery/jquery (2.1.4) from Github...
* Downloading FortAwesome/Font-Awesome (v4.3.0) from Github...
* Downloading lipis/flag-icon-css (0.7.1) from Github...
* Downloading twbs/bootstrap (v3.3.5) from Github...
* Done
Activate ``DJANGO_DEBUG``
=========================
``DEBUG`` is disabled by default, the development server
won't run properly by default thnen, to enable it export
won't run properly by default then, to enable it export
the ``DJANGO_DEBUG`` variable in the current shell::
$ export DJANGO_DEBUG=True
Database migrations
===================
Run database migrations, it'll use a file-based sqlite database by default::
Database migrations ensure the database schema is up to date with the project.
If you're not sure, you can run them anyway, they won't do any harm. Use the
following command::
$ ./manage.py migrate
$ memopol migrate
Operations to perform:
Synchronize unmigrated apps: django_filters, staticfiles, datetimewidget, autocomplete_light, messages, adminplus, compressor, humanize, django_extensions, constance, bootstrap3
Apply all migrations: legislature, votes, database, admin, positions, sessions, representatives, auth, contenttypes, representatives_votes, taggit
......@@ -142,12 +203,27 @@ Run database migrations, it'll use a file-based sqlite database by default::
Applying taggit.0002_auto_20150616_2121... OK
Provision with data
===================
You can load a small data sample for quick setup:
$ memopol loaddata small_sample.json
If you launch memopol for the first time, you need to launch this command :
$ memopol refresh_scores
Or actual data (takes a while)::
$ bin/update_all
Run the development server
==========================
Run the development server::
$ ./manage.py runserver
$ memopol runserver
Performing system checks...
......@@ -160,11 +236,4 @@ Run the development server::
The website is running on ``http://127.0.0.1:8000/``.
Provision with data
===================
To provision it with data (takes a while)::
$ bin/update_all
Continue to :doc:`administration`.
Hacker guide
~~~~~~~~~~~~
See a `hacking demo on the Memopol project in some epic
slides
<https://slides.com/jamespic/cd-devops/fullscreen#/>`_.
Read about it in `Continuous Delivery and DevOps
quickstart
<https://www.packtpub.com/application-development/continuous-delivery-and-devops-%E2%80%93-quickstart-guide-second-edition)>`_,
and I bet you'll order a paperback edition for reference !
Testing
=======
Use the ``tox -l`` command to list tests::
$ pip install tox
$ cd memopol/
$ tox -l
Use the ``tox -e`` command to execute a particular test suite::
$ tox -e py27
And use the ``tox`` command without argument to execute all test suites,
exactly like in CI.
Adding random recommendations
=============================
::
$ ./manage.py shell
$ memopol shell
In [1]: from representatives_votes.models import Proposal
In [2]: from votes.models import Recommendation
In [3]: import random
In [4]: for p in Proposal.objects.all(): Recommendation.objects.create(proposal=p, recommendation='for', weight=random.randint(1,10))
Creating test fixtures
======================
The largest test fixtures are, the longer it takes to load them, the longer the
test run is.
To create test fixtures for representatives_positions, insert some Position
objects, and reduce the database with::
memopol remove_representatives_without_position
memopol remove_groups_without_mandate
memopol remove_countries_without_group
For representatives_recommendations::
memopol remove_proposals_without_recommendation
memopol remove_dossiers_without_proposal
memopol remove_representatives_without_vote
memopol remove_groups_without_mandate
memopol remove_countries_without_group
docs/img/score_10years.png

3,79 ko

docs/img/score_1year.png

5,5 ko

docs/img/score_exp1k.png

3,1 ko

docs/img/score_exp6.png

4,61 ko

docs/img/score_formula.png

5,99 ko

......@@ -12,10 +12,13 @@ Contents:
:maxdepth: 2
usage
deployment
deploy-custom
administration
scores
api
development
hacker
setup_solr
Indices and tables
==================
......@@ -23,4 +26,3 @@ Indices and tables
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
Memopol Scores
~~~~~~~~~~~~~~
Score computation
=================
In Memopol, each representative has a score that shows how their votes match
the recommendations made by administrators on the instance. The total score
of a representative is the sum of their score on each dossier, which in turn is
the sum of their score on each proposal with a recommendation on the dossier.
Each recommendation made by administrators has a weight: a number that tells how
important a specific proposal is. When the representative vote on a proposal
matches the recommendation, their score on the proposal is set to +weight.
Otherwise, it is set to -weight.
Score decay parameters
======================
Memopol allows to set decay parameters so that older votes have a lower
importance in the total representative score. By default, those parameters are
set to values that disable the score decay, so that each vote contributes
identically to the total score no matter how old it is.
The formula used to compute score is the following:
.. image:: img/score_formula.png
Where:
* ``baseScore`` is the base score for the vote computed as explained above;
* ``voteAge`` is the age of the vote in days;
* ``decayNum`` and ``decayDenom`` define the decay rate;
* ``exponent`` define the steepness of the decay.
The corresponding parameters can be set from the Memopol administration
interface (Memopol Settings > Settings); settings keys are ``SCORE_DECAY_NUM``,
``SCORE_DECAY_DENOM``, ``SCORE_EXPONENT``. Additionnaly, the ``SCORE_DECIMALS``
parameter sets how many decimal places are visible when scores are displayed.
The default values for those settings disable score decay by setting
``SCORE_DECAY_NUM`` to 0, ``SCORE_DECAY_DENOM`` and ``SCORE_EXPONENT`` to 1.
If you want to use score decay, start by setting ``SCORE_DECAY_NUM`` to 1, and
``SCORE_DECAY_DENOM`` to the number of days you want votes to matter. The graph
below shows how a score of 1.0 will decay with a 1-year decay (the X axis is in
days).
.. image:: img/score_1year.png
Increasing ``SCORE_DECAY_DENOM`` will make votes matter longer. Here is the
same example but with a 10-year decay.
.. image:: img/score_10years.png
Increasing ``SCORE_EXPONENT`` instead will make the decay cutoff steeper. Here
is an example with a 1-year decay and the exponent set to 6.
.. image:: img/score_exp6.png
Increasing it dramatically will create a brutal cutoff; here is the same example
with the exponent set to 1000:
.. image:: img/score_exp1k.png
Setup Solr with Memopol
=======================
Solr is used to perform search in the data. Currently, it is used only for
reprensatative search autocomplete.
This howto will be based on the current stable version of Debian (jessie).
Installation on Debian Stable
-----------------------------
Solr is in the official repository of debian, you can install it launching::
# apt install solr-tomcat/stable
Package: solr-tomcat
Source: lucene-solr
Version: 3.6.2+dfsg-5
Installed-Size: 65,5 kB
Maintainer: Debian Java Maintainers <pkg-java-maintainers@lists.alioth.debian.org>
Depends: solr-common (= 3.6.2+dfsg-5), tomcat7
Conflicts: solr-jetty, solr-tomcat6
Homepage: http://lucene.apache.org
Section: java
Priority: optional
Download-Size: 8 598 B
APT-Sources: http://ftp.fr.debian.org/debian/ jessie/main amd64 Packages
Description: Enterprise search server based on Lucene3 - Tomcat integration
Solr is an open source enterprise search server based on the Lucene
Java search library, with XML/HTTP and JSON APIs, hit highlighting,
faceted search, caching, replication, and a web administration
interface. It runs in a Java servlet container such as Tomcat.
.
This package provides the Tomcat integration files for Solr.
By default, the solr server is listen on localhost:8080. Memopol is configured
to use this addess by default, in production. If you install a newer version of
Solr (6.5.1 is the latest release), you should update the settings.py::
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.solr_backend.SolrEngine',
'URL': 'http://127.0.0.1:8080/solr',
},
}
Configure Solr
---------------
You can use the default Solr configuration to use it, You just need to generate
the schema.xml file and copy it in `/etc/solr/`. To generate the schema.xml
file::
$ memopol build_solr_schema > schmema.xml
## Copy the schema.xml file into /etc/solr/conf
$ sudo cp schema.xml /etc/solr/conf/
## Restart tomcat
$ sudo /etc/init.d/tomcat7 restart
Last step, you need to build the index by using::
$ memopol rebuild_index
The solr is now setup and production ready.
Populate data in Solr
---------------------
Django-haystack plugin offers two ways to populate data. The first way is by
using a cron job to update the index, the other way is to use `Dango signals to
update / delete datas <https://django-haystack.readthedocs.io/en/v2.6.0/signal_processors.html>`_.
For now, Haystack is not configured to be used with Django signals. It is
necessary to add a cron job to update the index ::
$ memopol update_index
`More informations about cron with Solr <http://django-haystack.readthedocs.io/en/v2.6.0/searchindex_api.html?highlight=cron#keeping-the-index-fresh>`_
var gulp = require('gulp');
var less = require('gulp-less');
var watch = require('gulp-watch');
var minifycss = require('gulp-minify-css');
var rename = require('gulp-rename');
var gzip = require('gulp-gzip');
var livereload = require('gulp-livereload');
var gzip_options = {
threshold: '1kb',
gzipOptions: {
level: 9
}
};
var less_src = ['static/less/base.less', 'static/less/libs.less'];
/* Compile Our Sass */
gulp.task('less', function() {
return gulp.src(less_src)
.pipe(less())
.pipe(gulp.dest('static/stylesheets'))
.pipe(rename({suffix: '.min'}))
.pipe(minifycss())
.pipe(gulp.dest('static/stylesheets'))
// .pipe(gzip(gzip_options))
// .pipe(gulp.dest('static/stylesheets'))
.pipe(livereload());
});
/* Watch Files For Changes */
gulp.task('watch', function() {
livereload.listen();
gulp.watch('static/less/*.less', ['less']);
/* Trigger a live reload on any Django template changes */
gulp.watch('**/templates/*').on('change', livereload.changed);
});
gulp.task('default', ['less', 'watch']);
# coding: utf-8
# This file is part of mempol.
#
# mempol 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.
#
# mempol 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
import django_filters
from .models import MemopolRepresentative
class RepresentativeFilter(django_filters.FilterSet):
class Meta:
model = MemopolRepresentative
# fields = ['full_name', 'country', 'score']
fields = {
'full_name': ['icontains', 'exact'],
'slug': ['exact'],
'remote_id': ['exact'],
'gender': ['exact'],
'active': ['exact'],
'country__name': ['exact'],
'country__code': ['exact']
}
order_by = ['score', 'full_name']
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('representatives', '0003_auto_20150702_1827'),
]
operations = [
migrations.CreateModel(
name='MemopolRepresentative',
fields=[
('representative_ptr', models.OneToOneField(parent_link=True, auto_created=True,
primary_key=True, serialize=False, to='representatives.Representative')),
('score', models.IntegerField(default=0)),
('country', models.ForeignKey(
to='representatives.Country', null=True)),
],
options={
'abstract': False,
},
bases=('representatives.representative',),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('representatives', '0004_auto_20150709_1601'),
('legislature', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='memopolrepresentative',
name='main_mandate',
field=models.ForeignKey(
default=True, to='representatives.Mandate', 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 datetime import datetime
from django.db import models
from representatives.contrib.parltrack.import_representatives import \
representative_post_save
from representatives.models import Country, Mandate, Representative
from votes.models import MemopolVote
# from django.utils.functional import cached_property
class MemopolRepresentative(Representative):
country = models.ForeignKey(Country, null=True)
score = models.IntegerField(default=0)
main_mandate = models.ForeignKey(Mandate, null=True, default=None)
def update_score(self):
score = 0
for vote in MemopolVote.objects.filter(representative=self):
score += vote.absolute_score
self.score = score
self.save()
def update_all(self):
self.update_score()
def active_mandates(self):
return self.mandates.filter(
end_date__gte=datetime.now()
)
def former_mandates(self):
return self.mandates.filter(
end_date__lte=datetime.now()
)
def votes_with_proposal(self):
return MemopolVote.objects.select_related(
'proposal',
'proposal__recommendation'
).filter(representative=self)
def mempol_representative(sender, representative, data, **kwargs):
update = False
try:
memopol_representative = MemopolRepresentative.objects.get(
representative_ptr=representative)
except MemopolRepresentative.DoesNotExist:
memopol_representative = MemopolRepresentative(
representative_ptr=representative)
# Please forgive the horror your are about to witness, but this is
# really necessary. Django wants to update the parent model when we
# save a child model.
memopol_representative.__dict__.update(representative.__dict__)
try:
country = sorted(data.get('Constituencies', []),
key=lambda c: c.get('end') if c is not None else 1
)[-1]['country']
except IndexError:
pass
else:
if sender.cache.get('countries', None) is None:
sender.cache['countries'] = {c.name: c.pk for c in
Country.objects.all()}
country_id = sender.cache['countries'].get(country)
if memopol_representative.country_id != country_id:
memopol_representative.country_id = country_id
update = True
if sender.mep_cache['groups']:
main_mandate = sorted(sender.mep_cache['groups'],
key=lambda m: m.end_date)[-1]
if memopol_representative.main_mandate_id != main_mandate.pk:
memopol_representative.main_mandate_id = main_mandate.pk
update = True
if update:
memopol_representative.save()
representative_post_save.connect(mempol_representative)
# 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 datetime import datetime
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db.models import Q
from django.http import Http404
from django.shortcuts import get_object_or_404, render
from legislature.models import MemopolRepresentative
from representatives.models import Group
def retrieve(request, pk=None, name=None):
if pk:
representative = get_object_or_404(
MemopolRepresentative,
id=pk
)
elif name:
representative = get_object_or_404(
MemopolRepresentative,
full_name=name
)
else:
return Http404()
return render(
request,
'legislature/representative_view.html',
{'representative': representative}
)
def representatives_by_group(request, group_kind, group_abbr=None,
group_name=None, search=None, group_id=None):
if group_id:
representative_list = MemopolRepresentative.objects.filter(
mandates__group_id=group_id,
mandates__end_date__gte=datetime.now()
)
elif group_abbr:
representative_list = MemopolRepresentative.objects.filter(
mandates__group__abbreviation=group_abbr,
mandates__group__kind=group_kind,
mandates__end_date__gte=datetime.now()
)
elif group_name:
representative_list = MemopolRepresentative.objects.filter(
Q(mandates__group__name__icontains=group_name),
mandates__group__kind=group_kind,
mandates__end_date__gte=datetime.now()
)
elif search:
try:
Group.objects.get(abbreviation=search, kind=group_kind)
representative_list = MemopolRepresentative.objects.filter(
mandates__group__abbreviation=search,
mandates__group__kind=group_kind,
mandates__end_date__gte=datetime.now()
)
except Group.DoesNotExist:
representative_list = MemopolRepresentative.objects.filter(
Q(mandates__group__abbreviation__icontains=search) |
Q(mandates__group__name__icontains=search),
mandates__group__kind=group_kind,
mandates__end_date__gte=datetime.now()
)
# Select distinct representatives and filter by search
representative_list = list(set(
_filter_by_search(request, representative_list)
))
return _render_list(request, representative_list)
def _filter_by_search(request, representative_list):
"""
Return a representative_list filtered by
the representative name provided in search form
"""
search = request.GET.get('search')
if search:
return representative_list.filter(
Q(full_name__icontains=search)
)
else:
return representative_list
def _render_list(request, representative_list, num_by_page=30):
"""
Render a paginated list of representatives
"""
paginator = Paginator(representative_list, num_by_page)
page = request.GET.get('page')
try:
representatives = paginator.page(page)
except PageNotAnInteger:
representatives = paginator.page(1)
except EmptyPage:
representatives = paginator.page(paginator.num_pages)
context = {}
queries_without_page = request.GET.copy()
if 'page' in queries_without_page:
del queries_without_page['page']
context['queries'] = queries_without_page
context['representatives'] = representatives
context['representative_num'] = paginator.count
return render(
request,
'legislature/representative_list.html',
context
)
def groups_by_kind(request, kind):
groups = Group.objects.filter(
kind=kind,
mandates__end_date__gte=datetime.now()
).distinct().order_by('name')
return render(
request,
'legislature/groups_list.html',
{'groups': groups}
)
- load representatives_extras
- load humanize
.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
= representative.score|score_label
%tr
%th Country
%td
= representative.country|country_flag
%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|naturalday:'d/m/Y' }}
({{ representative.get_gender_display }})
-# List representatives' groups
- extends 'base.html'
- load representatives_extras
- block content
%table.table
- for group in groups
%tr
%td
%a{'href': '{{ group | by_group_url }}'}w
- if group.abbreviation
={group.abbreviation}
%td
%a{'href': '{{ group | by_group_url }}'}= group.name
-# List representatives
- extends 'base.html'
- load humanize
- load representatives_extras
- load bootstrap3
- block head
{{ position_form.media }}
- block content
.row
.col-md-6
- include 'legislature/blocks/representative_block.html' with representative=representative
.col-md-6
%h2#votes Votes
%table.table.table-condensed.votes
%tr
%th Title
%th.icon-cell
Memopol recommendation
%th.icon-cell
Representative vote
%th.icon-cell
Score
- for vote in representative.votes_with_proposal.all
%tr
%td= vote.proposal.recommendation.title
%td.icon-cell
= vote.proposal.recommendation.recommendation|position_icon
%td.icon-cell
= vote.position|position_icon
%td.icon-cell
= vote.absolute_score|score_label
%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|mandate_date:'d/m/Y'
%td= mandate.end_date|mandate_date:'d/m/Y'
%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
- for tag in position.tags.all
%span.label.label-default
= tag
%td
%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