Commit 5781f25f authored by Nicolas Joyard's avatar Nicolas Joyard

Gestion prise en charge + ajouts mineurs UI

parent d85cfcb2
......@@ -8,6 +8,7 @@ from sqlalchemy.inspection import inspect
from .base import BaseImporter
from ..models import db, Etape, Groupe, Parlementaire
from ..models.constants import ETAPE_NA, ETAPE_A_ENVOYER
def parse_date(date):
......@@ -63,7 +64,7 @@ class NosDeputesImporter(BaseImporter):
depute = Parlementaire.query.filter_by(**id_data).first()
if not depute:
id_data.update({'etape': self.etape_nv})
id_data.update({'etape': self.etape_ae})
depute = Parlementaire(**id_data)
db.session.add(depute)
created = True
......@@ -102,13 +103,13 @@ class NosDeputesImporter(BaseImporter):
return created, updated
def import_deputes(self):
self.etape_na = Etape.query.filter_by(label='N/A').first()
self.etape_na = Etape.query.filter_by(ordre=ETAPE_NA).first()
if not self.etape_na:
self.error('Etape N/A introuvable, exécuter import_etapes ?')
return
self.etape_nv = Etape.query.filter_by(label='À envoyer').first()
if not self.etape_nv:
self.etape_ae = Etape.query.filter_by(ordre=ETAPE_A_ENVOYER).first()
if not self.etape_ae:
self.error('Etape À envoyer introuvable, exécuter import_etapes ?')
return
......
......@@ -20,15 +20,20 @@ SEXES = {
# L'ordre est utilisé comme clé primaire lors de cet import.
#
ETAPE_NA = 0
ETAPE_A_ENVOYER = 10
ETAPE_A_CONFIRMER = 15
ETAPE_ENVOYE = 20
ETAPES = [
{
'ordre': 0,
'ordre': ETAPE_NA,
'label': 'N/A',
'description': '',
'couleur': '',
},
{
'ordre': 10,
'ordre': ETAPE_A_ENVOYER,
'label': 'À envoyer',
'description': """
La demande d'accès aux relevés de comptes du parlementaire n'a pas
......@@ -37,7 +42,7 @@ ETAPES = [
'couleur': '#cccccc',
},
{
'ordre': 15,
'ordre': ETAPE_A_CONFIRMER,
'label': 'À confirmer',
'description': """
Un utilisateur a souhaité se charger de l'envoi de la demande, mais
......@@ -46,7 +51,7 @@ ETAPES = [
'couleur': '#8888aa',
},
{
'ordre': 20,
'ordre': ETAPE_ENVOYE,
'label': 'Envoyé',
'description': """
La demande d'accès aux relevés de comptes du parlementaire a été
......
......@@ -30,6 +30,12 @@ def setup(app):
groupe.nom, groupe.couleur, groupe.sigle
)
@app.template_filter('label_etape')
def label_etape(etape):
return '<span class="label" title="%s" ' \
'style="background-color: %s;">%s</span>' % (
etape.description, etape.couleur, etape.label
)
_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')
......
......@@ -5,6 +5,7 @@ from sqlalchemy.orm import joinedload
from sqlalchemy.sql.expression import func
from ..models import db, Etape, Parlementaire
from ..models.constants import ETAPE_NA, ETAPE_A_ENVOYER
def setup_routes(app):
......@@ -12,14 +13,14 @@ def setup_routes(app):
@app.route('/', endpoint='home')
def home():
pqs = Parlementaire.query.options(joinedload(Parlementaire.etape)) \
.filter(Etape.label == 'À envoyer') \
.filter(Etape.ordre == ETAPE_A_ENVOYER) \
.order_by(func.random())
eqs = db.session.query(Etape) \
.outerjoin(Etape.parlementaires) \
.add_columns(func.count(Parlementaire.id)
.label('nb')) \
.filter(Etape.ordre > 0) \
.filter(Etape.ordre > ETAPE_NA) \
.group_by(Etape) \
.order_by(Etape.ordre) \
.all()
......
# -*- coding: utf-8 -*-
from flask import abort, render_template
from datetime import datetime
from flask import abort, flash, redirect, render_template, session, url_for
from sqlalchemy.orm import joinedload, contains_eager
from ..models import Etape, Parlementaire
from .util import redirect_back, require_user
from ..models import db, Action, Etape, Parlementaire
from ..models.constants import ETAPE_A_ENVOYER, ETAPE_A_CONFIRMER
def setup_routes(app):
......@@ -35,3 +39,40 @@ def setup_routes(app):
'parlementaire.html.j2',
parlementaire=parl
)
@app.route('/parlementaires/<id>/envoi', endpoint='envoi')
@require_user
def envoi(id):
try:
# SELECT FOR UPDATE sur le parlementaire pour éviter une race
# condition sur son étape courante
parl = Parlementaire.query \
.filter_by(id=id) \
.with_for_update() \
.first()
if not parl:
abort(404)
if parl.etape.ordre != ETAPE_A_ENVOYER:
msg = 'Oups, la situation a changé pour ce parlementaire...'
return redirect_back(error=msg,
fallback=url_for('parlementaire', id=id))
parl.etape = Etape.query.filter_by(ordre=ETAPE_A_CONFIRMER).first()
action = Action(
date=datetime.utcnow(),
nick=session['user']['nick'],
email=session['user']['email'],
parlementaire=parl,
etape=parl.etape
)
db.session.add(action)
db.session.commit()
return redirect(url_for('parlementaire', id=id))
finally:
db.session.rollback()
\ No newline at end of file
......@@ -12,22 +12,22 @@ def setup_routes(app):
def login():
nick = sanitize(request.form['nick'])
if nick != request.form['nick']:
flash('Seuls les caractères suivants sont autorisés: '
'a-z 0-9 _ - @ . ', category='error')
return redirect_back()
msg = 'Seuls les caractères suivants sont autorisés: ' \
'a-z 0-9 _ - @ . '
return redirect_back(error=msg)
if not len(nick):
flash('Veuillez saisir un pseudonyme !', category='error')
return redirect_back()
msg = 'Veuillez saisir un pseudonyme !'
return redirect_back(error=msg)
if not check_email(request.form['email']):
flash('Veuillez saisir une adresse e-mail valide pour assurer le '
'suivi de l\'envoi des demandes !', category='error')
return redirect_back()
msg = 'Veuillez saisir une adresse e-mail valide pour assurer ' \
'le suivi de l\'envoi des demandes !'
return redirect_back(error=msg)
session['user'] = {
'nick': nick,
'email': request.form['email'],
'email': request.form['email']
}
return redirect_back()
......
......@@ -4,8 +4,8 @@ import re
import unicodedata
from urllib.parse import urlparse, urljoin
from flask import (abort, make_response, redirect, render_template, request,
session)
from flask import (abort, flash, make_response, redirect, render_template,
request, session)
SLUG_STRIP_RE = re.compile(r'[^\w\s-]')
......@@ -29,9 +29,14 @@ def is_safe_url(target):
ref_url.netloc == test_url.netloc
def redirect_back():
def redirect_back(fallback=None, error=None):
if error:
flash(error, category='error')
if request.referrer and is_safe_url(request.referrer):
return redirect(request.referrer)
elif fallback and is_safe_url(fallback):
return redirect(fallback)
else:
return redirect(url_for('home'))
......@@ -47,9 +52,8 @@ def check_email(text):
def require_user(f):
def decorator(*args, **kwargs):
if not session.get('user'):
flash('Vous devez vous identifier pour accéder à cette page',
category='error')
return redirect_back()
return redirect_back(error='Vous devez vous identifier pour '
'accéder à cette page')
return f(*args, **kwargs)
......
......@@ -73,4 +73,18 @@ th.col-right {
.login-form input {
width: 20em;
}
\ No newline at end of file
}
#letter-container {
text-align: center;
}
#letter-container iframe {
width: 21cm;
height: 29.7cm;
margin-left: auto;
margin-right: auto;
border: 1px solid #ccc;
}
......@@ -34,7 +34,7 @@
<div>
{% for etape in etapes %}
<p>
<span class="label" style="background-color: {{ etape.couleur }};">{{ etape.label }}</span>
{{ etape|label_etape }}
<small>{{ etape.description }}</small>
</p>
{% endfor %}
......
......@@ -35,11 +35,16 @@
<img class="chamber-icon" src="{{ url_for('static', filename=parl.chambre|lower+'.png') }}">
<span class="search-vector">{{ parl.nom_complet }} {{ parl.nom_circo }} {{ parl.num_deptmt }} {{ parl.groupe.sigle }} {{ parl.groupe.nom }}</span>
</td>
<td data-value="{{ parl.nom }} {{ parl.prenom }}">{{ parl.nom_complet }}</td>
<td data-value="{{ parl.nom }} {{ parl.prenom }}">
{{ parl.nom_complet }}
{% if not parl.adresse %}
<span class="label label-danger">Adresse manquante !</span>
{% endif %}
</td>
<td class="col-center">{{ parl.groupe|label_groupe }}</td>
<td data-value="{{ parl.num_deptmt }} {{ parl.num_circo }}">{{ parl.nom_circo }} n°{{ parl.num_circo }}</td>
<td class="col-center">
<span class="label" style="background-color: {{ parl.etape.couleur }};">{{ parl.etape.label }}</span>
{{ parl.etape|label_etape }}
</td>
<td class="col-right">
{% if parl.etape.ordre == 10 %}
......
{% extends "_base.html.j2" %}
{% block header %}
<style>
#letter-container {
text-align: center;
}
iframe {
width: 21cm;
height: 29.7cm;
margin-left: auto;
margin-right: auto;
border: 1px solid #ccc;
}
</style>
{% endblock %}
{% block menuitem %}
<li role="presentation" class="active"><a href="#">{{ parlementaire.nom_complet }}</a></li>
{% endblock %}
{% block content %}
<div class="col-md-6">
<div class="col-md-4">
<section class="panel panel-default">
<article class="panel-body parl-card">
<img class="parl-photo" src="{{ parlementaire.url_photo }}/120" align="left">
<div class="parl-detail">
<b>{{ parlementaire.nom_complet }}</b> {{ parlementaire.groupe|label_groupe }}<br>
{{ parlementaire|fonc_parlementaire }} &ndash; {{ parlementaire.nom_circo }} n°{{ parlementaire.num_circo }}<br><br><br>
{{ parlementaire|fonc_parlementaire }} &ndash; {{ parlementaire.nom_circo }} n°{{ parlementaire.num_circo }}<br><br>
{{ parlementaire.etape|label_etape }}
{% if not parlementaire.adresse %}
<span class="label label-danger">Adresse manquante !</span>
{% endif %}
</div>
</article>
</section>
......@@ -38,7 +24,7 @@
{% if session.user %}
<section class="panel panel-default">
<article class="panel-body">
<a class="btn btn-primary btn-lg btn-block" href="#">Je prends en charge l'envoi !</button>
<a class="btn btn-primary btn-lg btn-block" href="{{ url_for('envoi', id=parlementaire.id) }}">Je prends en charge l'envoi !</button>
<a class="btn btn-default btn-lg btn-block" href="{{ url_for('demande_pdf', id=parlementaire.id, mode='download') }}">Télécharger le courrier au format PDF</a>
</article>
</section>
......@@ -48,7 +34,7 @@
{% if parlementaire.etape.ordre == 10 %}
{% if session.user %}
<div class="col-md-6">
<div class="col-md-8">
<section class="panel panel-default">
<header class="panel-heading">
<b>Quelle est la marche à suivre ?</b>
......@@ -66,14 +52,16 @@
</iframe>
</div>
{% else %}
<div class="col-md-6">
<div class="col-md-8">
<div class="alert alert-warning" role="alert">
Pour envoyer une demande et assurer son suivi, il faut au préalable <a href="#" id="identify-link">vous identifier</a>.
</div>
</div>
{% endif %}
{% else %}
...
<div class="col-md-8">
...
</div>
{% endif %}
{% endblock %}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment