kfet.open
kfet.open app - Base data (raw_open, last_update...) is stored and shared through cache system. - 2 websockets groups: one for team users, one for other users. - UI is initialized and kept up-to-date with WS. - raw_open and force_close can be updated with standard HTTP requests. At this time, there isn't any restriction on raw_open view. Common sense tell us to change this behavior. Misc - Clean channels routing. - 'PermConsumerMixin': user who sent the message is available as argument in connection_groups method, which returns groups to which the user should be appended on websocket connection (and discarded on disconnection). - New kfet.utils module: should be used for mixins, whatever is useful and not concerns the kfet app. - Clean JS dependencies.
This commit is contained in:
parent
2381af92e3
commit
b8110c11a4
|
@ -1,3 +1,6 @@
|
||||||
from kfet.routing import channel_routing as kfet_channel_routings
|
from channels.routing import include
|
||||||
|
|
||||||
channel_routing = kfet_channel_routings
|
|
||||||
|
routing = [
|
||||||
|
include('kfet.routing.routing', path=r'^/ws/k-fet'),
|
||||||
|
]
|
||||||
|
|
|
@ -53,6 +53,7 @@ INSTALLED_APPS = [
|
||||||
'django_cas_ng',
|
'django_cas_ng',
|
||||||
'bootstrapform',
|
'bootstrapform',
|
||||||
'kfet',
|
'kfet',
|
||||||
|
'kfet.open',
|
||||||
'channels',
|
'channels',
|
||||||
'widget_tweaks',
|
'widget_tweaks',
|
||||||
'custommail',
|
'custommail',
|
||||||
|
@ -91,7 +92,6 @@ TEMPLATES = [
|
||||||
'djconfig.context_processors.config',
|
'djconfig.context_processors.config',
|
||||||
'gestioncof.shared.context_processor',
|
'gestioncof.shared.context_processor',
|
||||||
'kfet.context_processors.auth',
|
'kfet.context_processors.auth',
|
||||||
'kfet.context_processors.kfet_open',
|
|
||||||
'kfet.context_processors.config',
|
'kfet.context_processors.config',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -180,7 +180,7 @@ CHANNEL_LAYERS = {
|
||||||
port=REDIS_PORT, db=REDIS_DB)
|
port=REDIS_PORT, db=REDIS_DB)
|
||||||
)],
|
)],
|
||||||
},
|
},
|
||||||
"ROUTING": "cof.routing.channel_routing",
|
"ROUTING": "cof.routing.routing",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,53 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from django.core.serializers.json import json, DjangoJSONEncoder
|
from .utils import DjangoJsonWebsocketConsumer, PermConsumerMixin
|
||||||
|
|
||||||
from channels.generic.websockets import JsonWebsocketConsumer
|
|
||||||
|
|
||||||
|
|
||||||
class DjangoJsonWebsocketConsumer(JsonWebsocketConsumer):
|
|
||||||
"""Custom Json Websocket Consumer.
|
|
||||||
|
|
||||||
Encode to JSON with DjangoJSONEncoder.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def encode_json(cls, content):
|
|
||||||
return json.dumps(content, cls=DjangoJSONEncoder)
|
|
||||||
|
|
||||||
|
|
||||||
class PermConsumerMixin(object):
|
|
||||||
"""Add support to check permissions on Consumers.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
perms_connect (list): Required permissions to connect to this
|
|
||||||
consumer.
|
|
||||||
|
|
||||||
"""
|
|
||||||
http_user = True # Enable message.user
|
|
||||||
perms_connect = []
|
|
||||||
|
|
||||||
def connect(self, message, **kwargs):
|
|
||||||
"""Check permissions on connection."""
|
|
||||||
if message.user.has_perms(self.perms_connect):
|
|
||||||
super().connect(message, **kwargs)
|
|
||||||
else:
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
class KfetOpen(JsonWebsocketConsumer):
|
|
||||||
def connection_groups(self, **kwargs):
|
|
||||||
return ['kfet.is_open']
|
|
||||||
|
|
||||||
def connect(self, message, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def receive(self, content, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def disconnect(self, message, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class KPsul(PermConsumerMixin, DjangoJsonWebsocketConsumer):
|
class KPsul(PermConsumerMixin, DjangoJsonWebsocketConsumer):
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from django.contrib.auth.context_processors import PermWrapper
|
from django.contrib.auth.context_processors import PermWrapper
|
||||||
from .views import KFET_OPEN, KFET_FORCE_CLOSE
|
|
||||||
|
|
||||||
|
|
||||||
from kfet.config import kfet_config
|
from kfet.config import kfet_config
|
||||||
|
|
||||||
|
@ -16,15 +14,5 @@ def auth(request):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def kfet_open(request):
|
|
||||||
(kfet_open, kfet_open_date) = KFET_OPEN()
|
|
||||||
kfet_force_close = KFET_FORCE_CLOSE()
|
|
||||||
return {
|
|
||||||
'kfet_open': kfet_open,
|
|
||||||
'kfet_open_date': kfet_open_date.isoformat(),
|
|
||||||
'kfet_force_close': kfet_force_close,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def config(request):
|
def config(request):
|
||||||
return {'kfet_config': kfet_config}
|
return {'kfet_config': kfet_config}
|
||||||
|
|
|
@ -6,7 +6,4 @@ from django.contrib.auth.decorators import user_passes_test
|
||||||
def kfet_is_team(user):
|
def kfet_is_team(user):
|
||||||
return user.has_perm('kfet.is_team')
|
return user.has_perm('kfet.is_team')
|
||||||
|
|
||||||
def can_force_close(user):
|
|
||||||
return user.has_perm('force_close_kfet')
|
|
||||||
|
|
||||||
teamkfet_required = user_passes_test(kfet_is_team)
|
teamkfet_required = user_passes_test(kfet_is_team)
|
||||||
|
|
|
@ -25,18 +25,17 @@ from gestioncof.models import CofProfile
|
||||||
# -----
|
# -----
|
||||||
|
|
||||||
class DateTimeWidget(forms.DateTimeInput):
|
class DateTimeWidget(forms.DateTimeInput):
|
||||||
def __init__(self, attrs = None):
|
def __init__(self, attrs=None):
|
||||||
super(DateTimeWidget, self).__init__(attrs)
|
super(DateTimeWidget, self).__init__(attrs)
|
||||||
self.attrs['format'] = '%Y-%m-%d %H:%M'
|
self.attrs['format'] = '%Y-%m-%d %H:%M'
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
css = {
|
css = {
|
||||||
'all': ('kfet/css/bootstrap-datetimepicker.min.css',)
|
'all': ('kfet/css/bootstrap-datetimepicker.min.css',)
|
||||||
}
|
}
|
||||||
js = (
|
js = ('kfet/js/bootstrap-datetimepicker.min.js',)
|
||||||
'kfet/js/moment.js',
|
|
||||||
'kfet/js/moment-fr.js',
|
|
||||||
'kfet/js/bootstrap-datetimepicker.min.js',
|
|
||||||
)
|
|
||||||
# -----
|
# -----
|
||||||
# Account forms
|
# Account forms
|
||||||
# -----
|
# -----
|
||||||
|
|
|
@ -73,7 +73,7 @@ class Account(models.Model):
|
||||||
"Modifier le mot de passe d'une personne de l'équipe"),
|
"Modifier le mot de passe d'une personne de l'équipe"),
|
||||||
('special_add_account',
|
('special_add_account',
|
||||||
"Créer un compte avec une balance initiale"),
|
"Créer un compte avec une balance initiale"),
|
||||||
('force_close_kfet', "Fermer manuelement la K-Fêt"),
|
('can_force_close', "Fermer manuelement la K-Fêt"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
1
kfet/open/__init__.py
Normal file
1
kfet/open/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from .open import OpenKfet, kfet_open # noqa
|
25
kfet/open/consumers.py
Normal file
25
kfet/open/consumers.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
from ..decorators import kfet_is_team
|
||||||
|
from ..utils import DjangoJsonWebsocketConsumer, PermConsumerMixin
|
||||||
|
|
||||||
|
from .open import kfet_open
|
||||||
|
|
||||||
|
|
||||||
|
class OpenKfetConsumer(PermConsumerMixin, DjangoJsonWebsocketConsumer):
|
||||||
|
"""Consumer for K-Fêt Open.
|
||||||
|
|
||||||
|
WS groups:
|
||||||
|
kfet.open.base: Only carries the values visible for all users.
|
||||||
|
kfet.open.team: Carries all values (raw status...).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def connection_groups(self, user, **kwargs):
|
||||||
|
"""Select which group the user should be connected."""
|
||||||
|
if kfet_is_team(user):
|
||||||
|
return ['kfet.open.team']
|
||||||
|
return ['kfet.open.base']
|
||||||
|
|
||||||
|
def connect(self, message, *args, **kwargs):
|
||||||
|
"""Send current status on connect."""
|
||||||
|
super().connect(message, *args, **kwargs)
|
||||||
|
self.send(kfet_open.export(message.user))
|
84
kfet/open/open.py
Normal file
84
kfet/open/open.py
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from ..decorators import kfet_is_team
|
||||||
|
from ..utils import CachedMixin
|
||||||
|
|
||||||
|
|
||||||
|
class OpenKfet(CachedMixin, object):
|
||||||
|
"""Manage "open" status of a place.
|
||||||
|
|
||||||
|
Stores raw data (e.g. sent by raspberry), and user-set values
|
||||||
|
(as force_close).
|
||||||
|
Setting differents `cache_prefix` allows multiple places management.
|
||||||
|
Current state persists through cache.
|
||||||
|
|
||||||
|
"""
|
||||||
|
cached = {
|
||||||
|
'_raw_open': False,
|
||||||
|
'_last_update': None,
|
||||||
|
'force_close': False,
|
||||||
|
}
|
||||||
|
cache_prefix = 'kfetopen'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def raw_open(self):
|
||||||
|
"""Defined as property to update `last_update` on `raw_open` update."""
|
||||||
|
return self._raw_open
|
||||||
|
|
||||||
|
@raw_open.setter
|
||||||
|
def raw_open(self, value):
|
||||||
|
self._last_update = timezone.now()
|
||||||
|
self._raw_open = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_update(self):
|
||||||
|
"""Prevent `last_update` to be set."""
|
||||||
|
return self._last_update
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_open(self):
|
||||||
|
"""Take into account force_close."""
|
||||||
|
return False if self.force_close else self.raw_open
|
||||||
|
|
||||||
|
def _export(self):
|
||||||
|
"""Export internal state.
|
||||||
|
|
||||||
|
Used by WS initialization and updates.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(tuple): (base, team)
|
||||||
|
- team for team users.
|
||||||
|
- base for others.
|
||||||
|
|
||||||
|
"""
|
||||||
|
base = {
|
||||||
|
'is_open': self.is_open,
|
||||||
|
'last_update': self.last_update,
|
||||||
|
}
|
||||||
|
restrict = {
|
||||||
|
'raw_open': self.raw_open,
|
||||||
|
'force_close': self.force_close,
|
||||||
|
}
|
||||||
|
return base, {**base, **restrict}
|
||||||
|
|
||||||
|
def export(self, user=None):
|
||||||
|
"""Export internal state.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(dict): Internal state. Only variables visible for the user are
|
||||||
|
exported, according to its permissions. If no user is given, it
|
||||||
|
returns all available values.
|
||||||
|
|
||||||
|
"""
|
||||||
|
base, team = self._export()
|
||||||
|
return team if user is None or kfet_is_team(user) else base
|
||||||
|
|
||||||
|
def send_ws(self):
|
||||||
|
"""Send internal state to websocket channels."""
|
||||||
|
from .consumers import OpenKfetConsumer
|
||||||
|
base, team = self._export()
|
||||||
|
OpenKfetConsumer.group_send('kfet.open.base', base)
|
||||||
|
OpenKfetConsumer.group_send('kfet.open.team', team)
|
||||||
|
|
||||||
|
|
||||||
|
kfet_open = OpenKfet()
|
8
kfet/open/routing.py
Normal file
8
kfet/open/routing.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from channels.routing import route_class
|
||||||
|
|
||||||
|
from . import consumers
|
||||||
|
|
||||||
|
|
||||||
|
routing = [
|
||||||
|
route_class(consumers.OpenKfetConsumer)
|
||||||
|
]
|
48
kfet/open/static/kfetopen/kfet-open.css
Normal file
48
kfet/open/static/kfetopen/kfet-open.css
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
.kfetopen-st-opened .bullet { background: #73C252; }
|
||||||
|
.kfetopen-st-closed .bullet { background: #B42B26; }
|
||||||
|
.kfetopen-st-unknown .bullet { background: #D4BE4C; }
|
||||||
|
.kfetopen-st-fake_closed .bullet {
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
45deg,
|
||||||
|
#73C252, #73C252 5px, #B42B26 5px, #B42B26 10px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kfetopen {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kfetopen .base {
|
||||||
|
height: 50px;
|
||||||
|
padding: 15px;
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kfetopen .details {
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px !important;
|
||||||
|
font-size: 16px;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kfetopen .bullet {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kfetopen .warning {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kfetopen .status-text {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kfetopen .force-close-btn {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
126
kfet/open/static/kfetopen/kfet-open.js
Normal file
126
kfet/open/static/kfetopen/kfet-open.js
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
var OpenWS = new KfetWebsocket({
|
||||||
|
relative_url: "open/"
|
||||||
|
});
|
||||||
|
|
||||||
|
var OpenKfet = function(force_close_url, admin) {
|
||||||
|
this.force_close_url = force_close_url;
|
||||||
|
this.admin = admin;
|
||||||
|
|
||||||
|
this.status = this.UNKNOWN;
|
||||||
|
this.dom = {
|
||||||
|
status_text: $('.kfetopen .status-text'),
|
||||||
|
force_close_btn: $('.kfetopen .force-close-btn'),
|
||||||
|
warning: $('.kfetopen .warning')
|
||||||
|
},
|
||||||
|
|
||||||
|
this.dom.force_close_btn.click( () => this.toggle_force_close() );
|
||||||
|
setInterval( () => this.refresh(), this.refresh_interval * 1000);
|
||||||
|
OpenWS.add_handler( data => this.refresh(data) );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
OpenKfet.prototype = {
|
||||||
|
// Status is unknown after . minutes without update.
|
||||||
|
time_unknown: 2,
|
||||||
|
// Maximum interval (seconds) between two UI refresh.
|
||||||
|
refresh_interval: 10,
|
||||||
|
|
||||||
|
// Prefix for classes describing place status.
|
||||||
|
class_prefix: 'kfetopen-st-',
|
||||||
|
// Set status-classes on this dom element.
|
||||||
|
target: 'body',
|
||||||
|
|
||||||
|
// Status
|
||||||
|
OPENED: "opened",
|
||||||
|
CLOSED: "closed",
|
||||||
|
UNKNOWN: "unknown",
|
||||||
|
|
||||||
|
// Admin status
|
||||||
|
FAKE_CLOSED: "fake_closed",
|
||||||
|
|
||||||
|
// Display values
|
||||||
|
status_text: {
|
||||||
|
opened: "ouverte",
|
||||||
|
closed: "fermée",
|
||||||
|
unknown: "_____"
|
||||||
|
},
|
||||||
|
force_text: {
|
||||||
|
activate: "Fermer manuellement",
|
||||||
|
deactivate: "Réouvrir la K-Fêt"
|
||||||
|
},
|
||||||
|
|
||||||
|
get is_recent() {
|
||||||
|
return this.last_update && moment().diff(this.last_update, 'minute') <= this.time_unknown;
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: function(data) {
|
||||||
|
if (data)
|
||||||
|
$.extend(this, data);
|
||||||
|
this.refresh_status();
|
||||||
|
this.refresh_dom();
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh_status: function() {
|
||||||
|
// find new status
|
||||||
|
let status = this.UNKNOWN;
|
||||||
|
if (this.is_recent)
|
||||||
|
status = this.is_open ? this.OPENED : this.CLOSED;
|
||||||
|
this.status = status;
|
||||||
|
|
||||||
|
// admin specific
|
||||||
|
if (this.admin) {
|
||||||
|
let admin_status = status;
|
||||||
|
if (status == this.CLOSED && this.raw_open)
|
||||||
|
admin_status = this.FAKE_CLOSED;
|
||||||
|
this.admin_status = admin_status;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh_dom: function() {
|
||||||
|
let status = this.status;
|
||||||
|
this.clear_class();
|
||||||
|
|
||||||
|
this.add_class(status);
|
||||||
|
this.dom.status_text.html(this.status_text[status]);
|
||||||
|
|
||||||
|
// admin specific
|
||||||
|
if (this.admin) {
|
||||||
|
this.add_class(this.admin_status);
|
||||||
|
if (this.force_close) {
|
||||||
|
this.dom.warning.addClass('in');
|
||||||
|
this.dom.force_close_btn.html(this.force_text['deactivate']);
|
||||||
|
} else {
|
||||||
|
this.dom.warning.removeClass('in');
|
||||||
|
this.dom.force_close_btn.html(this.force_text['activate']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggle_force_close: function(new_value, password) {
|
||||||
|
$.post({
|
||||||
|
url: this.force_close_url,
|
||||||
|
data: {force_close: !this.force_close},
|
||||||
|
beforeSend: function ($xhr) {
|
||||||
|
$xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
||||||
|
if (password !== undefined)
|
||||||
|
$xhr.setRequestHeader("KFetPassword", password);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail(function($xhr) {
|
||||||
|
switch ($xhr.status) {
|
||||||
|
case 403:
|
||||||
|
requestAuth({'errors': {}}, this.toggle_force_close);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
clear_class: function() {
|
||||||
|
let re = new RegExp('(^|\\s)' + this.class_prefix + '\\S+', 'g');
|
||||||
|
$(this.target).attr('class', (i, c) => c ? c.replace(re, '') : '');
|
||||||
|
},
|
||||||
|
|
||||||
|
add_class: function(status) {
|
||||||
|
$(this.target).addClass(this.class_prefix + status);
|
||||||
|
}
|
||||||
|
};
|
13
kfet/open/templates/kfetopen/init.html
Normal file
13
kfet/open/templates/kfetopen/init.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "kfetopen/kfet-open.css" %}">
|
||||||
|
<script type="text/javascript" src="{% static "kfetopen/kfet-open.js" %}"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$( function() {
|
||||||
|
kfet_open = new OpenKfet(
|
||||||
|
"{% url "kfet.open.edit_force_close" %}",
|
||||||
|
{{ perms.kfet.is_team|yesno:"true,false" }}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
303
kfet/open/tests.py
Normal file
303
kfet/open/tests.py
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.contrib.auth.models import AnonymousUser, Permission, User
|
||||||
|
from django.test import Client
|
||||||
|
|
||||||
|
from channels.channel import Group
|
||||||
|
from channels.test import ChannelTestCase, WSClient
|
||||||
|
|
||||||
|
from . import kfet_open, OpenKfet
|
||||||
|
from .consumers import OpenKfetConsumer
|
||||||
|
|
||||||
|
|
||||||
|
class OpenKfetTest(ChannelTestCase):
|
||||||
|
"""OpenKfet object unit-tests suite."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.kfet_open = OpenKfet()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.kfet_open.clear_cache()
|
||||||
|
|
||||||
|
def test_defaults(self):
|
||||||
|
"""Default values."""
|
||||||
|
self.assertFalse(self.kfet_open.raw_open)
|
||||||
|
self.assertIsNone(self.kfet_open.last_update)
|
||||||
|
self.assertFalse(self.kfet_open.force_close)
|
||||||
|
self.assertFalse(self.kfet_open.is_open)
|
||||||
|
|
||||||
|
def test_raw_open(self):
|
||||||
|
"""Get and set raw_open; last_update is renewed."""
|
||||||
|
for raw_open in [True, False]:
|
||||||
|
prev_update = self.kfet_open.last_update
|
||||||
|
self.kfet_open.raw_open = raw_open
|
||||||
|
self.assertEqual(raw_open, self.kfet_open.raw_open)
|
||||||
|
self.assertNotEqual(prev_update, self.kfet_open.last_update)
|
||||||
|
|
||||||
|
def test_force_close(self):
|
||||||
|
"""Get and set force_close."""
|
||||||
|
for force_close in [True, False]:
|
||||||
|
self.kfet_open.force_close = force_close
|
||||||
|
self.assertEqual(force_close, self.kfet_open.force_close)
|
||||||
|
|
||||||
|
def test_is_open(self):
|
||||||
|
"""If force_close is disabled, is_open is raw_open."""
|
||||||
|
self.kfet_open.force_close = False
|
||||||
|
for raw_open in [True, False]:
|
||||||
|
self.kfet_open.raw_open = raw_open
|
||||||
|
self.assertEqual(raw_open, self.kfet_open.is_open)
|
||||||
|
|
||||||
|
def test_is_open_force_close(self):
|
||||||
|
"""If force_close is enabled, is_open is False."""
|
||||||
|
self.kfet_open.force_close = True
|
||||||
|
for raw_open in [True, False]:
|
||||||
|
self.kfet_open.raw_open = raw_open
|
||||||
|
self.assertFalse(self.kfet_open.is_open)
|
||||||
|
|
||||||
|
def test_export_user(self):
|
||||||
|
"""Export is limited for an anonymous user."""
|
||||||
|
export = self.kfet_open.export(AnonymousUser())
|
||||||
|
self.assertSetEqual(
|
||||||
|
set(['is_open', 'last_update']),
|
||||||
|
set(export),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_export_team(self):
|
||||||
|
"""Export all values for a team member."""
|
||||||
|
user = User.objects.create_user('team', '', 'team')
|
||||||
|
user.user_permissions.add(Permission.objects.get(codename='is_team'))
|
||||||
|
export = self.kfet_open.export(user)
|
||||||
|
self.assertSetEqual(
|
||||||
|
set(['is_open', 'last_update', 'raw_open', 'force_close']),
|
||||||
|
set(export),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_export(self):
|
||||||
|
"""Export all by default."""
|
||||||
|
export = self.kfet_open.export()
|
||||||
|
self.assertSetEqual(
|
||||||
|
set(['is_open', 'last_update', 'raw_open', 'force_close']),
|
||||||
|
set(export),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_send_ws(self):
|
||||||
|
Group('kfet.open.base').add('test.open.base')
|
||||||
|
Group('kfet.open.team').add('test.open.team')
|
||||||
|
|
||||||
|
self.kfet_open.send_ws()
|
||||||
|
|
||||||
|
recv_base = self.get_next_message('test.open.base', require=True)
|
||||||
|
base = json.loads(recv_base['text'])
|
||||||
|
self.assertSetEqual(
|
||||||
|
set(['is_open', 'last_update']),
|
||||||
|
set(base),
|
||||||
|
)
|
||||||
|
|
||||||
|
recv_admin = self.get_next_message('test.open.team', require=True)
|
||||||
|
admin = json.loads(recv_admin['text'])
|
||||||
|
self.assertSetEqual(
|
||||||
|
set(['is_open', 'last_update', 'raw_open', 'force_close']),
|
||||||
|
set(admin),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenKfetViewsTest(ChannelTestCase):
|
||||||
|
"""OpenKfet views unit-tests suite."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# get some permissions
|
||||||
|
perms = {
|
||||||
|
'kfet.is_team': Permission.objects.get(codename='is_team'),
|
||||||
|
'kfet.can_force_close': Permission.objects.get(codename='can_force_close'),
|
||||||
|
}
|
||||||
|
|
||||||
|
# authenticated user and its client
|
||||||
|
self.u = User.objects.create_user('user', '', 'user')
|
||||||
|
self.c = Client()
|
||||||
|
self.c.login(username='user', password='user')
|
||||||
|
|
||||||
|
# team user and its clients
|
||||||
|
self.t = User.objects.create_user('team', '', 'team')
|
||||||
|
self.t.user_permissions.add(perms['kfet.is_team'])
|
||||||
|
self.c_t = Client()
|
||||||
|
self.c_t.login(username='team', password='team')
|
||||||
|
|
||||||
|
# admin user and its client
|
||||||
|
self.a = User.objects.create_user('admin', '', 'admin')
|
||||||
|
self.a.user_permissions.add(
|
||||||
|
perms['kfet.is_team'], perms['kfet.can_force_close'],
|
||||||
|
)
|
||||||
|
self.c_a = Client()
|
||||||
|
self.c_a.login(username='admin', password='admin')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
kfet_open.clear_cache()
|
||||||
|
|
||||||
|
def test_door(self):
|
||||||
|
"""Edit raw_status."""
|
||||||
|
for sent, expected in [(1, True), (0, False)]:
|
||||||
|
resp = Client().post('/k-fet/open/raw_open', {'raw_open': sent})
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
self.assertEqual(expected, kfet_open.raw_open)
|
||||||
|
|
||||||
|
def test_force_close(self):
|
||||||
|
"""Edit force_close."""
|
||||||
|
for sent, expected in [(1, True), (0, False)]:
|
||||||
|
resp = self.c_a.post('/k-fet/open/force_close', {'force_close': sent})
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
self.assertEqual(expected, kfet_open.force_close)
|
||||||
|
|
||||||
|
def test_force_close_forbidden(self):
|
||||||
|
"""Can't edit force_close without kfet.can_force_close permission."""
|
||||||
|
clients = [Client(), self.c, self.c_t]
|
||||||
|
for client in clients:
|
||||||
|
resp = client.post('/k-fet/open/force_close', {'force_close': 0})
|
||||||
|
self.assertEqual(403, resp.status_code)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenKfetConsumerTest(ChannelTestCase):
|
||||||
|
"""OpenKfet consumer unit-tests suite."""
|
||||||
|
|
||||||
|
def test_standard_user(self):
|
||||||
|
"""Lambda user is added to kfet.open.base group."""
|
||||||
|
# setup anonymous client
|
||||||
|
c = WSClient()
|
||||||
|
|
||||||
|
# connect
|
||||||
|
c.send_and_consume('websocket.connect', path='/ws/k-fet/open',
|
||||||
|
fail_on_none=True)
|
||||||
|
|
||||||
|
# initialization data is replied on connection
|
||||||
|
self.assertIsNotNone(c.receive())
|
||||||
|
|
||||||
|
# client belongs to the 'kfet.open' group...
|
||||||
|
OpenKfetConsumer.group_send('kfet.open.base', {'test': 'plop'})
|
||||||
|
self.assertEqual(c.receive(), {'test': 'plop'})
|
||||||
|
|
||||||
|
# ...but not to the 'kfet.open.admin' one
|
||||||
|
OpenKfetConsumer.group_send('kfet.open.team', {'test': 'plop'})
|
||||||
|
self.assertIsNone(c.receive())
|
||||||
|
|
||||||
|
def test_team_user(self):
|
||||||
|
"""Team user is added to kfet.open.team group."""
|
||||||
|
# setup team user and its client
|
||||||
|
t = User.objects.create_user('team', '', 'team')
|
||||||
|
t.user_permissions.add(
|
||||||
|
Permission.objects.get(codename='is_team')
|
||||||
|
)
|
||||||
|
c = WSClient()
|
||||||
|
c.force_login(t)
|
||||||
|
|
||||||
|
# connect
|
||||||
|
c.send_and_consume('websocket.connect', path='/ws/k-fet/open',
|
||||||
|
fail_on_none=True)
|
||||||
|
|
||||||
|
# initialization data is replied on connection
|
||||||
|
self.assertIsNotNone(c.receive())
|
||||||
|
|
||||||
|
# client belongs to the 'kfet.open.admin' group...
|
||||||
|
OpenKfetConsumer.group_send('kfet.open.team', {'test': 'plop'})
|
||||||
|
self.assertEqual(c.receive(), {'test': 'plop'})
|
||||||
|
|
||||||
|
# ... but not to the 'kfet.open' one
|
||||||
|
OpenKfetConsumer.group_send('kfet.open.base', {'test': 'plop'})
|
||||||
|
self.assertIsNone(c.receive())
|
||||||
|
|
||||||
|
|
||||||
|
class OpenKfetScenarioTest(ChannelTestCase):
|
||||||
|
"""OpenKfet functionnal tests suite."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# anonymous client (for views)
|
||||||
|
self.c = Client()
|
||||||
|
# anonymous client (for websockets)
|
||||||
|
self.c_ws = WSClient()
|
||||||
|
|
||||||
|
# root user
|
||||||
|
self.r = User.objects.create_superuser('root', '', 'root')
|
||||||
|
# its client (for views)
|
||||||
|
self.r_c = Client()
|
||||||
|
self.r_c.login(username='root', password='root')
|
||||||
|
# its client (for websockets)
|
||||||
|
self.r_c_ws = WSClient()
|
||||||
|
self.r_c_ws.force_login(self.r)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
kfet_open.clear_cache()
|
||||||
|
|
||||||
|
def ws_connect(self, ws_client):
|
||||||
|
ws_client.send_and_consume(
|
||||||
|
'websocket.connect', path='/ws/k-fet/open',
|
||||||
|
fail_on_none=True,
|
||||||
|
)
|
||||||
|
return ws_client.receive(json=True)
|
||||||
|
|
||||||
|
def test_scenario_0(self):
|
||||||
|
"""Clients connect."""
|
||||||
|
# test for anonymous user
|
||||||
|
msg = self.ws_connect(self.c_ws)
|
||||||
|
self.assertSetEqual(
|
||||||
|
set(['is_open', 'last_update']),
|
||||||
|
set(msg),
|
||||||
|
)
|
||||||
|
|
||||||
|
# test for root user
|
||||||
|
msg = self.ws_connect(self.r_c_ws)
|
||||||
|
self.assertSetEqual(
|
||||||
|
set(['is_open', 'last_update', 'raw_open', 'force_close']),
|
||||||
|
set(msg),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_scenario_1(self):
|
||||||
|
"""Clients connect, door opens, enable force close."""
|
||||||
|
self.ws_connect(self.c_ws)
|
||||||
|
self.ws_connect(self.r_c_ws)
|
||||||
|
|
||||||
|
# door sent "I'm open!"
|
||||||
|
self.c.post('/k-fet/open/raw_open', {'raw_open': True})
|
||||||
|
|
||||||
|
# anonymous user agree
|
||||||
|
msg = self.c_ws.receive(json=True)
|
||||||
|
self.assertTrue(msg['is_open'])
|
||||||
|
|
||||||
|
# root user too
|
||||||
|
msg = self.r_c_ws.receive(json=True)
|
||||||
|
self.assertTrue(msg['is_open'])
|
||||||
|
self.assertTrue(msg['raw_open'])
|
||||||
|
|
||||||
|
# admin says "no it's closed"
|
||||||
|
self.r_c.post('/k-fet/open/force_close', {'force_close': True})
|
||||||
|
|
||||||
|
# so anonymous user see it's closed
|
||||||
|
msg = self.c_ws.receive(json=True)
|
||||||
|
self.assertFalse(msg['is_open'])
|
||||||
|
|
||||||
|
# root user too
|
||||||
|
msg = self.r_c_ws.receive(json=True)
|
||||||
|
self.assertFalse(msg['is_open'])
|
||||||
|
# but root knows things
|
||||||
|
self.assertTrue(msg['raw_open'])
|
||||||
|
self.assertTrue(msg['force_close'])
|
||||||
|
|
||||||
|
def test_scenario_2(self):
|
||||||
|
"""Starting falsely closed, clients connect, disable force close."""
|
||||||
|
kfet_open.raw_open = True
|
||||||
|
kfet_open.force_close = True
|
||||||
|
|
||||||
|
msg = self.ws_connect(self.c_ws)
|
||||||
|
self.assertFalse(msg['is_open'])
|
||||||
|
|
||||||
|
msg = self.ws_connect(self.r_c_ws)
|
||||||
|
self.assertFalse(msg['is_open'])
|
||||||
|
self.assertTrue(msg['raw_open'])
|
||||||
|
self.assertTrue(msg['force_close'])
|
||||||
|
|
||||||
|
self.r_c.post('/k-fet/open/force_close', {'force_close': False})
|
||||||
|
|
||||||
|
msg = self.c_ws.receive(json=True)
|
||||||
|
self.assertTrue(msg['is_open'])
|
||||||
|
|
||||||
|
msg = self.r_c_ws.receive(json=True)
|
||||||
|
self.assertTrue(msg['is_open'])
|
||||||
|
self.assertTrue(msg['raw_open'])
|
||||||
|
self.assertFalse(msg['force_close'])
|
11
kfet/open/urls.py
Normal file
11
kfet/open/urls.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^raw_open$', views.raw_open,
|
||||||
|
name='kfet.open.edit_raw_open'),
|
||||||
|
url(r'^force_close$', views.force_close,
|
||||||
|
name='kfet.open.edit_force_close'),
|
||||||
|
]
|
27
kfet/open/views.py
Normal file
27
kfet/open/views.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
from django.contrib.auth.decorators import permission_required
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
|
from .open import kfet_open
|
||||||
|
|
||||||
|
|
||||||
|
TRUE_STR = ['1', 'True', 'true']
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@require_POST
|
||||||
|
def raw_open(request):
|
||||||
|
raw_open = request.POST.get('raw_open') in TRUE_STR
|
||||||
|
kfet_open.raw_open = raw_open
|
||||||
|
kfet_open.send_ws()
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
|
@permission_required('kfet.can_force_close', raise_exception=True)
|
||||||
|
@require_POST
|
||||||
|
def force_close(request):
|
||||||
|
force_close = request.POST.get('force_close') in TRUE_STR
|
||||||
|
kfet_open.force_close = force_close
|
||||||
|
kfet_open.send_ws()
|
||||||
|
return HttpResponse()
|
|
@ -1,13 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import (absolute_import, division,
|
from channels.routing import include, route_class
|
||||||
print_function, unicode_literals)
|
|
||||||
from builtins import *
|
|
||||||
|
|
||||||
from channels.routing import route, route_class
|
from . import consumers
|
||||||
from kfet import consumers
|
|
||||||
|
|
||||||
channel_routing = [
|
|
||||||
route_class(consumers.KPsul, path=r"^/ws/k-fet/k-psul/$"),
|
routing = [
|
||||||
route_class(consumers.KfetOpen, path=r"^/ws/k-fet/is_open/$"),
|
route_class(consumers.KPsul, path=r'^/k-psul/$'),
|
||||||
|
include('kfet.open.routing.routing', path=r'^/open'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,6 +10,7 @@ from django.dispatch import receiver
|
||||||
def messages_on_login(sender, request, user, **kwargs):
|
def messages_on_login(sender, request, user, **kwargs):
|
||||||
if (not user.username == 'kfet_genericteam' and
|
if (not user.username == 'kfet_genericteam' and
|
||||||
user.has_perm('kfet.is_team') and
|
user.has_perm('kfet.is_team') and
|
||||||
|
hasattr(request, 'GET') and
|
||||||
'k-fet' in request.GET.get('next', '')):
|
'k-fet' in request.GET.get('next', '')):
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
|
|
|
@ -52,12 +52,3 @@ li.carte-line {
|
||||||
.unbreakable.carte-inverted {
|
.unbreakable.carte-inverted {
|
||||||
background: #FFDBC7;
|
background: #FFDBC7;
|
||||||
}
|
}
|
||||||
|
|
||||||
#open_status {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#open_status_parent {
|
|
||||||
padding-left:0px;
|
|
||||||
padding-right:0px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -30,6 +30,15 @@ textarea {
|
||||||
border-radius:0 !important;
|
border-radius:0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.glyphicon.spinning {
|
||||||
|
animation: spin 1s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from { transform: scale(1) rotate(0deg); }
|
||||||
|
to { transform: scale(1) rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
margin-bottom:0;
|
margin-bottom:0;
|
||||||
border-bottom:1px solid #ddd;
|
border-bottom:1px solid #ddd;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar .navbar-brand {
|
.navbar .navbar-brand {
|
||||||
padding: 3px 15px 3px 25px;
|
padding: 3px 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar .navbar-brand img {
|
.navbar .navbar-brand img {
|
||||||
|
@ -51,55 +51,37 @@
|
||||||
box-shadow: inset 0 5px 5px -5px #000;
|
box-shadow: inset 0 5px 5px -5px #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav .dropdown .dropdown-menu {
|
.navbar .dropdown .dropdown-menu {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
#kfet-open {
|
.navbar .dropdown .dropdown-menu > li > a {
|
||||||
font-weight: bold;
|
|
||||||
font-size: 14px;
|
|
||||||
width:10px;
|
|
||||||
height:10px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: white;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#kfet-open-wrapper {
|
|
||||||
padding-top: 18px;
|
|
||||||
margin: 0px 10px;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav .dropdown .dropdown-menu > li > a {
|
|
||||||
padding: 8px 20px;
|
padding: 8px 20px;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav .dropdown .dropdown-menu > li > a:hover,
|
.navbar .dropdown .dropdown-menu > li > a:hover,
|
||||||
.navbar-nav .dropdown .dropdown-meny > li > a:focus {
|
.navbar .dropdown .dropdown-meny > li > a:focus {
|
||||||
color: #c8102e;
|
color: #c8102e;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav .dropdown .dropdown-menu .divider {
|
.navbar .dropdown .dropdown-menu .divider {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.navbar-nav .dropdown .dropdown-menu {
|
.navbar .dropdown .dropdown-menu {
|
||||||
display: block;
|
display: block;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.15s;
|
transition: opacity 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav .dropdown:hover .dropdown-menu {
|
.navbar .dropdown:hover .dropdown-menu {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,19 +24,24 @@ $(document).ready(function() {
|
||||||
class KfetWebsocket {
|
class KfetWebsocket {
|
||||||
|
|
||||||
static get defaults() {
|
static get defaults() {
|
||||||
return {"relative_url": "", "default_msg": {}, "handlers": []};
|
return {
|
||||||
|
relative_url: '',
|
||||||
|
default_msg: {},
|
||||||
|
handlers: [],
|
||||||
|
base_path: '/ws/k-fet/'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
$.extend(this, this.constructor.defaults, data);
|
$.extend(this, this.constructor.defaults, data);
|
||||||
|
if (window.location.pathname.startsWith('/gestion/'))
|
||||||
|
this.base_path = '/gestion' + this.base_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
get url() {
|
get url() {
|
||||||
var websocket_protocol = window.location.protocol == 'https:' ? 'wss' : 'ws';
|
var protocol = window.location.protocol == 'https:' ? 'wss' : 'ws';
|
||||||
var location_host = window.location.host;
|
var host = window.location.host;
|
||||||
var location_url = window.location.pathname.startsWith('/gestion/') ? location_host + '/gestion' : location_host;
|
return protocol + "://" + host + this.base_path + this.relative_url;
|
||||||
|
|
||||||
return websocket_protocol+"://" + location_url + this.relative_url ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add_handler(handler) {
|
add_handler(handler) {
|
||||||
|
@ -60,7 +65,7 @@ class KfetWebsocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
var OperationWebSocket = new KfetWebsocket({
|
var OperationWebSocket = new KfetWebsocket({
|
||||||
'relative_url': '/ws/k-fet/k-psul/',
|
'relative_url': 'k-psul/',
|
||||||
'default_msg': {'opegroups':[],'opes':[],'checkouts':[],'articles':[]},
|
'default_msg': {'opegroups':[],'opes':[],'checkouts':[],'articles':[]},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
function kfet_open(init_date, init_status, init_force_close, force_close_url, force_open_url) {
|
|
||||||
// VARIABLES
|
|
||||||
var kfet_open_bullet = $('#kfet-open');
|
|
||||||
var open_status = $('#open_status');
|
|
||||||
var force_close_button = $('#force_close_button');
|
|
||||||
// bullet
|
|
||||||
var open_color = "#73C252";
|
|
||||||
var closed_color = "#B42B26";
|
|
||||||
var unknown_color = "#ECD03E";
|
|
||||||
// status bar
|
|
||||||
var open_color_status = "#73C252";
|
|
||||||
var closed_color_status = "#B42B26";
|
|
||||||
var unknown_color_status = "#D4BE4C";
|
|
||||||
|
|
||||||
var kfet_open_date = init_date;
|
|
||||||
var kfet_open = init_status;
|
|
||||||
var force_close = init_force_close;
|
|
||||||
|
|
||||||
// EVENT
|
|
||||||
force_close_button.click(forceClose);
|
|
||||||
|
|
||||||
// INITIALISATION
|
|
||||||
update_open();
|
|
||||||
update_force_button();
|
|
||||||
|
|
||||||
// On recharge toute les 30sec
|
|
||||||
// (dans le cas où le statut deviendrait inconnu)
|
|
||||||
setInterval(function() {
|
|
||||||
update_open();
|
|
||||||
}, 30 * 1000); // 30 * 1000 milsec
|
|
||||||
|
|
||||||
// FONCTIONS
|
|
||||||
function forceClose(password = '') {
|
|
||||||
if (force_close) {
|
|
||||||
force_url = force_open_url;
|
|
||||||
} else {
|
|
||||||
force_url = force_close_url;
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
dataType: "html",
|
|
||||||
url : force_url,
|
|
||||||
method : "GET",
|
|
||||||
beforeSend: function ($xhr) {
|
|
||||||
if (password != '')
|
|
||||||
$xhr.setRequestHeader("KFetPassword", password);
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.fail(function($xhr) {
|
|
||||||
var data = $xhr.responseJSON;
|
|
||||||
switch ($xhr.status) {
|
|
||||||
case 403:
|
|
||||||
requestAuth({'errors':{}}, forceClose);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
lock = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function kfet_open_min() {
|
|
||||||
return moment().diff(kfet_open_date, 'minute');
|
|
||||||
}
|
|
||||||
function do_kfet_close() {
|
|
||||||
kfet_open_bullet.css({'background-color': closed_color});
|
|
||||||
open_status.parent().css({'background-color': closed_color_status});
|
|
||||||
open_status.html("FERMÉE");
|
|
||||||
}
|
|
||||||
function do_kfet_open() {
|
|
||||||
kfet_open_bullet.css({'background-color': open_color});
|
|
||||||
open_status.parent().css({'background-color': open_color_status});
|
|
||||||
open_status.html("OUVERTE");
|
|
||||||
}
|
|
||||||
function do_kfet_unknown() {
|
|
||||||
kfet_open_bullet.css({'background-color': unknown_color});
|
|
||||||
open_status.parent().css({'background-color': unknown_color_status});
|
|
||||||
open_status.html("?????");
|
|
||||||
}
|
|
||||||
function update_open() {
|
|
||||||
var nb_min = kfet_open_min();
|
|
||||||
console.log("K-Fêt ouverte : " + (kfet_open&&(!force_close)));
|
|
||||||
console.log(nb_min + " minute(s) depuis la dernière mise à jour");
|
|
||||||
if (force_close) {
|
|
||||||
do_kfet_close();
|
|
||||||
} else {
|
|
||||||
if (nb_min > 15) {
|
|
||||||
do_kfet_unknown();
|
|
||||||
} else if (kfet_open){
|
|
||||||
do_kfet_open();
|
|
||||||
} else {
|
|
||||||
do_kfet_close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function update_force_button() {
|
|
||||||
if (force_close) {
|
|
||||||
force_close_button.html('Réouvrir la K-Fêt');
|
|
||||||
} else {
|
|
||||||
force_close_button.html('Fermer manuellement');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// SYNCHRONIZATION
|
|
||||||
|
|
||||||
var websocket_protocol = window.location.protocol == 'https:' ? 'wss' : 'ws';
|
|
||||||
var location_host = window.location.host;
|
|
||||||
var location_url = window.location.pathname.startsWith('/gestion/') ? location_host + '/gestion' : location_host;
|
|
||||||
var socket = new ReconnectingWebSocket(websocket_protocol+"://" + location_url + "/ws/k-fet/is_open/");
|
|
||||||
|
|
||||||
socket.onmessage = function(e) {
|
|
||||||
var data = JSON.parse(e.data);
|
|
||||||
|
|
||||||
if (data['door_action']) {
|
|
||||||
console.log("* Message reçu de la part de la porte.");
|
|
||||||
|
|
||||||
kfet_open_date = moment.utc(data['door_action']['kfet_open_date']);
|
|
||||||
kfet_open = data['door_action']['kfet_open'];
|
|
||||||
|
|
||||||
update_open();
|
|
||||||
}
|
|
||||||
if (data['force_action']) {
|
|
||||||
force_close = data['force_action']['force_close'];
|
|
||||||
console.log("* Message reçu de la part d'un-e utilisat-rice-eur. Close = " + force_close);
|
|
||||||
|
|
||||||
update_open();
|
|
||||||
update_force_button();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,10 +4,6 @@
|
||||||
{% load l10n %}
|
{% load l10n %}
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/js.cookie.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||||
{% if account.user == request.user %}
|
{% if account.user == request.user %}
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/Chart.bundle.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/Chart.bundle.js' %}"></script>
|
||||||
|
|
|
@ -10,40 +10,23 @@
|
||||||
{# CSS #}
|
{# CSS #}
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||||
<link href='https://fonts.googleapis.com/css?family=Roboto:400,700|Oswald:400,700|Roboto+Mono:400,700' rel='stylesheet' type='text/css'>
|
<link href='https://fonts.googleapis.com/css?family=Roboto:400,700|Oswald:400,700|Roboto+Mono:400,700' rel='stylesheet' type='text/css'>
|
||||||
|
<link rel="stylesheet" style="text/css" href="{% static 'kfet/css/jquery-ui.min.css' %}">
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/jquery-confirm.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/jquery-confirm.css' %}">
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/index.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/index.css' %}">
|
||||||
|
|
||||||
{# JS #}
|
{# JS #}
|
||||||
|
<script type="text/javascript" src="{% static "kfet/js/js.cookie.js" %}"></script>
|
||||||
<script src="https://code.jquery.com/jquery-3.1.0.min.js" integrity="sha256-cCueBR6CsyA4/9szpPfrX3s49M9vUU5BgtiJj06wt/s=" crossorigin="anonymous"></script>
|
<script src="https://code.jquery.com/jquery-3.1.0.min.js" integrity="sha256-cCueBR6CsyA4/9szpPfrX3s49M9vUU5BgtiJj06wt/s=" crossorigin="anonymous"></script>
|
||||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'kfet/js/jquery-ui.min.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/kfet_open.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/reconnecting-websocket.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/reconnecting-websocket.js' %}"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
|
||||||
|
|
||||||
{# K-Fêt open #}
|
{% include "kfetopen/init.html" %}
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function () {
|
|
||||||
$('#genericteam').on('click', function () {
|
|
||||||
setTimeout(function () { location.reload() }, 1000);
|
|
||||||
});
|
|
||||||
init_date = moment.utc("{{ kfet_open_date }}");
|
|
||||||
init_status = {{ kfet_open | yesno:"true,false"}};
|
|
||||||
init_force_close = {{ kfet_force_close | yesno:"true,false"}};
|
|
||||||
force_close_url = "{% url 'kfet.force_close' %}?close";
|
|
||||||
force_open_url = "{% url 'kfet.force_close' %}";
|
|
||||||
kfet_open(init_date,
|
|
||||||
init_status,
|
|
||||||
init_force_close,
|
|
||||||
force_close_url,
|
|
||||||
force_open_url);
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% block extra_head %}{% endblock %}
|
{% block extra_head %}{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,25 @@
|
||||||
<a class="navbar-brand" href="{% url 'kfet.home' %}">
|
<a class="navbar-brand" href="{% url 'kfet.home' %}">
|
||||||
<img src="{% static 'kfet/img/logo3.png' %}">
|
<img src="{% static 'kfet/img/logo3.png' %}">
|
||||||
</a>
|
</a>
|
||||||
<span id="kfet-open-wrapper"><span id="kfet-open"></span></span>
|
<div class="kfetopen dropdown">
|
||||||
|
<ul class="base nav">
|
||||||
|
<li class="bullet"></li>
|
||||||
|
{% if perms.kfet.is_team %}
|
||||||
|
<li class="warning fade">
|
||||||
|
<span class="glyphicon glyphicon-warning-sign"></span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
<div class="details dropdown-menu">
|
||||||
|
La K-Fêt est
|
||||||
|
<span class="status-text">
|
||||||
|
<span class="glyphicon glyphicon-refresh spinning"></span>
|
||||||
|
</span>.
|
||||||
|
{% if perms.kfet.is_team %}
|
||||||
|
<button class="btn btn-primary force-close-btn"> </button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
|
@ -61,3 +79,13 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('#genericteam').on('click', function () {
|
||||||
|
setTimeout(function () { location.reload() }, 1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
|
@ -3,15 +3,8 @@
|
||||||
{% load l10n %}
|
{% load l10n %}
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/jquery-ui.min.css' %}">
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/bootstrap-datetimepicker.min.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/bootstrap-datetimepicker.min.css' %}">
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/multiple-select.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/multiple-select.css' %}">
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/js.cookie.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-ui.min.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/bootstrap-datetimepicker.min.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/bootstrap-datetimepicker.min.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/multiple-select.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/multiple-select.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "kfet/base_col_2.html" %}
|
{% extends "kfet/base_col_1.html" %}
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
{% load kfet_tags %}
|
{% load kfet_tags %}
|
||||||
|
|
||||||
|
@ -9,26 +9,7 @@
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/home.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/home.css' %}">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block fixed-size %}col-sm-2{% endblock %}
|
{% block main-size %}col-sm-10 col-sm-offset-1{% endblock %}
|
||||||
{% block main-size %}col-sm-10{% endblock %}
|
|
||||||
|
|
||||||
{% block fixed-content %}
|
|
||||||
|
|
||||||
<div class="content-left-top">
|
|
||||||
<div class="line line-bigsub">La K-Fêt est</div>
|
|
||||||
</div>
|
|
||||||
<div class="content-left-top" id="open_status_parent">
|
|
||||||
<div class="line line-big" id="open_status">?????</div>
|
|
||||||
</div>
|
|
||||||
{% if perms.kfet.is_team %}
|
|
||||||
<div class="buttons">
|
|
||||||
<a class="btn btn-primary btn-lg" id="force_close_button">
|
|
||||||
Fermer manuellement
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block main-content %}
|
{% block main-content %}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
{% extends "kfet/base_col_1.html" %}
|
{% extends "kfet/base_col_1.html" %}
|
||||||
{% load staticfiles %}
|
{% load staticfiles widget_tweaks %}
|
||||||
{% load widget_tweaks %}
|
|
||||||
|
|
||||||
{% block extra_head %}
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/reconnecting-websocket.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.js' %}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block title %}Nouvel inventaire{% endblock %}
|
{% block title %}Nouvel inventaire{% endblock %}
|
||||||
{% block header-title %}Création d'un inventaire{% endblock %}
|
{% block header-title %}Création d'un inventaire{% endblock %}
|
||||||
|
|
|
@ -2,12 +2,8 @@
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<link rel="stylesheet" style="text/css" href="{% static 'kfet/css/jquery-ui.min.css' %}">
|
|
||||||
<link rel="stylesheet" style="text/css" href="{% static 'kfet/css/kpsul_grid.css' %}">
|
<link rel="stylesheet" style="text/css" href="{% static 'kfet/css/kpsul_grid.css' %}">
|
||||||
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
|
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/js.cookie.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-ui.min.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
{% extends 'kfet/base.html' %}
|
{% extends 'kfet/base.html' %}
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
|
|
||||||
{% block extra_head %}
|
|
||||||
<link rel="stylesheet" style="text/css" href="{% static 'kfet/css/jquery-ui.min.css' %}">
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/js.cookie.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-ui.min.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.js' %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block title %}Transferts{% endblock %}
|
{% block title %}Transferts{% endblock %}
|
||||||
{% block content-header-title %}Transferts{% endblock %}
|
{% block content-header-title %}Transferts{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/transfers_form.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/transfers_form.css' %}">
|
||||||
<script type="text/javascript" src="{% static 'kfet/js/js.cookie.js' %}"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}Nouveaux transferts{% endblock %}
|
{% block title %}Nouveaux transferts{% endblock %}
|
||||||
|
|
18
kfet/urls.py
18
kfet/urls.py
|
@ -1,6 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import include, url
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
from kfet import views
|
from kfet import views
|
||||||
from kfet import autocomplete
|
from kfet import autocomplete
|
||||||
|
@ -198,17 +198,6 @@ urlpatterns = [
|
||||||
(views.SettingsUpdate.as_view()),
|
(views.SettingsUpdate.as_view()),
|
||||||
name='kfet.settings.update'),
|
name='kfet.settings.update'),
|
||||||
|
|
||||||
# -----
|
|
||||||
# K-Fêt Open urls
|
|
||||||
# -----
|
|
||||||
|
|
||||||
url('^kfet_open/$',
|
|
||||||
views.UpdateKfetOpen.as_view(),
|
|
||||||
name='kfet.kfet_open'),
|
|
||||||
url('^kfet_close/$',
|
|
||||||
permission_required('kfet.can_force_close')
|
|
||||||
(views.UpdateForceClose.as_view()),
|
|
||||||
name='kfet.force_close'),
|
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
# Transfers urls
|
# Transfers urls
|
||||||
|
@ -254,3 +243,8 @@ urlpatterns = [
|
||||||
url(r'^orders/(?P<pk>\d+)/to_inventory$', views.order_to_inventory,
|
url(r'^orders/(?P<pk>\d+)/to_inventory$', views.order_to_inventory,
|
||||||
name='kfet.order.to_inventory'),
|
name='kfet.order.to_inventory'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
urlpatterns += [
|
||||||
|
# K-Fêt Open urls
|
||||||
|
url('^open/', include('kfet.open.urls')),
|
||||||
|
]
|
||||||
|
|
106
kfet/utils.py
Normal file
106
kfet/utils.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
|
|
||||||
|
from channels.channel import Group
|
||||||
|
from channels.generic.websockets import JsonWebsocketConsumer
|
||||||
|
|
||||||
|
|
||||||
|
# Storage
|
||||||
|
|
||||||
|
class CachedMixin:
|
||||||
|
"""Object with cached properties.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
cached (dict): Keys are cached properties. Associated value is the
|
||||||
|
returned default by getters in case the key is missing from cache.
|
||||||
|
cache_prefix (str): Used to prefix keys in cache.
|
||||||
|
|
||||||
|
"""
|
||||||
|
cached = {}
|
||||||
|
cache_prefix = ''
|
||||||
|
|
||||||
|
def __init__(self, cache_prefix=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
if cache_prefix is not None:
|
||||||
|
self.cache_prefix = cache_prefix
|
||||||
|
|
||||||
|
def cachekey(self, attr):
|
||||||
|
return '{}__{}'.format(self.cache_prefix, attr)
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
if attr in self.cached:
|
||||||
|
return cache.get(self.cachekey(attr), self.cached.get(attr))
|
||||||
|
elif hasattr(super(), '__getattr__'):
|
||||||
|
return super().__getattr__(attr)
|
||||||
|
else:
|
||||||
|
raise AttributeError("can't get attribute")
|
||||||
|
|
||||||
|
def __setattr__(self, attr, value):
|
||||||
|
if attr in self.cached:
|
||||||
|
cache.set(self.cachekey(attr), value)
|
||||||
|
elif hasattr(super(), '__setattr__'):
|
||||||
|
super().__setattr__(attr, value)
|
||||||
|
else:
|
||||||
|
raise AttributeError("can't set attribute")
|
||||||
|
|
||||||
|
def clear_cache(self):
|
||||||
|
cache.delete_many([
|
||||||
|
self.cachekey(attr) for attr in self.cached.keys()
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
# Consumers
|
||||||
|
|
||||||
|
class DjangoJsonWebsocketConsumer(JsonWebsocketConsumer):
|
||||||
|
"""Custom Json Websocket Consumer.
|
||||||
|
|
||||||
|
Encode to JSON with DjangoJSONEncoder.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def encode_json(cls, content):
|
||||||
|
return json.dumps(content, cls=DjangoJSONEncoder)
|
||||||
|
|
||||||
|
|
||||||
|
class PermConsumerMixin:
|
||||||
|
"""Add support to check permissions on consumers.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
perms_connect (list): Required permissions to connect to this
|
||||||
|
consumer.
|
||||||
|
|
||||||
|
message.user is appended as argument to each connection_groups method call.
|
||||||
|
|
||||||
|
"""
|
||||||
|
http_user = True # Enable message.user
|
||||||
|
perms_connect = []
|
||||||
|
|
||||||
|
def connect(self, message, **kwargs):
|
||||||
|
"""Check permissions on connection."""
|
||||||
|
if message.user.has_perms(self.perms_connect):
|
||||||
|
super().connect(message, **kwargs)
|
||||||
|
else:
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def raw_connect(self, message, **kwargs):
|
||||||
|
# Same as original raw_connect method of JsonWebsocketConsumer
|
||||||
|
# We add user to connection_groups call.
|
||||||
|
groups = self.connection_groups(user=message.user, **kwargs)
|
||||||
|
for group in groups:
|
||||||
|
Group(group, channel_layer=message.channel_layer).add(message.reply_channel)
|
||||||
|
self.connect(message, **kwargs)
|
||||||
|
|
||||||
|
def raw_disconnect(self, message, **kwargs):
|
||||||
|
# Same as original raw_connect method of JsonWebsocketConsumer
|
||||||
|
# We add user to connection_groups call.
|
||||||
|
groups = self.connection_groups(user=message.user, **kwargs)
|
||||||
|
for group in groups:
|
||||||
|
Group(group, channel_layer=message.channel_layer).discard(message.reply_channel)
|
||||||
|
self.disconnect(message, **kwargs)
|
||||||
|
|
||||||
|
def connection_groups(self, user, **kwargs):
|
||||||
|
"""`message.user` is available as `user` arg. Original behavior."""
|
||||||
|
super().connection_groups(user, user, **kwargs)
|
|
@ -6,9 +6,7 @@ from urllib.parse import urlencode
|
||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.views.generic import (
|
from django.views.generic import ListView, DetailView, TemplateView, FormView
|
||||||
DetailView, FormView, ListView, TemplateView, View,
|
|
||||||
)
|
|
||||||
from django.views.generic.detail import BaseDetailView
|
from django.views.generic.detail import BaseDetailView
|
||||||
from django.views.generic.edit import CreateView, UpdateView
|
from django.views.generic.edit import CreateView, UpdateView
|
||||||
from django.core.urlresolvers import reverse, reverse_lazy
|
from django.core.urlresolvers import reverse, reverse_lazy
|
||||||
|
@ -17,7 +15,7 @@ from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
from django.contrib.auth.models import User, Permission, Group
|
from django.contrib.auth.models import User, Permission, Group
|
||||||
from django.http import JsonResponse, Http404, HttpResponse
|
from django.http import JsonResponse, Http404
|
||||||
from django.forms import formset_factory
|
from django.forms import formset_factory
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F, Sum, Prefetch, Count
|
from django.db.models import F, Sum, Prefetch, Count
|
||||||
|
@ -81,64 +79,6 @@ class Home(TemplateView):
|
||||||
return super(TemplateView, self).dispatch(*args, **kwargs)
|
return super(TemplateView, self).dispatch(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def KFET_OPEN():
|
|
||||||
kfet_open_date = cache.get('KFET_OPEN_DATE', None)
|
|
||||||
kfet_open = cache.get('KFET_OPEN', None)
|
|
||||||
if kfet_open_date is None:
|
|
||||||
kfet_open_date = timezone.now()
|
|
||||||
cache.set('KFET_OPEN_DATE', kfet_open_date)
|
|
||||||
if kfet_open is None:
|
|
||||||
kfet_open = False
|
|
||||||
cache.set('KFET_OPEN', kfet_open)
|
|
||||||
return (kfet_open, kfet_open_date)
|
|
||||||
|
|
||||||
|
|
||||||
def KFET_FORCE_CLOSE():
|
|
||||||
kfet_force_close = cache.get('KFET_FORCE_CLOSE', None)
|
|
||||||
if kfet_force_close is None:
|
|
||||||
kfet_force_close = False
|
|
||||||
cache.set('KFET_FORCE_CLOSE', kfet_force_close)
|
|
||||||
return kfet_force_close
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateKfetOpen(View):
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
is_open = "open" in request.GET
|
|
||||||
cache.set('KFET_OPEN', is_open)
|
|
||||||
cache.set('KFET_OPEN_DATE', timezone.now())
|
|
||||||
|
|
||||||
# Websocket
|
|
||||||
websocket_data = {
|
|
||||||
'door_action': {
|
|
||||||
'kfet_open': is_open,
|
|
||||||
'kfet_open_date': timezone.now().isoformat(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
consumers.KfetOpen.group_send('kfet.is_open', websocket_data)
|
|
||||||
|
|
||||||
(is_open_get, time) = KFET_OPEN()
|
|
||||||
return HttpResponse("%r at %s" % (is_open_get, time.isoformat()))
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateForceClose(View):
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
force_close = "close" in request.GET
|
|
||||||
cache.set('KFET_FORCE_CLOSE', force_close)
|
|
||||||
|
|
||||||
# Websocket
|
|
||||||
websocket_data = {
|
|
||||||
'force_action': {
|
|
||||||
'force_close': force_close,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
consumers.KfetOpen.group_send('kfet.is_open', websocket_data)
|
|
||||||
|
|
||||||
force_close_get = KFET_FORCE_CLOSE()
|
|
||||||
time = timezone.now()
|
|
||||||
return HttpResponse("closed : %r at %s" % (force_close_get,
|
|
||||||
time.isoformat()))
|
|
||||||
|
|
||||||
|
|
||||||
@teamkfet_required
|
@teamkfet_required
|
||||||
def login_genericteam(request):
|
def login_genericteam(request):
|
||||||
# Check si besoin de déconnecter l'utilisateur de CAS
|
# Check si besoin de déconnecter l'utilisateur de CAS
|
||||||
|
|
|
@ -14,12 +14,12 @@ unicodecsv==0.14.1
|
||||||
icalendar==3.10
|
icalendar==3.10
|
||||||
django-bootstrap-form==3.2.1
|
django-bootstrap-form==3.2.1
|
||||||
asgiref==1.1.1
|
asgiref==1.1.1
|
||||||
daphne==1.2.0
|
daphne==1.3.0
|
||||||
asgi-redis==1.3.0
|
asgi-redis==1.3.0
|
||||||
statistics==1.0.3.5
|
statistics==1.0.3.5
|
||||||
future==0.15.2
|
future==0.15.2
|
||||||
django-widget-tweaks==1.4.1
|
django-widget-tweaks==1.4.1
|
||||||
git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail
|
git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail
|
||||||
ldap3
|
ldap3
|
||||||
channels==1.1.3
|
channels==1.1.5
|
||||||
python-dateutil
|
python-dateutil
|
||||||
|
|
Loading…
Reference in a new issue