560 lines
18 KiB
Python
560 lines
18 KiB
Python
# chrono - agendas system
|
|
# Copyright (C) 2022 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import datetime
|
|
import json
|
|
from operator import itemgetter
|
|
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.db.models import Prefetch
|
|
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
|
|
from django.shortcuts import get_object_or_404
|
|
from django.urls import reverse
|
|
from django.views.generic import CreateView, DeleteView, DetailView, FormView, ListView, UpdateView
|
|
|
|
from chrono.agendas.models import Agenda
|
|
from chrono.manager.views import ManagedAgendaMixin, ViewableAgendaMixin
|
|
from chrono.pricing.forms import (
|
|
AgendaPricingForm,
|
|
CriteriaForm,
|
|
NewCriteriaForm,
|
|
PricingCriteriaCategoryAddForm,
|
|
PricingCriteriaCategoryEditForm,
|
|
PricingVariableFormSet,
|
|
)
|
|
from chrono.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingCriteriaCategory
|
|
|
|
|
|
class PricingListView(ListView):
|
|
template_name = 'chrono/pricing/manager_pricing_list.html'
|
|
model = Pricing
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
pricing_list = PricingListView.as_view()
|
|
|
|
|
|
class CriteriaListView(ListView):
|
|
template_name = 'chrono/pricing/manager_criteria_list.html'
|
|
model = CriteriaCategory
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
return CriteriaCategory.objects.prefetch_related('criterias')
|
|
|
|
|
|
criteria_list = CriteriaListView.as_view()
|
|
|
|
|
|
class PricingAddView(CreateView):
|
|
template_name = 'chrono/pricing/manager_pricing_form.html'
|
|
model = Pricing
|
|
fields = ['label']
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-detail', args=[self.object.pk])
|
|
|
|
|
|
pricing_add = PricingAddView.as_view()
|
|
|
|
|
|
class PricingDetailView(DetailView):
|
|
template_name = 'chrono/pricing/manager_pricing_detail.html'
|
|
model = Pricing
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
return (
|
|
super()
|
|
.get_queryset()
|
|
.prefetch_related(
|
|
Prefetch(
|
|
'categories', queryset=CriteriaCategory.objects.order_by('pricingcriteriacategory__order')
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
pricing_detail = PricingDetailView.as_view()
|
|
|
|
|
|
class PricingEditView(UpdateView):
|
|
template_name = 'chrono/pricing/manager_pricing_form.html'
|
|
model = Pricing
|
|
fields = ['label', 'slug']
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-detail', args=[self.object.pk])
|
|
|
|
|
|
pricing_edit = PricingEditView.as_view()
|
|
|
|
|
|
class PricingDeleteView(DeleteView):
|
|
template_name = 'chrono/manager_confirm_delete.html'
|
|
model = Pricing
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-list')
|
|
|
|
|
|
pricing_delete = PricingDeleteView.as_view()
|
|
|
|
|
|
class PricingExport(DetailView):
|
|
model = Pricing
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
response = HttpResponse(content_type='application/json')
|
|
today = datetime.date.today()
|
|
attachment = 'attachment; filename="export_pricing_{}_{}.json"'.format(
|
|
self.get_object().slug, today.strftime('%Y%m%d')
|
|
)
|
|
response['Content-Disposition'] = attachment
|
|
json.dump({'pricing_models': [self.get_object().export_json()]}, response, indent=2)
|
|
return response
|
|
|
|
|
|
pricing_export = PricingExport.as_view()
|
|
|
|
|
|
class PricingVariableEdit(FormView):
|
|
template_name = 'chrono/pricing/manager_pricing_variable_form.html'
|
|
model = Pricing
|
|
form_class = PricingVariableFormSet
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.object = get_object_or_404(Pricing, pk=kwargs['pk'])
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
kwargs['object'] = self.object
|
|
return super().get_context_data(**kwargs)
|
|
|
|
def get_initial(self):
|
|
return sorted(
|
|
({'key': k, 'value': v} for k, v in self.object.extra_variables.items()),
|
|
key=itemgetter('key'),
|
|
)
|
|
|
|
def form_valid(self, form):
|
|
self.object.extra_variables = {}
|
|
for sub_data in form.cleaned_data:
|
|
if not sub_data.get('key'):
|
|
continue
|
|
self.object.extra_variables[sub_data['key']] = sub_data['value']
|
|
self.object.save()
|
|
return HttpResponseRedirect(self.get_success_url())
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-detail', args=[self.object.pk])
|
|
|
|
|
|
pricing_variable_edit = PricingVariableEdit.as_view()
|
|
|
|
|
|
class PricingCriteriaCategoryAddView(FormView):
|
|
template_name = 'chrono/pricing/manager_pricing_criteria_category_form.html'
|
|
model = Pricing
|
|
form_class = PricingCriteriaCategoryAddForm
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.object = get_object_or_404(Pricing, pk=kwargs['pk'])
|
|
if self.object.categories.count() >= 3:
|
|
raise Http404
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['pricing'] = self.object
|
|
return kwargs
|
|
|
|
def get_context_data(self, **kwargs):
|
|
kwargs['object'] = self.object
|
|
return super().get_context_data(**kwargs)
|
|
|
|
def form_valid(self, form):
|
|
PricingCriteriaCategory.objects.create(pricing=self.object, category=form.cleaned_data['category'])
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-detail', args=[self.object.pk])
|
|
|
|
|
|
pricing_criteria_category_add = PricingCriteriaCategoryAddView.as_view()
|
|
|
|
|
|
class PricingCriteriaCategoryEditView(FormView):
|
|
template_name = 'chrono/pricing/manager_pricing_criteria_category_form.html'
|
|
model = Pricing
|
|
form_class = PricingCriteriaCategoryEditForm
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.object = get_object_or_404(Pricing, pk=kwargs['pk'])
|
|
self.category = get_object_or_404(self.object.categories, pk=kwargs['category_pk'])
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['pricing'] = self.object
|
|
kwargs['category'] = self.category
|
|
return kwargs
|
|
|
|
def get_context_data(self, **kwargs):
|
|
kwargs['object'] = self.object
|
|
kwargs['category'] = self.category
|
|
return super().get_context_data(**kwargs)
|
|
|
|
def form_valid(self, form):
|
|
old_criterias = self.object.criterias.filter(category=self.category)
|
|
new_criterias = form.cleaned_data['criterias']
|
|
removed_criterias = set(old_criterias) - set(new_criterias)
|
|
self.object.criterias.remove(*removed_criterias)
|
|
self.object.criterias.add(*new_criterias)
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-detail', args=[self.object.pk])
|
|
|
|
|
|
pricing_criteria_category_edit = PricingCriteriaCategoryEditView.as_view()
|
|
|
|
|
|
class PricingCriteriaCategoryDeleteView(DeleteView):
|
|
template_name = 'chrono/manager_confirm_delete.html'
|
|
model = CriteriaCategory
|
|
pk_url_kwarg = 'category_pk'
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.pricing = get_object_or_404(Pricing, pk=kwargs['pk'])
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
return self.pricing.categories.all()
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
self.pricing.categories.remove(self.object)
|
|
self.pricing.criterias.remove(*self.pricing.criterias.filter(category=self.object))
|
|
return HttpResponseRedirect(self.get_success_url())
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-detail', args=[self.pricing.pk])
|
|
|
|
|
|
pricing_criteria_category_delete = PricingCriteriaCategoryDeleteView.as_view()
|
|
|
|
|
|
class PricingCriteriaCategoryOrder(DetailView):
|
|
model = Pricing
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
if 'new-order' not in request.GET:
|
|
return HttpResponseBadRequest('missing new-order parameter')
|
|
pricing = self.get_object()
|
|
try:
|
|
new_order = [int(x) for x in request.GET['new-order'].split(',')]
|
|
except ValueError:
|
|
return HttpResponseBadRequest('incorrect new-order parameter')
|
|
categories = pricing.categories.all()
|
|
if set(new_order) != {x.pk for x in categories} or len(new_order) != len(categories):
|
|
return HttpResponseBadRequest('incorrect new-order parameter')
|
|
for i, c_id in enumerate(new_order):
|
|
PricingCriteriaCategory.objects.filter(pricing=pricing, category=c_id).update(order=i + 1)
|
|
return HttpResponse(status=204)
|
|
|
|
|
|
pricing_criteria_category_order = PricingCriteriaCategoryOrder.as_view()
|
|
|
|
|
|
class CriteriaCategoryAddView(CreateView):
|
|
template_name = 'chrono/pricing/manager_criteria_category_form.html'
|
|
model = CriteriaCategory
|
|
fields = ['label']
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-criteria-list')
|
|
|
|
|
|
criteria_category_add = CriteriaCategoryAddView.as_view()
|
|
|
|
|
|
class CriteriaCategoryEditView(UpdateView):
|
|
template_name = 'chrono/pricing/manager_criteria_category_form.html'
|
|
model = CriteriaCategory
|
|
fields = ['label', 'slug']
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-criteria-list')
|
|
|
|
|
|
criteria_category_edit = CriteriaCategoryEditView.as_view()
|
|
|
|
|
|
class CriteriaCategoryDeleteView(DeleteView):
|
|
template_name = 'chrono/manager_confirm_delete.html'
|
|
model = CriteriaCategory
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-criteria-list')
|
|
|
|
|
|
criteria_category_delete = CriteriaCategoryDeleteView.as_view()
|
|
|
|
|
|
class CriteriaCategoryExport(DetailView):
|
|
model = CriteriaCategory
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
response = HttpResponse(content_type='application/json')
|
|
today = datetime.date.today()
|
|
attachment = 'attachment; filename="export_pricing_category_{}_{}.json"'.format(
|
|
self.get_object().slug, today.strftime('%Y%m%d')
|
|
)
|
|
response['Content-Disposition'] = attachment
|
|
json.dump({'pricing_categories': [self.get_object().export_json()]}, response, indent=2)
|
|
return response
|
|
|
|
|
|
criteria_category_export = CriteriaCategoryExport.as_view()
|
|
|
|
|
|
class CriteriaOrder(DetailView):
|
|
model = CriteriaCategory
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
if 'new-order' not in request.GET:
|
|
return HttpResponseBadRequest('missing new-order parameter')
|
|
category = self.get_object()
|
|
try:
|
|
new_order = [int(x) for x in request.GET['new-order'].split(',')]
|
|
except ValueError:
|
|
return HttpResponseBadRequest('incorrect new-order parameter')
|
|
criterias = category.criterias.all()
|
|
if set(new_order) != {x.pk for x in criterias} or len(new_order) != len(criterias):
|
|
return HttpResponseBadRequest('incorrect new-order parameter')
|
|
criterias_by_id = {c.pk: c for c in criterias}
|
|
for i, c_id in enumerate(new_order):
|
|
criterias_by_id[c_id].order = i + 1
|
|
criterias_by_id[c_id].save()
|
|
return HttpResponse(status=204)
|
|
|
|
|
|
criteria_order = CriteriaOrder.as_view()
|
|
|
|
|
|
class CriteriaAddView(CreateView):
|
|
template_name = 'chrono/pricing/manager_criteria_form.html'
|
|
model = Criteria
|
|
form_class = NewCriteriaForm
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.category_pk = kwargs.pop('category_pk')
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
if not kwargs.get('instance'):
|
|
kwargs['instance'] = self.model()
|
|
kwargs['instance'].category_id = self.category_pk
|
|
return kwargs
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-criteria-list')
|
|
|
|
|
|
criteria_add = CriteriaAddView.as_view()
|
|
|
|
|
|
class CriteriaEditView(UpdateView):
|
|
template_name = 'chrono/pricing/manager_criteria_form.html'
|
|
model = Criteria
|
|
form_class = CriteriaForm
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.category_pk = kwargs.pop('category_pk')
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
return Criteria.objects.filter(category=self.category_pk)
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-criteria-list')
|
|
|
|
|
|
criteria_edit = CriteriaEditView.as_view()
|
|
|
|
|
|
class CriteriaDeleteView(DeleteView):
|
|
template_name = 'chrono/manager_confirm_delete.html'
|
|
model = Criteria
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.category_pk = kwargs.pop('category_pk')
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
return Criteria.objects.filter(category=self.category_pk)
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-pricing-criteria-list')
|
|
|
|
|
|
criteria_delete = CriteriaDeleteView.as_view()
|
|
|
|
|
|
class AgendaPricingAddView(ManagedAgendaMixin, CreateView):
|
|
template_name = 'chrono/pricing/manager_agenda_pricing_form.html'
|
|
model = AgendaPricing
|
|
form_class = AgendaPricingForm
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-agenda-pricing-detail', args=[self.agenda.pk, self.object.pk])
|
|
|
|
|
|
agenda_pricing_add = AgendaPricingAddView.as_view()
|
|
|
|
|
|
class AgendaPricingDetailView(ViewableAgendaMixin, DetailView):
|
|
model = AgendaPricing
|
|
pk_url_kwarg = 'pricing_pk'
|
|
template_name = 'chrono/pricing/manager_agenda_pricing_detail.html'
|
|
|
|
def set_agenda(self, **kwargs):
|
|
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events')
|
|
|
|
def get_queryset(self):
|
|
return AgendaPricing.objects.filter(agenda=self.agenda).prefetch_related(
|
|
'pricing__criterias__category'
|
|
)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
kwargs['user_can_manage'] = self.agenda.can_be_managed(self.request.user)
|
|
return super().get_context_data(**kwargs)
|
|
|
|
|
|
agenda_pricing_detail = AgendaPricingDetailView.as_view()
|
|
|
|
|
|
class AgendaPricingEditView(ManagedAgendaMixin, UpdateView):
|
|
template_name = 'chrono/pricing/manager_agenda_pricing_form.html'
|
|
model = AgendaPricing
|
|
pk_url_kwarg = 'pricing_pk'
|
|
form_class = AgendaPricingForm
|
|
|
|
def set_agenda(self, **kwargs):
|
|
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events')
|
|
|
|
def get_queryset(self):
|
|
return AgendaPricing.objects.filter(agenda=self.agenda)
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-agenda-pricing-detail', args=[self.agenda.pk, self.object.pk])
|
|
|
|
|
|
agenda_pricing_edit = AgendaPricingEditView.as_view()
|
|
|
|
|
|
class AgendaPricingDeleteView(ManagedAgendaMixin, DeleteView):
|
|
template_name = 'chrono/manager_confirm_delete.html'
|
|
model = AgendaPricing
|
|
pk_url_kwarg = 'pricing_pk'
|
|
|
|
def set_agenda(self, **kwargs):
|
|
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events')
|
|
|
|
def get_queryset(self):
|
|
return AgendaPricing.objects.filter(agenda=self.agenda)
|
|
|
|
|
|
agenda_pricing_delete = AgendaPricingDeleteView.as_view()
|