From d718984a5910ad331060d81e271ecb65059c2b66 Mon Sep 17 00:00:00 2001 From: Arthur Gerbaud Date: Sun, 7 Aug 2016 16:22:19 +0200 Subject: [PATCH] add decorators 'webpage' decorator for 'website', rewrite views --- maraudes/views.py | 36 +++++----- suivi/views.py | 17 ++--- sujets/views.py | 33 ++++----- utilisateurs/views.py | 3 - website/decorators.py | 29 ++++++++ website/mixins.py | 153 ++++++++++++++++++++++++++++++++++++++++ website/views.py | 160 ------------------------------------------ 7 files changed, 219 insertions(+), 212 deletions(-) delete mode 100644 utilisateurs/views.py create mode 100644 website/decorators.py create mode 100644 website/mixins.py diff --git a/maraudes/views.py b/maraudes/views.py index 0987f06..c471543 100644 --- a/maraudes/views.py +++ b/maraudes/views.py @@ -6,7 +6,7 @@ from django.contrib import messages from django.shortcuts import render, redirect # Views from django.views import generic -from website import views + # Models from .models import ( Maraude, Maraudeur, Rencontre, Lieu, @@ -22,13 +22,9 @@ from .forms import ( RencontreForm, RencontreInlineFormSet, ObservationInlineFormSet, ObservationInlineFormSetNoExtra, MaraudeAutoDateForm, MonthSelectForm, ) +from website import decorators as website +webpage = website.webpage(ajax=False, permissions=['maraudes.view_maraudes']) -class MaraudesView(views.WebsiteProtectedMixin): - - class PageInfo: - title = "Maraudes ALSA" - - permissions = ['maraudes.view_maraudes'] class DerniereMaraudeMixin(object): @@ -47,7 +43,10 @@ class DerniereMaraudeMixin(object): context['dernieres_maraudes'] = self.dernieres_maraudes return context -class IndexView(MaraudesView, DerniereMaraudeMixin, generic.TemplateView): + + +@webpage +class IndexView(DerniereMaraudeMixin, generic.TemplateView): class PageInfo: title = "Maraude - Tableau de bord" @@ -58,8 +57,8 @@ class IndexView(MaraudesView, DerniereMaraudeMixin, generic.TemplateView): ## MARAUDES - -class MaraudeDetailsView(MaraudesView, DerniereMaraudeMixin, generic.DetailView): +@webpage +class MaraudeDetailsView(DerniereMaraudeMixin, generic.DetailView): model = Maraude context_object_name = "maraude" template_name = "maraudes/details.html" @@ -80,7 +79,8 @@ class MaraudeDetailsView(MaraudesView, DerniereMaraudeMixin, generic.DetailView) -class MaraudeListView(MaraudesView, generic.ListView): +@webpage +class MaraudeListView(generic.ListView): model = Maraude template_name = "maraudes/list.html" paginate_by = 10 @@ -97,8 +97,8 @@ class MaraudeListView(MaraudesView, generic.ListView): ## COMPTE-RENDU DE MARAUDE - -class CompteRenduCreateView(MaraudesView, generic.DetailView): +@webpage +class CompteRenduCreateView(generic.DetailView): model = Maraude template_name = "compte_rendu/compterendu_create.html" context_object_name = "maraude" @@ -186,7 +186,8 @@ class CompteRenduCreateView(MaraudesView, generic.DetailView): -class CompteRenduUpdateView(MaraudesView, generic.DetailView): +@webpage +class CompteRenduUpdateView(generic.DetailView): """ Mettre à jour le compte-rendu de la maraude """ model = Maraude context_object_name = "maraude" @@ -253,8 +254,8 @@ class CompteRenduUpdateView(MaraudesView, generic.DetailView): ## PLANNING - -class PlanningView(MaraudesView, generic.TemplateView): +@webpage +class PlanningView(generic.TemplateView): """ Display and edit the planning of next Maraudes """ template_name = "planning/planning.html" @@ -323,7 +324,8 @@ class PlanningView(MaraudesView, generic.TemplateView): ## LIEU -class LieuCreateView(views.WebsiteProtectedWithAjaxMixin, generic.edit.CreateView): +@website.webpage(ajax=True, permissions=['maraudes.add_lieu']) +class LieuCreateView(generic.edit.CreateView): model = Lieu template_name = "maraudes/lieu_create.html" fields = "__all__" diff --git a/suivi/views.py b/suivi/views.py index e9f17a8..3bc2975 100644 --- a/suivi/views.py +++ b/suivi/views.py @@ -1,23 +1,18 @@ from django.shortcuts import render from django.views import generic -from website import views +from website import decorators as website from sujets.models import Sujet # Create your views here. +webpage = website.webpage(ajax=False, permissions=['sujets.view_sujets']) -class SuivisView(views.WebsiteProtectedMixin): - class PageInfo: - title = "Suivi des bénéficiaires" - - permissions = ['sujets.view_sujets'] - - -class IndexView(SuivisView, generic.TemplateView): +@webpage +class IndexView(generic.TemplateView): template_name = "suivi/index.html" class PageInfo: @@ -25,8 +20,8 @@ class IndexView(SuivisView, generic.TemplateView): header = "Suivi" header_small = "Tableau de bord" - -class SuiviSujetView(SuivisView, generic.DetailView): +@webpage +class SuiviSujetView(generic.DetailView): model = Sujet template_name = "suivi/details.html" context_object_name = "sujet" diff --git a/sujets/views.py b/sujets/views.py index 17668b4..94c9619 100644 --- a/sujets/views.py +++ b/sujets/views.py @@ -1,25 +1,20 @@ from django.shortcuts import render from django.views import generic -from website import views +from website import decorators as website from .models import Sujet from django.forms import ModelForm + +webpage = website.webpage(ajax=True, permissions=['sujets.view_sujets']) # Create your views here. -class SujetsView(views.WebsiteProtectedMixin): +# TODO: deal with setting an active_app name other than module name - class PageInfo: - title = "Sujets" - - def get_active_app(self): - return super(views.WebsiteProtectedMixin, self).get_active_app(app_name='suivi') - - - -class SujetDetailsView(SujetsView, generic.DetailView): +@webpage +class SujetDetailsView(generic.DetailView): template_name = "sujets/sujet_details.html" model = Sujet @@ -28,7 +23,8 @@ class SujetDetailsView(SujetsView, generic.DetailView): header = "{{ sujet }}" header_small = "suivi" -class SujetListView(SujetsView, generic.ListView): +@webpage +class SujetListView(generic.ListView): model = Sujet template_name = "sujets/sujet_liste.html" @@ -36,8 +32,8 @@ class SujetListView(SujetsView, generic.ListView): title = "Sujet - Liste des sujets" header = "Liste des sujets" - -class SujetUpdateView(SujetsView, generic.edit.UpdateView): +@webpage +class SujetUpdateView(generic.edit.UpdateView): template_name = "sujets/sujet_update.html" model = Sujet fields = '__all__' @@ -55,8 +51,8 @@ class SujetCreateForm(ModelForm): fields = ['nom', 'surnom', 'prenom', 'genre', 'premiere_rencontre'] - -class SujetCreateView(views.WebsiteProtectedWithAjaxMixin, generic.edit.CreateView): +@website.webpage(ajax=True, permissions=['sujets.add_sujet']) +class SujetCreateView(generic.edit.CreateView): template_name = "sujets/sujet_create.html" form_class = SujetCreateForm @@ -64,8 +60,6 @@ class SujetCreateView(views.WebsiteProtectedWithAjaxMixin, generic.edit.CreateVi title = "Nouveau sujet" header = "Nouveau sujet" - permissions = ['sujets.view_sujets', 'sujets.add_sujet'] - def post(self, request, *args, **kwargs): if 'next' in self.request.POST: self.success_url = self.request.POST["next"] @@ -78,6 +72,3 @@ class SujetCreateView(views.WebsiteProtectedWithAjaxMixin, generic.edit.CreateVi except: context['next'] = None return context - - #Hack - get_active_app = SujetsView.get_active_app diff --git a/utilisateurs/views.py b/utilisateurs/views.py deleted file mode 100644 index 91ea44a..0000000 --- a/utilisateurs/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/website/decorators.py b/website/decorators.py new file mode 100644 index 0000000..256d5c8 --- /dev/null +++ b/website/decorators.py @@ -0,0 +1,29 @@ +from .mixins import * + +def _insert_bases(cls, bases): + old_bases = cls.__bases__ + new_bases = tuple(bases) + old_bases + print(new_bases) + cls.__bases__ = new_bases + +def webpage(**options): + try: ajax = options.pop('ajax') + except KeyError: ajax = False + try: permissions = options.pop('permissions') + except KeyError: permissions = [] + + new_bases = [] + if ajax: + new_bases.append(WebsiteAjaxTemplateMixin) + else: + new_bases.append(WebsiteTemplateMixin) + if permissions: + new_bases.append(PermissionRequiredMixin) + + def update_class(cls): + _insert_bases(cls, new_bases) + if permissions: + cls.permissions = permissions + return cls + + return update_class diff --git a/website/mixins.py b/website/mixins.py new file mode 100644 index 0000000..a7f2c7e --- /dev/null +++ b/website/mixins.py @@ -0,0 +1,153 @@ +import datetime +from django.utils import timezone +from django.core.exceptions import ImproperlyConfigured +from django.apps import apps +from django.contrib.auth.decorators import login_required, permission_required +from django.template import Template, Context +from django.views.generic.base import ContextMixin, TemplateResponseMixin + +## Utils ## +def get_apps(app_names): + _apps = [] + for name in app_names: + _apps.append( + apps.get_app_config(name) + ) + return _apps + +## Mixins ## + +class PermissionRequiredMixin(object): + permissions = [] + @classmethod + def as_view(cls, **initkwargs): + view = super(PermissionRequiredMixin, cls).as_view(**initkwargs) + return permission_required(cls.permissions)(view) + + + +class TemplateFieldsMetaclass(type): + """ Loads Template objects with given string for + header, header_small, title, ... + + Theses strings shall be found in cls.Template + """ + def __init__(cls, bases, Dict): + pass + + + +class WebsiteTemplateMixin(TemplateResponseMixin): + """ Mixin for easy integration of 'website' templates + + Each child can specify: + - title : title of the page + - header : header of the page + - header_small : sub-header of the page + + If 'content_template' is not defined, value will fallback to template_name + in child view. + """ + base_template = "base_site.html" + content_template = None + + class Configuration: + stylesheets = ['base.css'] + navbar_apps = ['maraudes', 'suivi'] + apps = get_apps(navbar_apps) + page_blocks = ['header', 'header_small', 'title'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._page_blocks = [] + if not hasattr(self, "PageInfo"): + raise ImproperlyConfigured("You must define a PageInfo on ", self) + for attr, val in self.PageInfo.__dict__.items(): + if attr[0] is not "_" and type(val) is str: + setattr(self, attr, Template(val)) + self._page_blocks.append(attr) + + def get_template_names(self): + """ Ensure same template for all children views. """ + return [self.base_template] + + def get_content_template(self): + if hasattr(self, 'template_name'): #Ensure easy integration with other views + self.content_template = self.template_name + return self.content_template + + def get_active_app(self, app_name=None): + if not app_name: + app_name = self.__class__.__module__.split(".")[0] + return apps.get_app_config(app_name) + + def get_panels(self): + """ Panneaux """ + return None + + def get_prochaine_maraude_for_user(self): + """ Retourne le prochain objet Maraude auquel + l'utilisateur participe, ou None """ + maraudeur_cls = apps.get_model('utilisateurs', model_name="Maraudeur") + maraude_cls = apps.get_model('maraudes', model_name="Maraude") + try: #TODO: Clean up this ugly thing + self.maraudeur = maraudeur_cls.objects.get(username=self.request.user.username) + except: + self.maraudeur = None + + if self.maraudeur: + return maraude_cls.objects.get_next_of(self.maraudeur) + return None + + def get_prochaine_maraude(self): + return apps.get_model('maraudes', model_name="Maraude").objects.next + + def _update_context_with_rendered_blocks(self, context): + """ Render text for existing PageInfo attributes. + See Configuration.page_blocks for valid attribute names """ + render_context = Context(context) + for attr in self._page_blocks: + name = "page_%s" % attr + context[name] = getattr(self, attr).render(render_context) + return context + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context['stylesheets'] = self.Configuration.stylesheets + context['apps'] = self.Configuration.apps + context['active_app'] = self.get_active_app() + + context['content_template'] = self.get_content_template() + context['panels'] = self.get_panels() + + context['prochaine_maraude_abs'] = self.get_prochaine_maraude() + context['prochaine_maraude'] = self.get_prochaine_maraude_for_user() + + self._update_context_with_rendered_blocks(context) + return context + +class WebsiteAjaxTemplateMixin(WebsiteTemplateMixin): + """ Mixin that returns content_template instead of base_template when + request is Ajax. + """ + is_ajax = False + + def dispatch(self, request, *args, **kwargs): + if not hasattr(self, 'content_template'): + self.content_template = self.get_content_template() + if request.is_ajax(): + self.is_ajax = True + return super().dispatch(request, *args, **kwargs) + + def get_template_names(self): + if self.is_ajax: + return [self.content_template] + return super().get_template_names() + +class WebsiteProtectedMixin(WebsiteTemplateMixin, PermissionRequiredMixin): + pass + +class WebsiteProtectedWithAjaxMixin(WebsiteAjaxTemplateMixin, PermissionRequiredMixin): + pass + diff --git a/website/views.py b/website/views.py index 2cab3a9..407e62e 100644 --- a/website/views.py +++ b/website/views.py @@ -1,163 +1,3 @@ -import datetime -from django.utils import timezone -from django.views.generic.base import ContextMixin, TemplateResponseMixin - -from django.core.exceptions import ImproperlyConfigured - -from django.apps import apps -from django.contrib.auth.decorators import login_required, permission_required -from django.template import Template, Context - -## Utils ## -def get_apps(app_names): - _apps = [] - for name in app_names: - _apps.append( - apps.get_app_config(name) - ) - return _apps - -## Mixins ## - -class PermissionRequiredMixin(object): - permissions = [] - @classmethod - def as_view(cls, **initkwargs): - view = super(PermissionRequiredMixin, cls).as_view(**initkwargs) - return permission_required(cls.permissions)(view) - - - -class TemplateFieldsMetaclass(type): - """ Loads Template objects with given string for - header, header_small, title, ... - - Theses strings shall be found in cls.Template - """ - def __init__(cls, bases, Dict): - pass - - - -class WebsiteTemplateMixin(TemplateResponseMixin): - """ Mixin for easy integration of 'website' templates - - Each child can specify: - - title : title of the page - - header : header of the page - - header_small : sub-header of the page - - If 'content_template' is not defined, value will fallback to template_name - in child view. - """ - content_template = None - - class Configuration: - stylesheets = ['base.css'] - navbar_apps = ['maraudes', 'suivi'] - - apps = get_apps(navbar_apps) - - page_blocks = ['header', 'header_small', 'title'] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self._page_blocks = [] - if not hasattr(self, "PageInfo"): - raise ImproperlyConfigured("You must define a PageInfo on ", self) - for attr, val in self.PageInfo.__dict__.items(): - if attr[0] is not "_" and type(val) is str: - setattr(self, attr, Template(val)) - self._page_blocks.append(attr) - - def get_template_names(self): - """ Ensure same template for all children views. """ - return ["base_site.html"] - - def get_content_template(self): - if hasattr(self, 'template_name'): #Ensure easy integration with other views - self.content_template = self.template_name - return self.content_template - - def get_active_app(self, app_name=None): - if not app_name: - app_name = self.__class__.__module__.split(".")[0] - return apps.get_app_config(app_name) - - def get_panels(self): - """ Panneaux """ - return None - - def get_prochaine_maraude_for_user(self): - """ Retourne le prochain objet Maraude auquel - l'utilisateur participe, ou None """ - maraudeur_cls = apps.get_model('utilisateurs', model_name="Maraudeur") - maraude_cls = apps.get_model('maraudes', model_name="Maraude") - try: #TODO: Clean up this ugly thing - self.maraudeur = maraudeur_cls.objects.get(username=self.request.user.username) - except: - self.maraudeur = None - - if self.maraudeur: - return maraude_cls.objects.get_next_of(self.maraudeur) - return None - - def get_prochaine_maraude(self): - return apps.get_model('maraudes', model_name="Maraude").objects.next - - def _update_context_with_rendered_blocks(self, context): - """ Render text for existing PageInfo attributes. - See Configuration.page_blocks for valid attribute names """ - render_context = Context(context) - for attr in self._page_blocks: - name = "page_%s" % attr - context[name] = getattr(self, attr).render(render_context) - return context - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - - context['stylesheets'] = self.Configuration.stylesheets - context['apps'] = self.Configuration.apps - context['active_app'] = self.get_active_app() - - context['content_template'] = self.get_content_template() - context['panels'] = self.get_panels() - - context['prochaine_maraude_abs'] = self.get_prochaine_maraude() - context['prochaine_maraude'] = self.get_prochaine_maraude_for_user() - - self._update_context_with_rendered_blocks(context) - return context - - -class WebsiteProtectedMixin(WebsiteTemplateMixin, PermissionRequiredMixin): - pass - -class WebsiteProtectedWithAjaxMixin(WebsiteProtectedMixin): - """ Mixin that enables the use of 'ajax_template_name' custom template - when request is Ajax. - """ - is_ajax = False - - def dispatch(self, request, *args, **kwargs): - if not hasattr(self, 'ajax_template_name'): - self.ajax_template_name = "%s_inner.html" % self.template_name.split(".")[0] - print('%s :' % self, self.ajax_template_name) - if request.is_ajax(): - self.is_ajax = True - self.template_name = self.ajax_template_name - return super().dispatch(request, *args, **kwargs) - - def get_template_names(self): - if self.is_ajax: - return [self.template_name] - else: - return super().get_template_names() - -## Views : Index ## - from django.shortcuts import redirect def index_view(request):