chrono/chrono/pricing/views.py

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()