From 4808650fa05f1ee859885476dd2b82a8315835a5 Mon Sep 17 00:00:00 2001 From: Qwann Date: Thu, 9 Feb 2017 14:05:29 +0100 Subject: [PATCH 01/43] kfet_open is updatable --- kfet/migrations/00048_kfet_open_cache.py | 24 +++++++++++++++++++++++ kfet/models.py | 2 ++ kfet/urls.py | 3 +++ kfet/views.py | 25 +++++++++++++++++++++++- 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 kfet/migrations/00048_kfet_open_cache.py diff --git a/kfet/migrations/00048_kfet_open_cache.py b/kfet/migrations/00048_kfet_open_cache.py new file mode 100644 index 00000000..ed9b7041 --- /dev/null +++ b/kfet/migrations/00048_kfet_open_cache.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0047_auto_20170104_1528'), + ] + + operations = [ + migrations.AddField( + model_name='settings', + name='value_boolean', + field=models.NullBooleanField(default=None), + ), + migrations.AddField( + model_name='settings', + name='value_datetime', + field=models.DateTimeField(blank=True, null=True, default=None), + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 419cd0a0..359bdfca 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -569,6 +569,8 @@ class GlobalPermissions(models.Model): ('special_add_account', "Créer un compte avec une balance initiale") ) + + class Settings(models.Model): name = models.CharField( max_length = 45, diff --git a/kfet/urls.py b/kfet/urls.py index 271ed917..11d2a1b9 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -167,6 +167,9 @@ urlpatterns = [ permission_required('kfet.change_settings') (views.SettingsUpdate.as_view()), name='kfet.settings.update'), + url('^settings/kfet_open/$', + views.UpdateKfetOpen.as_view(), + name='kfet.settings.kfet_open'), # ----- # Transfers urls diff --git a/kfet/views.py b/kfet/views.py index 7083d489..023dab55 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -7,7 +7,7 @@ from builtins import * from django.shortcuts import render, get_object_or_404, redirect from django.core.exceptions import PermissionDenied, ValidationError from django.core.cache import cache -from django.views.generic import ListView, DetailView +from django.views.generic import ListView, DetailView, View from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView from django.core.urlresolvers import reverse_lazy from django.contrib import messages @@ -40,6 +40,29 @@ import statistics def home(request): return render(request, "kfet/base.html") + +def KFET_OPEN(): + kfet_open_date = cache.get('KFET_OPEN_DATE') + kfet_open = cache.get('KFET_OPEN') + if not kfet_open_date: + kfet_open_date = timezone.now() + cache.set('KFET_OPEN_DATE', kfet_open_date) + if not kfet_open: + kfet_open = False + cache.set('KFET_OPEN', kfet_open) + return (kfet_open, kfet_open_date) + + +class UpdateKfetOpen(View): + def get(self, request, *args, **kwargs): + open_string = request.GET.get('open') + is_open = not (open_string == "false" or open_string == "False") + cache.set('KFET_OPEN', is_open) + cache.set('KFET_OPEN_DATE', timezone.now()) + (is_open_get, time) = KFET_OPEN() + return HttpResponse("%r at %s" % (is_open_get, time.isoformat())) + + @teamkfet_required def login_genericteam(request): # Check si besoin de déconnecter l'utilisateur de CAS From f87f1ceff138ee2c41853a1538dac16ab79e3269 Mon Sep 17 00:00:00 2001 From: Qwann Date: Sat, 11 Feb 2017 00:29:12 +0100 Subject: [PATCH 02/43] kfetOpen bullet working --- cof/settings_dev.py | 1 + kfet/consumers.py | 18 +++++++++++ kfet/context_processors.py | 10 ++++++ kfet/routing.py | 1 + kfet/static/kfet/css/nav.css | 20 +++++++++++- kfet/static/kfet/js/kfet_open.js | 54 +++++++++++++++++++++++++++++++ kfet/templates/kfet/base.html | 2 ++ kfet/templates/kfet/base_nav.html | 7 ++-- kfet/templates/kfet/kpsul.html | 1 - kfet/views.py | 13 ++++++-- 10 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 kfet/static/kfet/js/kfet_open.js diff --git a/cof/settings_dev.py b/cof/settings_dev.py index f6521222..4d604cac 100644 --- a/cof/settings_dev.py +++ b/cof/settings_dev.py @@ -83,6 +83,7 @@ TEMPLATES = [ 'django.core.context_processors.static', 'gestioncof.shared.context_processor', 'kfet.context_processors.auth', + 'kfet.context_processors.kfet_open', ], }, }, diff --git a/kfet/consumers.py b/kfet/consumers.py index dcd69bdf..72e3b21e 100644 --- a/kfet/consumers.py +++ b/kfet/consumers.py @@ -24,3 +24,21 @@ class KPsul(JsonWebsocketConsumer): def disconnect(self, message, **kwargs): pass + +class KfetOpen(JsonWebsocketConsumer): + + # Set to True if you want them, else leave out + strict_ordering = False + slight_ordering = False + + 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 diff --git a/kfet/context_processors.py b/kfet/context_processors.py index ef4f2e64..f3b8f76f 100644 --- a/kfet/context_processors.py +++ b/kfet/context_processors.py @@ -5,6 +5,8 @@ from __future__ import (absolute_import, division, from builtins import * from django.contrib.auth.context_processors import PermWrapper +from .views import KFET_OPEN + def auth(request): if hasattr(request, 'real_user'): @@ -13,3 +15,11 @@ def auth(request): 'perms': PermWrapper(request.real_user), } return {} + + +def kfet_open(resquest): + (kfet_open, kfet_open_date) = KFET_OPEN() + return { + 'kfet_open': kfet_open, + 'kfet_open_date': kfet_open_date, + } diff --git a/kfet/routing.py b/kfet/routing.py index 5ea343cb..5db0101f 100644 --- a/kfet/routing.py +++ b/kfet/routing.py @@ -9,4 +9,5 @@ from kfet import consumers channel_routing = [ route_class(consumers.KPsul, path=r"^/ws/k-fet/k-psul/$"), + route_class(consumers.KfetOpen, path=r"^/ws/k-fet/is_open/$"), ] diff --git a/kfet/static/kfet/css/nav.css b/kfet/static/kfet/css/nav.css index 5ffc7b24..9e2c5462 100644 --- a/kfet/static/kfet/css/nav.css +++ b/kfet/static/kfet/css/nav.css @@ -14,7 +14,7 @@ nav { } nav .navbar-brand { - padding:3px 25px; + padding:3px 15px 3px 25px; } nav .navbar-brand img { @@ -44,6 +44,24 @@ nav a { background-color:#C8102E; } +#kfet-open { + 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; +} + .dropdown-menu { padding:0; } diff --git a/kfet/static/kfet/js/kfet_open.js b/kfet/static/kfet/js/kfet_open.js new file mode 100644 index 00000000..9f5e4c22 --- /dev/null +++ b/kfet/static/kfet/js/kfet_open.js @@ -0,0 +1,54 @@ +function kfet_open(init_date, init_satus) { + // VARIABLES + var kfet_open_bullet = $('#kfet-open'); + var open_color = "#73C252"; + var closed_color = "#B42B26"; + var unknown_color = "#ECD03E"; + var kfet_open_date = init_date; + var kfet_open = init_status; + // INITIALISAITION + update_open_bullet(); + // FONCTIONS + function nb_min_diff() { + var date_now = new Date(); + // On calcule le nb de minutes depuis le dernier + // envoi d'information + tmp = date_now - kfet_open_date; + + tmp = Math.floor(tmp/1000); // Nombre de secondes entre les 2 dates + diff_sec = tmp % 60; // Extraction du nombre de secondes + + tmp = Math.floor((tmp-diff_sec)/60); // Nombre de minutes (partie entière) + + return tmp; + } + function update_open_bullet() { + nb_min = nb_min_diff(); + console.log("K-Fêt ouverte : " + kfet_open); + console.log(nb_min + " minute(s) depuis la dernière mise à jour"); + if (nb_min > 5) { + kfet_open_bullet.css({'background-color': unknown_color}); + } else if (kfet_open){ + kfet_open_bullet.css({'background-color': open_color}); + } else { + kfet_open_bullet.css({'background-color': closed_color}); + } + } + // SYNCHRONIZATION + websocket_msg_default = {'last_op': 0} + + 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 = $.extend({}, websocket_msg_default, JSON.parse(e.data)); + console.log("Message reçu de la part de la porte."); + + kfet_open_date = new Date(data['kfet_open_date']); + kfet_open = data['kfet_open']; + + update_open_bullet(); + } +} diff --git a/kfet/templates/kfet/base.html b/kfet/templates/kfet/base.html index 173a5fb7..7587cb7b 100644 --- a/kfet/templates/kfet/base.html +++ b/kfet/templates/kfet/base.html @@ -18,6 +18,8 @@ + + {% block extra_head %}{% endblock %} diff --git a/kfet/templates/kfet/base_nav.html b/kfet/templates/kfet/base_nav.html index b5c98375..d884d110 100644 --- a/kfet/templates/kfet/base_nav.html +++ b/kfet/templates/kfet/base_nav.html @@ -12,6 +12,7 @@ + - - diff --git a/kfet/templates/kfet/home.html b/kfet/templates/kfet/home.html index 40a1debd..6d4f163f 100644 --- a/kfet/templates/kfet/home.html +++ b/kfet/templates/kfet/home.html @@ -20,8 +20,8 @@
?????
diff --git a/kfet/urls.py b/kfet/urls.py index aad89cd4..47f14b52 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -211,7 +211,10 @@ urlpatterns = [ url('^kfet_open/$', views.UpdateKfetOpen.as_view(), - name='kfet.settings.kfet_open'), + name='kfet.kfet_open'), + url('^force_close/$', + views.UpdateForceClose.as_view(), + name='kfet.force_close'), # ----- # Transfers urls diff --git a/kfet/views.py b/kfet/views.py index 7856cf22..77bb061d 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -94,15 +94,37 @@ class UpdateKfetOpen(View): cache.set('KFET_OPEN_DATE', timezone.now()) # Websocket - websocket_data = {} - websocket_data['kfet_open'] = is_open - websocket_data['kfet_open_date'] = timezone.now() + websocket_data = { + 'door_action': { + 'kfet_open': is_open, + 'kfet_open_date': timezone.now(), + }, + } 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 def login_genericteam(request): # Check si besoin de déconnecter l'utilisateur de CAS From f18bb9f336dbca42af6375a227232106325bf083 Mon Sep 17 00:00:00 2001 From: Qwann Date: Thu, 9 Mar 2017 17:27:58 +0100 Subject: [PATCH 12/43] permission added --- kfet/templates/kfet/home.html | 2 ++ kfet/views.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/kfet/templates/kfet/home.html b/kfet/templates/kfet/home.html index 6d4f163f..a4f9ee4c 100644 --- a/kfet/templates/kfet/home.html +++ b/kfet/templates/kfet/home.html @@ -19,11 +19,13 @@
?????
+ {% if perms.kfet.is_team %} + {% endif %}
diff --git a/kfet/views.py b/kfet/views.py index 77bb061d..4278aa88 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -124,6 +124,11 @@ class UpdateForceClose(View): return HttpResponse("closed : %r at %s" % (force_close_get, time.isoformat())) + @method_decorator(login_required) + @method_decorator(teamkfet_required) + def dispatch(self, *args, **kwargs): + return super(UpdateForceClose, self).dispatch(*args, **kwargs) + @teamkfet_required def login_genericteam(request): From cd31c5525459b380b2ad0676f84b48a5ac8966a9 Mon Sep 17 00:00:00 2001 From: Qwann Date: Fri, 10 Mar 2017 16:40:36 +0100 Subject: [PATCH 13/43] permission working --- kfet/decorators.py | 4 ++++ kfet/models.py | 2 +- kfet/static/kfet/js/kfet_open.js | 38 ++++++++++++++++++++++++++------ kfet/views.py | 3 ++- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/kfet/decorators.py b/kfet/decorators.py index 9af9247f..accfa143 100644 --- a/kfet/decorators.py +++ b/kfet/decorators.py @@ -9,4 +9,8 @@ from django_cas_ng.decorators import user_passes_test def kfet_is_team(user): return user.has_perm('kfet.is_team') +def can_force_close(user): + return user.has_perm('force_close_kfet') + teamkfet_required = user_passes_test(lambda u: kfet_is_team(u)) +force_close_required = user_passes_test(lambda u: can_force_close(u)) diff --git a/kfet/models.py b/kfet/models.py index b95755b7..928b7d88 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -594,7 +594,7 @@ class GlobalPermissions(models.Model): ('edit_balance_account', "Modifier la balance d'un compte"), ('change_account_password', "Modifier le mot de passe d'une personne de l'équipe"), ('special_add_account', "Créer un compte avec une balance initiale"), - ('can_close_kfet', "Peut fermer manuelement la K-Fêt"), + ('force_close_kfet', "Fermer manuelement la K-Fêt"), ) diff --git a/kfet/static/kfet/js/kfet_open.js b/kfet/static/kfet/js/kfet_open.js index 93374094..8ea0be70 100644 --- a/kfet/static/kfet/js/kfet_open.js +++ b/kfet/static/kfet/js/kfet_open.js @@ -17,13 +17,7 @@ function kfet_open(init_date, init_satus, init_force_close, force_close_url, for var force_close = init_force_close; // EVENT - force_close_button.click(function() { - if (force_close) { - $.get(force_open_url, function(data) {}); - } else { - $.get(force_close_url, function(data) {}); - } - }); + force_close_button.click(forceClose); // INITIALISAITION update_open(); @@ -36,6 +30,36 @@ function kfet_open(init_date, init_satus, init_force_close, force_close_url, for }, 30 * 1000); // 60 * 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); + }, + }) + .done(function() {}) + .fail(function($xhr) { + var data = $xhr.responseJSON; + switch ($xhr.status) { + case 403: + requestAuth({'errors':{}}, forceClose); + break; + case 400: + alert('lol'); + break; + } + lock = 0; + }); + } + function nb_min_diff() { var date_now = new Date(); // On calcule le nb de minutes depuis le dernier diff --git a/kfet/views.py b/kfet/views.py index 4278aa88..af633cd7 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -22,7 +22,7 @@ from django.utils import timezone from django.utils.crypto import get_random_string from django.utils.decorators import method_decorator from gestioncof.models import CofProfile, Clipper -from kfet.decorators import teamkfet_required +from kfet.decorators import teamkfet_required, force_close_required from kfet.models import ( Account, Checkout, Article, Settings, AccountNegative, CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory, @@ -126,6 +126,7 @@ class UpdateForceClose(View): @method_decorator(login_required) @method_decorator(teamkfet_required) + @method_decorator(force_close_required) def dispatch(self, *args, **kwargs): return super(UpdateForceClose, self).dispatch(*args, **kwargs) From b0643c09288cdca6edd4c98c53d3884d13334fe1 Mon Sep 17 00:00:00 2001 From: Qwann Date: Sat, 11 Mar 2017 02:04:30 +0100 Subject: [PATCH 14/43] typo --- kfet/context_processors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/context_processors.py b/kfet/context_processors.py index 0d98c7e1..6c053ea9 100644 --- a/kfet/context_processors.py +++ b/kfet/context_processors.py @@ -17,7 +17,7 @@ def auth(request): return {} -def kfet_open(resquest): +def kfet_open(request): (kfet_open, kfet_open_date) = KFET_OPEN() kfet_force_close = KFET_FORCE_CLOSE() return { From ef8fec89fed8b401f0c8db714f6c498736395be2 Mon Sep 17 00:00:00 2001 From: Qwann Date: Wed, 5 Apr 2017 17:50:49 +0200 Subject: [PATCH 15/43] migration renamed --- kfet/migrations/0049_merge.py | 1 - .../{0048_kfet_open_cache.py => 0053_kfet_open_cache.py} | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) rename kfet/migrations/{0048_kfet_open_cache.py => 0053_kfet_open_cache.py} (92%) diff --git a/kfet/migrations/0049_merge.py b/kfet/migrations/0049_merge.py index dedc340b..0ce9a525 100644 --- a/kfet/migrations/0049_merge.py +++ b/kfet/migrations/0049_merge.py @@ -7,7 +7,6 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('kfet', '0048_kfet_open_cache'), ('kfet', '0048_article_hidden'), ('kfet', '0048_default_datetime'), ] diff --git a/kfet/migrations/0048_kfet_open_cache.py b/kfet/migrations/0053_kfet_open_cache.py similarity index 92% rename from kfet/migrations/0048_kfet_open_cache.py rename to kfet/migrations/0053_kfet_open_cache.py index ed9b7041..9669e684 100644 --- a/kfet/migrations/0048_kfet_open_cache.py +++ b/kfet/migrations/0053_kfet_open_cache.py @@ -7,7 +7,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('kfet', '0047_auto_20170104_1528'), + ('kfet', '0052_category_addcost'), ] operations = [ From deb0d4de1e4331dbc25ffb6dc9aef1934ceea58f Mon Sep 17 00:00:00 2001 From: Qwann Date: Fri, 7 Apr 2017 17:23:41 +0200 Subject: [PATCH 16/43] moving migration again --- kfet/migrations/0053_kfet_open_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/migrations/0053_kfet_open_cache.py b/kfet/migrations/0053_kfet_open_cache.py index 9669e684..a1a8f213 100644 --- a/kfet/migrations/0053_kfet_open_cache.py +++ b/kfet/migrations/0053_kfet_open_cache.py @@ -7,7 +7,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('kfet', '0052_category_addcost'), + ('kfet', '0053_kfet_open_cache'), ] operations = [ From e18c2c698c4b38c9de7b98e12664570e42dcfb6e Mon Sep 17 00:00:00 2001 From: Qwann Date: Fri, 7 Apr 2017 17:41:23 +0200 Subject: [PATCH 17/43] new migration --- kfet/migrations/0053_kfet_open_cache.py | 24 ------------------- kfet/migrations/0054_force_kfet_close_perm.py | 18 ++++++++++++++ 2 files changed, 18 insertions(+), 24 deletions(-) delete mode 100644 kfet/migrations/0053_kfet_open_cache.py create mode 100644 kfet/migrations/0054_force_kfet_close_perm.py diff --git a/kfet/migrations/0053_kfet_open_cache.py b/kfet/migrations/0053_kfet_open_cache.py deleted file mode 100644 index a1a8f213..00000000 --- a/kfet/migrations/0053_kfet_open_cache.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('kfet', '0053_kfet_open_cache'), - ] - - operations = [ - migrations.AddField( - model_name='settings', - name='value_boolean', - field=models.NullBooleanField(default=None), - ), - migrations.AddField( - model_name='settings', - name='value_datetime', - field=models.DateTimeField(blank=True, null=True, default=None), - ), - ] diff --git a/kfet/migrations/0054_force_kfet_close_perm.py b/kfet/migrations/0054_force_kfet_close_perm.py new file mode 100644 index 00000000..52290026 --- /dev/null +++ b/kfet/migrations/0054_force_kfet_close_perm.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0053_created_at'), + ] + + operations = [ + migrations.AlterModelOptions( + name='globalpermissions', + options={'managed': False, 'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes'), ('manage_perms', 'Gérer les permissions K-Fêt'), ('manage_addcosts', 'Gérer les majorations'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'), ('view_negs', 'Voir la liste des négatifs'), ('order_to_inventory', "Générer un inventaire à partir d'une commande"), ('edit_balance_account', "Modifier la balance d'un compte"), ('change_account_password', "Modifier le mot de passe d'une personne de l'équipe"), ('special_add_account', 'Créer un compte avec une balance initiale'), ('force_close_kfet', 'Fermer manuelement la K-Fêt'))}, + ), + ] From 15873085e11425d0cc1ca44ef3fd65c1091bfb8b Mon Sep 17 00:00:00 2001 From: Qwann Date: Sun, 9 Apr 2017 20:01:52 +0200 Subject: [PATCH 18/43] small fixes --- kfet/consumers.py | 5 ----- kfet/decorators.py | 2 -- kfet/static/kfet/js/kfet_open.js | 16 ++++++---------- kfet/urls.py | 5 +++-- kfet/views.py | 13 +++---------- 5 files changed, 12 insertions(+), 29 deletions(-) diff --git a/kfet/consumers.py b/kfet/consumers.py index 72e3b21e..8a0df05f 100644 --- a/kfet/consumers.py +++ b/kfet/consumers.py @@ -26,11 +26,6 @@ class KPsul(JsonWebsocketConsumer): pass class KfetOpen(JsonWebsocketConsumer): - - # Set to True if you want them, else leave out - strict_ordering = False - slight_ordering = False - def connection_groups(self, **kwargs): return ['kfet.is_open'] diff --git a/kfet/decorators.py b/kfet/decorators.py index 592bf566..3dc76767 100644 --- a/kfet/decorators.py +++ b/kfet/decorators.py @@ -9,6 +9,4 @@ def kfet_is_team(user): def can_force_close(user): return user.has_perm('force_close_kfet') -teamkfet_required = user_passes_test(lambda u: kfet_is_team(u)) -force_close_required = user_passes_test(lambda u: can_force_close(u)) teamkfet_required = user_passes_test(kfet_is_team) diff --git a/kfet/static/kfet/js/kfet_open.js b/kfet/static/kfet/js/kfet_open.js index 8ea0be70..e2cccf81 100644 --- a/kfet/static/kfet/js/kfet_open.js +++ b/kfet/static/kfet/js/kfet_open.js @@ -1,4 +1,4 @@ -function kfet_open(init_date, init_satus, init_force_close, force_close_url, force_open_url) { +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'); @@ -19,15 +19,15 @@ function kfet_open(init_date, init_satus, init_force_close, force_close_url, for // EVENT force_close_button.click(forceClose); - // INITIALISAITION + // INITIALISATION update_open(); update_force_button(); - // On recherge toute les 30sec - // (dans le cas où le statut deviendrait inconn) + // On recharge toute les 30sec + // (dans le cas où le statut deviendrait inconnu) setInterval(function() { update_open(); - }, 30 * 1000); // 60 * 1000 milsec + }, 30 * 1000); // 30 * 1000 milsec // FONCTIONS function forceClose(password = '') { @@ -45,16 +45,12 @@ function kfet_open(init_date, init_satus, init_force_close, force_close_url, for $xhr.setRequestHeader("KFetPassword", password); }, }) - .done(function() {}) .fail(function($xhr) { var data = $xhr.responseJSON; switch ($xhr.status) { case 403: requestAuth({'errors':{}}, forceClose); break; - case 400: - alert('lol'); - break; } lock = 0; }); @@ -89,7 +85,7 @@ function kfet_open(init_date, init_satus, init_force_close, force_close_url, for open_status.html("?????"); } function update_open() { - nb_min = nb_min_diff(); + var nb_min = nb_min_diff(); 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) { diff --git a/kfet/urls.py b/kfet/urls.py index 718eac40..7b47d8c9 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -205,8 +205,9 @@ urlpatterns = [ url('^kfet_open/$', views.UpdateKfetOpen.as_view(), name='kfet.kfet_open'), - url('^force_close/$', - views.UpdateForceClose.as_view(), + url('^kfet_close/$', + permission_required('kfet.can_force_close') + (views.UpdateForceClose.as_view()), name='kfet.force_close'), # ----- diff --git a/kfet/views.py b/kfet/views.py index 51948de1..7865ec34 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -7,9 +7,8 @@ from django.shortcuts import render, get_object_or_404, redirect from django.core.exceptions import PermissionDenied from django.core.cache import cache from django.views.generic import ListView, DetailView, TemplateView, View -from django.views.generic.list import BaseListView, MultipleObjectTemplateResponseMixin -from django.views.generic.detail import BaseDetailView, SingleObjectTemplateResponseMixin -from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView +from django.views.generic.detail import BaseDetailView +from django.views.generic.edit import CreateView, UpdateView from django.core.urlresolvers import reverse, reverse_lazy from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin @@ -25,7 +24,7 @@ from django.utils import timezone from django.utils.crypto import get_random_string from django.utils.decorators import method_decorator from gestioncof.models import CofProfile -from kfet.decorators import teamkfet_required, force_close_required +from kfet.decorators import teamkfet_required from kfet.models import ( Account, Checkout, Article, Settings, AccountNegative, CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory, @@ -125,12 +124,6 @@ class UpdateForceClose(View): return HttpResponse("closed : %r at %s" % (force_close_get, time.isoformat())) - @method_decorator(login_required) - @method_decorator(teamkfet_required) - @method_decorator(force_close_required) - def dispatch(self, *args, **kwargs): - return super(UpdateForceClose, self).dispatch(*args, **kwargs) - @teamkfet_required def login_genericteam(request): From 5c6a73c59733dca6e0953b181ba00ab02500967e Mon Sep 17 00:00:00 2001 From: Qwann Date: Sun, 9 Apr 2017 20:54:30 +0200 Subject: [PATCH 19/43] kfet_open uses moment.js --- kfet/context_processors.py | 2 +- kfet/static/kfet/js/kfet_open.js | 18 ++++-------------- kfet/templates/kfet/base.html | 6 +++++- kfet/templates/kfet/kpsul.html | 3 --- kfet/views.py | 2 +- 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/kfet/context_processors.py b/kfet/context_processors.py index 6c053ea9..9364c724 100644 --- a/kfet/context_processors.py +++ b/kfet/context_processors.py @@ -22,6 +22,6 @@ def kfet_open(request): kfet_force_close = KFET_FORCE_CLOSE() return { 'kfet_open': kfet_open, - 'kfet_open_date': kfet_open_date, + 'kfet_open_date': kfet_open_date.isoformat(), 'kfet_force_close': kfet_force_close, } diff --git a/kfet/static/kfet/js/kfet_open.js b/kfet/static/kfet/js/kfet_open.js index e2cccf81..a8f41757 100644 --- a/kfet/static/kfet/js/kfet_open.js +++ b/kfet/static/kfet/js/kfet_open.js @@ -56,18 +56,8 @@ function kfet_open(init_date, init_status, init_force_close, force_close_url, fo }); } - function nb_min_diff() { - var date_now = new Date(); - // On calcule le nb de minutes depuis le dernier - // envoi d'information - tmp = date_now - kfet_open_date; - - tmp = Math.floor(tmp/1000); // Nombre de secondes entre les 2 dates - diff_sec = tmp % 60; // Extraction du nombre de secondes - - tmp = Math.floor((tmp-diff_sec)/60); // Nombre de minutes (partie entière) - - return tmp; + function kfet_open_min() { + return moment().diff(kfet_open_date, 'minute') } function do_kfet_close() { kfet_open_bullet.css({'background-color': closed_color}); @@ -85,7 +75,7 @@ function kfet_open(init_date, init_status, init_force_close, force_close_url, fo open_status.html("?????"); } function update_open() { - var nb_min = nb_min_diff(); + 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) { @@ -121,7 +111,7 @@ function kfet_open(init_date, init_status, init_force_close, force_close_url, fo if (data['door_action']) { console.log("* Message reçu de la part de la porte."); - kfet_open_date = new Date(data['door_action']['kfet_open_date']); + kfet_open_date = moment.utc(data['door_action']['kfet_open_date']); kfet_open = data['door_action']['kfet_open']; update_open(); diff --git a/kfet/templates/kfet/base.html b/kfet/templates/kfet/base.html index 81ca3dfd..d992b209 100644 --- a/kfet/templates/kfet/base.html +++ b/kfet/templates/kfet/base.html @@ -21,13 +21,17 @@ + + + + {# K-Fêt open #} - - - {% endblock %} diff --git a/kfet/views.py b/kfet/views.py index 7865ec34..0a3b6f99 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -97,7 +97,7 @@ class UpdateKfetOpen(View): websocket_data = { 'door_action': { 'kfet_open': is_open, - 'kfet_open_date': timezone.now(), + 'kfet_open_date': timezone.now().isoformat(), }, } consumers.KfetOpen.group_send('kfet.is_open', websocket_data) From cb9ba76a4f7bde681c92f5f030e945d8b359d652 Mon Sep 17 00:00:00 2001 From: Qwann Date: Mon, 10 Apr 2017 16:47:13 +0200 Subject: [PATCH 20/43] small fixes --- kfet/static/kfet/js/kfet_open.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/static/kfet/js/kfet_open.js b/kfet/static/kfet/js/kfet_open.js index a8f41757..b6886c7b 100644 --- a/kfet/static/kfet/js/kfet_open.js +++ b/kfet/static/kfet/js/kfet_open.js @@ -57,7 +57,7 @@ function kfet_open(init_date, init_status, init_force_close, force_close_url, fo } function kfet_open_min() { - return moment().diff(kfet_open_date, 'minute') + return moment().diff(kfet_open_date, 'minute'); } function do_kfet_close() { kfet_open_bullet.css({'background-color': closed_color}); From e0b0a531125f82378a7aa0182a70d5ccfc4a5b3b Mon Sep 17 00:00:00 2001 From: Qwann Date: Mon, 10 Apr 2017 17:18:43 +0200 Subject: [PATCH 21/43] stupidness removed --- kfet/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kfet/views.py b/kfet/views.py index 0a3b6f99..dd4fb29a 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -68,20 +68,20 @@ class Home(TemplateView): def KFET_OPEN(): - kfet_open_date = cache.get('KFET_OPEN_DATE') - kfet_open = cache.get('KFET_OPEN') - if not kfet_open_date: + 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 not kfet_open: + 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') - if not 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 From be8d249ed7669cace82650856b731b3c8535113a Mon Sep 17 00:00:00 2001 From: Qwann Date: Mon, 10 Apr 2017 17:47:39 +0200 Subject: [PATCH 22/43] remove useless code --- kfet/static/kfet/js/kfet_open.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kfet/static/kfet/js/kfet_open.js b/kfet/static/kfet/js/kfet_open.js index b6886c7b..08a030a5 100644 --- a/kfet/static/kfet/js/kfet_open.js +++ b/kfet/static/kfet/js/kfet_open.js @@ -98,7 +98,6 @@ function kfet_open(init_date, init_status, init_force_close, force_close_url, fo } } // SYNCHRONIZATION - websocket_msg_default = {'last_op': 0} var websocket_protocol = window.location.protocol == 'https:' ? 'wss' : 'ws'; var location_host = window.location.host; @@ -106,7 +105,7 @@ function kfet_open(init_date, init_status, init_force_close, force_close_url, fo var socket = new ReconnectingWebSocket(websocket_protocol+"://" + location_url + "/ws/k-fet/is_open/"); socket.onmessage = function(e) { - var data = $.extend({}, websocket_msg_default, JSON.parse(e.data)); + var data = JSON.parse(e.data); if (data['door_action']) { console.log("* Message reçu de la part de la porte."); From 2c408389389a701f9e2e15ae1c23f65108d4a942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 10 Apr 2017 18:58:52 +0200 Subject: [PATCH 23/43] Add real cache support - Fix cache per process issue with a real cache system - Configuration seems too easy... but it seems to work --- provisioning/bootstrap.sh | 2 +- requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/provisioning/bootstrap.sh b/provisioning/bootstrap.sh index 269e4f25..e94d3ac4 100644 --- a/provisioning/bootstrap.sh +++ b/provisioning/bootstrap.sh @@ -9,7 +9,7 @@ DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4" # Installation de paquets utiles apt-get update && apt-get install -y python3-pip python3-dev python3-venv \ - libmysqlclient-dev libjpeg-dev git redis-server + libmysqlclient-dev libjpeg-dev git redis-server memcached pip install -U pip # Configuration et installation de mysql. Le mot de passe root est le même que diff --git a/requirements.txt b/requirements.txt index ce081588..202f5dda 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,3 +21,4 @@ git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_customma ldap3 git+https://github.com/Aureplop/channels.git#egg=channels python-dateutil +python-memcached From ab31c20649601514a24411328021daf6a636a46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 10 Apr 2017 19:07:19 +0200 Subject: [PATCH 24/43] missing CACHES value... --- cof/settings_dev.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cof/settings_dev.py b/cof/settings_dev.py index 18aadaad..3bc4b807 100644 --- a/cof/settings_dev.py +++ b/cof/settings_dev.py @@ -115,6 +115,15 @@ USE_L10N = True USE_TZ = True +# Cache system +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': '127.0.0.1:11211', + } +} + + # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.8/howto/static-files/ From 36771c2c4f6a3c31c0d71e26ce5f2adda8445017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 10 Apr 2017 21:36:00 +0200 Subject: [PATCH 25/43] Use redis for cache. - Cache use db #1 of redis. - Channel layer (of channels) use db #0 of redis. - `settings` try getting redis connection variables from environment. - Drop memcached system --- cof/settings_dev.py | 39 ++++++++++++++++++++++++++++----------- provisioning/bootstrap.sh | 2 +- requirements.txt | 2 +- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/cof/settings_dev.py b/cof/settings_dev.py index 3bc4b807..97b4bf7c 100644 --- a/cof/settings_dev.py +++ b/cof/settings_dev.py @@ -115,15 +115,6 @@ USE_L10N = True USE_TZ = True -# Cache system -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': '127.0.0.1:11211', - } -} - - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.8/howto/static-files/ @@ -176,13 +167,39 @@ RECAPTCHA_PUBLIC_KEY = "DUMMY" RECAPTCHA_PRIVATE_KEY = "DUMMY" RECAPTCHA_USE_SSL = True -# Channels settings + +# Redis settings (for cache and channel layers) + +REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") +REDIS_USER = os.environ.get("REDIS_USER", "") +REDIS_PASS = os.environ.get("REDIS_PASS", "") +REDIS_PORT = os.environ.get("REDIS_PORT", "6379") + +REDIS_AUTH = REDIS_USER+":"+REDIS_PASS+"@" if REDIS_USER or REDIS_PASS else '' +REDIS_BASE_URL = "redis://" + REDIS_AUTH + REDIS_HOST + ":" + REDIS_PORT + + +# Cache settings (use db #1 of redis) + +CACHE_REDIS_URL = REDIS_BASE_URL + "/1" + +CACHES = { + 'default': { + 'BACKEND': 'redis_cache.RedisCache', + 'LOCATION': CACHE_REDIS_URL, + } +} + + +# Channels settings (use db #0 of redis) + +CHANNEL_REDIS_URL = REDIS_BASE_URL + "/0" CHANNEL_LAYERS = { "default": { "BACKEND": "asgi_redis.RedisChannelLayer", "CONFIG": { - "hosts": [(os.environ.get("REDIS_HOST", "localhost"), 6379)], + "hosts": [CHANNEL_REDIS_URL], }, "ROUTING": "cof.routing.channel_routing", } diff --git a/provisioning/bootstrap.sh b/provisioning/bootstrap.sh index e94d3ac4..269e4f25 100644 --- a/provisioning/bootstrap.sh +++ b/provisioning/bootstrap.sh @@ -9,7 +9,7 @@ DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4" # Installation de paquets utiles apt-get update && apt-get install -y python3-pip python3-dev python3-venv \ - libmysqlclient-dev libjpeg-dev git redis-server memcached + libmysqlclient-dev libjpeg-dev git redis-server pip install -U pip # Configuration et installation de mysql. Le mot de passe root est le même que diff --git a/requirements.txt b/requirements.txt index 202f5dda..2012351a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ django-autoslug==1.9.3 django-cas-ng==3.5.5 django-grappelli==2.8.1 django-recaptcha==1.0.5 +django-redis-cache==1.7.1 mysqlclient==1.3.7 Pillow==3.3.0 six==1.10.0 @@ -21,4 +22,3 @@ git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_customma ldap3 git+https://github.com/Aureplop/channels.git#egg=channels python-dateutil -python-memcached From dbf5844f6a996399b2227f57a136a39897002673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sat, 15 Apr 2017 14:41:55 +0200 Subject: [PATCH 26/43] Clean settings redis --- cof/settings/common.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/cof/settings/common.py b/cof/settings/common.py index 98f4f713..a3b7527f 100644 --- a/cof/settings/common.py +++ b/cof/settings/common.py @@ -19,6 +19,26 @@ except ImportError: except KeyError: raise RuntimeError("Secrets missing") + +# Redis settings (for cache and channel layers) +try: + from .secret import REDIS_USER, REDIS_PASS +except ImportError: + REDIS_USER = os.environ.get("REDIS_USER", "") + REDIS_PASS = os.environ.get("REDIS_PASS", "") + +if REDIS_USER or REDIS_PASS: + REDIS_AUTH = REDIS_USER+":"+REDIS_PASS+"@" +else: + REDIS_AUTH = '' + +REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") +REDIS_PORT = os.environ.get("REDIS_PORT", "6379") + +REDIS_BASE_URL = "redis://" + REDIS_AUTH + REDIS_HOST + ":" + REDIS_PORT +# To select a specific redis database, do: REDIS_BASE_URL + '/' + + # Other secrets try: from .secret import ( @@ -153,18 +173,6 @@ AUTHENTICATION_BACKENDS = ( RECAPTCHA_USE_SSL = True - -# Redis settings (for cache and channel layers) - -REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") -REDIS_USER = os.environ.get("REDIS_USER", "") -REDIS_PASS = os.environ.get("REDIS_PASS", "") -REDIS_PORT = os.environ.get("REDIS_PORT", "6379") - -REDIS_AUTH = REDIS_USER+":"+REDIS_PASS+"@" if REDIS_USER or REDIS_PASS else '' -REDIS_BASE_URL = "redis://" + REDIS_AUTH + REDIS_HOST + ":" + REDIS_PORT - - # Cache settings (use db #1 of redis) CACHE_REDIS_URL = REDIS_BASE_URL + "/1" From e7266e7a9d834b2372c38c5339ad6412beae39a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Wed, 26 Apr 2017 11:28:18 +0200 Subject: [PATCH 27/43] use new settings for redis --- cof/settings/common.py | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/cof/settings/common.py b/cof/settings/common.py index 14f672e0..372d7d81 100644 --- a/cof/settings/common.py +++ b/cof/settings/common.py @@ -20,25 +20,6 @@ except KeyError: raise RuntimeError("Secrets missing") -# Redis settings (for cache and channel layers) -try: - from .secret import REDIS_USER, REDIS_PASS -except ImportError: - REDIS_USER = os.environ.get("REDIS_USER", "") - REDIS_PASS = os.environ.get("REDIS_PASS", "") - -if REDIS_USER or REDIS_PASS: - REDIS_AUTH = REDIS_USER+":"+REDIS_PASS+"@" -else: - REDIS_AUTH = '' - -REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") -REDIS_PORT = os.environ.get("REDIS_PORT", "6379") - -REDIS_BASE_URL = "redis://" + REDIS_AUTH + REDIS_HOST + ":" + REDIS_PORT -# To select a specific redis database, do: REDIS_BASE_URL + '/' - - # Other secrets try: from .secret import ( @@ -174,19 +155,19 @@ AUTHENTICATION_BACKENDS = ( RECAPTCHA_USE_SSL = True -# Cache settings (use db #1 of redis) - -CACHE_REDIS_URL = REDIS_BASE_URL + "/1" +# Cache settings CACHES = { 'default': { 'BACKEND': 'redis_cache.RedisCache', - 'LOCATION': CACHE_REDIS_URL, + 'LOCATION': 'redis://:{passwd}@{host}:{port}/db' + .format(passwd=REDIS_PASSWD, host=REDIS_HOST, + port=REDIS_PORT, db=REDIS_DB), } } -# Channels settings (use db #0 of redis) +# Channels settings CHANNEL_LAYERS = { "default": { From 0e03fc85ee2528d67957c6fa298f232ee2391410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Fri, 2 Jun 2017 17:25:04 +0100 Subject: [PATCH 28/43] The BdA receives the reminder emails --- bda/__init__.py | 10 ++++++++++ bda/models.py | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/bda/__init__.py b/bda/__init__.py index e69de29b..a5eecc65 100644 --- a/bda/__init__.py +++ b/bda/__init__.py @@ -0,0 +1,10 @@ +from django.contrib.auth.models import User + + +def get_generic_user(): + generic, created = User.objects.get_or_create(username="bda_generic") + if created: + generic.email = "bda@ens.fr" + generic.first_name = "Bureau des Arts" + generic.save() + return generic diff --git a/bda/models.py b/bda/models.py index 0228b4c0..42090374 100644 --- a/bda/models.py +++ b/bda/models.py @@ -11,6 +11,8 @@ from django.contrib.auth.models import User from django.conf import settings from django.utils import timezone, formats +from . import get_generic_user + class Tirage(models.Model): title = models.CharField("Titre", max_length=300) @@ -104,11 +106,9 @@ class Spectacle(models.Model): members[member.id][1] = 2 else: members[member.id] = [member, 1] - # FIXME : faire quelque chose de ça, un utilisateur bda_generic ? - # # Pour le BdA - # members[0] = ['BdA', 1, 'bda@ens.fr'] - # members[-1] = ['BdA', 2, 'bda@ens.fr'] # On écrit un mail personnalisé à chaque participant + bda_generic = get_generic_user() + members[-1] = [bda_generic, 1] datatuple = [( 'bda-rappel', {'member': member[0], 'nb_attr': member[1], 'show': self}, @@ -121,7 +121,7 @@ class Spectacle(models.Model): self.rappel_sent = timezone.now() self.save() # On renvoie la liste des destinataires - return members.values() + return map(lambda t: t[0], members.values()) @property def is_past(self): From 2a3614540fc143e7e62f091991c4bd9106692724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Fri, 2 Jun 2017 17:32:23 +0100 Subject: [PATCH 29/43] minor PEP8 changes --- bda/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bda/views.py b/bda/views.py index 00a1b300..406ca81c 100644 --- a/bda/views.py +++ b/bda/views.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from collections import defaultdict -from functools import partial import random import hashlib import time @@ -27,7 +26,7 @@ from django.views.generic.list import ListView from gestioncof.decorators import cof_required, buro_required from bda.models import ( Spectacle, Participant, ChoixSpectacle, Attribution, Tirage, - SpectacleRevente, Salle, Quote, CategorieSpectacle + SpectacleRevente, Salle, CategorieSpectacle ) from bda.algorithm import Algorithm from bda.forms import ( @@ -305,7 +304,8 @@ def do_tirage(tirage_elt, token): # On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues ChoixRevente = Participant.choicesrevente.through - # Suppression des reventes demandées/enregistrées (si le tirage est relancé) + # Suppression des reventes demandées/enregistrées + # (si le tirage est relancé) ( ChoixRevente.objects .filter(spectacle__tirage=tirage_elt) From 31c034a96ae2cf4072d6607df45038ce98657d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Fri, 2 Jun 2017 18:01:27 +0100 Subject: [PATCH 30/43] send_rappel: use django messages + css tweaks --- bda/templates/bda/mails-rappel.html | 47 +++++++++++++---------------- bda/views.py | 8 +++++ gestioncof/static/css/cof.css | 7 +++-- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/bda/templates/bda/mails-rappel.html b/bda/templates/bda/mails-rappel.html index 73625d1c..21fcc407 100644 --- a/bda/templates/bda/mails-rappel.html +++ b/bda/templates/bda/mails-rappel.html @@ -3,41 +3,36 @@ {% block realcontent %}

Mails de rappels

{% if sent %} -

Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes

-
    - {% for member in members %} -
  • {{ member.get_full_name }} ({{ member.email }})
  • - {% endfor %} -
+

Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes

+
    + {% for member in members %} +
  • {{ member.get_full_name }} ({{ member.email }})
  • + {% endfor %} +
{% else %} -

Voulez vous envoyer les mails de rappel pour le spectacle - {{ show.title }} ?

- {% if show.rappel_sent %} -

Attention, les mails ont déjà été envoyés le - {{ show.rappel_sent }}

- {% endif %} +

Voulez vous envoyer les mails de rappel pour le spectacle {{ show.title }} ?

{% endif %} - {% if not sent %} -
- {% csrf_token %} -
- -
-
- {% endif %} +
+ {% if not sent %} +
+ {% csrf_token %} +
+ +
+
+ {% endif %} +
-
-

Forme des mails

Une seule place

- {% for part in exemple_mail_1place %} -
{{ part }}
- {% endfor %} + {% for part in exemple_mail_1place %} +
{{ part }}
+ {% endfor %}

Deux places

{% for part in exemple_mail_2places %} -
{{ part }}
+
{{ part }}
{% endfor %} {% endblock %} diff --git a/bda/views.py b/bda/views.py index 406ca81c..602e5025 100644 --- a/bda/views.py +++ b/bda/views.py @@ -673,6 +673,14 @@ def send_rappel(request, spectacle_id): # Demande de confirmation else: ctxt['sent'] = False + if show.rappel_sent: + messages.warning( + request, + "Attention, un mail de rappel pour ce spectale a déjà été " + "envoyé le {}".format(formats.localize( + timezone.template_localtime(show.rappel_sent) + )) + ) return render(request, "bda/mails-rappel.html", ctxt) diff --git a/gestioncof/static/css/cof.css b/gestioncof/static/css/cof.css index e1cdd763..91cf726a 100644 --- a/gestioncof/static/css/cof.css +++ b/gestioncof/static/css/cof.css @@ -40,8 +40,9 @@ a { background: transparent; } - - +div.empty-form { + padding-bottom: 2em; +} a:hover { color: #444; @@ -341,10 +342,12 @@ fieldset legend { font-weight: 700; font-size: large; color:#DE826B; + padding-bottom: .5em; } #main-content h4 { color:#DE826B; + padding-bottom: .5em; } #main-content h2, From fbdfdeef46f725e49ee73f88643ad5fe70a57c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Fri, 2 Jun 2017 18:30:02 +0100 Subject: [PATCH 31/43] Add a link to the reminder emails sending page --- bda/templates/bda-participants.html | 27 ++++++++++++++++++--------- bda/urls.py | 5 ++++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/bda/templates/bda-participants.html b/bda/templates/bda-participants.html index 289d1761..85af4a2e 100644 --- a/bda/templates/bda-participants.html +++ b/bda/templates/bda-participants.html @@ -36,17 +36,26 @@

Ajouter une attribution

-
- - -
- - +
+ +
+ Page d'envoi manuel des mails de rappel +
+ + + diff --git a/kfet/open/tests.py b/kfet/open/tests.py new file mode 100644 index 00000000..8cf1b6d0 --- /dev/null +++ b/kfet/open/tests.py @@ -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']) diff --git a/kfet/open/urls.py b/kfet/open/urls.py new file mode 100644 index 00000000..bd227b96 --- /dev/null +++ b/kfet/open/urls.py @@ -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'), +] diff --git a/kfet/open/views.py b/kfet/open/views.py new file mode 100644 index 00000000..5245b4c4 --- /dev/null +++ b/kfet/open/views.py @@ -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() diff --git a/kfet/routing.py b/kfet/routing.py index 5db0101f..54de69ae 100644 --- a/kfet/routing.py +++ b/kfet/routing.py @@ -1,13 +1,11 @@ # -*- coding: utf-8 -*- -from __future__ import (absolute_import, division, - print_function, unicode_literals) -from builtins import * +from channels.routing import include, route_class -from channels.routing import route, route_class -from kfet import consumers +from . import consumers -channel_routing = [ - route_class(consumers.KPsul, path=r"^/ws/k-fet/k-psul/$"), - route_class(consumers.KfetOpen, path=r"^/ws/k-fet/is_open/$"), + +routing = [ + route_class(consumers.KPsul, path=r'^/k-psul/$'), + include('kfet.open.routing.routing', path=r'^/open'), ] diff --git a/kfet/signals.py b/kfet/signals.py index e81f264a..12a1cd5f 100644 --- a/kfet/signals.py +++ b/kfet/signals.py @@ -10,6 +10,7 @@ from django.dispatch import receiver def messages_on_login(sender, request, user, **kwargs): if (not user.username == 'kfet_genericteam' and user.has_perm('kfet.is_team') and + hasattr(request, 'GET') and 'k-fet' in request.GET.get('next', '')): messages.info( request, diff --git a/kfet/static/kfet/css/home.css b/kfet/static/kfet/css/home.css index 2b831d9b..718159c3 100644 --- a/kfet/static/kfet/css/home.css +++ b/kfet/static/kfet/css/home.css @@ -52,12 +52,3 @@ li.carte-line { .unbreakable.carte-inverted { background: #FFDBC7; } - -#open_status { - color: white; -} - -#open_status_parent { - padding-left:0px; - padding-right:0px; -} diff --git a/kfet/static/kfet/css/index.css b/kfet/static/kfet/css/index.css index 15b425e2..5aa74e6e 100644 --- a/kfet/static/kfet/css/index.css +++ b/kfet/static/kfet/css/index.css @@ -30,6 +30,15 @@ textarea { 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 { margin-bottom:0; border-bottom:1px solid #ddd; diff --git a/kfet/static/kfet/css/nav.css b/kfet/static/kfet/css/nav.css index 4258e123..bec05ccf 100644 --- a/kfet/static/kfet/css/nav.css +++ b/kfet/static/kfet/css/nav.css @@ -6,7 +6,7 @@ } .navbar .navbar-brand { - padding: 3px 15px 3px 25px; + padding: 3px 25px; } .navbar .navbar-brand img { @@ -51,55 +51,37 @@ box-shadow: inset 0 5px 5px -5px #000; } -.navbar-nav .dropdown .dropdown-menu { +.navbar .dropdown .dropdown-menu { padding: 0; border: 0; border-radius: 0; background-color: #FFF; } -#kfet-open { - 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 { +.navbar .dropdown .dropdown-menu > li > a { padding: 8px 20px; color: #000; } -.navbar-nav .dropdown .dropdown-menu > li > a:hover, -.navbar-nav .dropdown .dropdown-meny > li > a:focus { +.navbar .dropdown .dropdown-menu > li > a:hover, +.navbar .dropdown .dropdown-meny > li > a:focus { color: #c8102e; background-color: transparent; } -.navbar-nav .dropdown .dropdown-menu .divider { +.navbar .dropdown .dropdown-menu .divider { margin: 0; } @media (min-width: 768px) { - .navbar-nav .dropdown .dropdown-menu { + .navbar .dropdown .dropdown-menu { display: block; visibility: hidden; opacity: 0; transition: opacity 0.15s; } - .nav .dropdown:hover .dropdown-menu { + .navbar .dropdown:hover .dropdown-menu { visibility: visible; opacity: 1; } diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index 72ae675a..b07bb0b1 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -24,19 +24,24 @@ $(document).ready(function() { class KfetWebsocket { static get defaults() { - return {"relative_url": "", "default_msg": {}, "handlers": []}; + return { + relative_url: '', + default_msg: {}, + handlers: [], + base_path: '/ws/k-fet/' + }; } constructor(data) { $.extend(this, this.constructor.defaults, data); + if (window.location.pathname.startsWith('/gestion/')) + this.base_path = '/gestion' + this.base_path; } - - get url() { - 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; - return websocket_protocol+"://" + location_url + this.relative_url ; + get url() { + var protocol = window.location.protocol == 'https:' ? 'wss' : 'ws'; + var host = window.location.host; + return protocol + "://" + host + this.base_path + this.relative_url; } add_handler(handler) { @@ -60,7 +65,7 @@ class KfetWebsocket { } var OperationWebSocket = new KfetWebsocket({ - 'relative_url': '/ws/k-fet/k-psul/', + 'relative_url': 'k-psul/', 'default_msg': {'opegroups':[],'opes':[],'checkouts':[],'articles':[]}, }); diff --git a/kfet/static/kfet/js/kfet_open.js b/kfet/static/kfet/js/kfet_open.js deleted file mode 100644 index 08a030a5..00000000 --- a/kfet/static/kfet/js/kfet_open.js +++ /dev/null @@ -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(); - } - } -} diff --git a/kfet/templates/kfet/account_read.html b/kfet/templates/kfet/account_read.html index 282e035f..fbdfd61f 100644 --- a/kfet/templates/kfet/account_read.html +++ b/kfet/templates/kfet/account_read.html @@ -4,10 +4,6 @@ {% load l10n %} {% block extra_head %} - - - - {% if account.user == request.user %} diff --git a/kfet/templates/kfet/base.html b/kfet/templates/kfet/base.html index 281e261d..a4df77cd 100644 --- a/kfet/templates/kfet/base.html +++ b/kfet/templates/kfet/base.html @@ -10,40 +10,23 @@ {# CSS #} + {# JS #} + + - - - + - {# K-Fêt open #} - + {% include "kfetopen/init.html" %} {% block extra_head %}{% endblock %} diff --git a/kfet/templates/kfet/base_nav.html b/kfet/templates/kfet/base_nav.html index f90eb004..512d32e7 100644 --- a/kfet/templates/kfet/base_nav.html +++ b/kfet/templates/kfet/base_nav.html @@ -12,7 +12,25 @@ - + + + diff --git a/kfet/templates/kfet/history.html b/kfet/templates/kfet/history.html index add461ab..0c5fb518 100644 --- a/kfet/templates/kfet/history.html +++ b/kfet/templates/kfet/history.html @@ -3,15 +3,8 @@ {% load l10n %} {% block extra_head %} - - - - - - - diff --git a/kfet/templates/kfet/home.html b/kfet/templates/kfet/home.html index 04174c3f..e5175dc3 100644 --- a/kfet/templates/kfet/home.html +++ b/kfet/templates/kfet/home.html @@ -1,4 +1,4 @@ -{% extends "kfet/base_col_2.html" %} +{% extends "kfet/base_col_1.html" %} {% load staticfiles %} {% load kfet_tags %} @@ -9,26 +9,7 @@ {% endblock %} -{% block fixed-size %}col-sm-2{% endblock %} -{% block main-size %}col-sm-10{% endblock %} - -{% block fixed-content %} - -
-
La K-Fêt est
-
-
-
?????
-
-{% if perms.kfet.is_team %} - -{% endif %} - -{% endblock %} +{% block main-size %}col-sm-10 col-sm-offset-1{% endblock %} {% block main-content %} diff --git a/kfet/templates/kfet/inventory_create.html b/kfet/templates/kfet/inventory_create.html index fd1f576f..6b9b533a 100644 --- a/kfet/templates/kfet/inventory_create.html +++ b/kfet/templates/kfet/inventory_create.html @@ -1,11 +1,5 @@ {% extends "kfet/base_col_1.html" %} -{% load staticfiles %} -{% load widget_tweaks %} - -{% block extra_head %} - - -{% endblock %} +{% load staticfiles widget_tweaks %} {% block title %}Nouvel inventaire{% endblock %} {% block header-title %}Création d'un inventaire{% endblock %} diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index df37a703..cdf50a36 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -2,12 +2,8 @@ {% load staticfiles %} {% block extra_head %} - - - - {% endblock %} diff --git a/kfet/templates/kfet/transfers.html b/kfet/templates/kfet/transfers.html index cbdf0fe3..2881e9e2 100644 --- a/kfet/templates/kfet/transfers.html +++ b/kfet/templates/kfet/transfers.html @@ -1,14 +1,6 @@ {% extends 'kfet/base.html' %} {% load staticfiles %} -{% block extra_head %} - - - - - -{% endblock %} - {% block title %}Transferts{% endblock %} {% block content-header-title %}Transferts{% endblock %} diff --git a/kfet/templates/kfet/transfers_create.html b/kfet/templates/kfet/transfers_create.html index ec9ddc2a..b2d53c96 100644 --- a/kfet/templates/kfet/transfers_create.html +++ b/kfet/templates/kfet/transfers_create.html @@ -3,7 +3,6 @@ {% block extra_head %} - {% endblock %} {% block title %}Nouveaux transferts{% endblock %} diff --git a/kfet/urls.py b/kfet/urls.py index c48766ae..b1c4152f 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -1,6 +1,6 @@ # -*- 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 kfet import views from kfet import autocomplete @@ -198,17 +198,6 @@ urlpatterns = [ (views.SettingsUpdate.as_view()), 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 @@ -254,3 +243,8 @@ urlpatterns = [ url(r'^orders/(?P\d+)/to_inventory$', views.order_to_inventory, name='kfet.order.to_inventory'), ] + +urlpatterns += [ + # K-Fêt Open urls + url('^open/', include('kfet.open.urls')), +] diff --git a/kfet/utils.py b/kfet/utils.py new file mode 100644 index 00000000..56207096 --- /dev/null +++ b/kfet/utils.py @@ -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) diff --git a/kfet/views.py b/kfet/views.py index f7b2aac9..105cbbdc 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -6,9 +6,7 @@ from urllib.parse import urlencode from django.shortcuts import render, get_object_or_404, redirect from django.core.exceptions import PermissionDenied from django.core.cache import cache -from django.views.generic import ( - DetailView, FormView, ListView, TemplateView, View, -) +from django.views.generic import ListView, DetailView, TemplateView, FormView from django.views.generic.detail import BaseDetailView from django.views.generic.edit import CreateView, UpdateView 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.decorators import login_required, permission_required 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.db import transaction from django.db.models import F, Sum, Prefetch, Count @@ -81,64 +79,6 @@ class Home(TemplateView): 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 def login_genericteam(request): # Check si besoin de déconnecter l'utilisateur de CAS diff --git a/requirements.txt b/requirements.txt index b4d83eec..d730bd6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,12 +14,12 @@ unicodecsv==0.14.1 icalendar==3.10 django-bootstrap-form==3.2.1 asgiref==1.1.1 -daphne==1.2.0 +daphne==1.3.0 asgi-redis==1.3.0 statistics==1.0.3.5 future==0.15.2 django-widget-tweaks==1.4.1 git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail ldap3 -channels==1.1.3 +channels==1.1.5 python-dateutil From 782e105644f83ca906e59d68bce23d47d9c0a241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Wed, 21 Jun 2017 23:31:27 +0200 Subject: [PATCH 39/43] typo --- kfet/models.py | 2 +- kfet/open/open.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kfet/models.py b/kfet/models.py index a8bf7608..d6b1cfbc 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -73,7 +73,7 @@ class Account(models.Model): "Modifier le mot de passe d'une personne de l'équipe"), ('special_add_account', "Créer un compte avec une balance initiale"), - ('can_force_close', "Fermer manuelement la K-Fêt"), + ('can_force_close', "Fermer manuellement la K-Fêt"), ) def __str__(self): diff --git a/kfet/open/open.py b/kfet/open/open.py index 54eda9e4..c308ce3c 100644 --- a/kfet/open/open.py +++ b/kfet/open/open.py @@ -9,7 +9,7 @@ class OpenKfet(CachedMixin, object): Stores raw data (e.g. sent by raspberry), and user-set values (as force_close). - Setting differents `cache_prefix` allows multiple places management. + Setting differents `cache_prefix` allows different places management. Current state persists through cache. """ From 98f5f0c3912cb4272f9b2b669e51acc17f0e4902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Thu, 22 Jun 2017 05:44:05 +0200 Subject: [PATCH 40/43] update refresh/unknown interval --- kfet/open/static/kfetopen/kfet-open.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kfet/open/static/kfetopen/kfet-open.js b/kfet/open/static/kfetopen/kfet-open.js index 6edfeb7e..3887bff0 100644 --- a/kfet/open/static/kfetopen/kfet-open.js +++ b/kfet/open/static/kfetopen/kfet-open.js @@ -21,9 +21,9 @@ var OpenKfet = function(force_close_url, admin) { OpenKfet.prototype = { // Status is unknown after . minutes without update. - time_unknown: 2, + time_unknown: 15, // Maximum interval (seconds) between two UI refresh. - refresh_interval: 10, + refresh_interval: 20, // Prefix for classes describing place status. class_prefix: 'kfetopen-st-', From 19847ac9d8efab9891c6fb3ea6e3cd7e69589690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Thu, 22 Jun 2017 15:48:45 +0200 Subject: [PATCH 41/43] add token check to raw_open edit view --- cof/settings/common.py | 2 +- cof/settings/secret_example.py | 2 ++ kfet/open/tests.py | 10 ++++++++-- kfet/open/views.py | 5 +++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/cof/settings/common.py b/cof/settings/common.py index 2384cf87..5ed17865 100644 --- a/cof/settings/common.py +++ b/cof/settings/common.py @@ -24,7 +24,7 @@ except KeyError: try: from .secret import ( SECRET_KEY, RECAPTCHA_PUBLIC_KEY, RECAPTCHA_PRIVATE_KEY, ADMINS, - REDIS_PASSWD, REDIS_DB, REDIS_HOST, REDIS_PORT + REDIS_PASSWD, REDIS_DB, REDIS_HOST, REDIS_PORT, KFETOPEN_TOKEN, ) except ImportError: raise RuntimeError("Secrets missing") diff --git a/cof/settings/secret_example.py b/cof/settings/secret_example.py index eeb5271c..a1d35b68 100644 --- a/cof/settings/secret_example.py +++ b/cof/settings/secret_example.py @@ -6,3 +6,5 @@ REDIS_PORT = 6379 REDIS_DB = 0 REDIS_HOST = "127.0.0.1" ADMINS = None + +KFETOPEN_TOKEN = "plop" diff --git a/kfet/open/tests.py b/kfet/open/tests.py index 8cf1b6d0..54386586 100644 --- a/kfet/open/tests.py +++ b/kfet/open/tests.py @@ -136,7 +136,10 @@ class OpenKfetViewsTest(ChannelTestCase): 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}) + resp = Client().post('/k-fet/open/raw_open', { + 'raw_open': sent, + 'token': 'plop', + }) self.assertEqual(200, resp.status_code) self.assertEqual(expected, kfet_open.raw_open) @@ -254,7 +257,10 @@ class OpenKfetScenarioTest(ChannelTestCase): self.ws_connect(self.r_c_ws) # door sent "I'm open!" - self.c.post('/k-fet/open/raw_open', {'raw_open': True}) + self.c.post('/k-fet/open/raw_open', { + 'raw_open': True, + 'token': 'plop', + }) # anonymous user agree msg = self.c_ws.receive(json=True) diff --git a/kfet/open/views.py b/kfet/open/views.py index 5245b4c4..4f1efa5f 100644 --- a/kfet/open/views.py +++ b/kfet/open/views.py @@ -1,3 +1,5 @@ +from django.conf import settings +from django.core.exceptions import PermissionDenied from django.contrib.auth.decorators import permission_required from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt @@ -12,6 +14,9 @@ TRUE_STR = ['1', 'True', 'true'] @csrf_exempt @require_POST def raw_open(request): + token = request.POST.get('token') + if token != settings.KFETOPEN_TOKEN: + raise PermissionDenied raw_open = request.POST.get('raw_open') in TRUE_STR kfet_open.raw_open = raw_open kfet_open.send_ws() From 5673fabeff1ba72755ee656cbc397ea202157973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Thu, 22 Jun 2017 16:36:08 +0200 Subject: [PATCH 42/43] Better status management. Status is mainly computed in Python. That fix inconsistent datetime between client and server. Client only receives status and keep timestamp of last received ws msg. --- kfet/open/open.py | 41 ++++++++++++---- kfet/open/static/kfetopen/kfet-open.js | 25 +++------- kfet/open/tests.py | 65 +++++++++++++++----------- 3 files changed, 78 insertions(+), 53 deletions(-) diff --git a/kfet/open/open.py b/kfet/open/open.py index c308ce3c..7fd90c21 100644 --- a/kfet/open/open.py +++ b/kfet/open/open.py @@ -1,3 +1,5 @@ +from datetime import timedelta + from django.utils import timezone from ..decorators import kfet_is_team @@ -13,6 +15,17 @@ class OpenKfet(CachedMixin, object): Current state persists through cache. """ + # status is unknown after this duration + time_unknown = timedelta(minutes=15) + + # status + OPENED = 'opened' + CLOSED = 'closed' + UNKNOWN = 'unknown' + # admin status + FAKE_CLOSED = 'fake_closed' + + # cached attributes config cached = { '_raw_open': False, '_last_update': None, @@ -40,6 +53,19 @@ class OpenKfet(CachedMixin, object): """Take into account force_close.""" return False if self.force_close else self.raw_open + def status(self): + if (self.last_update is None or + timezone.now() - self.last_update >= self.time_unknown): + return self.UNKNOWN + return self.OPENED if self.is_open else self.CLOSED + + def admin_status(self, status=None): + if status is None: + status = self.status() + if status == self.CLOSED and self.raw_open: + return self.FAKE_CLOSED + return status + def _export(self): """Export internal state. @@ -51,27 +77,26 @@ class OpenKfet(CachedMixin, object): - base for others. """ + status = self.status() base = { - 'is_open': self.is_open, - 'last_update': self.last_update, + 'status': status, } restrict = { - 'raw_open': self.raw_open, + 'admin_status': self.admin_status(status), 'force_close': self.force_close, } return base, {**base, **restrict} - def export(self, user=None): - """Export internal state. + def export(self, user): + """Export internal state for a given user. 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. + exported, according to its permissions. """ base, team = self._export() - return team if user is None or kfet_is_team(user) else base + return team if kfet_is_team(user) else base def send_ws(self): """Send internal state to websocket channels.""" diff --git a/kfet/open/static/kfetopen/kfet-open.js b/kfet/open/static/kfetopen/kfet-open.js index 3887bff0..b86cc5bc 100644 --- a/kfet/open/static/kfetopen/kfet-open.js +++ b/kfet/open/static/kfetopen/kfet-open.js @@ -54,26 +54,13 @@ OpenKfet.prototype = { }, refresh: function(data) { - if (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; + this.last_update = moment(); } + if (!this.is_recent) + this.status = this.UNKNOWN; + this.refresh_dom(); }, refresh_dom: function() { @@ -96,7 +83,7 @@ OpenKfet.prototype = { } }, - toggle_force_close: function(new_value, password) { + toggle_force_close: function(password) { $.post({ url: this.force_close_url, data: {force_close: !this.force_close}, diff --git a/kfet/open/tests.py b/kfet/open/tests.py index 54386586..1d6d5529 100644 --- a/kfet/open/tests.py +++ b/kfet/open/tests.py @@ -1,7 +1,9 @@ import json +from datetime import timedelta from django.contrib.auth.models import AnonymousUser, Permission, User from django.test import Client +from django.utils import timezone from channels.channel import Group from channels.test import ChannelTestCase, WSClient @@ -54,11 +56,30 @@ class OpenKfetTest(ChannelTestCase): self.kfet_open.raw_open = raw_open self.assertFalse(self.kfet_open.is_open) + def test_status(self): + # (raw_open, force_close, expected status, expected admin) + cases = [ + (False, False, OpenKfet.CLOSED, OpenKfet.CLOSED), + (False, True, OpenKfet.CLOSED, OpenKfet.CLOSED), + (True, False, OpenKfet.OPENED, OpenKfet.OPENED), + (True, True, OpenKfet.CLOSED, OpenKfet.FAKE_CLOSED), + ] + for raw_open, force_close, exp_stat, exp_adm_stat in cases: + self.kfet_open.raw_open = raw_open + self.kfet_open.force_close = force_close + self.assertEqual(exp_stat, self.kfet_open.status()) + self.assertEqual(exp_adm_stat, self.kfet_open.admin_status()) + + def test_status_unknown(self): + self.kfet_open.raw_open = True + self.kfet_open._last_update = timezone.now() - timedelta(days=30) + self.assertEqual(OpenKfet.UNKNOWN, self.kfet_open.status()) + 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(['status']), set(export), ) @@ -68,15 +89,7 @@ class OpenKfetTest(ChannelTestCase): 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(['status', 'admin_status', 'force_close']), set(export), ) @@ -89,14 +102,14 @@ class OpenKfetTest(ChannelTestCase): 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(['status']), 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(['status', 'admin_status', 'force_close']), set(admin), ) @@ -240,14 +253,14 @@ class OpenKfetScenarioTest(ChannelTestCase): # test for anonymous user msg = self.ws_connect(self.c_ws) self.assertSetEqual( - set(['is_open', 'last_update']), + set(['status']), 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(['status', 'admin_status', 'force_close']), set(msg), ) @@ -264,25 +277,25 @@ class OpenKfetScenarioTest(ChannelTestCase): # anonymous user agree msg = self.c_ws.receive(json=True) - self.assertTrue(msg['is_open']) + self.assertEqual(OpenKfet.OPENED, msg['status']) # root user too msg = self.r_c_ws.receive(json=True) - self.assertTrue(msg['is_open']) - self.assertTrue(msg['raw_open']) + self.assertEqual(OpenKfet.OPENED, msg['status']) + self.assertEqual(OpenKfet.OPENED, msg['admin_status']) # 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']) + self.assertEqual(OpenKfet.CLOSED, msg['status']) # root user too msg = self.r_c_ws.receive(json=True) - self.assertFalse(msg['is_open']) + self.assertEqual(OpenKfet.CLOSED, msg['status']) # but root knows things - self.assertTrue(msg['raw_open']) + self.assertEqual(OpenKfet.FAKE_CLOSED, msg['admin_status']) self.assertTrue(msg['force_close']) def test_scenario_2(self): @@ -291,19 +304,19 @@ class OpenKfetScenarioTest(ChannelTestCase): kfet_open.force_close = True msg = self.ws_connect(self.c_ws) - self.assertFalse(msg['is_open']) + self.assertEqual(OpenKfet.CLOSED, msg['status']) msg = self.ws_connect(self.r_c_ws) - self.assertFalse(msg['is_open']) - self.assertTrue(msg['raw_open']) + self.assertEqual(OpenKfet.CLOSED, msg['status']) + self.assertEqual(OpenKfet.FAKE_CLOSED, msg['admin_status']) 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']) + self.assertEqual(OpenKfet.OPENED, msg['status']) msg = self.r_c_ws.receive(json=True) - self.assertTrue(msg['is_open']) - self.assertTrue(msg['raw_open']) + self.assertEqual(OpenKfet.OPENED, msg['status']) + self.assertEqual(OpenKfet.OPENED, msg['admin_status']) self.assertFalse(msg['force_close']) From 815ba50603a0d8978f8203dbb0ba007c780dd3fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Thu, 22 Jun 2017 16:59:41 +0200 Subject: [PATCH 43/43] fix for small devices --- kfet/templates/kfet/base_nav.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/templates/kfet/base_nav.html b/kfet/templates/kfet/base_nav.html index 512d32e7..d3f748bf 100644 --- a/kfet/templates/kfet/base_nav.html +++ b/kfet/templates/kfet/base_nav.html @@ -13,7 +13,7 @@