* 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 authored and GitHub committed 2017-02-11 18:20:13 +01:00
1 parent 288ca2cc20
commit 0be59a61a7
61 files changed
+656 -516

No files matched your search

+16
View File
@@ -8,3 +8,19 @@ class Config(AppConfig):
menu_icon = "road" menu_icon = "road"
def get_index_url(self): def get_index_url(self):
return "/maraudes/" 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)
+1
View File
@@ -87,6 +87,7 @@ class Maraude(models.Model):
limit_choices_to={ limit_choices_to={
'is_superuser': False, 'is_superuser': False,
'is_staff': True, 'is_staff': True,
'is_active': True,
} }
) )
@@ -1,15 +1,15 @@
{% load notes %} {% load notes %}
<h3> Compte-rendu </h3> <div class="col-lg-6 col-md-12">
<table class="table table-bordered"> <table class="table table-bordered">
{% for note in notes %} {% for note in notes %}
{% inline_table note header="sujet" %} {% inline_table note header="sujet" %}
{% endfor %} {% endfor %}
</table> </table>
</div>
<div class="col-lg-6 col-md-12">
<div class="well bg-info"> <div class="well bg-info">
<p><strong>Informations</strong></p> <p><strong>Informations</strong></p>
<p>Rencontres : {{ maraude.observation_count}}</p> <p>Rencontres : {{ maraude.rencontre_count}}</p>
<p>Personnes rencontrées : {{ maraude.observation_count}}</p>
</div>
</div> </div>
@@ -1,6 +1,6 @@
{% load bootstrap3 %}{% load staticfiles %} {% 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"> <script type="text/javascript">
/* Dynamic Formsets */ /* Dynamic Formsets */
$(function() { $(function() {
@@ -38,6 +38,14 @@
<div class="row"> <div class="row">
<div class="col-lg-6 col-md-12"> <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"> <form method="post" action="{% url 'maraudes:create' maraude.pk %}?finalize=False">
{% csrf_token %} {% csrf_token %}
<div class="panel panel-primary panel-collapse"> <div class="panel panel-primary panel-collapse">
@@ -71,7 +79,7 @@
</div> </div>
</div> </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 fade" id="form-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@@ -85,3 +93,12 @@
</div> </div>
</div> </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>
@@ -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>
Whitespace-only changes.
-2
View File
@@ -1,9 +1,7 @@
<div class="col-md-12 col-lg-6">
{% if maraude.est_terminee %} {% if maraude.est_terminee %}
{% include "compte_rendu/compterendu.html" %} {% include "compte_rendu/compterendu.html" %}
{% else %} {% else %}
{% if perms.maraudes.can_add_compterendu %}<a class="btn btn-primary" href="{% url 'maraudes:create' maraude.pk %}">Écrire le compte-rendu</a> {% 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 %} {% else %} <p class="alert alert-info">Le compte-rendu n'a pas encore été écrit</p>{% endif %}
{% endif %} {% endif %}
</div>
+7 -1
View File
@@ -11,7 +11,13 @@
avec {% if user.is_superuser %}{{prochaine_maraude.binome}}{%else%}{{prochaine_maraude.referent}}{%endif%}. avec {% if user.is_superuser %}{{prochaine_maraude.binome}}{%else%}{{prochaine_maraude.referent}}{%endif%}.
</strong></p> </strong></p>
<hr /> <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 %} {% else %}<p>Aucune maraude prévue.</p>{% endif %}
</div> </div>
</div> </div>
@@ -1,12 +1,9 @@
{% if object.est_terminee %} {% if object.est_terminee %}<a href="{% url 'maraudes:details' object.id %}" class="btn btn-link">
<a href="{% url 'maraudes:details' object.id %}" class="btn btn-link"> {% else %}<a href="#" class="btn btn-link disabled">{% endif %}
{% else %} {{object.date}}</a>
<a href="#" class="btn btn-link disabled"> <div class="pull-right">
{% endif %}
{{object.date}}
</a>
<span class="label label-info">{{ object.binome }} & {{ object.referent }}</span> <span class="label label-info">{{ object.binome }} & {{ object.referent }}</span>
{% if object.est_terminee %} {% if object.est_terminee %}
<span class="label label-success">{{object.rencontres.count}} rencontres</span> <span class="label label-success">{{object.rencontres.count}} rencontres</span>
{% endif %} {% endif %}
</div>
+11 -13
View File
@@ -1,14 +1,13 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% load tables %} {% load tables %}
<div class="col-md-12 col-lg-6"> <div class="col-lg-10">
<div class="panel panel-primary"> <div class="panel panel-primary">
<!-- Default panel contents --> <!-- Default panel contents -->
<div class="panel-heading text-center"> <div class="panel-heading text-center">
<h3 class="panel-title">Maraudes passées</h3> <h3 class="panel-title">Maraudes passées</h3>
</div> </div>
<!-- Table --> <!-- 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 %} {% if is_paginated %}
<div class="panel-footer text-center"> <div class="panel-footer text-center">
<ul class="pagination"> <ul class="pagination">
@@ -20,15 +19,14 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="col-lg-3 col-md-3"> <div class="pull-left">
<div class="panel panel-default"> <div class="btn-group">
<!-- Default panel contents --> <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<div class="panel-heading text-center"> Filtrer <span class="caret"></span>
<h3 class="panel-title">Filtres</h3> </button>
</div> <ul class="dropdown-menu">
<div class="list-group"> <li><a href="{% url 'maraudes:liste' %}">Pas de filtre</a></li>
<a href="#" class="list-group-item disabled">Vos maraudes seulement</a></li> <li><a href="?filter=month-only">Ce mois-ci</a></li>
<a href="#" class="list-group-item disabled">Ce mois-ci</a></li> </ul>
</div>
</div> </div>
</div> </div>
@@ -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 %}
@@ -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 %}
+23 -93
View File
@@ -1,7 +1,6 @@
import datetime import datetime
import calendar import calendar
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property
from django.contrib import messages from django.contrib import messages
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
# Views # Views
@@ -22,56 +21,14 @@ from .forms import ( RencontreForm, RencontreInlineFormSet,
ObservationInlineFormSet, ObservationInlineFormSetNoExtra, ObservationInlineFormSet, ObservationInlineFormSetNoExtra,
MaraudeAutoDateForm, MonthSelectForm, ) 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 from django.core.mail import send_mail
class DerniereMaraudeMixin(object): from .apps import maraudes
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
@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" template_name = "maraudes/index.html"
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
@@ -101,48 +58,44 @@ class IndexView(DerniereMaraudeMixin, generic.TemplateView):
return Maraude.objects.next return Maraude.objects.next
## MARAUDES ## MARAUDES
@maraudes @maraudes.using(title=('{{maraude.date}}', 'compte-rendu'))
class MaraudeDetailsView(DerniereMaraudeMixin, generic.DetailView): class MaraudeDetailsView(generic.DetailView):
""" Vue détaillé d'un compte-rendu de maraude """ """ Vue détaillé d'un compte-rendu de maraude """
model = CompteRendu model = CompteRendu
context_object_name = "maraude" context_object_name = "maraude"
template_name = "maraudes/details.html" 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): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['notes'] = self.object.get_observations() context['notes'] = self.object.get_observations()
return context return context
@maraudes.using(title=('Liste des maraudes',))
@maraudes class MaraudeListView(generic.ListView):
class MaraudeListView(DerniereMaraudeMixin, generic.ListView):
""" Vue de la liste des compte-rendus de maraude """ """ Vue de la liste des compte-rendus de maraude """
model = CompteRendu model = CompteRendu
template_name = "maraudes/liste.html" template_name = "maraudes/liste.html"
paginate_by = 30 paginate_by = 30
class PageInfo:
title = "Maraude - Liste des maraudes"
header = "Liste des maraudes"
def get_queryset(self): def get_queryset(self):
today = datetime.date.today() current_date = timezone.localtime(timezone.now()).date()
return super().get_queryset().filter( qs = super().get_queryset().filter(
date__lte=timezone.localtime(timezone.now()).date() date__lte=current_date
).order_by('-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 DE MARAUDE
@compte_rendu @maraudes.using(title=('{{maraude.date}}', 'rédaction'))
class CompteRenduCreateView(generic.DetailView): class CompteRenduCreateView(generic.DetailView):
""" Vue pour la création d'un compte-rendu de maraude """ """ Vue pour la création d'un compte-rendu de maraude """
@@ -153,11 +106,6 @@ class CompteRenduCreateView(generic.DetailView):
form = None form = None
inline_formset = None inline_formset = None
class PageInfo:
title = "{{maraude}} - Compte-rendu"
header = "{{maraude.date}}"
header_small = "écriture du compte-rendu"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
#WARNING: Overrides app_menu and replace it #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... # Add text to some mails ? Transmission, message à un référent, etc...
# Send mail to Maraudeurs # Send mail to Maraudeurs
_from = maraude.referent.email _from = maraude.referent.email
exclude = (maraude.referent, maraude.binome) # Shall select only Maraudeur where 'is_active' is True !
recipients = [] recipients = [m for m in Maraudeur.objects.all() if m not in (maraude.referent, maraude.binome)]
for m in Maraudeur.objects.all():
if not m in exclude:
recipients.append(m.email)
objet = "Compte-rendu de maraude : %s" % maraude.date objet = "Compte-rendu de maraude : %s" % maraude.date
message = "Sujets rencontrés : ..." #TODO: Mail content message = "Sujets rencontrés : ..." #TODO: Mail content
send_mail(objet, message, _from, recipients) 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): class CompteRenduUpdateView(generic.DetailView):
""" Vue pour mettre à jour le compte-rendu de la maraude """ """ Vue pour mettre à jour le compte-rendu de la maraude """
@@ -253,11 +198,6 @@ class CompteRenduUpdateView(generic.DetailView):
context_object_name = "maraude" context_object_name = "maraude"
template_name = "compte_rendu/compterendu_update.html" template_name = "compte_rendu/compterendu_update.html"
class PageInfo:
title = "{{maraude}} - Compte-rendu"
header = "{{maraude.date}}"
header_small = "compte-rendu"
base_formset = None base_formset = None
inline_formsets = [] inline_formsets = []
rencontres_queryset = None rencontres_queryset = None
@@ -311,17 +251,12 @@ class CompteRenduUpdateView(generic.DetailView):
## PLANNING ## PLANNING
@maraudes @maraudes.using(title=('Planning',))
class PlanningView(generic.TemplateView): class PlanningView(generic.TemplateView):
""" Display and edit the planning of next Maraudes """ """ Display and edit the planning of next Maraudes """
template_name = "planning/planning.html" 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): def _parse_request(self):
self.current_date = datetime.date.today() self.current_date = datetime.date.today()
try: self.month = int(self.request.GET['month']) try: self.month = int(self.request.GET['month'])
@@ -381,18 +316,13 @@ class PlanningView(generic.TemplateView):
## LIEU ## LIEU
@maraudes_ajax @maraudes.using(ajax=True)
class LieuCreateView(generic.edit.CreateView): class LieuCreateView(generic.edit.CreateView):
model = Lieu model = Lieu
template_name = "maraudes/lieu_create.html" template_name = "maraudes/lieu_create.html"
fields = "__all__" fields = "__all__"
success_url = "/maraudes/" success_url = "/maraudes/"
class PageInfo:
pass
permissions = ['maraudes.add_lieu']
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if 'next' in self.request.POST: if 'next' in self.request.POST:
self.success_url = self.request.POST["next"] self.success_url = self.request.POST["next"]
+6 -2
View File
@@ -23,8 +23,8 @@ LOGIN_URL = 'index'
BOOTSTRAP3 = { BOOTSTRAP3 = {
# The URL to the jQuery JavaScript file # The URL to the jQuery JavaScript file
'base_url': os.path.join(STATIC_URL, 'bootstrap/'), 'base_url': os.path.join(STATIC_URL, 'css', 'bootstrap/'),
'jquery_url': '//code.jquery.com/jquery.min.js', 'jquery_url': os.path.join(STATIC_URL, 'scripts', 'jquery.min.js'),
'include_jquery': True, 'include_jquery': True,
# Label class to use in horizontal forms # Label class to use in horizontal forms
'horizontal_label_class': 'col-md-2', 'horizontal_label_class': 'col-md-2',
@@ -32,6 +32,10 @@ BOOTSTRAP3 = {
'horizontal_field_class': 'col-md-10', 'horizontal_field_class': 'col-md-10',
} }
# Django-select2 Configuration
SELECT2_JS = 'scripts/select2.min.js'
SELECT2_CSS = 'css/select2.min.css'
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
'website.backends.MyBackend' 'website.backends.MyBackend'
+11
View File
@@ -3,3 +3,14 @@ from django.apps import AppConfig
class SuiviConfig(AppConfig): class SuiviConfig(AppConfig):
name = 'suivi' name = 'suivi'
from utilisateurs.models import Maraudeur
from website.decorators import Webpage
suivi = Webpage("suivi", icon="eye-open", defaults={
'restricted': [Maraudeur],
'ajax': False,
}
)
suivi.app_menu.add_link(('Liste des sujets', 'suivi:liste', 'list'))
+1 -1
View File
@@ -1,5 +1,4 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{{ form.media.js }}{{ form.media.css }}
<form action="" method="POST">{% csrf_token %} <form action="" method="POST">{% csrf_token %}
{% with "inline" as layout %} {% with "inline" as layout %}
<div class="form-{{layout}} well col-md-10 col-md-offset-2"> <div class="form-{{layout}} well col-md-10 col-md-offset-2">
@@ -14,3 +13,4 @@
</div> {% endwith %} </div> {% endwith %}
<div class="pull-right">{% bootstrap_button "Enregistrer l'appel" button_type="submit" %}</div> <div class="pull-right">{% bootstrap_button "Enregistrer l'appel" button_type="submit" %}</div>
</form> </form>
{{ form.media.js }}{{ form.media.css }}
@@ -1,10 +0,0 @@
{% 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 'sujets:create' %}">{% bootstrap_icon "plus" %} Nouveau sujet</a></li>
<li><a href="{% url 'admin:app_list' app_label='sujets' %}">
{% bootstrap_icon "wrench" %} Gérer les sujets</a></li>
</ul>
-6
View File
@@ -1,6 +0,0 @@
{% load bootstrap3 %}
<li class="app-menu">
<a href="{% url 'suivi:liste' %}">Liste des sujets
<span class="pull-right">{% bootstrap_icon "list" %}</span>
</a>
</li>
+10 -26
View File
@@ -7,15 +7,7 @@ from .forms import *
from notes.mixins import NoteFormMixin from notes.mixins import NoteFormMixin
from notes.forms import AutoNoteForm from notes.forms import AutoNoteForm
# Create your views here. # Create your views here.
from utilisateurs.models import Maraudeur
from website import decorators as website
suivi = website.app_config(
name="suivi",
groups=[Maraudeur],
menu=["suivi/menu/sujets.html"],
admin_menu=["suivi/menu/admin_sujets.html"],
ajax=False,
)
from maraudes.compte_rendu import CompteRendu from maraudes.compte_rendu import CompteRendu
@@ -30,12 +22,10 @@ def derniers_sujets_rencontres():
sujets.add(obs.sujet) sujets.add(obs.sujet)
return sujets return sujets
@suivi from .apps import suivi
@suivi.using(title=("Suivi", "Tableau de bord"))
class IndexView(NoteFormMixin, generic.TemplateView): class IndexView(NoteFormMixin, generic.TemplateView):
class PageInfo:
title = "Suivi des bénéficiaires"
header = "Suivi"
header_small = "Tableau de bord"
#NoteFormMixin #NoteFormMixin
forms = { forms = {
'appel': AppelForm, 'appel': AppelForm,
@@ -54,11 +44,10 @@ class IndexView(NoteFormMixin, generic.TemplateView):
context['derniers_sujets'] = ", ".join(map(str, derniers_sujets_rencontres())) context['derniers_sujets'] = ", ".join(map(str, derniers_sujets_rencontres()))
return context return context
@suivi
@suivi.using(title=('Liste des sujets',))
class SujetListView(generic.ListView): class SujetListView(generic.ListView):
class PageInfo:
title = "Sujet - Liste des sujets"
header = "Liste des sujets"
#ListView #ListView
model = Sujet model = Sujet
template_name = "sujets/sujet_liste.html" template_name = "sujets/sujet_liste.html"
@@ -77,15 +66,10 @@ class SujetListView(generic.ListView):
context['query_text'] = self.request.POST.get('q', None) context['query_text'] = self.request.POST.get('q', None)
return context return context
# Import app_config from 'sujets' application, using
# its admin_menu option
from sujets.views import sujets @suivi.using(title=('{{sujet}}', 'suivi'))
@sujets
class SuiviSujetView(NoteFormMixin, generic.DetailView): class SuiviSujetView(NoteFormMixin, generic.DetailView):
class PageInfo:
title = "Sujet - {{sujet}}"
header = "{{sujet}}"
header_small = "suivi"
#NoteFormMixin #NoteFormMixin
forms = { forms = {
'note': AutoNoteForm, 'note': AutoNoteForm,
@@ -1,7 +0,0 @@
{% if user.is_superuser %}
{% load bootstrap3 %}
<li class="dropdown app-menu"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Sujet<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="{% url 'admin:notes_note_changelist' %}?sujet__personne_ptr__exact={{sujet.pk}}">{% bootstrap_icon "wrench" %} Éditer les notes</a></li>
</ul>
{% endif %}
Whitespace-only changes.
@@ -1,16 +1,63 @@
{% load boolean_icons %}
<table class="table"> <table class="table">
<tr><th colspan="4" class="active">Informations générales</th></tr> <tr>
<tr><th>Première rencontre :</th><td> {{ sujet.premiere_rencontre }}</td></tr> <th colspan="4" class="active">Informations générales</th>
<tr><th colspan="4" class="active">État-civil</th></tr> </tr>
<tr><th>Nom</th><td>{{ sujet.nom|default:'?' }}</td><th>Prénom</th><td>{{ sujet.prenom|default:'?' }}</td></tr> <tr>
<tr><th>Sexe</th><td>{{ sujet.genre }}</td><th>Âge</th><td>{{ sujet.age|default:'?' }}</td></tr> <th>Première rencontre :</th>
<tr><th colspan="4" class="active">Problématiques</th></tr> <td> {{ sujet.premiere_rencontre }}</td>
<tr><th>Psychiatrique</th><td>{{ sujet.prob_psychiatrie }}</td><th>Addiction</th><td>{{ sujet.prob_addiction }}</td></tr> </tr>
<tr><th>Administratif</th><td>{{ sujet.prob_administratif }}</td><th>Somatique</th><td>{{ sujet.prob_somatique }}</td></tr> <tr>
<tr><th colspan="4" class="active">Habitation</th></tr></td></tr> <th colspan="4" class="active">État-civil</th>
<tr><th>Type</th><td>{{ sujet.habitation }}</td><th>Connu du SIAO</th><td>{{ sujet.connu_siao }}</td></tr> </tr>
<tr><th colspan="4" class="active">Ressources</th></tr></td></tr> <tr>
<tr><td colspan="4">{{ sujet.ressources }}</td></tr> <th>Nom</th>
<tr><th colspan="4" class="active">Parcours de vie</th></tr></td></tr> <td>{{ sujet.nom|default:'?' }}</td>
<tr><td colspan="2">{{ sujet.parcours_de_vie }}</td><th>Lien familial</th><td>{{ sujet.lien_familial }}</td></tr> <th>Prénom</th>
<td>{{ sujet.prenom|default:'?' }}</td>
</tr>
<tr>
<th>Sexe</th>
<td>{{ sujet.genre }}</td>
<th>Âge</th>
<td>{{ sujet.age|default:'?' }}</td>
</tr>
<tr>
<th colspan="4" class="active">Problématiques</th>
</tr>
<tr>
<th>Psychiatrique</th>
<td>{{ sujet.prob_psychiatrie|as_icon }}</td>
<th>Addiction</th>
<td>{{ sujet.prob_addiction|as_icon }}</td>
</tr>
<tr>
<th>Administratif</th>
<td>{{ sujet.prob_administratif|as_icon }}</td>
<th>Somatique</th>
<td>{{ sujet.prob_somatique|as_icon }}</td>
</tr>
<tr>
<th colspan="4" class="active">Habitation</th>
</tr>
<tr>
<th>Type</th>
<td>{{ sujet.habitation }}</td>
<th>Connu du SIAO</th>
<td>{{ sujet.connu_siao|as_icon }}</td>
</tr>
<tr>
<th colspan="4" class="active">Ressources</th>
</tr>
<tr>
<td colspan="4">{{ sujet.ressources }}</td>
</tr>
<tr>
<th colspan="4" class="active">Parcours de vie</th>
</tr>
<tr>
<td colspan="2">{{ sujet.parcours_de_vie }}</td>
<th>Lien familial</th>
<td>{{ sujet.lien_familial|as_icon }}</td>
</tr>
</table> </table>
+16 -16
View File
@@ -1,10 +1,12 @@
{% load tables %} {% load tables %}
{% load bootstrap3 %} {% load bootstrap3 %}
<div class="col-md-12 col-lg-6"> <div class="col-lg-10">
<div class="panel panel-primary"> <div class="panel panel-primary">
<!-- Default panel contents --> <!-- Default panel contents -->
<div class="panel-heading text-center"> <div class="panel-heading text-center">
<h3 class="panel-title">Sujets rencontrés</h3> <h3 class="panel-title">Sujets rencontrés
</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<form action="" method="POST" class="form form-group">{% csrf_token %} <form action="" method="POST" class="form form-group">{% csrf_token %}
@@ -41,19 +43,17 @@
{% endif %}{% endif %} {% endif %}{% endif %}
</div> </div>
</div> </div>
<div class="pull-left">
<div class="col-lg-3 col-md-3"> <div class="btn-group">
<div class="panel panel-default"> <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<!-- Default panel contents --> Filtrer <span class="caret"></span>
<div class="panel-heading text-center"> </button>
<h3 class="panel-title">Filtres</h3> <ul class="dropdown-menu">
</div> <li><a href="{% url 'suivi:liste' %}">Pas de filtre</a></li>
<div class="list-group"> <li role="separator" class="divider"></li>
<a href="#" class="list-group-item disabled">Rencontrés ce mois-ci</a></li> <li><a href="?filter=month-only">Rencontrés ce mois-ci</a></li>
<a href="#" class="list-group-item disabled"><strong>Mes accompagnements</strong></a></li> <li><a href="?filter=">Hébérgé en urgence</a></li>
<a href="#" class="list-group-item disabled">Hébérgé en urgence</a></li> <li><a href="?filter=">Sans abri</a></li>
<a href="#" class="list-group-item disabled">Sans logement</a></li> </ul>
</div>
</div> </div>
</div> </div>
+6 -24
View File
@@ -6,49 +6,31 @@ from .forms import SujetCreateForm
### Webpage config ### Webpage config
from utilisateurs.models import Maraudeur from utilisateurs.models import Maraudeur
from website import decorators as website from website.decorators import Webpage
sujets = website.app_config( sujets = Webpage( "suivi", menu=False, defaults={
name="suivi", 'restricted': [Maraudeur],
groups=[Maraudeur], 'ajax': True,
menu=["suivi/menu/sujets.html"], }
admin_menu=["sujets/menu/admin_sujet.html"],
ajax=True,
) )
### Views ### Views
@sujets @sujets.using(title=('{{object}}', 'details'))
class SujetDetailsView(generic.DetailView): class SujetDetailsView(generic.DetailView):
class PageInfo:
title = "Sujet - {{ sujet }}"
header = "{{ sujet }}"
header_small = "informations"
#DetailView #DetailView
template_name = "sujets/sujet_details.html" template_name = "sujets/sujet_details.html"
model = Sujet model = Sujet
@sujets @sujets
class SujetUpdateView(generic.edit.UpdateView): class SujetUpdateView(generic.edit.UpdateView):
class PageInfo:
title = "Mise à jour - {{sujet}}"
header = "{{sujet}}"
header_small = "mise à jour"
#UpdateView #UpdateView
template_name = "sujets/sujet_update.html" template_name = "sujets/sujet_update.html"
model = Sujet model = Sujet
fields = '__all__' fields = '__all__'
@sujets @sujets
class SujetCreateView(generic.edit.CreateView): class SujetCreateView(generic.edit.CreateView):
class PageInfo:
title = "Nouveau sujet"
header = "Nouveau sujet"
#CreateView #CreateView
template_name = "sujets/sujet_create.html" template_name = "sujets/sujet_create.html"
form_class = SujetCreateForm form_class = SujetCreateForm
+1 -1
View File
@@ -12,7 +12,7 @@ class MaraudeurAdmin(admin.ModelAdmin):
('Informations', {'fields': [('first_name', 'last_name')]}), ('Informations', {'fields': [('first_name', 'last_name')]}),
] ]
list_display = ('first_name', 'last_name', 'is_active') list_display = ('username', 'is_active')
+11
View File
@@ -1,5 +1,16 @@
from django.apps import AppConfig from django.apps import AppConfig
from website.decorators import Webpage
from .models import Professionnel
class UtilisateursConfig(AppConfig): class UtilisateursConfig(AppConfig):
name = 'utilisateurs' name = 'utilisateurs'
utilisateurs = Webpage('utilisateurs',
icon="user",
defaults={
'users': [Professionnel],
'ajax': False,
'title': ('Utilisateurs','app'),
})
@@ -0,0 +1,2 @@
{{ user.first_name }}, {{ user.last_name }}
+7
View File
@@ -0,0 +1,7 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.UtilisateurView.as_view(), name="index"),
]
+14
View File
@@ -0,0 +1,14 @@
from django.views import generic
from .apps import utilisateurs
from .models import Professionnel
@utilisateurs
class UtilisateurView(generic.DetailView):
template_name = "utilisateurs/details.html"
model = Professionnel
def get_object(self):
qs = self.get_queryset()
return qs.filter(pk=self.request.user.pk)
+81 -27
View File
@@ -1,39 +1,93 @@
from .mixins import * from .mixins import (WebsiteTemplateMixin, WebsiteAjaxTemplateMixin,
SpecialUserRequiredMixin)
from .navbar import ApplicationMenu
def _insert_bases(cls, bases): def _insert_bases(cls, bases):
""" Insert new bases in given view class """
old_bases = cls.__bases__ old_bases = cls.__bases__
new_bases = tuple(bases) + old_bases new_bases = tuple(bases) + old_bases
cls.__bases__ = new_bases cls.__bases__ = new_bases
def app_config(**options):
""" Insert per-application configuration options :
-- name : name of the app to register under in navbar class Webpage:
-- groups : user groups needed to access this application """ Webpage configurator. It is used as a decorator.
-- menu : user menu templates to be used
-- admin_menu : admin menu templates, only appear for superuser The constructor takes one positionnal argument:
-- ajax : view will return content_template for Ajax requests - app_name : name of the application where this view shall be categorized.
and keyword arguments:
- defaults : mapping of default options.
- menu : does it register a menu ? default is True
- icon : bootstrap name of menu header icon, ignored if 'menu' is False.
Options are :
- title: tuple of (header, header_small), header_small is optionnal.
- restricted: set of group to which access is restricted.
- ajax: can this view be called as ajax ?
""" """
name = options.pop('name', None)
groups = options.pop('groups', []) #Transition from app_users
menu = options.pop('menu', [])
admin_menu = options.pop('admin_menu', [])
ajax = options.pop('ajax', False)
new_bases = [] options = [
if ajax: ('title', ('Unset', 'small header')),
new_bases.append(WebsiteAjaxTemplateMixin) ('restricted', []),
('ajax', False)
]
def __init__(self, app_name, icon=None, defaults={}, menu=True):
self.app_name = app_name
if menu: # Build ApplicationMenu subclass
app_menu = type(
app_name.title() + "Menu",
(ApplicationMenu,),
{'name': app_name,
'header': (app_name.title(), '%s:index' % app_name, icon),
'_links': [],
'_dropdowns': [],
}
)
self.app_menu = app_menu
else: else:
new_bases.append(WebsiteTemplateMixin) self.app_menu = None
if groups: #TODO: use group instaed of user class self._defaults = {}
new_bases.append(SpecialUserRequiredMixin) self._updated = {} # Store updated options
# Set all default options
for opt_name, opt_default in self.options:
self._set_option(opt_name, defaults.get(opt_name, opt_default))
def class_decorator(cls): def __getattr__(self, attr):
_insert_bases(cls, new_bases) """ Return the overriden value if any, default overwise """
cls._user_menu = menu return self._updated.get(attr, self._defaults[attr])
cls._admin_menu = admin_menu
cls.app_name = name
cls.app_users = groups.copy()
return cls
return class_decorator def _set_option(self, attr, value):
""" Set the default value if there is none already, updated overwise """
if not attr in self._defaults:
self._defaults[attr] = value
else:
if attr in self._updated:
raise RuntimeError(attr, 'has already been updated !')
self._updated[attr] = value
def __call__(self, view_cls):
""" Setup the view and return it """
bases_to_add = []
if self.ajax: bases_to_add.append(WebsiteAjaxTemplateMixin)
else: bases_to_add.append(WebsiteTemplateMixin)
if self.restricted: bases_to_add.append(SpecialUserRequiredMixin)
_insert_bases(view_cls, bases_to_add)
# Setup configuration. ISSUE: defaults values will be overriden !
view_cls.app_name = self.app_name
view_cls.header = self.title
view_cls.app_users = self.restricted
self._updated = {} # Reset updated attributes to avoid misbehavior
return view_cls
def using(self, **kwargs):
""" Overrides defaults options with the values given """
for opt_name, _ in self.options:
if opt_name in kwargs:
self._set_option(opt_name, kwargs[opt_name])
return self
+28 -125
View File
@@ -1,151 +1,64 @@
import datetime
from django.utils import timezone
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.apps import apps
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
from django.template import Template, Context from django.template import Template, Context
from django.views.generic.base import ContextMixin, TemplateResponseMixin from django.views.generic.base import TemplateResponseMixin
## Mixins ## ## Mixins ##
def special_user_required(authorized_users):
valid_cls = tuple(authorized_users)
def check_special_user(user):
print('check user is instance of', valid_cls)
if isinstance(user, valid_cls):
return True
else:
return False
return user_passes_test(check_special_user)
class SpecialUserRequiredMixin(object): class SpecialUserRequiredMixin(object):
""" Requires that the User is an instance of some class """
app_users = [] app_users = []
@classmethod @classmethod
def as_view(cls, **initkwargs): def as_view(cls, **initkwargs):
view = super().as_view(**initkwargs) view = super().as_view(**initkwargs)
return special_user_required(cls.app_users)(view) return cls.special_user_required(cls.app_users)(view)
@staticmethod
def special_user_required(authorized_users):
valid_cls = tuple(authorized_users)
if not valid_cls: # No restriction usually means misconfiguration !
raise ImproperlyConfigured(
'A view was configured as "restricted" with no restricting parameters !')
def check_special_user(user):
class TemplateFieldsMetaclass(type): if isinstance(user, valid_cls):
""" Loads Template objects with given string for return True
header, header_small, title, ... else:
return False
Theses strings shall be found in cls.Template return user_passes_test(check_special_user)
"""
def __init__(cls, bases, Dict):
pass
def user_processor(request, context): def user_processor(request, context):
context['user_group'] = request.user.__class__.__qualname__ context['user_group'] = request.user.__class__.__qualname__
return context return context
def header_processor(header, context):
class NavbarMixin(object): context['page_header'] = Template(header[0]).render(context)
context['page_header_small'] = Template(header[1]).render(context) if len(header) == 2 else ''
registered_apps = ['maraudes', 'suivi'] context['page_title'] = " - ".join((context['page_header'], context['page_header_small']))
app_name = None return context
def get_apps_config(self): class WebsiteTemplateMixin(TemplateResponseMixin):
""" Load additionnal config data on each app registered in navbar
Add :
- menu_icon : glyphicon in sidebar
- disabled : show/hide in sidebar
"""
## Utils ##
APP_ICONS = {
'maraudes': 'road',
'suivi': 'eye-open',
}
app_names = self.registered_apps
self._apps = []
for name in app_names:
app_config = apps.get_app_config(name)
app_config.menu_icon = APP_ICONS[name]
#TODO: Seems unsafe (only need module perm)
app_config.disabled = not self.request.user.has_module_perms(name)
self._apps.append(app_config)
return self._apps
@property
def apps(self):
if not hasattr(self, '_apps'):
self._apps = self.get_apps_config()
return self._apps
def get_active_app(self):
if not self.app_name:
self.app_name = self.__class__.__module__.split(".")[0]
# If app is website, there is no "active" application
if self.app_name == "website":
return None
active_app = apps.get_app_config(self.app_name)
if not active_app in self.apps: #TODO: how do we deal with this ?
raise ValueError("%s must be registered in Configuration.navbar_apps" % active_app)
return active_app
@property
def active_app(self):
if not hasattr(self, '_active_app'):
self._active_app = self.get_active_app()
return self._active_app
@property
def menu(self):
""" Renvoie la liste des templates utilisés comme menu pour l'application
active
"""
if not self.request.user.is_superuser:
return self._user_menu
return self._user_menu + self._admin_menu
class WebsiteTemplateMixin(NavbarMixin, TemplateResponseMixin):
""" Mixin for easy integration of 'website' templates """ Mixin for easy integration of 'website' templates
Each child can specify:
- title : title of the page
- header : header of the page
- header_small : sub-header of the page
If 'content_template' is not defined, value will fallback to template_name If 'content_template' is not defined, value will fallback to template_name
in child view. in child view.
""" """
base_template = "base_site.html" base_template = "base_site.html"
content_template = None content_template = None
app_name = None
_user_menu = []
_admin_menu = []
_groups = []
class Configuration: class Configuration:
stylesheets = ['base.css'] stylesheets = ['css/base.css']
page_blocks = ['header', 'header_small', 'title']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.user = None self.user = None
self._page_blocks = []
if not hasattr(self, "PageInfo"):
raise ImproperlyConfigured("You must define a PageInfo on ", self)
for attr, val in self.PageInfo.__dict__.items():
if attr[0] is not "_" and type(val) is str:
setattr(self, attr, Template(val))
self._page_blocks.append(attr)
def get_template_names(self): def get_template_names(self):
""" Ensure same template for all children views. """ """ Ensure same template for all children views. """
@@ -159,30 +72,20 @@ class WebsiteTemplateMixin(NavbarMixin, TemplateResponseMixin):
raise ImproperlyConfigured(self, "has no template defined !") raise ImproperlyConfigured(self, "has no template defined !")
return self.content_template return self.content_template
def _update_context_with_rendered_blocks(self, context):
""" Render text for existing PageInfo attributes.
See Configuration.page_blocks for valid attribute names """
render_context = Context(context)
for attr in self._page_blocks:
name = "page_%s" % attr
context[name] = getattr(self, attr).render(render_context)
return context
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = Context(super().get_context_data(**kwargs))
self._update_context_with_rendered_blocks(context)
#Website processor #Website processor
context['stylesheets'] = self.Configuration.stylesheets context['stylesheets'] = self.Configuration.stylesheets
context['apps'] = self.apps context['active_app'] = self.app_name # Set by Webpage decorator
context['active_app'] = self.active_app
# User processor # User processor
context = header_processor(self.header, context)
context = user_processor(self.request, context) context = user_processor(self.request, context)
#Webpage #Webpage
context['content_template'] = self.get_content_template() context['content_template'] = self.get_content_template()
context['app_menu'] = self.menu
return context return context
class WebsiteAjaxTemplateMixin(WebsiteTemplateMixin): class WebsiteAjaxTemplateMixin(WebsiteTemplateMixin):
""" Mixin that returns content_template instead of base_template when """ Mixin that returns content_template instead of base_template when
request is Ajax. request is Ajax.
+102
View File
@@ -0,0 +1,102 @@
""" Draft for navbar application menu
"""
from django.urls import reverse
registered = []
class Link:
""" Navbar link
Constructor takes one required argument :
- text : text to display for the link
and two optional arguments :
- target : str or tuple (str, dict)
- icon : bootstrap icon name, no validation
"""
def __init__(self, text, target="#", icon=None):
self.text = text
self._target = target
self.icon = icon
@property
def href(self):
""" Lazy creation of html 'href' value, using 'target' instance attribute """
if not hasattr(self, '_href'):
if self._target == "#": #Create void link
self._href = "#"
else:
try:
target, kwargs = self._target
except ValueError:
target = self._target
kwargs = {}
assert type(target) == str
assert type(kwargs) == dict
self._href = reverse(target, **kwargs)
return self._href
class LinkManager:
""" Per-class manager of links """
def __init__(self):
self.items = []
def __get__(self, instance, owner):
if instance: #Filtering done at each call, not optimized at all !
if not instance.user.is_superuser:
return [link for link in self.items if link.admin_link == False]
else:
return self.items.copy()
return self
def add(self, link):
self.items.append(link)
def __repr__(self):
return '<LinkManager: [' + ', '.join((l.text for l in self.items)) + ']>'
class MenuRegistry(type):
""" Metaclass that registers subclass into module level variable 'registered' """
def __new__(metacls, name, bases, attrs):
cls = type.__new__(metacls, name, bases, attrs)
if name != "ApplicationMenu":
print('registering menu', cls)
registered.append(cls)
# Create Link instance for header attributes
try:
header, target, icon = cls.header
except ValueError:
header = cls.header
target = "#"
icon = None
cls.header = Link(header, target, icon)
cls.links = LinkManager()
return cls
class ApplicationMenu(metaclass=MenuRegistry):
name = None
header = None
def __init__(self, view, user):
self.view = view
self.user = user
self.is_active = self.name == self.view.app_name
@classmethod
def add_link(cls, link, admin=False):
if not isinstance(link, Link):
link = Link(*link)
link.admin_link = admin
cls.links.add(link)
-14
View File
@@ -1,14 +0,0 @@
/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=7f853f3d936c9ba68499a06009229bc9)
* Config saved to config.json and https://gist.github.com/7f853f3d936c9ba68499a06009229bc9
*//*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-downloadLine truncated
@@ -1,23 +1,19 @@
#menu {
border: none;
border-right: 4px solid #980300;
}
.dropdown-toggle{
border-right: 4px solid #980300 !important;
}
.dropdown-menu {
border-bottom: none !important;
}
.navbar-fixed-side .navbar-nav>li>a { .navbar-fixed-side .navbar-nav>li>a {
border-bottom: none; border-bottom: none;
font-variant: small-caps; font-variant: small-caps;
color: #fff; color: #fff;
} }
#menu {
border: none;
border-right: 4px solid #980300;
background-color: #121212;
}
@media (max-width:768px){
#menu { border: none; }
}
.app-menu { .app-menu {
background-color: #121212; background-color: #121212;
@@ -25,10 +21,12 @@
} }
@media (max-width:768px){ .active{
#menu { border: none; } border-right: 2px solid #980300 !important;
} }
.jumbotron { .dropdown-menu {
background-color: #fefefe; border-bottom: none !important;
} }
File renamed without changes.
+11
View File
@@ -0,0 +1,11 @@
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,700");/*!
* bootswatch v3.3.7
* Homepage: http://bootswatch.com
* Copyright 2012-2016 Thomas Park
* Licensed under MIT
* Based on Bootstrap
*//*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-downloadLine truncated
File renamed without changes.
+1
View File
@@ -0,0 +1 @@
.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle;}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none;}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px;}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none;}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap;}.select2-container .select2-search--inline{float:left;}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none;}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051;}.select2-results{display:block;}.select2-results__options{list-style:none;margin:0;padding:0;}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none;}.select2-results__option[aria-selected]{cursor:pointer;}.select2-container--open .select2-dropdown{left:0;}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0;}.select2-search--dropdown{display:block;padding:4px;}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box;}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none;}.select2-search--dropdown.select2-search--hide{display:none;}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0);}.select2-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px;}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px;}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999;}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px;}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0;}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left;}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto;}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default;}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none;}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px;}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%;}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left;}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px;}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px;}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:poLine truncated
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

-21
View File
@@ -1,21 +0,0 @@
/* Maraudes application base stylesheet */
.rencontre {
background-color: #efefef;
width:100%;
border: 5px dotted white;
margin:2px;
margin-bottom:8px;
font-family: Arial;
}
.header td {
color: white;
background-color: black;
text-align:center;
font-weight:bold;
font-family: Arial;
padding: 2px;
}
+43
View File
@@ -0,0 +1,43 @@
/* 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()
}
},
error: function (xhr, ajaxOptions, thrownError) {
// handle response errors here
}
});
});
};
$.fn.openModalEvent = function(id, href, title)
{
$('#'+id).click(function() {
$('#form-modal-body').load(href, function()
{
$('.modal-title').text(title);
$('#form-modal').modal('toggle');
formAjaxSubmit("#form-modal-body form", "#form-modal");
});
});
};
})(jQuery);
File renamed without changes.
+5
View File
@@ -0,0 +1,5 @@
/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"Line truncated
if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=rLine truncated
},cur:function(){var a=Zb.propHooks[this.prop];return a&&a.get?a.get(this):Zb.propHooks._default.get(this)},run:function(a){var b,c=Zb.propHooks[this.prop];return this.pos=b=this.options.duration?m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Zb.propHooks._default.set(this),this}},Zb.prototype.init.prototype=Zb.prototype,Zb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Zb.propHooks.scrollTop=Zb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Zb.prototype.init,m.fx.step={};var $b,_b,ac=/^(?:toggle|show|hide)$/,bc=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cc=/queueHooks$/,dc=[ic],ec={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bc.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bc.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fc(){return setTimeout(function(){$b=void 0}),$b=m.now()}function gc(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hc(a,b,c){for(var d,e=(ec[b]||[]).concat(ec["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ic(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fb(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fb(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ac.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fb(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hc(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jc(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kc(a,b,c){var d,e,f=0,g=dc.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$b||fc(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$b||fc(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jc(k,j.opts.specialEasing);g>f;f++)if(d=dc[f].call(j,a,k,j.opts))return d;return m.map(k,hc,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kc,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],ec[c]=ec[c]||[],ec[c].unshift(b)},prefilter:function(a,b){b?dc.unshift(a):dc.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fLine truncated
//# sourceMappingURL=jquery.min.map
+2
View File
@@ -0,0 +1,2 @@
/*! Select2 4.0.0 | https://github.com/select2/select2/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2&&a.fn.select2.amd)var b=a.fn.select2.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return u.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n=b&&b.split("/"),o=s.map,p=o&&o["*"]||{};if(a&&"."===a.charAt(0))if(b){for(n=n.slice(0,n.length-1),a=a.split("/"),g=a.length-1,s.nodeIdCompat&&w.test(a[g])&&(a[g]=a[g].replace(w,"")),a=n.concat(a),k=0;k<a.length;k+=1)if(m=a[k],"."===m)a.splice(k,1),k-=1;else if(".."===m){if(1===k&&(".."===a[2]||".."===a[0]))break;k>0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}else 0===a.indexOf("./")&&(a=a.substring(2));if((n||p)&&o){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),n)for(l=n.length;l>0;l-=1)if(e=o[n.slice(0,l).join("/")],e&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&p&&p[d]&&(i=p[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){return n.apply(b,v.call(arguments,0).concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){q[a]=b}}function j(a){if(e(r,a)){var c=r[a];delete r[a],t[a]=!0,m.apply(b,c)}if(!e(q,a)&&!e(t,a))throw new Error("No "+a);return q[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return function(){return s&&s.config&&s.config[a]||{}}}var m,n,o,p,q={},r={},s={},t={},u=Object.prototype.hasOwnProperty,v=[].slice,w=/\.js$/;o=function(a,b){var c,d=k(a),e=d[0];return a=d[1],e&&(e=f(e,b),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(b)):f(a,b):(a=f(a,b),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},p={require:function(a){return g(a)},exports:function(a){var b=q[a];return"undefined"!=typeof b?b:q[a]={}},module:function(a){return{id:a,uri:"",exports:q[a],config:l(a)}}},m=function(a,c,d,f){var h,k,l,m,n,s,u=[],v=typeof d;if(f=f||a,"undefined"===v||"function"===v){for(c=!c.length&&d.length?["require","exports","module"]:c,n=0;n<c.length;n+=1)if(m=o(c[n],f),k=m.f,"require"===k)u[n]=p.require(a);else if("exports"===k)u[n]=p.exports(a),s=!0;else if("module"===k)h=u[n]=p.module(a);else if(e(q,k)||e(r,k)||e(t,k))u[n]=j(k);else{if(!m.p)throw new Error(a+" missing "+k);m.p.load(m.n,g(f,!0),i(k),{}),u[n]=q[k]}l=d?d.apply(q[a],u):void 0,a&&(h&&h.exports!==b&&h.exports!==q[a]?q[a]=h.exports:l===b&&s||(q[a]=l))}else a&&(q[a]=d)},a=c=n=function(a,c,d,e,f){if("string"==typeof a)return p[a]?p[a](c):j(o(a,c).f);if(!a.splice){if(s=a,s.deps&&n(s.deps,s.callback),!c)return;c.splice?(a=c,c=d,d=null):a=b}return c=c||function(){},"function"==typeof d&&(d=e,e=f),e?m(b,a,c,d):setTimeout(function(){m(b,a,c,d)},4),n},n.config=function(a){return n(a)},a._defined=q,d=function(a,b,c){b.splice||(c=b,b=[]),e(q,a)||e(r,a)||(r[a]=[a,b,c])},d.amd={jQuery:!0}}(),b.requirejs=a,b.require=c,b.define=d}}(),b.define("almond",function(){}),b.define("jquery",[],function(){var b=a||$;return null==b&&console&&console.error&&console.error("Select2: An instance of jQuery or a jQuery-compatible library was not found. Make sure that you are including jQuery before Select2 on your web page."),b}),b.define("select2/utils",["jquery"],function(a){function b(a){var b=a.prototype,c=[];for(var d in b){var e=b[d];"function"==typeof e&&"constructor"!==d&&c.push(d)}return c}var c={};c.Extend=function(a,b){function c(){this.constructor=a}var d={}.hasOwnProperty;for(var e in b)d.call(b,e)&&(a[e]=b[e]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},c.Decorate=function(a,c){function d(){var b=Array.prototype.unshift,d=c.prototype.constructor.length,e=a.prototype.constructor;d>0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;h<g.length;h++){var i=g[h];d.prototype[i]=a.prototype[i]}for(var j=(function(a){var b=function(){};a in d.prototype&&(b=d.prototype[a]);var e=c.prototype[a];return function(){var a=Array.prototype.unshift;return a.call(arguments,b),e.apply(this,arguments)}}),k=0;k<f.length;k++){var l=f[k];d.prototype[l]=j(l)}return d};var d=function(){this.listeners={}};return d.prototype.on=function(a,b){this.listeners=this.listeners||{},a in this.listeners?this.listeners[a].push(b):this.listeners[a]=[b]},d.prototype.trigger=function(a){var b=Array.prototype.slice;this.listeners=this.listeners||{},a in this.listeners&&this.invoke(this.listeners[a],b.call(arguments,1)),"*"in this.listeners&&this.invoke(this.listeners["*"],arguments)},d.prototype.invoke=function(a,b){for(var c=0,d=a.length;d>c;c++)a[c].apply(this,b)},c.Observable=d,c.generateChars=function(a){for(var b="",c=0;a>c;c++){var d=Math.floor(36*Math.random());b+=d.toString(3Line truncated
if(b=c.data(a[0],"data"),null!=b)return b;if(a.is("option"))b={id:a.val(),text:a.text(),disabled:a.prop("disabled"),selected:a.prop("selected"),title:a.prop("title")};else if(a.is("optgroup")){b={text:a.prop("label"),children:[],title:a.prop("title")};for(var d=a.children("option"),e=[],f=0;f<d.length;f++){var g=c(d[f]),h=this.item(g);e.push(h)}b.children=e}return b=this._normalizeItem(b),b.element=a[0],c.data(a[0],"data",b),b},d.prototype._normalizeItem=function(a){c.isPlainObject(a)||(a={id:a,text:a}),a=c.extend({},{text:""},a);var b={selected:!1,disabled:!1};return null!=a.id&&(a.id=a.id.toString()),null!=a.text&&(a.text=a.text.toString()),null==a._resultId&&a.id&&null!=this.container&&(a._resultId=this.generateResultId(this.container,a)),c.extend({},b,a)},d.prototype.matches=function(a,b){var c=this.options.get("matcher");return c(a,b)},d}),b.define("select2/data/array",["./select","../utils","jquery"],function(a,b,c){function d(a,b){var c=b.get("data")||[];d.__super__.constructor.call(this,a,b),this.addOptions(this.convertToOptions(c))}return b.Extend(d,a),d.prototype.select=function(a){var b=this.$element.find("option").filter(function(b,c){return c.value==a.id.toString()});0===b.length&&(b=this.option(a),this.addOptions(b)),d.__super__.select.call(this,a)},d.prototype.convertToOptions=function(a){function d(a){return function(){return c(this).val()==a.id}}for(var e=this,f=this.$element.find("option"),g=f.map(function(){return e.item(c(this)).id}).get(),h=[],i=0;i<a.length;i++){var j=this._normalizeItem(a[i]);if(c.inArray(j.id,g)>=0){var k=f.filter(d(j)),l=this.item(k),m=(c.extend(!0,{},l,j),this.option(l));k.replaceWith(m)}else{var n=this.option(j);if(j.children){var o=this.convertToOptions(j.children);b.appendMany(n,o)}h.push(n)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(b,c){this.ajaxOptions=this._applyDefaults(c.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),a.__super__.constructor.call(this,b,c)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return{q:a.term}},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url(a)),"function"==typeof f.data&&(f.data=f.data(a)),this.ajaxOptions.delay&&""!==a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");if(void 0!==f&&(this.createTag=f),b.call(this,c,d),a.isArray(e))for(var g=0;g<e.length;g++){var h=e[g],i=this._normalizeItem(h),j=this.option(i);this.$element.append(j)}}return b.prototype.query=function(a,b,c){function d(a,f){for(var g=a.results,h=0;h<g.length;h++){var i=g[h],j=null!=i.children&&!d({results:i.children},!0),k=i.text===b.term;if(k||j)return f?!1:(a.data=g,void c(a))}if(f)return!0;var l=e.createTag(b);if(null!=l){var m=e.option(l);m.attr("data-select2-tag",!0),e.addOptions([m]),e.insertTag(g,l)}a.results=g,c(a)}var e=this;return this._removeOldTags(),null==b.term||null!=b.page?void a.call(this,b,c):void a.call(this,b,d)},b.prototype.createTag=function(b,c){var d=a.trim(c.term);return""===d?null:{id:d,text:d}},b.prototype.insertTag=function(a,b,c){b.unshift(c)},b.prototype._removeOldTags=function(){var b=(this._lastTag,this.$element.find("option[data-select2-tag]"));b.each(function(){this.selected||a(this).remove()})},b}),b.define("select2/data/tokenizer",["jquery"],function(a){function b(a,b,c){var d=c.get("tokenizer");void 0!==d&&(this.tokenizer=d),a.call(this,b,c)}return b.prototype.bind=function(a,b,c){a.call(this,b,c),this.$search=b.dropdown.$search||b.selection.$search||c.find(".select2-search__field")},b.prototype.query=function(a,b,c){function d(a){e.select(a)}var e=this;b.term=b.term||"";var f=this.tokenizer(b,this.options,d);f.term!==b.term&&(this.$search.length&&(this.$search.val(f.term),this.$search.focus()),b.term=f.term),a.call(this,b,c)},b.prototype.tokenizer=function(b,c,d,e){for(var f=d.get("tokenSeparators")||[],g=c.term,h=0,i=this.createTag||function(a){return{id:a.term,text:a.term}};h<g.length;){var j=g[h];if(-1!==a.inArray(j,f)){var k=g.substr(0,h),l=a.extend({},c,{term:k}),m=i(l);e(m),g=g.substr(h+1)||"",h=0}else h++}return{term:g}},b}),b.define("select2/data/Line truncated
+3 -3
View File
@@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
{% load staticfiles %} {% load bootstrap3 %} {% load staticfiles %} {% load bootstrap3 %} {% load navbar %}
<html lang="fr"> <html lang="fr">
<head> <head>
<title>{% block title %}La maraude{% endblock %}</title> <title>{% block title %}La maraude{% endblock %}</title>
{% bootstrap_css %}{% bootstrap_javascript %} {% bootstrap_css %}{% bootstrap_javascript %}
<!-- Side Navbar from http://www.samrayner.com/bootstrap-side-navbar/inverse.html --> <!-- Side Navbar from http://www.samrayner.com/bootstrap-side-navbar/inverse.html -->
<link href="/static/bootstrap/navbar-fixed-side.css" rel="stylesheet" /> <link href="/static/css/bootstrap/navbar-fixed-side.css" rel="stylesheet" />
{% if stylesheets %}{% for stylesheet in stylesheets %} {% if stylesheets %}{% for stylesheet in stylesheets %}
<link rel="stylesheet" type="text/css" href="{% static stylesheet %}" />{% endfor %}{% endif %} <link rel="stylesheet" type="text/css" href="{% static stylesheet %}" />{% endfor %}{% endif %}
</head> </head>
@@ -13,7 +13,7 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-3 col-lg-2"> <div class="col-md-3 col-lg-2">
{% include "navbar.html" %} {% navbar %}
</div> </div>
<div class="col-md-9 col-lg-10"> <div class="col-md-9 col-lg-10">
<h1 class="page-header">{% block page_header %}{% endblock %}</h1> <h1 class="page-header">{% block page_header %}{% endblock %}</h1>
+11
View File
@@ -0,0 +1,11 @@
{% load bootstrap3 %}
<li {% if active %}class="active"{%endif%}>
<a href="{{header.href}}">{% if header.icon %}{% bootstrap_icon header.icon %} &middot; {% endif %}<strong>{{header.text}}</strong></a>
</li>
{% if active %}{% for link in links %}
<li class="app-menu">
<a href="{{link.href}}">{{ link.text }}
{% if link.icon %}<span class="pull-right">{% bootstrap_icon link.icon %}</span>{% endif %}
</a>
</li>
{% endfor %}{% endif %}
+52
View File
@@ -0,0 +1,52 @@
{% load bootstrap3 %}{% load staticfiles %}{% load navbar %}
<nav class="navbar navbar-inverse navbar-fixed-side" role="navigation" id="menu">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" data-target=".navbar-collapse" data-toggle="collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{% url 'index' %}">La Maraude ALSA</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">{% for app in apps %}
{% navbar_menu app %}
{% endfor %}</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a id="UserMenu" href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<strong style="color:#fff;">{% bootstrap_icon "user" %} &middot; {{user}} </strong>&middot; {{ user_group }}<span class="caret"></span></a>
<ul class="dropdown-menu" aria-labelledby="UserMenu">
{% if next %}
<p class="well-sm text-center"><strong style="color:#980300;">Vous n'avez pas l'autorisation<br/> d'accéder à cette page.</strong></p>
{%endif%}
{% if user.is_authenticated %}
{% if user.is_superuser %}<li><a href="{% url 'admin:index' %}">{% bootstrap_icon "wrench" %} Administration</a></li>{% endif %}
<li><a href="{% url 'logout' %}">{% bootstrap_icon "log-out" %} Déconnecter</a></li>
{% else %}
<li>
<form class="navbar-form navbar-left" method="post" action="/login/">{% csrf_token %}
{% if next %}<input name="next" value="{{next}}" hidden />{% endif %}
<div class="form-group form-horizontal">
<div class="form-group">
<label class="col-md-2 sr-only control-label" for="id_username">Username</label>
<div class="col-md-10">
<input autofocus="" class="form-control" id="id_username" maxlength="254" name="username" placeholder="Username" title="" type="text" required />
</div>
</div>
<div class="form-group">
<label class="col-md-2 sr-only control-label" for="id_password">Password</label>
<div class="col-md-10"><input class="form-control" id="id_password" name="password" placeholder="Password" title="" type="password" required />
</div>
</div>
</div>
<div class="text-center"><button class="btn btn-primary navbar-button" type="submit">Connexion</button></div>
</form>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
+14
View File
@@ -0,0 +1,14 @@
from django import template
from django.utils.html import format_html
register = template.Library()
@register.filter
def as_icon(value):
icons = {True: "ok",
False: "remove",
None: "asterisk"
}
if not value in icons:
raise ValueError(value, 'is not a boolean or empty value !')
else:
return format_html('<span class="glyphicon glyphicon-{}"></span>', icons[value])
+51
View File
@@ -0,0 +1,51 @@
#-*- coding:utf-8 -*-
from django import template
from django.urls import reverse
register = template.Library()
class NavbarNode(template.Node):
_apps = None
def get_menus(self, view, user):
if not self._apps:
from website.navbar import registered
if not registered:
print('WARNING: No app registered into "navbar" module')
self._apps = registered.copy()
return [app_menu(view, user) for app_menu in self._apps]
def get_template(self):
return template.loader.get_template('navbar/layout.html')
def render(self, context):
request = context.get('request')
user, view = context.get('user'), context.get('view')
apps = self.get_menus(view, user)
# Add user menu
context = template.Context({
'apps': apps,
'user': user,
'user_group': context.get('user_group', None),
'next': context.get('next', None),
})
return self.get_template().render(context, request)
@register.tag
def navbar(parser, token):
return NavbarNode()
@register.inclusion_tag("navbar/app-menu.html")
def navbar_menu(app_menu):
return {
'active': app_menu.is_active,
'header': app_menu.header,
'links': app_menu.links,
}
+2
View File
@@ -6,6 +6,7 @@ from .views import Index, login_view
from maraudes import urls as maraudes_urls from maraudes import urls as maraudes_urls
from suivi import urls as suivi_urls from suivi import urls as suivi_urls
from sujets import urls as sujets_urls from sujets import urls as sujets_urls
from utilisateurs import urls as utilisateurs_urls
urlpatterns = [ urlpatterns = [
# Authentification # Authentification
@@ -19,4 +20,5 @@ urlpatterns = [
url(r'^maraudes/', include(maraudes_urls, namespace="maraudes")), url(r'^maraudes/', include(maraudes_urls, namespace="maraudes")),
url(r'^suivi/', include(suivi_urls, namespace="suivi")), url(r'^suivi/', include(suivi_urls, namespace="suivi")),
url(r'^sujets/', include(sujets_urls, namespace="sujets")), url(r'^sujets/', include(sujets_urls, namespace="sujets")),
url(r'^utilisateurs/', include(utilisateurs_urls, namespace="utilisateurs")),
] ]
+1 -1
View File
@@ -10,7 +10,7 @@ class Index(WebsiteTemplateMixin, views.generic.TemplateView):
template_name = "main.html" template_name = "main.html"
app_menu = None app_menu = None
header = ('La Maraude ALSA', 'accueil')
class PageInfo: class PageInfo:
title = "La maraude ALSA" title = "La maraude ALSA"
header = "La Maraude ALSA" header = "La Maraude ALSA"