Remaster (#38)
* setup new 'statistiques' module * added 'graphos' package and created first test graph * put graphos in requirements, deleted local folder * added "load_csv" management command ! * added update of premiere_rencontre field in 'load_csv' management command * added missing urls.py file * added 'merge' action and view * added 'info_completed' ratio * linked sujets:merge views inside suivi:details * added link to maraudes:details in notes table headers, if any * Major reorganisation, moved 'suivi' and 'sujets' to 'notes', cleanup in 'maraudes', dropping 'website' mixins (mostly useless) * small cleanup * worked on Maraude and Sujet lists * corrected missing line in notes.__init__ * restored 'details' view for maraudes and sujets insie 'notes' module * worked on 'notes': added navigation between maraude's compte-rendu, right content in details, header to list tables * changed queryset for CompteRenduDetailsView to all notes of same date, minor layout changes * added right content to 'details-sujet', created 'statistiques' view and update templates * restored 'statistiques' ajax view in 'details-sujet', fixed 'merge_two' util function * added auto-creation of FicheStatistique (plus some tests), pagination for notes in 'details-sujet' * added error-prone cases in paginator * fixed non-working modals, added titles * added UpdateStatistiques capacity in CompteRenduCreate view * fixed missing AjaxTemplateMixin for CreateSujetView, worked on compte-rendu creation scripts * fixed MaraudeManager.all_of() for common Maraudeurs, added color hints in planning * re-instated statistiques module link and first test page * added FinalizeView to send a mail before finalizing compte-rendu * Added PieChart view for FicheStatistique fields * small style updates, added 'age' and 'genre' fields from sujets in statistiques.PieChartView * worked on statistiques, fixed small issues in 'notes' list views * small theme change * removed some dead code * fixed notes.tests, fixed statistiques.info_completed display, added filter in SujetLisView * added some tests * added customised admin templates * added authenticate in CustomAuthenticatationBackend, more verbose login thanks to messages * added django-nose for test coverage * Corrected raising exception on first migration On first migration, qs.exists() would previously be called and raising an Exception, sot he migrations would fail. * Better try block * cleaned up custom settings.py, added some overrides of django base_settings * corrected bad dictionnary key
This commit is contained in:
1 parent
0be59a61a7
commit
be087464fc
155 files changed
+3568
-1988
No files matched your search
Whitespace-only changes.
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -1,24 +0,0 @@
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
from utilisateurs.models import Maraudeur
|
||||
|
||||
|
||||
def user_models():
|
||||
return (Maraudeur,)
|
||||
|
||||
class MyBackend(ModelBackend):
|
||||
|
||||
def get_user(self, user_id):
|
||||
""" Essaye de récupérer une classe enfant de User existante, telle que
|
||||
définie dans 'utilisateurs.models'. Fallback to default user.
|
||||
"""
|
||||
for user_model in user_models():
|
||||
try:
|
||||
return user_model.objects.get(pk=user_id)
|
||||
except user_model.DoesNotExist:
|
||||
print('Tried %s.' % user_model.__class__)
|
||||
return super().get_user(user_id)
|
||||
|
||||
def has_perm(self, *args, **kwargs):
|
||||
print('call has_perm', args, kwargs)
|
||||
return super().has_perm(*args, **kwargs)
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.utils import timezone as tz
|
||||
|
||||
def website_processor(request):
|
||||
return {'date': tz.now().date}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
from .mixins import (WebsiteTemplateMixin, WebsiteAjaxTemplateMixin,
|
||||
SpecialUserRequiredMixin)
|
||||
from .navbar import ApplicationMenu
|
||||
|
||||
|
||||
|
||||
def _insert_bases(cls, bases):
|
||||
""" Insert new bases in given view class """
|
||||
old_bases = cls.__bases__
|
||||
new_bases = tuple(bases) + old_bases
|
||||
cls.__bases__ = new_bases
|
||||
|
||||
|
||||
|
||||
class Webpage:
|
||||
""" Webpage configurator. It is used as a decorator.
|
||||
|
||||
The constructor takes one positionnal argument:
|
||||
- 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 ?
|
||||
|
||||
"""
|
||||
|
||||
options = [
|
||||
('title', ('Unset', 'small header')),
|
||||
('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:
|
||||
self.app_menu = None
|
||||
|
||||
self._defaults = {}
|
||||
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 __getattr__(self, attr):
|
||||
""" Return the overriden value if any, default overwise """
|
||||
return self._updated.get(attr, self._defaults[attr])
|
||||
|
||||
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
|
||||
+2
-91
@@ -1,102 +1,13 @@
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.template import Template, Context
|
||||
from django.views.generic.base import TemplateResponseMixin
|
||||
|
||||
## Mixins ##
|
||||
|
||||
|
||||
class SpecialUserRequiredMixin(object):
|
||||
""" Requires that the User is an instance of some class """
|
||||
app_users = []
|
||||
|
||||
@classmethod
|
||||
def as_view(cls, **initkwargs):
|
||||
view = super().as_view(**initkwargs)
|
||||
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):
|
||||
if isinstance(user, valid_cls):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return user_passes_test(check_special_user)
|
||||
|
||||
|
||||
def user_processor(request, context):
|
||||
context['user_group'] = request.user.__class__.__qualname__
|
||||
return context
|
||||
|
||||
def header_processor(header, context):
|
||||
context['page_header'] = Template(header[0]).render(context)
|
||||
context['page_header_small'] = Template(header[1]).render(context) if len(header) == 2 else ''
|
||||
context['page_title'] = " - ".join((context['page_header'], context['page_header_small']))
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class WebsiteTemplateMixin(TemplateResponseMixin):
|
||||
""" Mixin for easy integration of 'website' templates
|
||||
|
||||
If 'content_template' is not defined, value will fallback to template_name
|
||||
in child view.
|
||||
"""
|
||||
base_template = "base_site.html"
|
||||
content_template = None
|
||||
app_name = None
|
||||
|
||||
class Configuration:
|
||||
stylesheets = ['css/base.css']
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user = None
|
||||
|
||||
def get_template_names(self):
|
||||
""" Ensure same template for all children views. """
|
||||
return [self.base_template]
|
||||
|
||||
def get_content_template(self):
|
||||
# Ensure easy integration with generic views
|
||||
if hasattr(self, 'template_name'):
|
||||
self.content_template = self.template_name
|
||||
else:
|
||||
raise ImproperlyConfigured(self, "has no template defined !")
|
||||
return self.content_template
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = Context(super().get_context_data(**kwargs))
|
||||
#Website processor
|
||||
context['stylesheets'] = self.Configuration.stylesheets
|
||||
context['active_app'] = self.app_name # Set by Webpage decorator
|
||||
# User processor
|
||||
context = header_processor(self.header, context)
|
||||
context = user_processor(self.request, context)
|
||||
#Webpage
|
||||
context['content_template'] = self.get_content_template()
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class WebsiteAjaxTemplateMixin(WebsiteTemplateMixin):
|
||||
class AjaxTemplateMixin:
|
||||
""" Mixin that returns content_template instead of base_template when
|
||||
request is Ajax.
|
||||
"""
|
||||
is_ajax = False
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not hasattr(self, 'content_template') or not self.content_template:
|
||||
self.content_template = self.get_content_template()
|
||||
if not hasattr(self, 'ajax_template'):
|
||||
self.ajax_template = '%s_inner.html' % self.content_template.split(".")[0]
|
||||
self.ajax_template = '%s_inner.html' % self.template_name.split(".")[0]
|
||||
if request.is_ajax():
|
||||
self.is_ajax = True
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -1,102 +0,0 @@
|
||||
""" 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)
|
||||
|
||||
+115
-20
@@ -1,32 +1,127 @@
|
||||
|
||||
.navbar-fixed-side .navbar-nav>li>a {
|
||||
border-bottom: none;
|
||||
font-variant: small-caps;
|
||||
color: #fff;
|
||||
body {
|
||||
padding: 0px 0 10px 0;
|
||||
}
|
||||
|
||||
#menu {
|
||||
border: none;
|
||||
border-right: 4px solid #980300;
|
||||
background-color: #121212;
|
||||
#page-header {
|
||||
font-size:1.1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (max-width:768px){
|
||||
#menu { border: none; }
|
||||
.navbar-text.breadcrumb {
|
||||
padding: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.app-menu {
|
||||
background-color: #121212;
|
||||
border: none;
|
||||
.navbar-text.breadcrumb > li {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.navbar-text.breadcrumb > li > a {
|
||||
color: #e2f2f2;
|
||||
}
|
||||
|
||||
.navbar-text.breadcrumb > li > a:hover {
|
||||
color: #d9230f;
|
||||
}
|
||||
|
||||
/* Admin overrides */
|
||||
|
||||
#content-related {
|
||||
margin-right: 0px !important;
|
||||
}
|
||||
|
||||
|
||||
.active{
|
||||
border-right: 2px solid #980300 !important;
|
||||
/* Bootstrap Navbar custom */
|
||||
|
||||
.navbar-default {
|
||||
background-color: #2e2f2f;
|
||||
border-color: #a91b0c;
|
||||
}
|
||||
.navbar-default .navbar-brand {
|
||||
color: #f6f6f6;
|
||||
}
|
||||
.navbar-default .navbar-brand:hover,
|
||||
.navbar-default .navbar-brand:focus {
|
||||
color: #ffffff;
|
||||
}
|
||||
.navbar-default .navbar-text {
|
||||
color: #f6f6f6;
|
||||
}
|
||||
.navbar-default .navbar-nav > li > a {
|
||||
color: #f6f6f6;
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
min-height: 45px;
|
||||
}
|
||||
.navbar-default .navbar-nav > li > a:hover,
|
||||
.navbar-default .navbar-nav > li > a:focus {
|
||||
color: #ffffff;
|
||||
}
|
||||
.navbar-default .navbar-nav > li > .dropdown-menu {
|
||||
background-color: #2e2f2f;
|
||||
}
|
||||
.navbar-default .navbar-nav > li > .dropdown-menu > li > a {
|
||||
color: #f6f6f6;
|
||||
}
|
||||
.navbar-default .navbar-nav > li > .dropdown-menu > li > a:hover,
|
||||
.navbar-default .navbar-nav > li > .dropdown-menu > li > a:focus {
|
||||
color: #ffffff;
|
||||
background-color: #d2220f;
|
||||
}
|
||||
.navbar-default .navbar-nav > li > .dropdown-menu > li > .divider {
|
||||
background-color: #d2220f;
|
||||
}
|
||||
.navbar-default .navbar-nav .open .dropdown-menu > .active > a,
|
||||
.navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
|
||||
.navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
|
||||
color: #ffffff;
|
||||
background-color: #d2220f;
|
||||
}
|
||||
.navbar-default .navbar-nav > .active > a,
|
||||
.navbar-default .navbar-nav > .active > a:hover,
|
||||
.navbar-default .navbar-nav > .active > a:focus {
|
||||
color: #ffffff;
|
||||
background-color: #d2220f;
|
||||
}
|
||||
.navbar-default .navbar-nav > .open > a,
|
||||
.navbar-default .navbar-nav > .open > a:hover,
|
||||
.navbar-default .navbar-nav > .open > a:focus {
|
||||
color: #ffffff;
|
||||
background-color: #d2220f;
|
||||
}
|
||||
.navbar-default .navbar-toggle {
|
||||
border-color: #d2220f;
|
||||
}
|
||||
.navbar-default .navbar-toggle:hover,
|
||||
.navbar-default .navbar-toggle:focus {
|
||||
background-color: #d2220f;
|
||||
}
|
||||
.navbar-default .navbar-toggle .icon-bar {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
.navbar-default .navbar-collapse,
|
||||
.navbar-default .navbar-form {
|
||||
border-color: #f6f6f6;
|
||||
}
|
||||
.navbar-default .navbar-link {
|
||||
color: #f6f6f6;
|
||||
}
|
||||
.navbar-default .navbar-link:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
border-bottom: none !important;
|
||||
@media (max-width: 767px) {
|
||||
.navbar-default .navbar-nav .open .dropdown-menu > li > a {
|
||||
color: #f6f6f6;
|
||||
}
|
||||
.navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
|
||||
.navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
|
||||
color: #ffffff;
|
||||
}
|
||||
.navbar-default .navbar-nav .open .dropdown-menu > .active > a,
|
||||
.navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
|
||||
.navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
|
||||
color: #ffffff;
|
||||
background-color: #d2220f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -432,4 +432,4 @@
|
||||
"transition.js"
|
||||
],
|
||||
"customizerUrl": "http://getbootstrap.com/customize/?id=7f853f3d936c9ba68499a06009229bc9"
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -22,7 +22,8 @@
|
||||
}
|
||||
},
|
||||
error: function (xhr, ajaxOptions, thrownError) {
|
||||
// handle response errors here
|
||||
console.log("Error with ajax request : ");
|
||||
console.log(thrownError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/* Javascript plotting library for jQuery, version 0.8.3.
|
||||
|
||||
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
*/
|
||||
(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function($){var hasOwnProperty=Object.prototype.hasOwnProperty;if(!$.fn.detach){$.fn.detach=function(){return this.each(function(){if(this.parentNode){this.parentNode.removeChild(this)}})}}function Canvas(cls,container){var element=container.children("."+cls)[0];if(element==null){element=document.createElement("canvas");element.className=cls;$(element).css({direction:"ltr",position:"absolute",left:0,top:0}).appendTo(container);if(!element.getContext){if(window.G_vmlCanvasManager){element=window.G_vmlCanvasManager.initElement(element)}else{throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.")}}}this.element=element;var context=this.context=element.getContext("2d");var devicePixelRatio=window.devicePixelRatio||1,backingStoreRatio=context.webkitBackingStorePixelRatio||context.mozBackingStorePixelRatio||context.msBackingStorePixelRatio||context.oBackingStorePixelRatio||context.backingStorePixelRatio||1;this.pixelRatio=devicePixelRatio/backingStoreRatio;this.resize(container.width(),container.height());this.textContainer=null;this.text={};this._textCache={}}Canvas.prototype.resize=function(width,height){if(width<=0||height<=0){throw new Error("Invalid dimensions for plot, width = "+width+", height = "+height)}var element=this.element,context=this.context,pixelRatio=this.pixelRatio;if(this.width!=width){element.width=width*pixelRatio;element.style.width=width+"px";this.width=width}if(this.height!=height){element.height=height*pixelRatio;element.style.height=height+"px";this.height=height}context.restore();context.save();context.scale(pixelRatio,pixelRatio)};Canvas.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)};Canvas.prototype.render=function(){vaLine truncated
|
||||
if(yrange.from==null)yrange.from=yrange.axis.min;if(yrange.to==null)yrange.to=yrange.axis.max;if(xrange.to<xrange.axis.min||xrange.from>xrange.axis.max||yrange.to<yrange.axis.min||yrange.from>yrange.axis.max)continue;xrange.from=Math.max(xrange.from,xrange.axis.min);xrange.to=Math.min(xrange.to,xrange.axis.max);yrange.from=Math.max(yrange.from,yrange.axis.min);yrange.to=Math.min(yrange.to,yrange.axis.max);var xequal=xrange.from===xrange.to,yequal=yrange.from===yrange.to;if(xequal&&yequal){continue}xrange.from=Math.floor(xrange.axis.p2c(xrange.from));xrange.to=Math.floor(xrange.axis.p2c(xrange.to));yrange.from=Math.floor(yrange.axis.p2c(yrange.from));yrange.to=Math.floor(yrange.axis.p2c(yrange.to));if(xequal||yequal){var lineWidth=m.lineWidth||options.grid.markingsLineWidth,subPixel=lineWidth%2?.5:0;ctx.beginPath();ctx.strokeStyle=m.color||options.grid.markingsColor;ctx.lineWidth=lineWidth;if(xequal){ctx.moveTo(xrange.to+subPixel,yrange.from);ctx.lineTo(xrange.to+subPixel,yrange.to)}else{ctx.moveTo(xrange.from,yrange.to+subPixel);ctx.lineTo(xrange.to,yrange.to+subPixel)}ctx.stroke()}else{ctx.fillStyle=m.color||options.grid.markingsColor;ctx.fillRect(xrange.from,yrange.to,xrange.to-xrange.from,yrange.from-yrange.to)}}}axes=allAxes();bw=options.grid.borderWidth;for(var j=0;j<axes.length;++j){var axis=axes[j],box=axis.box,t=axis.tickLength,x,y,xoff,yoff;if(!axis.show||axis.ticks.length==0)continue;ctx.lineWidth=1;if(axis.direction=="x"){x=0;if(t=="full")y=axis.position=="top"?0:plotHeight;else y=box.top-plotOffset.top+(axis.position=="top"?box.height:0)}else{y=0;if(t=="full")x=axis.position=="left"?0:plotWidth;else x=box.left-plotOffset.left+(axis.position=="left"?box.width:0)}if(!axis.innermost){ctx.strokeStyle=axis.options.color;ctx.beginPath();xoff=yoff=0;if(axis.direction=="x")xoff=plotWidth+1;else yoff=plotHeight+1;if(ctx.lineWidth==1){if(axis.direction=="x"){y=Math.floor(y)+.5}else{x=Math.floor(x)+.5}}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff);ctx.stroke()}ctx.strokeStyle=axis.options.tickColor;ctx.beginPath();for(i=0;i<axis.ticks.length;++i){var v=axis.ticks[i].v;xoff=yoff=0;if(isNaN(v)||v<axis.min||v>axis.max||t=="full"&&(typeof bw=="object"&&bw[axis.position]>0||bw>0)&&(v==axis.min||v==axis.max))continue;if(axis.direction=="x"){x=axis.p2c(v);yoff=t=="full"?-plotHeight:t;if(axis.position=="top")yoff=-yoff}else{y=axis.p2c(v);xoff=t=="full"?-plotWidth:t;if(axis.position=="left")xoff=-xoff}if(ctx.lineWidth==1){if(axis.direction=="x")x=Math.floor(x)+.5;else y=Math.floor(y)+.5}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff)}ctx.stroke()}if(bw){bc=options.grid.borderColor;if(typeof bw=="object"||typeof bc=="object"){if(typeof bw!=="object"){bw={top:bw,right:bw,bottom:bw,left:bw}}if(typeof bc!=="object"){bc={top:bc,right:bc,bottom:bc,left:bc}}if(bw.top>0){ctx.strokeStyle=bc.top;ctx.lineWidth=bw.top;ctx.beginPath();ctx.moveTo(0-bw.left,0-bw.top/2);ctx.lineTo(plotWidth,0-bw.top/2);ctx.stroke()}if(bw.right>0){ctx.strokeStyle=bc.right;ctx.lineWidth=bw.right;ctx.beginPath();ctx.moveTo(plotWidth+bw.right/2,0-bw.top);ctx.lineTo(plotWidth+bw.right/2,plotHeight);ctx.stroke()}if(bw.bottom>0){ctx.strokeStyle=bc.bottom;ctx.lineWidth=bw.bottom;ctx.beginPath();ctx.moveTo(plotWidth+bw.right,plotHeight+bw.bottom/2);ctx.lineTo(0,plotHeight+bw.bottom/2);ctx.stroke()}if(bw.left>0){ctx.strokeStyle=bc.left;ctx.lineWidth=bw.left;ctx.beginPath();ctx.moveTo(0-bw.left/2,plotHeight+bw.bottom);ctx.lineTo(0-bw.left/2,0);ctx.stroke()}}else{ctx.lineWidth=bw;ctx.strokeStyle=options.grid.borderColor;ctx.strokeRect(-bw/2,-bw/2,plotWidth+bw,plotHeight+bw)}}ctx.restore()}function drawAxisLabels(){$.each(allAxes(),function(_,axis){var box=axis.box,legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=axis.options.font||"flot-tick-label tickLabel",tick,x,y,halign,valign;surface.removeText(layer);if(!axis.show||axis.ticks.length==0)return;for(var i=0;i<axis.ticks.length;++i){tick=axis.ticks[i];if(!tick.label||tick.v<axis.min||tick.v>axis.max)continue;if(axis.direction=="x"){halign="center";x=plotOffset.left+axis.p2c(tick.v);if(axis.position=="bottom"){y=box.top+box.padding}else{y=box.top+box.height-box.padding;valign="bottom"}}else{valign="middle";y=plotOffset.top+axis.p2c(tick.v);if(axis.position=="left"){x=box.left+box.width-box.padding;halign="right"}else{x=box.left+box.padding}}surface.addText(layer,x,y,tick.label,font,null,null,halign,valign)}})}function drawSeries(series){if(series.lines.show)drawSeriesLines(series);if(series.bars.show)drawSeriesBars(series);if(series.points.show)drawSeriesPoints(series)}function drawSeriesLines(series){function plotLine(datapoints,xoffset,yoffset,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,prevx=null,prevy=null;ctx.beginPath();for(var i=ps;i<points.length;i+=ps){var x1=points[i-ps],y1=points[i-ps+1],x2=points[i],y2=points[i+1];if(x1==null||x2==null)continue;if(y1<=y2&&y1<axisy.min){if(y2<axLine truncated
|
||||
@@ -1,8 +1,7 @@
|
||||
$(function() {
|
||||
|
||||
var input = $('#id_heure_debut')
|
||||
var min_value = input.attr('value').split(":")
|
||||
var is_morning = (min_value[0] <= 12)
|
||||
|
||||
$.fn.editHeureValue = function(mod) {
|
||||
var value = input.attr('value').split(":");
|
||||
var new_hour = parseInt(value[0]);
|
||||
@@ -19,23 +18,19 @@
|
||||
} else if (new_hour < 0) {
|
||||
new_hour += 24
|
||||
};
|
||||
|
||||
value[0] = new_hour;
|
||||
value[1] = new_minutes;
|
||||
var test_value = value[0] * 10000 + value[1] * 100 + parseInt(value[2]);
|
||||
var test_min_value = min_value[0] * 10000 + min_value[1] *100 + parseInt(min_value[2]);
|
||||
console.log('test:', test_value, 'min:', test_min_value)
|
||||
if (test_value >= test_min_value || (!is_morning && test_value < 120000)) {
|
||||
input.attr('value', value.join(":"));
|
||||
console.log('updated!')
|
||||
};
|
||||
input.attr('value', value.join(":"));
|
||||
|
||||
};
|
||||
|
||||
$('#minus-5').click(function() {
|
||||
$.fn.editHeureValue(-5)
|
||||
console.log('minus 5')
|
||||
//console.log('minus 5')
|
||||
});
|
||||
$('#plus-5').click(function() {
|
||||
$.fn.editHeureValue(5)
|
||||
console.log('plus 5')
|
||||
//console.log('plus 5')
|
||||
});
|
||||
});
|
||||
+64
-11
@@ -4,21 +4,74 @@
|
||||
<head>
|
||||
<title>{% block title %}La maraude{% endblock %}</title>
|
||||
{% bootstrap_css %}{% bootstrap_javascript %}
|
||||
<!-- Side Navbar from http://www.samrayner.com/bootstrap-side-navbar/inverse.html -->
|
||||
<link href="/static/css/bootstrap/navbar-fixed-side.css" rel="stylesheet" />
|
||||
{% 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/css/base.css" />
|
||||
{% block extrastyle %}{% endblock %}
|
||||
{% if stylesheets %}{% for stylesheet in stylesheets %}<link rel="stylesheet" type="text/css" href="{% static stylesheet %}" />{% endfor %}{% endif %}
|
||||
{% block extrahead %}{% endblock %}
|
||||
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<body {% block extra_body_attrs %}{% endblock %}>
|
||||
<div class="container-fluid">
|
||||
<!-- START: Navigation Bar -->
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-lg-2">
|
||||
{% navbar %}
|
||||
</div>
|
||||
<div class="col-md-9 col-lg-10">
|
||||
<h1 class="page-header">{% block page_header %}{% endblock %}</h1>
|
||||
<nav class="navbar navbar-static-top navbar-default">
|
||||
<div class="container-fluid">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false">
|
||||
<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="/">Maraude ALSA</a>
|
||||
</div>
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div class="collapse navbar-collapse" id="navbar-collapse">
|
||||
<ul class="nav navbar-nav navbar-left">
|
||||
{% if user.is_authenticated %}
|
||||
<li {% active namespace="maraudes" %}><a href="{% url "maraudes:index" %}">{% bootstrap_icon "road" %} Maraudes</a></li>
|
||||
<li {% active namespace="notes" %}><a href="{% url "notes:index" %}">{% bootstrap_icon "pencil" %} Notes</a></li>
|
||||
{% else %}
|
||||
<li {% active namespace="statistiques" %}><a href="{% url "statistiques:index" %}">{% bootstrap_icon "stats" %} Statistiques</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ol class="breadcrumb navbar-text">
|
||||
{% block breadcrumbs %}
|
||||
<li>{{ page_header }}</li>
|
||||
{% if page_header_small %}<li>{{ page_header_small }}</li>{% endif %}
|
||||
{% endblock %}
|
||||
</ol>
|
||||
{% if user.is_authenticated %}
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
|
||||
Menu <span class="glyphicon glyphicon-menu-hamburger"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url "statistiques:index" %}"><span class="glyphicon glyphicon-stats"></span> Statistiques</a></li>
|
||||
{% if user.is_superuser %}
|
||||
<li><a href="/admin/"><span class="glyphicon glyphicon-wrench"></span> Administration</a></li>
|
||||
{% endif %}
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="{% url "utilisateurs:index" %}"><span class="glyphicon glyphicon-user"></span> {{ user }}</a></li>
|
||||
<li><a href="/logout/"><span class="glyphicon glyphicon-log-out"></span> Déconnexion</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div><!-- /.navbar-collapse -->
|
||||
</div><!-- /.container-fluid -->
|
||||
</nav>
|
||||
</div>
|
||||
<!-- END: Navigation Bar -->
|
||||
<div class="row">
|
||||
<div class="col-lg-10 col-lg-push-2 col-md-9 col-md-push-3">
|
||||
{% bootstrap_messages %}
|
||||
{% block content %}{% endblock %}
|
||||
{% block page_content %}{% endblock %}
|
||||
</div>
|
||||
<div class="col-lg-2 col-lg-pull-10 col-md-3 col-md-pull-9">
|
||||
{% block sidebar %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<span style="color:#980300;">{{ page_header }}</span>
|
||||
<small>{{ page_header_small }}</small>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include content_template %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
<div class="col-md-12 col-lg-6">
|
||||
{% extends "base.html" %}
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% include "login.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="jumbotron">
|
||||
<h2>Objectifs</h2>
|
||||
<p>Description de la maraude à destination des visiteurs, partenaires, etc...</p>
|
||||
@@ -8,5 +15,5 @@
|
||||
et les <b>vendredis</b> en fin d'après-midi.
|
||||
</p>
|
||||
<p>Ils sont reconnaissables à leur vestes oranges, n'hésitez pas à les interpeller.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,41 +1,54 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{# Tweak columns layout for login box %}
|
||||
{% block panels %}<div class="col-md-8 col-md-offset-2">{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading"><h3 class="panel-title">Connexion</h3></div>
|
||||
<div class="panel-body text-center">
|
||||
{% if user.is_authenticated %}
|
||||
<p>Bienvenue {{ user.first_name|default:user.username }} !</p>
|
||||
{% if next %}
|
||||
<div class="alert alert-warning"><p>Votre compte ne donne pas accès à cette page. Veuillez vous
|
||||
connecter avec un autre compte.</p></div>
|
||||
<a href="{% url 'logout' %}" class="btn btn-danger">Déconnexion</a>
|
||||
{% else %}
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'maraudes:index' %}" class="btn btn-primary">Entrer</a>
|
||||
{% if user.is_superuser %}
|
||||
<a href="admin/" class="btn btn-warning">Administration</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'logout' %}" class="btn btn-danger">Déconnexion</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if next %}
|
||||
<div class="alert alert-danger"><p>Veuillez vous connecter pour accéder à cette page.</p></div>
|
||||
{% endif %}
|
||||
<form method="post" action="{% url 'login' %}">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% bootstrap_button "Connexion" button_type="submit" button_class="btn-lg btn-primary" %}
|
||||
<input type="hidden" name="next" value="{{ next|default:'/maraudes/' }}" />
|
||||
</form>
|
||||
{% endif %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Connexion</h3>
|
||||
</div>
|
||||
<div class="panel-body text-center">
|
||||
{% if user.is_authenticated %}
|
||||
<p>{{ user.first_name|default:user.username }}, vous êtes connecté !</p>
|
||||
{% if next %}
|
||||
<div class="alert alert-warning">
|
||||
<p>Votre compte ne donne pas accès à cette page. Veuillez vous connecter avec un autre compte.</p>
|
||||
</div>
|
||||
<a href="{% url 'logout' %}" class="btn btn-danger">Déconnexion</a>
|
||||
{% else %}
|
||||
<div class="btn-group-vertical">
|
||||
<a href="{% url 'maraudes:index' %}" class="btn btn-sm btn-primary">Entrer</a>
|
||||
{% if user.is_superuser %}
|
||||
<a href="admin/" class="btn btn-sm btn-default">Administration</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'logout' %}" class="btn btn-sm btn-default">Déconnexion</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<form class="form" method="post" action="{% url "login" %}">
|
||||
{% csrf_token %}
|
||||
{% if next %}
|
||||
<div class="alert alert-warning">
|
||||
<p>Vous devez vous connecter pour accéder à cette page.</p>
|
||||
</div>
|
||||
<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="input-group col-md-9">
|
||||
<span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
|
||||
<input autofocus="" class="form-control" id="id_username" maxlength="254" name="username" placeholder="Username" title="" type="text" required />
|
||||
</div>
|
||||
<div class="panel-footer"><p>version: 0.01</p></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 sr-only control-label" for="id_password">Password</label>
|
||||
<div class="input-group col-md-9">
|
||||
<span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
|
||||
<input class="form-control" id="id_password" name="password" placeholder="Password" title="" type="password" required />
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<button class="btn btn-primary navbar-button" type="submit">Connexion</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="panel-footer"><p>version: 0.2beta</p></div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<h1>{{ title }}</h1>
|
||||
@@ -1,60 +0,0 @@
|
||||
{% load bootstrap3 %}{% load staticfiles %}
|
||||
<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 %}{% if not app.disabled %}
|
||||
<li class="{% if app == active_app %}active{%endif%}">
|
||||
<a href="/{{app.label}}/">{% bootstrap_icon app.menu_icon %} · <strong>{{ app.name|title }}</strong></a>
|
||||
</li>
|
||||
{% if app == active_app %}{% for template in app_menu %}{% include template %}{% endfor %}{% endif %}
|
||||
{% endif %}{%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" %} · {{user}} </strong>· {{ 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>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,13 +1,10 @@
|
||||
<table class="table table-condensed">
|
||||
{% for row in rows %}
|
||||
<tr>
|
||||
{% for object in row %}
|
||||
<td>
|
||||
{% if object %}
|
||||
{% include cell_template with object=object %}
|
||||
{%endif%}
|
||||
</td>
|
||||
<table class="table table-condensed table-striped">
|
||||
{% if header %}<tr><th colspan="{{ cols_number }}" class="text-center">{{ header }}</th></tr>{% endif %}
|
||||
{% for row in rows %}<tr>
|
||||
{% for object in row %}<td>
|
||||
{% if object %}{% include cell_template with object=object %}{%endif%}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
File renamed without changes.
@@ -2,6 +2,8 @@
|
||||
|
||||
from django import template
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -48,4 +50,14 @@ def navbar_menu(app_menu):
|
||||
}
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def active(context, namespace=None, viewname=None):
|
||||
try:
|
||||
(cur_namespace, cur_viewname) = context.request.resolver_match.view_name.split(":")
|
||||
except:
|
||||
(cur_namespace, cur_viewname) = (None, context.request.resolver_match.view_name)
|
||||
|
||||
if namespace == cur_namespace:
|
||||
if not viewname or viewname == cur_viewname:
|
||||
return mark_safe("class=\"active\"")
|
||||
return ""
|
||||
@@ -11,21 +11,14 @@ def get_columns(iterable, cols):
|
||||
yield iterable[i*cols_len:(i+1)*cols_len]
|
||||
|
||||
@register.inclusion_tag("tables/table.html")
|
||||
def table(object_list, cols=2, cell_template="tables/table_cell.html"):
|
||||
def table(object_list, cols=2, cell_template="tables/table_cell_default.html", header=None):
|
||||
""" Render object list in table of given columns number """
|
||||
return {
|
||||
'cell_template': cell_template,
|
||||
'cols_number': cols,
|
||||
'header': header,
|
||||
'rows': tuple(zip_longest( *get_columns(object_list, cols),
|
||||
fillvalue=None
|
||||
))
|
||||
}
|
||||
|
||||
@register.inclusion_tag("tables/header_table.html")
|
||||
def header_table(object_list, cols=2):
|
||||
""" Display object list in table of given columns number """
|
||||
return {
|
||||
'cols': cols,
|
||||
'rows': tuple(zip_longest( *get_columns(object_list, cols),
|
||||
fillvalue=None
|
||||
))
|
||||
}
|
||||
+42
-1
@@ -1,3 +1,44 @@
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from utilisateurs.models import Maraudeur
|
||||
# Create your tests here.
|
||||
|
||||
class RestrictedAccessAnonymousUserTestCase(TestCase):
|
||||
|
||||
modules = ["maraudes", "notes", "utilisateurs"]
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def test_access_restricted_modules(self):
|
||||
for mod in self.modules:
|
||||
url = "/%s/" % mod
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, "/?next=%s" % url)
|
||||
|
||||
|
||||
class RestrictedAccessConnectedMaraudeurTestCase(TestCase):
|
||||
modules = ["maraudes", "notes/sujets"]
|
||||
def setUp(self):
|
||||
m = Maraudeur.objects.create(first_name="Astérix", last_name="LeGaulois")
|
||||
self.client = Client()
|
||||
self.client.force_login(m)
|
||||
|
||||
def test_access_restricted_modules(self):
|
||||
for mod in self.modules:
|
||||
url = "/%s/" % mod
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
class NonRestrictedAccessTestCase(TestCase):
|
||||
|
||||
urls = ["/statistiques/", "/"]
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def test_access(self):
|
||||
for url in self.urls:
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
+5
-6
@@ -4,21 +4,20 @@ from django.contrib.auth import views as auth_views
|
||||
|
||||
from .views import Index, login_view
|
||||
from maraudes import urls as maraudes_urls
|
||||
from suivi import urls as suivi_urls
|
||||
from sujets import urls as sujets_urls
|
||||
from notes import urls as notes_urls
|
||||
from utilisateurs import urls as utilisateurs_urls
|
||||
from statistiques import urls as stats_urls
|
||||
|
||||
urlpatterns = [
|
||||
# Authentification
|
||||
url(r'^$', Index.as_view(), name="index"),
|
||||
url(r'^login/$', login_view),
|
||||
url(r'^login/$', login_view, name="login"),
|
||||
url(r'^logout/$', auth_views.logout, {
|
||||
'template_name': 'logout.html',
|
||||
'next_page': 'index',
|
||||
}, name="logout"),
|
||||
# Applications
|
||||
url(r'^maraudes/', include(maraudes_urls, namespace="maraudes")),
|
||||
url(r'^suivi/', include(suivi_urls, namespace="suivi")),
|
||||
url(r'^sujets/', include(sujets_urls, namespace="sujets")),
|
||||
url(r'^notes/', include(notes_urls, namespace="notes")),
|
||||
url(r'^utilisateurs/', include(utilisateurs_urls, namespace="utilisateurs")),
|
||||
url(r'^statistiques/', include(stats_urls, namespace="statistiques")),
|
||||
]
|
||||
+10
-8
@@ -1,20 +1,17 @@
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django import views
|
||||
from .mixins import WebsiteTemplateMixin
|
||||
|
||||
from django.contrib.auth import login, authenticate
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
|
||||
|
||||
class Index(WebsiteTemplateMixin, views.generic.TemplateView):
|
||||
class Index(views.generic.TemplateView):
|
||||
|
||||
template_name = "main.html"
|
||||
template_name = "index.html"
|
||||
app_menu = None
|
||||
header = ('La Maraude ALSA', 'accueil')
|
||||
class PageInfo:
|
||||
title = "La maraude ALSA"
|
||||
header = "La Maraude ALSA"
|
||||
header_small = "accueil"
|
||||
|
||||
http_method_names = ['get',]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
@@ -25,7 +22,9 @@ class Index(WebsiteTemplateMixin, views.generic.TemplateView):
|
||||
|
||||
def _get_entry_point(user):
|
||||
from utilisateurs.models import Maraudeur
|
||||
from utilisateurs.backends import CustomUserAuthentication
|
||||
|
||||
print("Entry point for ", user, user.__class__)
|
||||
if isinstance(user, Maraudeur):
|
||||
return reverse('maraudes:index')
|
||||
else:
|
||||
@@ -43,6 +42,9 @@ def login_view(request):
|
||||
next = request.POST.get('next', None)
|
||||
if not next:
|
||||
next = _get_entry_point(user)
|
||||
messages.success(request, "%s, vous êtes connecté !" % user)
|
||||
return HttpResponseRedirect(next)
|
||||
else:
|
||||
messages.error(request, "Le nom d'utilisateur et/ou le mot de passe sont incorrects !")
|
||||
return HttpResponseRedirect('/')
|
||||
|
||||
Reference in new issue
Block a user