* started workin on 'navbar' module

* changed bootstrap theme to bootswatch/Simplex

* big work on navbar logic

* starting creating menus using navbar

* converted app views to new Wepage decorator, updated navbar

* reimplemented DernieresMaraudes as a dropdown instead of ContextMixin

* reorganised static files, minor code cleanups

* turned Link.href into lazy-evaluated property

* collapsed 'navbar' module into 'website', dynamic building of ApplicationMenu subclasses

* minor cleanup

* blah blah blah

* added way to add admin/non-admin links

* minor style change : red border for active page instead of all dropdowns

* deleted file

* prepare adding removing menu templates files, being replaced by code

* essayé de généraliser le code pour les modaux bootstrap, non testé git status

* more preparation and thinking on navbar app_menus logic...

* added LinkManager and DropdownManager, getting closer...

* small fix in DropdownManager.__get__

* boosted up work: keep it simple so it can be merged fast, major layout changes

* added month filter on maraudes:liste

* added 'as_icon' filter to display boolean/null values as bootstrap icons

* remove inactive user from planning selection

* removed all unused 'menu' templates

* set up django_select2 to use static files

* small fix after review
This commit is contained in:
artus40
2017-02-11 18:20:13 +01:00
committed by GitHub
parent 288ca2cc20
commit 0be59a61a7
61 changed files with 665 additions and 525 deletions

View File

@@ -8,3 +8,19 @@ class Config(AppConfig):
menu_icon = "road"
def get_index_url(self):
return "/maraudes/"
from utilisateurs.models import Maraudeur
from website.decorators import Webpage
maraudes = Webpage('maraudes',
icon="road",
defaults={
'users': [Maraudeur],
'ajax': False,
'title': ('Maraudes','app'),
})
# Setting up some links
maraudes.app_menu.add_link(('Liste des maraudes', 'maraudes:liste', "list"))
maraudes.app_menu.add_link(('Planning', 'maraudes:planning', "calendar"), admin=True)

View File

@@ -87,6 +87,7 @@ class Maraude(models.Model):
limit_choices_to={
'is_superuser': False,
'is_staff': True,
'is_active': True,
}
)

View File

@@ -1,15 +1,15 @@
{% load notes %}
<h3> Compte-rendu </h3>
<table class="table table-bordered">
<div class="col-lg-6 col-md-12">
<table class="table table-bordered">
{% for note in notes %}
{% inline_table note header="sujet" %}
{% endfor %}
</table>
<div class="well bg-info">
<p><strong>Informations</strong></p>
<p>Rencontres : {{ maraude.observation_count}}</p>
</table>
</div>
<div class="col-lg-6 col-md-12">
<div class="well bg-info">
<p><strong>Informations</strong></p>
<p>Rencontres : {{ maraude.rencontre_count}}</p>
<p>Personnes rencontrées : {{ maraude.observation_count}}</p>
</div>
</div>

View File

@@ -1,6 +1,6 @@
{% load bootstrap3 %}{% load staticfiles %}
<script type="text/javascript" src="{% static "jquery.formset.js" %}"></script>
<script type="text/javascript" src="{% static "scripts/jquery.formset.js" %}"></script>
<script type="text/javascript">
/* Dynamic Formsets */
$(function() {
@@ -38,6 +38,14 @@
<div class="row">
<div class="col-lg-6 col-md-12">
<!-- Modal buttons -->
<div class="well well-sm text-right"><strong>Créer un nouvel objet :</strong>
<div class="btn-group" role="group" aria-label="...">
<button id="new-sujet" class="btn btn-sm btn-primary">{% bootstrap_icon "user" %} Sujet</button>
<button id="new-lieu" class="btn btn-sm btn-primary">{% bootstrap_icon "globe" %} Lieu</button>
</div>
</div>
<form method="post" action="{% url 'maraudes:create' maraude.pk %}?finalize=False">
{% csrf_token %}
<div class="panel panel-primary panel-collapse">
@@ -71,7 +79,7 @@
</div>
</div>
</div>
<!-- Modal and button linking -->
<div class="modal fade" id="form-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@@ -85,3 +93,12 @@
</div>
</div>
</div>
<script type="text/javascript" src="{% static 'scripts/bootstrap-modal.js' %}"></script>
<script type="text/javascript">
$.fn.openModalEvent('new-sujet',
'{% url "sujets:create" %}?next={% url "maraudes:create" pk=maraude.pk %}',
'Nouveau sujet');
$.fn.openModalEvent('new-lieu',
'{% url "maraudes:lieu-create" %}?next={% url "maraudes:create" pk=maraude.pk %}',
'Nouveau lieu');
</script>

View File

@@ -1,54 +0,0 @@
{% load bootstrap3 %}
<li class="dropdown app-menu"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Création<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#" id="new-sujet">{% bootstrap_icon "user" %} Nouveau sujet</a></li>
<li><a href="#" id="new-lieu">{% bootstrap_icon "globe" %} Nouveau lieu</a></li>
</ul>
<script type="text/javascript">
/* Lier les boutons de création
* Thanks to Derek Morgan, https://dmorgan.info/posts/django-views-bootstrap-modals/
*/
$(function() {
var formAjaxSubmit = function(form, modal) {
$(form).submit(function (e) {
e.preventDefault();
$.ajax({
type: $(this).attr('method'),
url: $(this).attr('action'),
data: $(this).serialize(),
success: function (xhr, ajaxOptions, thrownError) {
if ( $(xhr).find('.has-error').length > 0 || $(xhr).find('.alert-danger').length > 0) {
$(modal).find('.modal-body').html(xhr);
formAjaxSubmit(form, modal);
} else {
$(modal).modal('toggle');
// Reload page ?
location.reload(true)
}
},
error: function (xhr, ajaxOptions, thrownError) {
// handle response errors here
}
});
});
};
/* TODO: Use formAjaxSubmit above, but reload page on form success */
$('#new-sujet').click(function() {
$('#form-modal-body').load('{% url "sujets:create" %}?next={% url "maraudes:create" pk=maraude.id %}', function () {
$('.modal-title').text("Nouveau sujet");
$('#form-modal').modal('toggle');
formAjaxSubmit("#form-modal-body form", "#form-modal");
});
});
$('#new-lieu').click(function() {
$('#form-modal-body').load('{% url "maraudes:lieu-create" %}?next={% url "maraudes:create" pk=maraude.id %}', function () {
$('.modal-title').text("Nouveau lieu");
$('#form-modal').modal('toggle');
formAjaxSubmit("#form-modal-body form", "#form-modal");
});
});
});
</script>

View File

@@ -1,9 +1,7 @@
<div class="col-md-12 col-lg-6">
{% if maraude.est_terminee %}
{% include "compte_rendu/compterendu.html" %}
{% else %}
{% if perms.maraudes.can_add_compterendu %}<a class="btn btn-primary" href="{% url 'maraudes:create' maraude.pk %}">Écrire le compte-rendu</a>
{% else %} <p class="alert alert-info">Le compte-rendu n'a pas encore été écrit</p>{% endif %}
{% endif %}
</div>

View File

@@ -11,7 +11,13 @@
avec {% if user.is_superuser %}{{prochaine_maraude.binome}}{%else%}{{prochaine_maraude.referent}}{%endif%}.
</strong></p>
<hr />
<p><mark>Informations, notes, rendez-vous ?</mark></p>
{% if prochaine_maraude.est_terminee %}
<a class="btn btn-sm btn-primary" href="{% url 'maraudes:details' pk=prochaine_maraude.pk %}">
Voir le compte-rendu
</a>{%else%}
<a class="btn btn-sm btn-primary" href="{% url 'maraudes:create' pk=prochaine_maraude.pk %}">
Rédiger le compte-rendu
</a>{% endif %}
{% else %}<p>Aucune maraude prévue.</p>{% endif %}
</div>
</div>

View File

@@ -1,12 +1,9 @@
{% if object.est_terminee %}
<a href="{% url 'maraudes:details' object.id %}" class="btn btn-link">
{% else %}
<a href="#" class="btn btn-link disabled">
{% endif %}
{{object.date}}
</a>
{% if object.est_terminee %}<a href="{% url 'maraudes:details' object.id %}" class="btn btn-link">
{% else %}<a href="#" class="btn btn-link disabled">{% endif %}
{{object.date}}</a>
<div class="pull-right">
<span class="label label-info">{{ object.binome }} & {{ object.referent }}</span>
{% if object.est_terminee %}
<span class="label label-success">{{object.rencontres.count}} rencontres</span>
{% endif %}
</div>

View File

@@ -1,14 +1,13 @@
{% load bootstrap3 %}
{% load tables %}
<div class="col-md-12 col-lg-6">
<div class="col-lg-10">
<div class="panel panel-primary">
<!-- Default panel contents -->
<div class="panel-heading text-center">
<h3 class="panel-title">Maraudes passées</h3>
</div>
<!-- Table -->
{% table object_list cols=2 cell_template="maraudes/list_table_cell.html" %}
{% table object_list cols=3 cell_template="maraudes/list_table_cell.html" %}
{% if is_paginated %}
<div class="panel-footer text-center">
<ul class="pagination">
@@ -20,15 +19,14 @@
{% endif %}
</div>
</div>
<div class="col-lg-3 col-md-3">
<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading text-center">
<h3 class="panel-title">Filtres</h3>
</div>
<div class="list-group">
<a href="#" class="list-group-item disabled">Vos maraudes seulement</a></li>
<a href="#" class="list-group-item disabled">Ce mois-ci</a></li>
</div>
</div>
</div>
<div class="pull-left">
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Filtrer <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="{% url 'maraudes:liste' %}">Pas de filtre</a></li>
<li><a href="?filter=month-only">Ce mois-ci</a></li>
</ul>
</div>
</div>

View File

@@ -1,7 +0,0 @@
{% if user.is_superuser %}{% load bootstrap3 %}
<li class="dropdown app-menu"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Administration<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="{% url 'maraudes:planning' %}">{% bootstrap_icon "calendar" %} Planning</a></li>
<li><a href="{% url 'admin:app_list' app_label=active_app.label %}">{% bootstrap_icon "wrench" %} Gérer les maraudes</a></li>
</ul>
{% endif %}

View File

@@ -1,15 +0,0 @@
{% load bootstrap3 %}
<li class="app-menu">
<a href="{% url 'maraudes:liste' %}">Liste des maraudes
<span class="pull-right">{% bootstrap_icon "list" %}</span></a>
</li>
{% if dernieres_maraudes %}
<li class="dropdown app-menu"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Dernières maraudes <b class="caret"></b></a>
<ul class="dropdown-menu">{% for m in dernieres_maraudes %}
<li><a href="{% url 'maraudes:details' m.pk %}" {% if maraude == m %}style="color:#fff;"{% endif %}>
<strong>{{ m }}</strong> <small>{{m.binome}} & {{m.referent}}</small></a>
</li>{% endfor %}
</ul>
</li>
{% endif %}

View File

@@ -1,7 +1,6 @@
import datetime
import calendar
from django.utils import timezone
from django.utils.functional import cached_property
from django.contrib import messages
from django.shortcuts import render, redirect
# Views
@@ -22,56 +21,14 @@ from .forms import ( RencontreForm, RencontreInlineFormSet,
ObservationInlineFormSet, ObservationInlineFormSetNoExtra,
MaraudeAutoDateForm, MonthSelectForm, )
from utilisateurs.models import Maraudeur
from website import decorators as website
maraudes = website.app_config(
name="maraudes",
groups=[Maraudeur],
menu=["maraudes/menu/dernieres_maraudes.html"],
admin_menu=["maraudes/menu/admin_menu.html"],
ajax=False,
)
compte_rendu = website.app_config(
name="maraudes",
groups=[Maraudeur],
menu=["compte_rendu/menu/creation.html"],
ajax=False,
)
maraudes_ajax = website.app_config(
name="maraudes",
groups=[Maraudeur],
ajax=True,
)
from django.core.mail import send_mail
class DerniereMaraudeMixin(object):
count = 5
@cached_property
def dernieres_maraudes(self):
""" Renvoie la liste des 'Maraude' passées et terminées """
return Maraude.objects.get_past().filter(
heure_fin__isnull=False
).order_by(
'-date'
)[:self.count]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['dernieres_maraudes'] = self.dernieres_maraudes
return context
from .apps import maraudes
@maraudes.using(title=('La Maraude', 'Tableau de bord'))
class IndexView(generic.TemplateView):
@maraudes
class IndexView(DerniereMaraudeMixin, generic.TemplateView):
class PageInfo:
title = "Maraude - Tableau de bord"
header = "La Maraude"
header_small = "Tableau de bord"
# TemplateView
template_name = "maraudes/index.html"
def get_context_data(self, *args, **kwargs):
@@ -101,48 +58,44 @@ class IndexView(DerniereMaraudeMixin, generic.TemplateView):
return Maraude.objects.next
## MARAUDES
@maraudes
class MaraudeDetailsView(DerniereMaraudeMixin, generic.DetailView):
@maraudes.using(title=('{{maraude.date}}', 'compte-rendu'))
class MaraudeDetailsView(generic.DetailView):
""" Vue détaillé d'un compte-rendu de maraude """
model = CompteRendu
context_object_name = "maraude"
template_name = "maraudes/details.html"
# Template
class PageInfo:
title = "Maraude - {{maraude.date}}"
header = "{{maraude.date}}"
header_small = "{{maraude.referent}} & {{maraude.binome}}"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['notes'] = self.object.get_observations()
return context
@maraudes
class MaraudeListView(DerniereMaraudeMixin, generic.ListView):
@maraudes.using(title=('Liste des maraudes',))
class MaraudeListView(generic.ListView):
""" Vue de la liste des compte-rendus de maraude """
model = CompteRendu
template_name = "maraudes/liste.html"
paginate_by = 30
class PageInfo:
title = "Maraude - Liste des maraudes"
header = "Liste des maraudes"
def get_queryset(self):
today = datetime.date.today()
return super().get_queryset().filter(
date__lte=timezone.localtime(timezone.now()).date()
current_date = timezone.localtime(timezone.now()).date()
qs = super().get_queryset().filter(
date__lte=current_date
).order_by('-date')
filtre = self.request.GET.get('filter', None)
if filtre == "month-only":
return qs.filter(date__month=current_date.month)
#Other cases...
else:
return qs
## COMPTE-RENDU DE MARAUDE
@compte_rendu
@maraudes.using(title=('{{maraude.date}}', 'rédaction'))
class CompteRenduCreateView(generic.DetailView):
""" Vue pour la création d'un compte-rendu de maraude """
@@ -153,11 +106,6 @@ class CompteRenduCreateView(generic.DetailView):
form = None
inline_formset = None
class PageInfo:
title = "{{maraude}} - Compte-rendu"
header = "{{maraude.date}}"
header_small = "écriture du compte-rendu"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#WARNING: Overrides app_menu and replace it
@@ -180,11 +128,8 @@ class CompteRenduCreateView(generic.DetailView):
# Add text to some mails ? Transmission, message à un référent, etc...
# Send mail to Maraudeurs
_from = maraude.referent.email
exclude = (maraude.referent, maraude.binome)
recipients = []
for m in Maraudeur.objects.all():
if not m in exclude:
recipients.append(m.email)
# Shall select only Maraudeur where 'is_active' is True !
recipients = [m for m in Maraudeur.objects.all() if m not in (maraude.referent, maraude.binome)]
objet = "Compte-rendu de maraude : %s" % maraude.date
message = "Sujets rencontrés : ..." #TODO: Mail content
send_mail(objet, message, _from, recipients)
@@ -245,7 +190,7 @@ class CompteRenduCreateView(generic.DetailView):
@compte_rendu
@maraudes.using(title=('{{maraude.date}}', 'mise à jour'))
class CompteRenduUpdateView(generic.DetailView):
""" Vue pour mettre à jour le compte-rendu de la maraude """
@@ -253,11 +198,6 @@ class CompteRenduUpdateView(generic.DetailView):
context_object_name = "maraude"
template_name = "compte_rendu/compterendu_update.html"
class PageInfo:
title = "{{maraude}} - Compte-rendu"
header = "{{maraude.date}}"
header_small = "compte-rendu"
base_formset = None
inline_formsets = []
rencontres_queryset = None
@@ -311,17 +251,12 @@ class CompteRenduUpdateView(generic.DetailView):
## PLANNING
@maraudes
@maraudes.using(title=('Planning',))
class PlanningView(generic.TemplateView):
""" Display and edit the planning of next Maraudes """
template_name = "planning/planning.html"
class PageInfo:
title = "Planning"
header = "Planning"
header_small = "{{month}} {{year}}" #TODO: does not parse extra context
def _parse_request(self):
self.current_date = datetime.date.today()
try: self.month = int(self.request.GET['month'])
@@ -381,18 +316,13 @@ class PlanningView(generic.TemplateView):
## LIEU
@maraudes_ajax
@maraudes.using(ajax=True)
class LieuCreateView(generic.edit.CreateView):
model = Lieu
template_name = "maraudes/lieu_create.html"
fields = "__all__"
success_url = "/maraudes/"
class PageInfo:
pass
permissions = ['maraudes.add_lieu']
def post(self, request, *args, **kwargs):
if 'next' in self.request.POST:
self.success_url = self.request.POST["next"]