0001-pricing-empty-app-65976.patch
chrono/agendas/migrations/0127_remove_pricing_models.py | ||
---|---|---|
1 |
from django.db import migrations |
|
2 | ||
3 | ||
4 |
class Migration(migrations.Migration): |
|
5 | ||
6 |
dependencies = [ |
|
7 |
('agendas', '0126_pricing'), |
|
8 |
] |
|
9 | ||
10 |
operations = [ |
|
11 |
migrations.RemoveField( |
|
12 |
model_name='agenda', |
|
13 |
name='pricings', |
|
14 |
), |
|
15 |
migrations.RemoveField( |
|
16 |
model_name='checktype', |
|
17 |
name='pricing', |
|
18 |
), |
|
19 |
migrations.RemoveField( |
|
20 |
model_name='checktype', |
|
21 |
name='pricing_rate', |
|
22 |
), |
|
23 |
] |
chrono/agendas/models.py | ||
---|---|---|
251 | 251 |
null=True, |
252 | 252 |
blank=True, |
253 | 253 |
) |
254 |
pricings = models.ManyToManyField( |
|
255 |
'pricing.Pricing', |
|
256 |
related_name='agendas', |
|
257 |
through='pricing.AgendaPricing', |
|
258 |
) |
|
259 | 254 | |
260 | 255 |
class Meta: |
261 | 256 |
ordering = ['label'] |
... | ... | |
404 | 399 |
agenda['event_display_template'] = self.event_display_template |
405 | 400 |
agenda['mark_event_checked_auto'] = self.mark_event_checked_auto |
406 | 401 |
agenda['events_type'] = self.events_type.slug if self.events_type else None |
407 |
agenda['pricings'] = [x.export_json() for x in self.agendapricing_set.all()] |
|
408 | 402 |
elif self.kind == 'meetings': |
409 | 403 |
agenda['meetingtypes'] = [x.export_json() for x in self.meetingtype_set.filter(deleted=False)] |
410 | 404 |
agenda['desks'] = [desk.export_json() for desk in self.desk_set.all()] |
... | ... | |
423 | 417 |
events = data.pop('events') |
424 | 418 |
notifications_settings = data.pop('notifications_settings', None) |
425 | 419 |
exceptions_desk = data.pop('exceptions_desk', None) |
426 |
pricings = data.pop('pricings', None) or [] |
|
427 | 420 |
elif data['kind'] == 'meetings': |
428 | 421 |
meetingtypes = data.pop('meetingtypes') |
429 | 422 |
desks = data.pop('desks') |
... | ... | |
455 | 448 |
data['events_type'] = EventsType.objects.get(slug=data['events_type']) |
456 | 449 |
except EventsType.DoesNotExist: |
457 | 450 |
raise AgendaImportError(_('Missing "%s" events type') % data['events_type']) |
458 |
if data['kind'] == 'events' and pricings: |
|
459 |
from chrono.pricing.models import Pricing |
|
460 | ||
461 |
for pricing_data in pricings: |
|
462 |
try: |
|
463 |
pricing_data['pricing'] = Pricing.objects.get(slug=pricing_data['pricing']) |
|
464 |
except Pricing.DoesNotExist: |
|
465 |
raise AgendaImportError(_('Missing "%s" pricing model') % pricing_data['pricing']) |
|
466 | 451 |
agenda, created = cls.objects.update_or_create(slug=data['slug'], defaults=data) |
467 | 452 |
if overwrite: |
468 | 453 |
AgendaReminderSettings.objects.filter(agenda=agenda).delete() |
... | ... | |
473 | 458 |
if overwrite: |
474 | 459 |
Event.objects.filter(agenda=agenda).delete() |
475 | 460 |
AgendaNotificationsSettings.objects.filter(agenda=agenda).delete() |
476 |
agenda.agendapricing_set.all().delete() |
|
477 | 461 |
for event_data in events: |
478 | 462 |
event_data['agenda'] = agenda |
479 | 463 |
Event.import_json(event_data) |
480 | 464 |
if notifications_settings: |
481 | 465 |
notifications_settings['agenda'] = agenda |
482 | 466 |
AgendaNotificationsSettings.import_json(notifications_settings) |
483 |
for pricing_data in pricings: |
|
484 |
from chrono.pricing.models import AgendaPricing |
|
485 | ||
486 |
pricing_data['agenda'] = agenda |
|
487 |
AgendaPricing.import_json(pricing_data) |
|
488 | 467 |
if exceptions_desk: |
489 | 468 |
exceptions_desk['agenda'] = agenda |
490 | 469 |
Desk.import_json(exceptions_desk) |
... | ... | |
3129 | 3108 |
choices=[('absence', _('Absence')), ('presence', _('Presence'))], |
3130 | 3109 |
default='absence', |
3131 | 3110 |
) |
3132 |
pricing = models.DecimalField( |
|
3133 |
_('Pricing'), max_digits=5, decimal_places=2, help_text=_('Fixed pricing'), blank=True, null=True |
|
3134 |
) |
|
3135 |
pricing_rate = models.PositiveIntegerField( |
|
3136 |
_('Pricing rate'), help_text=_('Percentage rate'), blank=True, null=True |
|
3137 |
) |
|
3138 | 3111 |
disabled = models.BooleanField(_('Disabled'), default=False) |
3139 | 3112 |
objects = CheckTypeManager() |
3140 | 3113 |
chrono/manager/forms.py | ||
---|---|---|
69 | 69 |
from .widgets import SplitDateTimeField, WeekdaysWidget |
70 | 70 | |
71 | 71 | |
72 |
class NewCheckTypeForm(forms.ModelForm):
|
|
72 |
class CheckTypeForm(forms.ModelForm): |
|
73 | 73 |
class Meta: |
74 | 74 |
model = CheckType |
75 |
fields = ['label', 'kind', 'pricing', 'pricing_rate'] |
|
76 | ||
77 |
def __init__(self, *args, **kwargs): |
|
78 |
super().__init__(*args, **kwargs) |
|
79 |
if not settings.CHRONO_ENABLE_PRICING: |
|
80 |
del self.fields['pricing'] |
|
81 |
del self.fields['pricing_rate'] |
|
82 | ||
83 |
def clean(self): |
|
84 |
super().clean() |
|
85 |
if self.cleaned_data.get('pricing') is not None and self.cleaned_data.get('pricing_rate') is not None: |
|
86 |
raise ValidationError(_('Please choose between pricing and pricing rate.')) |
|
87 | ||
88 | ||
89 |
class CheckTypeForm(NewCheckTypeForm): |
|
90 |
class Meta: |
|
91 |
model = CheckType |
|
92 |
fields = ['label', 'slug', 'pricing', 'pricing_rate', 'disabled'] |
|
75 |
fields = ['label', 'slug', 'disabled'] |
|
93 | 76 | |
94 | 77 |
def clean_slug(self): |
95 | 78 |
slug = self.cleaned_data['slug'] |
... | ... | |
1425 | 1408 |
categories = forms.BooleanField(label=_('Categories'), required=False, initial=True) |
1426 | 1409 |
check_type_groups = forms.BooleanField(label=_('Check type groups'), required=False, initial=True) |
1427 | 1410 |
events_types = forms.BooleanField(label=_('Events types'), required=False, initial=True) |
1428 |
pricing_categories = forms.BooleanField( |
|
1429 |
label=_('Pricing criteria categories'), required=False, initial=True |
|
1430 |
) |
|
1431 |
pricing_models = forms.BooleanField(label=_('Pricing models'), required=False, initial=True) |
|
1432 | ||
1433 |
def __init__(self, *args, **kwargs): |
|
1434 |
super().__init__(*args, **kwargs) |
|
1435 |
if not settings.CHRONO_ENABLE_PRICING: |
|
1436 |
del self.fields['pricing_categories'] |
|
1437 |
del self.fields['pricing_models'] |
|
1438 | 1411 | |
1439 | 1412 | |
1440 | 1413 |
class SharedCustodyRuleForm(forms.ModelForm): |
chrono/manager/static/css/style.scss | ||
---|---|---|
522 | 522 |
.page_break { |
523 | 523 |
height: 20px; |
524 | 524 |
} |
525 | ||
526 |
div.paragraph { |
|
527 |
background: white; |
|
528 |
box-sizing: border-box; |
|
529 |
border: 1px solid #386ede; |
|
530 |
border-radius: 3px; |
|
531 |
max-width: 100%; |
|
532 |
padding: 5px 15px; |
|
533 |
margin-bottom: 1rem; |
|
534 |
h4 { |
|
535 |
margin-top: 5px; |
|
536 |
border-bottom: none; |
|
537 |
} |
|
538 |
} |
|
539 | ||
540 |
.sortable { |
|
541 |
span.handle { |
|
542 |
cursor: move; |
|
543 |
display: inline-block; |
|
544 |
padding: 0.5ex; |
|
545 |
text-align: center; |
|
546 |
width: 2em; |
|
547 |
height: 100%; |
|
548 |
box-sizing: border-box; |
|
549 |
font-weight: normal; |
|
550 |
} |
|
551 |
} |
|
552 | ||
553 |
ul.objects-list.sortable { |
|
554 |
li { |
|
555 |
position: relative; |
|
556 |
& > a { |
|
557 |
display: inline-block; |
|
558 |
} |
|
559 |
} |
|
560 |
} |
chrono/manager/static/js/chrono.manager.js | ||
---|---|---|
48 | 48 |
total_form.val(form_num + 1); |
49 | 49 |
}) |
50 | 50 |
} |
51 | ||
52 |
$(document).on('click', '#add-pricing-variable-form', function() { |
|
53 |
if (typeof property_forms === "undefined") {var property_forms = $('.pricing-variable-form');} |
|
54 |
if (typeof total_forms === "undefined") {var total_form = $('#id_form-TOTAL_FORMS');} |
|
55 |
if (typeof form_num === "undefined") {var form_num = property_forms.length - 1;} |
|
56 |
var new_form = $(property_forms[0]).clone(); |
|
57 |
var form_regex = RegExp(`form-(\\d){1}-`,'g'); |
|
58 |
form_num++; |
|
59 |
new_form.html(new_form.html().replace(form_regex, `form-${form_num}-`)); |
|
60 |
new_form.appendTo('#pricing-variable-forms tbody'); |
|
61 |
$('#id_form-' + form_num + '-key').val(''); |
|
62 |
$('#id_form-' + form_num + '-value').val(''); |
|
63 |
total_form.val(form_num + 1); |
|
64 |
}) |
|
65 | ||
66 |
$('.sortable').sortable({ |
|
67 |
handle: '.handle', |
|
68 |
items: '.sortable-item', |
|
69 |
update : function(event, ui) { |
|
70 |
var new_order = ''; |
|
71 |
$(this).find('.sortable-item').each(function(i, x) { |
|
72 |
var item_id = $(x).data('item-id'); |
|
73 |
if (new_order) { |
|
74 |
new_order += ','; |
|
75 |
} |
|
76 |
new_order += item_id; |
|
77 |
}); |
|
78 |
$.ajax({ |
|
79 |
url: $(this).data('order-url'), |
|
80 |
data: {'new-order': new_order} |
|
81 |
}); |
|
82 |
} |
|
83 |
}); |
|
84 | 51 |
}); |
chrono/manager/templates/chrono/manager_events_agenda_settings.html | ||
---|---|---|
4 | 4 |
{% block agenda-extra-management-actions %} |
5 | 5 |
<a rel="popup" href="{% url 'chrono-manager-agenda-import-events' pk=object.id %}">{% trans 'Import Events' %}</a> |
6 | 6 |
<a rel="popup" href="{% url 'chrono-manager-agenda-add-event' pk=object.id %}">{% trans 'New Event' %}</a> |
7 |
{% if pricing_enabled %} |
|
8 |
<a rel="popup" href="{% url 'chrono-manager-agenda-pricing-add' pk=object.id %}">{% trans 'New pricing' %}</a> |
|
9 |
{% endif %} |
|
10 | 7 |
{% endblock %} |
11 | 8 | |
12 | 9 |
{% block agenda-settings %} |
... | ... | |
93 | 90 |
</div> |
94 | 91 |
</div> |
95 | 92 | |
96 |
{% if pricing_enabled %} |
|
97 |
<div class="section"> |
|
98 |
<h3>{% trans "Pricing" context 'pricing' %}</h3> |
|
99 |
<div> |
|
100 |
{% if agenda_pricings %} |
|
101 |
<ul class="objects-list single-links"> |
|
102 |
{% for agenda_pricing in agenda_pricings %} |
|
103 |
<li><a href="{% url 'chrono-manager-agenda-pricing-detail' agenda.pk agenda_pricing.pk %}">{{ agenda_pricing.pricing }} ({{ agenda_pricing.date_start|date:'d/m/Y' }} - {{ agenda_pricing.date_end|date:'d/m/Y' }})</a></li> |
|
104 |
{% endfor %} |
|
105 |
</ul> |
|
106 |
{% else %} |
|
107 |
<div class="big-msg-info"> |
|
108 |
{% blocktrans %} |
|
109 |
This agenda doesn't have any pricing defined yet. Click on the "New pricing" button in |
|
110 |
the top right of the page to add a first one. |
|
111 |
{% endblocktrans %} |
|
112 |
</div> |
|
113 |
{% endif %} |
|
114 |
</div> |
|
115 |
</div> |
|
116 |
{% endif %} |
|
117 | ||
118 | 93 |
<div class="section"> |
119 | 94 |
<h3>{% trans "Notifications" %} |
120 | 95 |
<a rel="popup" class="button" href="{% url 'chrono-manager-agenda-notifications-settings' pk=object.id %}">{% trans 'Configure' %}</a> |
chrono/manager/templates/chrono/manager_home.html | ||
---|---|---|
12 | 12 |
<li><a rel="popup" href="{% url 'chrono-manager-agendas-export' %}" data-autoclose-dialog="true">{% trans 'Export' %}</a></li> |
13 | 13 |
<li><a href="{% url 'chrono-manager-events-type-list' %}">{% trans 'Events types' %}</a></li> |
14 | 14 |
<li><a href="{% url 'chrono-manager-check-type-list' %}">{% trans 'Check types' %}</a></li> |
15 |
{% if pricing_enabled %} |
|
16 |
<li><a href="{% url 'chrono-manager-pricing-list' %}">{% trans 'Pricing' context 'pricing' %}</a></li> |
|
17 |
{% endif %} |
|
18 | 15 |
{% endif %} |
19 | 16 |
{% if has_access_to_unavailability_calendars %} |
20 | 17 |
<li><a href="{% url 'chrono-manager-unavailability-calendar-list' %}">{% trans 'Unavailability calendars' %}</a></li> |
chrono/manager/utils.py | ||
---|---|---|
30 | 30 |
Resource, |
31 | 31 |
UnavailabilityCalendar, |
32 | 32 |
) |
33 |
from chrono.pricing.models import CriteriaCategory, Pricing |
|
34 | 33 | |
35 | 34 | |
36 | 35 |
def export_site( |
... | ... | |
40 | 39 |
events_types=True, |
41 | 40 |
resources=True, |
42 | 41 |
categories=True, |
43 |
pricing_categories=True, |
|
44 |
pricing_models=True, |
|
45 | 42 |
): |
46 | 43 |
'''Dump site objects to JSON-dumpable dictionnary''' |
47 | 44 |
data = collections.OrderedDict() |
48 |
if pricing_models: |
|
49 |
data['pricing_models'] = [x.export_json() for x in Pricing.objects.all()] |
|
50 |
if pricing_categories: |
|
51 |
data['pricing_categories'] = [x.export_json() for x in CriteriaCategory.objects.all()] |
|
52 | 45 |
if categories: |
53 | 46 |
data['categories'] = [x.export_json() for x in Category.objects.all()] |
54 | 47 |
if resources: |
... | ... | |
75 | 68 |
or EventsType.objects.exists() |
76 | 69 |
or Resource.objects.exists() |
77 | 70 |
or Category.objects.exists() |
78 |
or CriteriaCategory.objects.exists() |
|
79 |
or Pricing.objects.exists() |
|
80 | 71 |
): |
81 | 72 |
return |
82 | 73 | |
... | ... | |
87 | 78 |
EventsType.objects.all().delete() |
88 | 79 |
Resource.objects.all().delete() |
89 | 80 |
Category.objects.all().delete() |
90 |
CriteriaCategory.objects.all().delete() |
|
91 |
Pricing.objects.all().delete() |
|
92 | 81 | |
93 | 82 |
results = { |
94 | 83 |
key: collections.defaultdict(list) |
... | ... | |
99 | 88 |
'events_types', |
100 | 89 |
'resources', |
101 | 90 |
'categories', |
102 |
'pricing_categories', |
|
103 |
'pricing_models', |
|
104 | 91 |
] |
105 | 92 |
} |
106 | 93 | |
... | ... | |
124 | 111 |
(CheckTypeGroup, 'check_type_groups'), |
125 | 112 |
(UnavailabilityCalendar, 'unavailability_calendars'), |
126 | 113 |
(Agenda, 'agendas'), |
127 |
(CriteriaCategory, 'pricing_categories'), |
|
128 |
(Pricing, 'pricing_models'), |
|
129 | 114 |
): |
130 | 115 |
objs = data.get(key, []) |
131 | 116 |
for obj in objs: |
chrono/manager/views.py | ||
---|---|---|
26 | 26 | |
27 | 27 |
import requests |
28 | 28 |
from dateutil.relativedelta import MO, relativedelta |
29 |
from django.conf import settings |
|
30 | 29 |
from django.contrib import messages |
31 | 30 |
from django.core.exceptions import PermissionDenied |
32 | 31 |
from django.db import IntegrityError, transaction |
... | ... | |
87 | 86 |
UnavailabilityCalendar, |
88 | 87 |
VirtualMember, |
89 | 88 |
) |
90 |
from chrono.pricing.models import AgendaPricing |
|
91 | 89 |
from chrono.utils.date import get_weekday_index |
92 | 90 | |
93 | 91 |
from .forms import ( |
... | ... | |
116 | 114 |
EventsTimesheetForm, |
117 | 115 |
ImportEventsForm, |
118 | 116 |
MeetingTypeForm, |
119 |
NewCheckTypeForm, |
|
120 | 117 |
NewDeskForm, |
121 | 118 |
NewEventForm, |
122 | 119 |
NewMeetingTypeForm, |
... | ... | |
165 | 162 |
def get_context_data(self, **kwargs): |
166 | 163 |
context = super().get_context_data(**kwargs) |
167 | 164 |
context['has_access_to_unavailability_calendars'] = self.has_access_to_unavailability_calendars() |
168 |
context['pricing_enabled'] = settings.CHRONO_ENABLE_PRICING |
|
169 | 165 |
return context |
170 | 166 | |
171 | 167 |
def get(self, request, *args, **kwargs): |
... | ... | |
763 | 759 |
class CheckTypeAddView(CreateView): |
764 | 760 |
template_name = 'chrono/manager_check_type_form.html' |
765 | 761 |
model = CheckType |
766 |
form_class = NewCheckTypeForm
|
|
762 |
fields = ['label', 'kind']
|
|
767 | 763 | |
768 | 764 |
def dispatch(self, request, *args, **kwargs): |
769 | 765 |
self.group_pk = kwargs.pop('group_pk') |
... | ... | |
1073 | 1069 |
x, |
1074 | 1070 |
), |
1075 | 1071 |
}, |
1076 |
'pricing_categories': { |
|
1077 |
'create_noop': _('No pricing criteria category created.'), |
|
1078 |
'create': lambda x: ungettext( |
|
1079 |
'A pricing criteria category has been created.', |
|
1080 |
'%(count)d pricing criteria categories have been created.', |
|
1081 |
x, |
|
1082 |
), |
|
1083 |
'update_noop': _('No pricing criteria category updated.'), |
|
1084 |
'update': lambda x: ungettext( |
|
1085 |
'A pricing criteria category has been updated.', |
|
1086 |
'%(count)d pricing criteria categories have been updated.', |
|
1087 |
x, |
|
1088 |
), |
|
1089 |
}, |
|
1090 |
'pricing_models': { |
|
1091 |
'create_noop': _('No pricing model created.'), |
|
1092 |
'create': lambda x: ungettext( |
|
1093 |
'A pricing model has been created.', |
|
1094 |
'%(count)d pricing models have been created.', |
|
1095 |
x, |
|
1096 |
), |
|
1097 |
'update_noop': _('No pricing model updated.'), |
|
1098 |
'update': lambda x: ungettext( |
|
1099 |
'A pricing model has been updated.', |
|
1100 |
'%(count)d pricing models have been updated.', |
|
1101 |
x, |
|
1102 |
), |
|
1103 |
}, |
|
1104 | 1072 |
} |
1105 | 1073 | |
1106 | 1074 |
global_noop = True |
... | ... | |
1121 | 1089 | |
1122 | 1090 |
obj_results['messages'] = "%s %s" % (message1, message2) |
1123 | 1091 | |
1124 |
a_count, uc_count, arg_count, pc_count, pm_count = (
|
|
1092 |
a_count, uc_count, arg_count = ( |
|
1125 | 1093 |
len(results['agendas']['all']), |
1126 | 1094 |
len(results['unavailability_calendars']['all']), |
1127 | 1095 |
len(results['check_type_groups']['all']), |
1128 |
len(results['pricing_categories']['all']), |
|
1129 |
len(results['pricing_models']['all']), |
|
1130 | 1096 |
) |
1131 |
if (a_count, uc_count, arg_count, pc_count, pm_count) == (1, 0, 0, 0, 0):
|
|
1097 |
if (a_count, uc_count, arg_count) == (1, 0, 0):
|
|
1132 | 1098 |
# only one agenda imported, redirect to settings page |
1133 | 1099 |
return HttpResponseRedirect( |
1134 | 1100 |
reverse('chrono-manager-agenda-settings', kwargs={'pk': results['agendas']['all'][0].pk}) |
1135 | 1101 |
) |
1136 |
if (a_count, uc_count, arg_count, pc_count, pm_count) == (0, 1, 0, 0, 0):
|
|
1102 |
if (a_count, uc_count, arg_count) == (0, 1, 0):
|
|
1137 | 1103 |
# only one unavailability calendar imported, redirect to settings page |
1138 | 1104 |
return HttpResponseRedirect( |
1139 | 1105 |
reverse( |
... | ... | |
1141 | 1107 |
kwargs={'pk': results['unavailability_calendars']['all'][0].pk}, |
1142 | 1108 |
) |
1143 | 1109 |
) |
1144 |
if (a_count, uc_count, arg_count, pc_count, pm_count) == (0, 0, 1, 0, 0):
|
|
1110 |
if (a_count, uc_count, arg_count) == (0, 0, 1):
|
|
1145 | 1111 |
# only one check type group imported, redirect to check type page |
1146 | 1112 |
return HttpResponseRedirect(reverse('chrono-manager-check-type-list')) |
1147 |
if (a_count, uc_count, arg_count, pc_count, pm_count) == (0, 0, 0, 1, 0): |
|
1148 |
# only one criteria category imported, redirect to criteria page |
|
1149 |
return HttpResponseRedirect(reverse('chrono-manager-pricing-criteria-list')) |
|
1150 |
if (a_count, uc_count, arg_count, pc_count, pm_count) == (0, 0, 0, 0, 1): |
|
1151 |
# only one pricing imported, redirect to pricing page |
|
1152 |
return HttpResponseRedirect( |
|
1153 |
reverse( |
|
1154 |
'chrono-manager-pricing-detail', |
|
1155 |
kwargs={'pk': results['pricing_models']['all'][0].pk}, |
|
1156 |
) |
|
1157 |
) |
|
1158 | 1113 | |
1159 | 1114 |
if global_noop: |
1160 | 1115 |
messages.info(self.request, _('No data found.')) |
... | ... | |
1165 | 1120 |
messages.info(self.request, results['events_types']['messages']) |
1166 | 1121 |
messages.info(self.request, results['resources']['messages']) |
1167 | 1122 |
messages.info(self.request, results['categories']['messages']) |
1168 |
messages.info(self.request, results['pricing_categories']['messages']) |
|
1169 |
messages.info(self.request, results['pricing_models']['messages']) |
|
1170 | 1123 | |
1171 | 1124 |
return super().form_valid(form) |
1172 | 1125 | |
... | ... | |
1925 | 1878 |
end_datetime__gt=now(), |
1926 | 1879 |
) |
1927 | 1880 |
context['desk'] = desk |
1928 |
context['pricing_enabled'] = settings.CHRONO_ENABLE_PRICING |
|
1929 |
context['agenda_pricings'] = ( |
|
1930 |
AgendaPricing.objects.filter(agenda=self.agenda) |
|
1931 |
.select_related('pricing') |
|
1932 |
.order_by('date_start', 'date_end') |
|
1933 |
) |
|
1934 | 1881 |
return context |
1935 | 1882 | |
1936 | 1883 |
def get_events(self): |
chrono/pricing/forms.py | ||
---|---|---|
1 |
# chrono - agendas system |
|
2 |
# Copyright (C) 2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from django import forms |
|
18 |
from django.forms import ValidationError |
|
19 |
from django.template import Template, TemplateSyntaxError |
|
20 |
from django.utils.translation import ugettext_lazy as _ |
|
21 | ||
22 |
from chrono.pricing.models import AgendaPricing, Criteria, CriteriaCategory |
|
23 | ||
24 | ||
25 |
class NewCriteriaForm(forms.ModelForm): |
|
26 |
class Meta: |
|
27 |
model = Criteria |
|
28 |
fields = ['label', 'condition'] |
|
29 | ||
30 |
def clean_condition(self): |
|
31 |
condition = self.cleaned_data['condition'] |
|
32 |
try: |
|
33 |
Template('{%% if %s %%}OK{%% endif %%}' % condition) |
|
34 |
except TemplateSyntaxError: |
|
35 |
raise ValidationError(_('Invalid syntax.')) |
|
36 | ||
37 |
return condition |
|
38 | ||
39 | ||
40 |
class CriteriaForm(NewCriteriaForm): |
|
41 |
class Meta: |
|
42 |
model = Criteria |
|
43 |
fields = ['label', 'slug', 'condition'] |
|
44 | ||
45 |
def clean_slug(self): |
|
46 |
slug = self.cleaned_data['slug'] |
|
47 | ||
48 |
if self.instance.category.criterias.filter(slug=slug).exclude(pk=self.instance.pk).exists(): |
|
49 |
raise ValidationError(_('Another criteria exists with the same identifier.')) |
|
50 | ||
51 |
return slug |
|
52 | ||
53 | ||
54 |
class PricingDuplicateForm(forms.Form): |
|
55 |
label = forms.CharField(label=_('New label'), max_length=150, required=False) |
|
56 | ||
57 | ||
58 |
class PricingVariableForm(forms.Form): |
|
59 |
key = forms.CharField(label=_('Variable name'), required=False) |
|
60 |
value = forms.CharField( |
|
61 |
label=_('Value template'), widget=forms.TextInput(attrs={'size': 60}), required=False |
|
62 |
) |
|
63 | ||
64 | ||
65 |
PricingVariableFormSet = forms.formset_factory(PricingVariableForm) |
|
66 | ||
67 | ||
68 |
class PricingCriteriaCategoryAddForm(forms.Form): |
|
69 |
category = forms.ModelChoiceField( |
|
70 |
label=_('Criteria category to add'), queryset=CriteriaCategory.objects.none(), required=True |
|
71 |
) |
|
72 | ||
73 |
def __init__(self, *args, **kwargs): |
|
74 |
self.pricing = kwargs.pop('pricing') |
|
75 |
super().__init__(*args, **kwargs) |
|
76 |
self.fields['category'].queryset = CriteriaCategory.objects.exclude(pricings=self.pricing) |
|
77 | ||
78 | ||
79 |
class PricingCriteriaCategoryEditForm(forms.Form): |
|
80 |
criterias = forms.ModelMultipleChoiceField( |
|
81 |
label=_('Criterias'), |
|
82 |
queryset=Criteria.objects.none(), |
|
83 |
required=True, |
|
84 |
widget=forms.CheckboxSelectMultiple(), |
|
85 |
) |
|
86 | ||
87 |
def __init__(self, *args, **kwargs): |
|
88 |
self.pricing = kwargs.pop('pricing') |
|
89 |
self.category = kwargs.pop('category') |
|
90 |
super().__init__(*args, **kwargs) |
|
91 |
self.fields['criterias'].queryset = self.category.criterias.all() |
|
92 |
self.initial['criterias'] = self.pricing.criterias.filter(category=self.category) |
|
93 | ||
94 | ||
95 |
class AgendaPricingForm(forms.ModelForm): |
|
96 |
class Meta: |
|
97 |
model = AgendaPricing |
|
98 |
fields = ['pricing', 'date_start', 'date_end'] |
|
99 |
widgets = { |
|
100 |
'date_start': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'), |
|
101 |
'date_end': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'), |
|
102 |
} |
|
103 | ||
104 |
def clean(self): |
|
105 |
cleaned_data = super().clean() |
|
106 | ||
107 |
if 'date_start' in cleaned_data and 'date_end' in cleaned_data: |
|
108 |
if cleaned_data['date_end'] <= cleaned_data['date_start']: |
|
109 |
self.add_error('date_end', _('End date must be greater than start date.')) |
|
110 |
else: |
|
111 |
overlapping_qs = AgendaPricing.objects.filter(agenda=self.instance.agenda,).extra( |
|
112 |
where=["(date_start, date_end) OVERLAPS (%s, %s)"], |
|
113 |
params=[cleaned_data['date_start'], cleaned_data['date_end']], |
|
114 |
) |
|
115 |
if self.instance.pk: |
|
116 |
overlapping_qs = overlapping_qs.exclude(pk=self.instance.pk) |
|
117 |
if overlapping_qs.exists(): |
|
118 |
raise forms.ValidationError(_('Pricing overlaps existing pricings.')) |
|
119 | ||
120 |
return cleaned_data |
|
121 | ||
122 | ||
123 |
class PricingMatrixForm(forms.Form): |
|
124 |
def __init__(self, *args, **kwargs): |
|
125 |
matrix = kwargs.pop('matrix') |
|
126 |
super().__init__(*args, **kwargs) |
|
127 |
for i in range(len(matrix.rows[0].cells)): |
|
128 |
self.fields['crit_%i' % i] = forms.DecimalField(required=True, max_digits=5, decimal_places=2) |
chrono/pricing/migrations/0004_remove_pricing_models.py | ||
---|---|---|
1 |
from django.db import migrations |
|
2 | ||
3 | ||
4 |
class Migration(migrations.Migration): |
|
5 | ||
6 |
dependencies = [ |
|
7 |
('agendas', '0127_remove_pricing_models'), |
|
8 |
('pricing', '0003_extra_variables'), |
|
9 |
] |
|
10 | ||
11 |
operations = [ |
|
12 |
migrations.AlterUniqueTogether( |
|
13 |
name='criteria', |
|
14 |
unique_together=None, |
|
15 |
), |
|
16 |
migrations.RemoveField( |
|
17 |
model_name='criteria', |
|
18 |
name='category', |
|
19 |
), |
|
20 |
migrations.RemoveField( |
|
21 |
model_name='pricing', |
|
22 |
name='categories', |
|
23 |
), |
|
24 |
migrations.RemoveField( |
|
25 |
model_name='pricing', |
|
26 |
name='criterias', |
|
27 |
), |
|
28 |
migrations.AlterUniqueTogether( |
|
29 |
name='pricingcriteriacategory', |
|
30 |
unique_together=None, |
|
31 |
), |
|
32 |
migrations.RemoveField( |
|
33 |
model_name='pricingcriteriacategory', |
|
34 |
name='category', |
|
35 |
), |
|
36 |
migrations.RemoveField( |
|
37 |
model_name='pricingcriteriacategory', |
|
38 |
name='pricing', |
|
39 |
), |
|
40 |
migrations.DeleteModel( |
|
41 |
name='AgendaPricing', |
|
42 |
), |
|
43 |
migrations.DeleteModel( |
|
44 |
name='Criteria', |
|
45 |
), |
|
46 |
migrations.DeleteModel( |
|
47 |
name='CriteriaCategory', |
|
48 |
), |
|
49 |
migrations.DeleteModel( |
|
50 |
name='Pricing', |
|
51 |
), |
|
52 |
migrations.DeleteModel( |
|
53 |
name='PricingCriteriaCategory', |
|
54 |
), |
|
55 |
] |
chrono/pricing/models.py | ||
---|---|---|
1 |
# chrono - agendas system |
|
2 |
# Copyright (C) 2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import copy |
|
18 |
import dataclasses |
|
19 |
import decimal |
|
20 |
from typing import List |
|
21 | ||
22 |
from django.contrib.postgres.fields import JSONField |
|
23 |
from django.db import models |
|
24 |
from django.template import Context, RequestContext, Template, TemplateSyntaxError |
|
25 |
from django.utils.text import slugify |
|
26 |
from django.utils.translation import ugettext_lazy as _ |
|
27 | ||
28 |
from chrono.agendas.models import Agenda, Booking, Subscription |
|
29 |
from chrono.utils.misc import AgendaImportError, clean_import_data, generate_slug |
|
30 | ||
31 | ||
32 |
class PricingError(Exception): |
|
33 |
def __init__(self, details=None): |
|
34 |
self.details = details or {} |
|
35 |
super().__init__() |
|
36 | ||
37 | ||
38 |
class AgendaPricingNotFound(PricingError): |
|
39 |
pass |
|
40 | ||
41 | ||
42 |
class CriteriaConditionNotFound(PricingError): |
|
43 |
pass |
|
44 | ||
45 | ||
46 |
class PricingDataError(PricingError): |
|
47 |
pass |
|
48 | ||
49 | ||
50 |
class PricingDataFormatError(PricingError): |
|
51 |
pass |
|
52 | ||
53 | ||
54 |
class PricingEventNotCheckedError(PricingError): |
|
55 |
pass |
|
56 | ||
57 | ||
58 |
class PricingBookingNotCheckedError(PricingError): |
|
59 |
pass |
|
60 | ||
61 | ||
62 |
class PricingSubscriptionError(PricingError): |
|
63 |
pass |
|
64 | ||
65 | ||
66 |
class PricingMultipleBookingError(PricingError): |
|
67 |
pass |
|
68 | ||
69 | ||
70 |
class PricingBookingCheckTypeError(PricingError): |
|
71 |
pass |
|
72 | ||
73 | ||
74 |
class CriteriaCategory(models.Model): |
|
75 |
label = models.CharField(_('Label'), max_length=150) |
|
76 |
slug = models.SlugField(_('Identifier'), max_length=160, unique=True) |
|
77 | ||
78 |
class Meta: |
|
79 |
ordering = ['label'] |
|
80 | ||
81 |
def __str__(self): |
|
82 |
return self.label |
|
83 | ||
84 |
def save(self, *args, **kwargs): |
|
85 |
if not self.slug: |
|
86 |
self.slug = generate_slug(self) |
|
87 |
super().save(*args, **kwargs) |
|
88 | ||
89 |
@property |
|
90 |
def base_slug(self): |
|
91 |
return slugify(self.label) |
|
92 | ||
93 |
@classmethod |
|
94 |
def import_json(cls, data, overwrite=False): |
|
95 |
criterias = data.pop('criterias', []) |
|
96 |
data = clean_import_data(cls, data) |
|
97 |
category, created = cls.objects.update_or_create(slug=data['slug'], defaults=data) |
|
98 | ||
99 |
if overwrite: |
|
100 |
Criteria.objects.filter(category=category).delete() |
|
101 | ||
102 |
for criteria in criterias: |
|
103 |
criteria['category'] = category |
|
104 |
Criteria.import_json(criteria) |
|
105 | ||
106 |
return created, category |
|
107 | ||
108 |
def export_json(self): |
|
109 |
return { |
|
110 |
'label': self.label, |
|
111 |
'slug': self.slug, |
|
112 |
'criterias': [a.export_json() for a in self.criterias.all()], |
|
113 |
} |
|
114 | ||
115 | ||
116 |
class Criteria(models.Model): |
|
117 |
category = models.ForeignKey( |
|
118 |
CriteriaCategory, verbose_name=_('Category'), on_delete=models.CASCADE, related_name='criterias' |
|
119 |
) |
|
120 |
label = models.CharField(_('Label'), max_length=150) |
|
121 |
slug = models.SlugField(_('Identifier'), max_length=160) |
|
122 |
condition = models.CharField(_('Condition'), max_length=1000) |
|
123 |
order = models.PositiveIntegerField() |
|
124 | ||
125 |
class Meta: |
|
126 |
ordering = ['order'] |
|
127 |
unique_together = ['category', 'slug'] |
|
128 | ||
129 |
def __str__(self): |
|
130 |
return self.label |
|
131 | ||
132 |
def save(self, *args, **kwargs): |
|
133 |
if self.order is None: |
|
134 |
max_order = ( |
|
135 |
Criteria.objects.filter(category=self.category) |
|
136 |
.aggregate(models.Max('order')) |
|
137 |
.get('order__max') |
|
138 |
or 0 |
|
139 |
) |
|
140 |
self.order = max_order + 1 |
|
141 |
if not self.slug: |
|
142 |
self.slug = generate_slug(self, category=self.category) |
|
143 |
super().save(*args, **kwargs) |
|
144 | ||
145 |
@property |
|
146 |
def base_slug(self): |
|
147 |
return slugify(self.label) |
|
148 | ||
149 |
@classmethod |
|
150 |
def import_json(cls, data): |
|
151 |
data = clean_import_data(cls, data) |
|
152 |
cls.objects.update_or_create(slug=data['slug'], category=data['category'], defaults=data) |
|
153 | ||
154 |
def export_json(self): |
|
155 |
return { |
|
156 |
'label': self.label, |
|
157 |
'slug': self.slug, |
|
158 |
'condition': self.condition, |
|
159 |
'order': self.order, |
|
160 |
} |
|
161 | ||
162 |
@property |
|
163 |
def identifier(self): |
|
164 |
return '%s:%s' % (self.category.slug, self.slug) |
|
165 | ||
166 |
def compute_condition(self, context): |
|
167 |
try: |
|
168 |
template = Template('{%% if %s %%}OK{%% endif %%}' % self.condition) |
|
169 |
except TemplateSyntaxError: |
|
170 |
return False |
|
171 |
return template.render(Context(context)) == 'OK' |
|
172 | ||
173 | ||
174 |
class Pricing(models.Model): |
|
175 |
label = models.CharField(_('Label'), max_length=150) |
|
176 |
slug = models.SlugField(_('Identifier'), max_length=160, unique=True) |
|
177 |
categories = models.ManyToManyField( |
|
178 |
CriteriaCategory, |
|
179 |
related_name='pricings', |
|
180 |
through='PricingCriteriaCategory', |
|
181 |
) |
|
182 |
criterias = models.ManyToManyField(Criteria) |
|
183 |
extra_variables = JSONField(blank=True, default=dict) |
|
184 | ||
185 |
class Meta: |
|
186 |
ordering = ['label'] |
|
187 | ||
188 |
def __str__(self): |
|
189 |
return self.label |
|
190 | ||
191 |
def save(self, *args, **kwargs): |
|
192 |
if not self.slug: |
|
193 |
self.slug = generate_slug(self) |
|
194 |
super().save(*args, **kwargs) |
|
195 | ||
196 |
@property |
|
197 |
def base_slug(self): |
|
198 |
return slugify(self.label) |
|
199 | ||
200 |
def get_extra_variables(self, request, original_context): |
|
201 |
result = {} |
|
202 |
context = RequestContext(request) |
|
203 |
context.push(original_context) |
|
204 |
for key, tplt in (self.extra_variables or {}).items(): |
|
205 |
try: |
|
206 |
template = Template(tplt) |
|
207 |
except TemplateSyntaxError: |
|
208 |
continue |
|
209 |
result[key] = template.render(context) |
|
210 |
return result |
|
211 | ||
212 |
def get_extra_variables_keys(self): |
|
213 |
return sorted((self.extra_variables or {}).keys()) |
|
214 | ||
215 |
@classmethod |
|
216 |
def import_json(cls, data, overwrite=False): |
|
217 |
data = data.copy() |
|
218 |
categories = data.pop('categories', []) |
|
219 |
categories_by_slug = {c.slug: c for c in CriteriaCategory.objects.all()} |
|
220 |
criterias_by_categories_and_slug = { |
|
221 |
(crit.category.slug, crit.slug): crit |
|
222 |
for crit in Criteria.objects.select_related('category').all() |
|
223 |
} |
|
224 |
for category_data in categories: |
|
225 |
category_slug = category_data['category'] |
|
226 |
if category_data['category'] not in categories_by_slug: |
|
227 |
raise AgendaImportError(_('Missing "%s" pricing category') % category_data['category']) |
|
228 |
for criteria_slug in category_data['criterias']: |
|
229 |
if (category_slug, criteria_slug) not in criterias_by_categories_and_slug: |
|
230 |
raise AgendaImportError( |
|
231 |
_('Missing "%s" pricing criteria for "%s" category') % (criteria_slug, category_slug) |
|
232 |
) |
|
233 |
data = clean_import_data(cls, data) |
|
234 |
pricing, created = cls.objects.update_or_create(slug=data['slug'], defaults=data) |
|
235 | ||
236 |
PricingCriteriaCategory.objects.filter(pricing=pricing).delete() |
|
237 |
criterias = [] |
|
238 |
for category_data in categories: |
|
239 |
pricing.categories.add( |
|
240 |
categories_by_slug[category_data['category']], |
|
241 |
through_defaults={'order': category_data['order']}, |
|
242 |
) |
|
243 |
for criteria_slug in category_data['criterias']: |
|
244 |
criterias.append(criterias_by_categories_and_slug[(category_data['category'], criteria_slug)]) |
|
245 |
pricing.criterias.set(criterias) |
|
246 | ||
247 |
return created, pricing |
|
248 | ||
249 |
def export_json(self): |
|
250 |
return { |
|
251 |
'label': self.label, |
|
252 |
'slug': self.slug, |
|
253 |
'extra_variables': self.extra_variables, |
|
254 |
'categories': [pcc.export_json() for pcc in PricingCriteriaCategory.objects.filter(pricing=self)], |
|
255 |
} |
|
256 | ||
257 |
def duplicate(self, label=None): |
|
258 |
# clone current pricing |
|
259 |
new_pricing = copy.deepcopy(self) |
|
260 |
new_pricing.pk = None |
|
261 |
new_pricing.label = label or _('Copy of %s') % self.label |
|
262 |
# reset slug |
|
263 |
new_pricing.slug = None |
|
264 |
new_pricing.save() |
|
265 | ||
266 |
# set criterias |
|
267 |
new_pricing.criterias.set(self.criterias.all()) |
|
268 | ||
269 |
# set categories |
|
270 |
for pcc in PricingCriteriaCategory.objects.filter(pricing=self): |
|
271 |
pcc.duplicate(pricing_target=new_pricing) |
|
272 | ||
273 |
return new_pricing |
|
274 | ||
275 | ||
276 |
class PricingCriteriaCategory(models.Model): |
|
277 |
pricing = models.ForeignKey(Pricing, on_delete=models.CASCADE) |
|
278 |
category = models.ForeignKey(CriteriaCategory, on_delete=models.CASCADE) |
|
279 |
order = models.PositiveIntegerField() |
|
280 | ||
281 |
class Meta: |
|
282 |
ordering = ['order'] |
|
283 |
unique_together = ['pricing', 'category'] |
|
284 | ||
285 |
def save(self, *args, **kwargs): |
|
286 |
if self.order is None: |
|
287 |
max_order = ( |
|
288 |
PricingCriteriaCategory.objects.filter(pricing=self.pricing) |
|
289 |
.aggregate(models.Max('order')) |
|
290 |
.get('order__max') |
|
291 |
or 0 |
|
292 |
) |
|
293 |
self.order = max_order + 1 |
|
294 |
super().save(*args, **kwargs) |
|
295 | ||
296 |
def export_json(self): |
|
297 |
return { |
|
298 |
'category': self.category.slug, |
|
299 |
'order': self.order, |
|
300 |
'criterias': [c.slug for c in self.pricing.criterias.all() if c.category == self.category], |
|
301 |
} |
|
302 | ||
303 |
def duplicate(self, pricing_target): |
|
304 |
new_pcc = copy.deepcopy(self) |
|
305 |
new_pcc.pk = None |
|
306 |
new_pcc.pricing = pricing_target |
|
307 |
new_pcc.save() |
|
308 |
return new_pcc |
|
309 | ||
310 | ||
311 |
@dataclasses.dataclass |
|
312 |
class PricingMatrixCell: |
|
313 |
criteria: Criteria |
|
314 |
value: decimal.Decimal |
|
315 | ||
316 | ||
317 |
@dataclasses.dataclass |
|
318 |
class PricingMatrixRow: |
|
319 |
criteria: Criteria |
|
320 |
cells: List[PricingMatrixCell] |
|
321 | ||
322 | ||
323 |
@dataclasses.dataclass |
|
324 |
class PricingMatrix: |
|
325 |
criteria: Criteria |
|
326 |
rows: List[PricingMatrixRow] |
|
327 | ||
328 | ||
329 |
class AgendaPricing(models.Model): |
|
330 |
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE) |
|
331 |
pricing = models.ForeignKey(Pricing, on_delete=models.CASCADE) |
|
332 |
date_start = models.DateField() |
|
333 |
date_end = models.DateField() |
|
334 |
pricing_data = JSONField(null=True) |
|
335 | ||
336 |
@classmethod |
|
337 |
def import_json(cls, data): |
|
338 |
data = clean_import_data(cls, data) |
|
339 |
cls.objects.update_or_create( |
|
340 |
agenda=data['agenda'], |
|
341 |
pricing=data['pricing'], |
|
342 |
date_start=data['date_start'], |
|
343 |
date_end=data['date_end'], |
|
344 |
defaults=data, |
|
345 |
) |
|
346 | ||
347 |
def export_json(self): |
|
348 |
return { |
|
349 |
'pricing': self.pricing.slug, |
|
350 |
'date_start': self.date_start.strftime('%Y-%m-%d'), |
|
351 |
'date_end': self.date_end.strftime('%Y-%m-%d'), |
|
352 |
'pricing_data': self.pricing_data, |
|
353 |
} |
|
354 | ||
355 |
@staticmethod |
|
356 |
def get_pricing_data(request, event, user_external_id, adult_external_id): |
|
357 |
agenda_pricing = AgendaPricing.get_agenda_pricing(event) |
|
358 |
context = agenda_pricing.get_pricing_context(request, user_external_id, adult_external_id) |
|
359 |
pricing, criterias = agenda_pricing.compute_pricing(context) |
|
360 |
modifier = agenda_pricing.get_booking_modifier(event, user_external_id) |
|
361 |
return agenda_pricing.aggregate_pricing_data(pricing, criterias, context, modifier) |
|
362 | ||
363 |
def aggregate_pricing_data(self, pricing, criterias, context, modifier): |
|
364 |
if modifier['modifier_type'] == 'fixed': |
|
365 |
pricing_amount = modifier['modifier_fixed'] |
|
366 |
else: |
|
367 |
pricing_amount = pricing * modifier['modifier_rate'] / 100 |
|
368 |
return { |
|
369 |
'pricing': pricing_amount, |
|
370 |
'calculation_details': { |
|
371 |
'pricing': pricing, |
|
372 |
'criterias': criterias, |
|
373 |
'context': context, |
|
374 |
}, |
|
375 |
'booking_details': modifier, |
|
376 |
} |
|
377 | ||
378 |
@staticmethod |
|
379 |
def get_agenda_pricing(event): |
|
380 |
agenda = event.agenda |
|
381 |
try: |
|
382 |
return agenda.agendapricing_set.get( |
|
383 |
date_start__lte=event.start_datetime, |
|
384 |
date_end__gt=event.start_datetime, |
|
385 |
) |
|
386 |
except (AgendaPricing.DoesNotExist, AgendaPricing.MultipleObjectsReturned): |
|
387 |
raise AgendaPricingNotFound |
|
388 | ||
389 |
def get_subscription(self, event, user_external_id): |
|
390 |
try: |
|
391 |
return self.agenda.subscriptions.get( |
|
392 |
user_external_id=user_external_id, |
|
393 |
date_start__lte=event.start_datetime, |
|
394 |
date_end__gt=event.start_datetime, |
|
395 |
) |
|
396 |
except (Subscription.DoesNotExist, Subscription.MultipleObjectsReturned): |
|
397 |
raise PricingSubscriptionError |
|
398 | ||
399 |
def get_pricing_context(self, request, user_external_id, adult_external_id): |
|
400 |
context = {'user_external_id': user_external_id, 'adult_external_id': adult_external_id} |
|
401 |
if ':' in user_external_id: |
|
402 |
context['user_external_raw_id'] = user_external_id.split(':')[1] |
|
403 |
if ':' in adult_external_id: |
|
404 |
context['adult_external_raw_id'] = adult_external_id.split(':')[1] |
|
405 |
return self.pricing.get_extra_variables(request, context) |
|
406 | ||
407 |
def compute_pricing(self, context): |
|
408 |
criterias = {} |
|
409 |
categories = [] |
|
410 |
# for each category (ordered) |
|
411 |
for category in self.pricing.categories.all().order_by('pricingcriteriacategory__order'): |
|
412 |
criterias[category.slug] = None |
|
413 |
categories.append(category.slug) |
|
414 |
# find the first matching criteria (criterias are ordered) |
|
415 |
for criteria in self.pricing.criterias.filter(category=category): |
|
416 |
condition = criteria.compute_condition(context) |
|
417 |
if condition: |
|
418 |
criterias[category.slug] = criteria.slug |
|
419 |
break |
|
420 | ||
421 |
# now search for pricing values matching found criterias |
|
422 |
pricing_data = self.pricing_data |
|
423 |
# for each category (ordered) |
|
424 |
for category in categories: |
|
425 |
criteria = criterias[category] |
|
426 |
if criteria is None: |
|
427 |
raise CriteriaConditionNotFound(details={'category': category}) |
|
428 |
if not isinstance(pricing_data, dict): |
|
429 |
raise PricingDataFormatError( |
|
430 |
details={'category': category, 'pricing': pricing_data, 'wanted': 'dict'} |
|
431 |
) |
|
432 |
key = '%s:%s' % (category, criteria) |
|
433 |
if key not in pricing_data: |
|
434 |
raise PricingDataError(details={'category': category, 'criteria': criteria}) |
|
435 |
pricing_data = pricing_data[key] |
|
436 | ||
437 |
try: |
|
438 |
pricing = decimal.Decimal(pricing_data) |
|
439 |
except (decimal.InvalidOperation, ValueError, TypeError): |
|
440 |
raise PricingDataFormatError(details={'pricing': pricing_data, 'wanted': 'decimal'}) |
|
441 | ||
442 |
return pricing, criterias |
|
443 | ||
444 |
def get_booking_modifier(self, event, user_external_id): |
|
445 |
# event must be checked |
|
446 |
if event.checked is False: |
|
447 |
raise PricingEventNotCheckedError |
|
448 | ||
449 |
# search for an available subscription |
|
450 |
self.get_subscription(event, user_external_id) |
|
451 | ||
452 |
# search for a booking |
|
453 |
try: |
|
454 |
booking = event.booking_set.get(user_external_id=user_external_id) |
|
455 |
except Booking.DoesNotExist: |
|
456 |
# no booking |
|
457 |
return { |
|
458 |
'status': 'not-booked', |
|
459 |
'modifier_type': 'rate', |
|
460 |
'modifier_rate': 0, |
|
461 |
} |
|
462 |
except Booking.MultipleObjectsReturned: |
|
463 |
raise PricingMultipleBookingError |
|
464 | ||
465 |
# booking cancelled |
|
466 |
if booking.cancellation_datetime is not None: |
|
467 |
return { |
|
468 |
'status': 'cancelled', |
|
469 |
'modifier_type': 'rate', |
|
470 |
'modifier_rate': 0, |
|
471 |
} |
|
472 | ||
473 |
# booking not cancelled, is must be checked |
|
474 |
if booking.user_was_present is None: |
|
475 |
raise PricingBookingNotCheckedError |
|
476 | ||
477 |
status = 'presence' if booking.user_was_present else 'absence' |
|
478 |
# no check_type, default rates |
|
479 |
if booking.user_check_type is None: |
|
480 |
return { |
|
481 |
'status': status, |
|
482 |
'check_type_group': None, |
|
483 |
'check_type': None, |
|
484 |
'modifier_type': 'rate', |
|
485 |
'modifier_rate': 100 if booking.user_was_present else 0, |
|
486 |
} |
|
487 | ||
488 |
check_type = booking.user_check_type |
|
489 |
kind_mapping = {'presence': True, 'absence': False} |
|
490 |
# check_type kind and user_was_present mismatch |
|
491 |
if kind_mapping[check_type.kind] != booking.user_was_present: |
|
492 |
raise PricingBookingCheckTypeError( |
|
493 |
details={ |
|
494 |
'check_type_group': check_type.group.slug, |
|
495 |
'check_type': check_type.slug, |
|
496 |
'reason': 'wrong-kind', |
|
497 |
} |
|
498 |
) |
|
499 | ||
500 |
# get pricing modifier |
|
501 |
if check_type.pricing is not None: |
|
502 |
return { |
|
503 |
'status': status, |
|
504 |
'check_type_group': check_type.group.slug, |
|
505 |
'check_type': check_type.slug, |
|
506 |
'modifier_type': 'fixed', |
|
507 |
'modifier_fixed': check_type.pricing, |
|
508 |
} |
|
509 |
if check_type.pricing_rate is not None: |
|
510 |
return { |
|
511 |
'status': status, |
|
512 |
'check_type_group': check_type.group.slug, |
|
513 |
'check_type': check_type.slug, |
|
514 |
'modifier_type': 'rate', |
|
515 |
'modifier_rate': check_type.pricing_rate, |
|
516 |
} |
|
517 |
# pricing not found |
|
518 |
raise PricingBookingCheckTypeError( |
|
519 |
details={ |
|
520 |
'check_type_group': check_type.group.slug, |
|
521 |
'check_type': check_type.slug, |
|
522 |
'reason': 'not-configured', |
|
523 |
} |
|
524 |
) |
|
525 | ||
526 |
def iter_pricing_matrix(self): |
|
527 |
categories = self.pricing.categories.all().order_by('pricingcriteriacategory__order')[:3] |
|
528 |
pricing_data = self.pricing_data or {} |
|
529 | ||
530 |
if not categories: |
|
531 |
return |
|
532 | ||
533 |
if len(categories) < 3: |
|
534 |
yield self.get_pricing_matrix( |
|
535 |
main_criteria=None, categories=categories, pricing_data=pricing_data |
|
536 |
) |
|
537 |
return |
|
538 | ||
539 |
# criterias are ordered |
|
540 |
for criteria in self.pricing.criterias.all(): |
|
541 |
if criteria.category != categories[0]: |
|
542 |
continue |
|
543 |
yield self.get_pricing_matrix( |
|
544 |
main_criteria=criteria, |
|
545 |
categories=categories[1:], |
|
546 |
pricing_data=pricing_data.get(criteria.identifier) or {}, |
|
547 |
) |
|
548 | ||
549 |
def get_pricing_matrix(self, main_criteria, categories, pricing_data): |
|
550 |
matrix = PricingMatrix( |
|
551 |
criteria=main_criteria, |
|
552 |
rows=[], |
|
553 |
) |
|
554 | ||
555 |
def get_pricing_matrix_cell(criteria_2, criteria_3, _pricing_data): |
|
556 |
try: |
|
557 |
value = decimal.Decimal(str(_pricing_data.get(criteria_3.identifier))) |
|
558 |
except (decimal.InvalidOperation, ValueError, TypeError): |
|
559 |
value = None |
|
560 |
return PricingMatrixCell(criteria=criteria_2, value=value) |
|
561 | ||
562 |
if len(categories) < 2: |
|
563 |
rows = [ |
|
564 |
PricingMatrixRow( |
|
565 |
criteria=criteria, |
|
566 |
cells=[get_pricing_matrix_cell(None, criteria, pricing_data)], |
|
567 |
) |
|
568 |
for criteria in self.pricing.criterias.all() |
|
569 |
if criteria.category == categories[0] |
|
570 |
] |
|
571 |
matrix.rows = rows |
|
572 |
else: |
|
573 |
criterias_2 = [c for c in self.pricing.criterias.all() if c.category == categories[0]] |
|
574 |
criterias_3 = [c for c in self.pricing.criterias.all() if c.category == categories[1]] |
|
575 | ||
576 |
rows = [ |
|
577 |
PricingMatrixRow( |
|
578 |
criteria=criteria_3, |
|
579 |
cells=[ |
|
580 |
get_pricing_matrix_cell( |
|
581 |
criteria_2, |
|
582 |
criteria_3, |
|
583 |
pricing_data.get(criteria_2.identifier) or {}, |
|
584 |
) |
|
585 |
for criteria_2 in criterias_2 |
|
586 |
], |
|
587 |
) |
|
588 |
for criteria_3 in criterias_3 |
|
589 |
] |
|
590 |
matrix.rows = rows |
|
591 | ||
592 |
return matrix |
chrono/pricing/templates/chrono/pricing/manager_agenda_pricing_detail.html | ||
---|---|---|
1 |
{% extends "chrono/manager_agenda_settings.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'chrono-manager-agenda-pricing-detail' agenda.pk object.pk %}">{{ object.pricing }}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2> |
|
11 |
{{ object.pricing }} ({{ object.date_start|date:'d/m/Y' }} - {{ object.date_end|date:'d/m/Y' }}) |
|
12 |
</h2> |
|
13 |
{% if user_can_manage %} |
|
14 |
<span class="actions"> |
|
15 |
<a class="extra-actions-menu-opener"></a> |
|
16 |
<ul class="extra-actions-menu"> |
|
17 |
<li><a rel="popup" href="{% url 'chrono-manager-agenda-pricing-edit' agenda.pk object.pk %}">{% trans 'Options' %}</a></li> |
|
18 |
<li><a rel="popup" href="{% url 'chrono-manager-agenda-pricing-delete' agenda.pk object.pk %}">{% trans 'Delete' %}</a></li> |
|
19 |
</ul> |
|
20 |
</span> |
|
21 |
{% endif %} |
|
22 |
{% endblock %} |
|
23 | ||
24 |
{% block content %} |
|
25 |
{% for matrix in object.iter_pricing_matrix %} |
|
26 |
<div class="section"> |
|
27 |
{% if matrix.criteria %}<h3>{{ matrix.criteria.label }}</h3>{% endif %} |
|
28 |
<div> |
|
29 |
<table class="main pricing-matrix-{{ matrix.criteria.slug }}"> |
|
30 |
{% if matrix.rows.0.cells.0.criteria %} |
|
31 |
<thead> |
|
32 |
<tr> |
|
33 |
<th></th> |
|
34 |
{% for cell in matrix.rows.0.cells %}<th scope="col">{{ cell.criteria.label }}</th>{% endfor %} |
|
35 |
</tr> |
|
36 |
</thead> |
|
37 |
{% endif %} |
|
38 |
<tbody> |
|
39 |
{% for row in matrix.rows %} |
|
40 |
<tr class="pricing-row-{{ row.criteria.slug }}"> |
|
41 |
<th scope="row">{{ row.criteria.label }}</th> |
|
42 |
{% for cell in row.cells %}<td class="pricing-cell-{{ cell.criteria.slug }}">{{ cell.value|floatformat:"2"|default_if_none:"" }}</td>{% endfor %} |
|
43 |
</tr> |
|
44 |
{% endfor %} |
|
45 |
</tbody> |
|
46 |
</table> |
|
47 |
{% if user_can_manage %} |
|
48 |
<p> |
|
49 |
<a class="pk-button" href="{% if matrix.criteria %}{% url 'chrono-manager-agenda-pricing-matrix-slug-edit' agenda.pk object.pk matrix.criteria.slug %}{% else %}{% url 'chrono-manager-agenda-pricing-matrix-edit' agenda.pk object.pk %}{% endif %}">{% trans "Edit pricing" %}</a> |
|
50 |
</p> |
|
51 |
{% endif %} |
|
52 |
</div> |
|
53 |
</div> |
|
54 |
{% empty %} |
|
55 |
<div class="big-msg-info"> |
|
56 |
{% blocktrans %} |
|
57 |
This pricing model is misconfigured. |
|
58 |
{% endblocktrans %} |
|
59 |
</div> |
|
60 |
{% endfor %} |
|
61 |
{% endblock %} |
chrono/pricing/templates/chrono/pricing/manager_agenda_pricing_form.html | ||
---|---|---|
1 |
{% extends "chrono/manager_agenda_settings.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
{% if form.instance.pk %} |
|
7 |
<a href="{% url 'chrono-manager-agenda-pricing-detail' agenda.pk object.pk %}">{{ object.pricing }}</a> |
|
8 |
<a href="{% url 'chrono-manager-agenda-pricing-edit' agenda.pk object.pk %}">{% trans "Edit" %}</a> |
|
9 |
{% else %} |
|
10 |
<a href="{% url 'chrono-manager-agenda-pricing-add' agenda.pk %}">{% trans "New pricing" %}</a> |
|
11 |
{% endif %} |
|
12 |
{% endblock %} |
|
13 | ||
14 |
{% block appbar %} |
|
15 |
{% if object.pk %} |
|
16 |
<h2>{% trans "Edit pricing" %}</h2> |
|
17 |
{% else %} |
|
18 |
<h2>{% trans "New pricing" %}</h2> |
|
19 |
{% endif %} |
|
20 |
{% endblock %} |
|
21 | ||
22 |
{% block content %} |
|
23 |
<form method="post" enctype="multipart/form-data"> |
|
24 |
{% csrf_token %} |
|
25 |
{{ form.as_p }} |
|
26 |
<div class="buttons"> |
|
27 |
<button class="submit-button">{% trans "Save" %}</button> |
|
28 |
{% if object.pk %} |
|
29 |
<a class="cancel" href="{% url 'chrono-manager-agenda-pricing-detail' agenda.pk object.pk %}">{% trans 'Cancel' %}</a> |
|
30 |
{% else %} |
|
31 |
<a class="cancel" href="{% url 'chrono-manager-agenda-settings' agenda.pk %}">{% trans 'Cancel' %}</a> |
|
32 |
{% endif %} |
|
33 |
</div> |
|
34 |
</form> |
|
35 |
{% endblock %} |
chrono/pricing/templates/chrono/pricing/manager_agenda_pricing_matrix_form.html | ||
---|---|---|
1 |
{% extends "chrono/pricing/manager_agenda_pricing_detail.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
{% if matrix.criteria %} |
|
7 |
<a href="{% url 'chrono-manager-agenda-pricing-matrix-slug-edit' agenda.pk object.pk matrix.criteria.slug %}">{% trans "Edit pricing" %}</a> |
|
8 |
{% else %} |
|
9 |
<a href="{% url 'chrono-manager-agenda-pricing-matrix-edit' agenda.pk object.pk %}">{% trans "Edit pricing" %}</a> |
|
10 |
{% endif %} |
|
11 |
{% endblock %} |
|
12 | ||
13 |
{% block appbar %} |
|
14 |
<h2>{% trans "Edit pricing" %}</h2> |
|
15 |
{% endblock %} |
|
16 | ||
17 |
{% block content %} |
|
18 |
<div class="section"> |
|
19 |
{% if matrix.criteria %}<h3>{{ matrix.criteria.label }}</h3>{% endif %} |
|
20 |
<div> |
|
21 |
<form method="post" enctype="multipart/form-data"> |
|
22 |
{% csrf_token %} |
|
23 |
{{ form.management_form }} |
|
24 |
<table class="main"> |
|
25 |
{% if matrix.rows.0.cells.0.criteria %} |
|
26 |
<thead> |
|
27 |
<tr> |
|
28 |
<th></th> |
|
29 |
{% for cell in matrix.rows.0.cells %} |
|
30 |
<th>{{ cell.criteria.label }}</th> |
|
31 |
{% endfor %} |
|
32 |
</tr> |
|
33 |
</thead> |
|
34 |
{% endif %} |
|
35 |
<tbody> |
|
36 |
{% for sub_form in form %} |
|
37 |
{% with row=matrix.rows|get:forloop.counter0 %} |
|
38 |
<tr> |
|
39 |
<th>{{ row.criteria.label }}</th> |
|
40 |
{% for field in sub_form %} |
|
41 |
<td> |
|
42 |
{{ field.errors.as_ul }} |
|
43 |
{{ field }} |
|
44 |
</td> |
|
45 |
{% endfor %} |
|
46 |
</tr> |
|
47 |
{% endwith %} |
|
48 |
{% endfor %} |
|
49 |
</tbody> |
|
50 |
</table> |
|
51 |
<div class="buttons"> |
|
52 |
<button class="submit-button">{% trans "Save" %}</button> |
|
53 |
<a class="cancel" href="{% url 'chrono-manager-agenda-pricing-detail' agenda.pk object.pk %}">{% trans 'Cancel' %}</a> |
|
54 |
</div> |
|
55 |
</form> |
|
56 |
</div> |
|
57 |
</div> |
|
58 |
{% endblock %} |
chrono/pricing/templates/chrono/pricing/manager_criteria_category_form.html | ||
---|---|---|
1 |
{% extends "chrono/pricing/manager_criteria_list.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
{% if object.pk %} |
|
7 |
<a href="{% url 'chrono-manager-pricing-criteria-category-edit' object.pk %}">{{ object }}</a> |
|
8 |
{% else %} |
|
9 |
<a href="{% url 'chrono-manager-pricing-criteria-category-add' %}">{% trans "New category" %}</a> |
|
10 |
{% endif %} |
|
11 |
{% endblock %} |
|
12 | ||
13 |
{% block appbar %} |
|
14 |
{% if object.pk %} |
|
15 |
<h2>{% trans "Edit category" %}</h2> |
|
16 |
{% else %} |
|
17 |
<h2>{% trans "New category" %}</h2> |
|
18 |
{% endif %} |
|
19 |
{% endblock %} |
|
20 | ||
21 |
{% block content %} |
|
22 | ||
23 |
<form method="post" enctype="multipart/form-data"> |
|
24 |
{% csrf_token %} |
|
25 |
{{ form.as_p }} |
|
26 |
<div class="buttons"> |
|
27 |
<button class="submit-button">{% trans "Save" %}</button> |
|
28 |
<a class="cancel" href="{% url 'chrono-manager-pricing-criteria-list' %}">{% trans 'Cancel' %}</a> |
|
29 |
</div> |
|
30 |
</form> |
|
31 |
{% endblock %} |
chrono/pricing/templates/chrono/pricing/manager_criteria_form.html | ||
---|---|---|
1 |
{% extends "chrono/pricing/manager_criteria_list.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
{% if form.instance.pk %} |
|
7 |
<a href="{% url 'chrono-manager-pricing-criteria-edit' form.instance.category_id form.instance.pk %}">{{ form.instance }}</a> |
|
8 |
{% else %} |
|
9 |
<a href="{% url 'chrono-manager-pricing-criteria-add' form.instance.category_id %}">{% trans "New criteria" %}</a> |
|
10 |
{% endif %} |
|
11 |
{% endblock %} |
|
12 | ||
13 |
{% block appbar %} |
|
14 |
{% if form.instance.pk %} |
|
15 |
<h2>{{ form.instance.category }} - {% trans "Edit criteria" %}</h2> |
|
16 |
{% else %} |
|
17 |
<h2>{{ form.instance.category }} - {% trans "New criteria" %}</h2> |
|
18 |
{% endif %} |
|
19 |
{% endblock %} |
|
20 | ||
21 |
{% block content %} |
|
22 | ||
23 |
<form method="post" enctype="multipart/form-data"> |
|
24 |
{% csrf_token %} |
|
25 |
{{ form.as_p }} |
|
26 |
<div class="buttons"> |
|
27 |
<button class="submit-button">{% trans "Save" %}</button> |
|
28 |
<a class="cancel" href="{% url 'chrono-manager-pricing-criteria-list' %}">{% trans 'Cancel' %}</a> |
|
29 |
</div> |
|
30 |
</form> |
|
31 |
{% endblock %} |
chrono/pricing/templates/chrono/pricing/manager_criteria_list.html | ||
---|---|---|
1 |
{% extends "chrono/pricing/manager_pricing_list.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'chrono-manager-pricing-criteria-list' %}">{% trans "Criterias" %}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2>{% trans 'Criterias' %}</h2> |
|
11 |
<span class="actions"> |
|
12 |
<a rel="popup" href="{% url 'chrono-manager-pricing-criteria-category-add' %}">{% trans 'New category' %}</a> |
|
13 |
</span> |
|
14 |
{% endblock %} |
|
15 | ||
16 | ||
17 |
{% block content %} |
|
18 |
<div class="pk-information"> |
|
19 |
<p>{% trans "Define here pricing criterias used in pricing models." %}</p> |
|
20 |
</div> |
|
21 |
{% if object_list %} |
|
22 |
<p class="hint"> |
|
23 |
{% blocktrans %} |
|
24 |
Use drag and drop with the ⣿ handles to reorder criterias inside a category. |
|
25 |
{% endblocktrans %} |
|
26 |
</p> |
|
27 |
{% endif %} |
|
28 |
{% for object in object_list %} |
|
29 |
<div class="section criteria-category"> |
|
30 |
<h3> |
|
31 |
<a rel="popup" href="{% url 'chrono-manager-pricing-criteria-category-edit' object.pk %}">{{ object }} [{{ object.slug }}]</a> |
|
32 |
<span> |
|
33 |
<a class="button" href="{% url 'chrono-manager-pricing-criteria-category-export' object.pk %}">{% trans "Export"%}</a> |
|
34 |
<a class="button" rel="popup" href="{% url 'chrono-manager-pricing-criteria-category-delete' object.pk %}">{% trans "Delete"%}</a> |
|
35 |
</span> |
|
36 |
</h3> |
|
37 |
<div> |
|
38 |
<ul class="objects-list single-links sortable" data-order-url="{% url 'chrono-manager-pricing-criteria-order' object.pk %}"> |
|
39 |
{% for criteria in object.criterias.all %} |
|
40 |
<li class="sortable-item" data-item-id="{{ criteria.pk }}"> |
|
41 |
<span class="handle">⣿</span> |
|
42 |
<a rel="popup" href="{% url 'chrono-manager-pricing-criteria-edit' object.pk criteria.pk %}">{{ criteria }}</a> |
|
43 |
<a class="delete" rel="popup" href="{% url 'chrono-manager-pricing-criteria-delete' object.pk criteria.pk %}">{% trans "delete"%}</a> |
|
44 |
</li> |
|
45 |
{% endfor %} |
|
46 |
<li><a class="add" rel="popup" href="{% url 'chrono-manager-pricing-criteria-add' object.pk %}">{% trans "Add a criteria" %}</a></li> |
|
47 |
</ul> |
|
48 |
</div> |
|
49 |
</div> |
|
50 |
{% empty %} |
|
51 |
<div class="big-msg-info"> |
|
52 |
{% blocktrans %} |
|
53 |
This site doesn't have any pricing category yet. Click on the "New category" button in the top |
|
54 |
right of the page to add a first one. |
|
55 |
{% endblocktrans %} |
|
56 |
</div> |
|
57 |
{% endfor %} |
|
58 |
{% endblock %} |
chrono/pricing/templates/chrono/pricing/manager_pricing_criteria_category_form.html | ||
---|---|---|
1 |
{% extends "chrono/pricing/manager_pricing_list.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'chrono-manager-pricing-detail' object.pk %}">{{ object }}</a> |
|
7 |
{% if category %} |
|
8 |
<a href="{% url 'chrono-manager-pricing-criteria-category-edit' object.pk category.pk %}">{% trans "Select criterias" %}</a> |
|
9 |
{% else %} |
|
10 |
<a href="{% url 'chrono-manager-pricing-criteria-category-add' object.pk %}">{% trans "Add a category" %}</a> |
|
11 |
{% endif %} |
|
12 |
{% endblock %} |
|
13 | ||
14 |
{% block appbar %} |
|
15 |
{% if category %} |
|
16 |
<h2>{% trans "Select criterias" %}</h2> |
|
17 |
{% else %} |
|
18 |
<h2>{% trans "Add a category" %}</h2> |
|
19 |
{% endif %} |
|
20 |
{% endblock %} |
|
21 | ||
22 |
{% block content %} |
|
23 |
<form method="post" enctype="multipart/form-data"> |
|
24 |
{% csrf_token %} |
|
25 |
{{ form.as_p }} |
|
26 |
<div class="buttons"> |
|
27 |
<button class="submit-button">{% trans "Save" %}</button> |
|
28 |
<a class="cancel" href="{% url 'chrono-manager-pricing-detail' object.pk %}">{% trans 'Cancel' %}</a> |
|
29 |
</div> |
|
30 |
</form> |
|
31 |
{% endblock %} |
chrono/pricing/templates/chrono/pricing/manager_pricing_detail.html | ||
---|---|---|
1 |
{% extends "chrono/pricing/manager_pricing_list.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'chrono-manager-pricing-detail' object.pk %}">{{ object }}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2> |
|
11 |
{{ object }} |
|
12 |
<span class="identifier">[{% trans "identifier:" %} {{ object.slug }}]</span> |
|
13 |
</h2> |
|
14 |
<span class="actions"> |
|
15 |
<a class="extra-actions-menu-opener"></a> |
|
16 |
<ul class="extra-actions-menu"> |
|
17 |
<li><a rel="popup" href="{% url 'chrono-manager-pricing-edit' pk=object.pk %}">{% trans 'Options' %}</a></li> |
|
18 |
<li><a rel="popup" class="action-duplicate" href="{% url 'chrono-manager-pricing-duplicate' pk=object.pk %}">{% trans 'Duplicate' %}</a></li> |
|
19 |
<li><a href="{% url 'chrono-manager-pricing-export' pk=object.pk %}">{% trans 'Export' %}</a></li> |
|
20 |
<li><a rel="popup" href="{% url 'chrono-manager-pricing-delete' pk=object.pk %}">{% trans 'Delete' %}</a></li> |
|
21 |
</ul> |
|
22 |
</span> |
|
23 |
{% endblock %} |
|
24 | ||
25 |
{% block content %} |
|
26 |
<div class="section"> |
|
27 |
<h3>{% trans "Variables" %}</h3> |
|
28 | ||
29 |
<div> |
|
30 |
{% if object.extra_variables %} |
|
31 |
<p> |
|
32 |
<label>{% trans 'Extra variables:' %}</label> |
|
33 |
{% for key in object.get_extra_variables_keys %}<i>{{ key }}</i>{% if not forloop.last %}, {% endif %}{% endfor %} |
|
34 |
</p> |
|
35 |
{% endif %} |
|
36 |
<a class="pk-button" rel="popup" href="{% url 'chrono-manager-pricing-variable-edit' pk=object.pk %}">{% trans 'Define variables' %}</a> |
|
37 |
</div> |
|
38 |
</div> |
|
39 | ||
40 |
<div class="section"> |
|
41 |
<h3>{% trans "Criterias" %}</h3> |
|
42 |
{% with criterias=object.criterias.all categories=object.categories.all %} |
|
43 |
{% if categories %} |
|
44 |
<div> |
|
45 |
{% blocktrans %}Use drag and drop with the ⣿ handles to reorder categories.{% endblocktrans %} |
|
46 |
</div> |
|
47 |
{% endif %} |
|
48 |
<div class="sortable" data-order-url="{% url 'chrono-manager-pricing-criteria-category-order' object.pk %}"> |
|
49 |
{% for category in categories %} |
|
50 |
<div class="paragraph sortable-item" data-item-id="{{ category.pk }}"> |
|
51 |
<h4><span class="handle">⣿</span>{% trans "Category:" %} {{ category }}</h4> |
|
52 |
<p>{% trans "Selected criterias:" %}</p> |
|
53 |
<ul> |
|
54 |
{% for criteria in criterias %} |
|
55 |
{% if criteria.category == category %} |
|
56 |
<li>{{ criteria }}</li> |
|
57 |
{% endif %} |
|
58 |
{% endfor %} |
|
59 |
</ul> |
|
60 |
<p> |
|
61 |
<a rel="popup" class="pk-button" href="{% url 'chrono-manager-pricing-criteria-category-edit' pk=object.pk category_pk=category.pk %}">{% trans "Change criterias selection" %}</a> |
|
62 |
<a rel="popup" class="pk-button" href="{% url 'chrono-manager-pricing-criteria-category-delete' pk=object.pk category_pk=category.pk %}">{% trans "Remove this category" %}</a> |
|
63 |
</p> |
|
64 |
</div> |
|
65 |
{% endfor %} |
|
66 |
{% if object.categories.count < 3 %} |
|
67 |
<p> |
|
68 |
<a rel="popup" class="pk-button" href="{% url 'chrono-manager-pricing-criteria-category-add' pk=object.pk %}">{% trans "Add a category" %} <span class="extra-info">({% trans "max 3 categories" %})</span></a> |
|
69 |
</p> |
|
70 |
{% endif %} |
|
71 |
{% endwith %} |
|
72 |
</div> |
|
73 |
</div> |
|
74 |
{% endblock %} |
chrono/pricing/templates/chrono/pricing/manager_pricing_duplicate_form.html | ||
---|---|---|
1 |
{% extends "chrono/pricing/manager_pricing_detail.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'chrono-manager-pricing-duplicate' object.pk %}">{% trans "Duplicate pricing model" %}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2>{% trans "Duplicate pricing model" %}</h2> |
|
11 |
{% endblock %} |
|
12 | ||
13 |
{% block content %} |
|
14 |
<form method="post" enctype="multipart/form-data"> |
|
15 |
{% csrf_token %} |
|
16 |
{{ form.as_p }} |
|
17 |
<div class="buttons"> |
|
18 |
<button class="submit-button">{% trans "Duplicate" %}</button> |
|
19 |
<a class="cancel" href="{% url 'chrono-manager-pricing-detail' object.pk %}">{% trans 'Cancel' %}</a> |
|
20 |
</div> |
|
21 |
</form> |
|
22 |
{% endblock %} |
chrono/pricing/templates/chrono/pricing/manager_pricing_form.html | ||
---|---|---|
1 |
{% extends "chrono/pricing/manager_pricing_list.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
{% if object.pk %} |
|
7 |
<a href="{% url 'chrono-manager-pricing-detail' object.pk %}">{{ object }}</a> |
|
8 |
<a href="{% url 'chrono-manager-pricing-edit' object.pk %}">{% trans "Edit" %}</a> |
|
9 |
{% else %} |
|
10 |
<a href="{% url 'chrono-manager-pricing-add' %}">{% trans "New pricing model" %}</a> |
|
11 |
{% endif %} |
|
12 |
{% endblock %} |
|
13 | ||
14 |
{% block appbar %} |
|
15 |
{% if object.pk %} |
|
16 |
<h2>{% trans "Edit pricing model" %}</h2> |
|
17 |
{% else %} |
|
18 |
<h2>{% trans "New pricing model" %}</h2> |
|
19 |
{% endif %} |
|
20 |
{% endblock %} |
|
21 | ||
22 |
{% block content %} |
|
23 |
<form method="post" enctype="multipart/form-data"> |
|
24 |
{% csrf_token %} |
|
25 |
{{ form.as_p }} |
|
26 |
<div class="buttons"> |
|
27 |
<button class="submit-button">{% trans "Save" %}</button> |
|
28 |
<a class="cancel" href="{% url 'chrono-manager-pricing-list' %}">{% trans 'Cancel' %}</a> |
|
29 |
</div> |
|
30 |
</form> |
|
31 |
{% endblock %} |
chrono/pricing/templates/chrono/pricing/manager_pricing_list.html | ||
---|---|---|
1 |
{% extends "chrono/manager_base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'chrono-manager-pricing-list' %}">{% trans "Pricing" context 'pricing' %}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2>{% trans 'Pricing' context 'pricing' %}</h2> |
|
11 |
<span class="actions"> |
|
12 |
<a href="{% url 'chrono-manager-pricing-criteria-list' %}">{% trans 'Criterias' %}</a> |
|
13 |
<a rel="popup" href="{% url 'chrono-manager-pricing-add' %}">{% trans 'New pricing model' %}</a> |
|
14 |
</span> |
|
15 |
{% endblock %} |
|
16 | ||
17 |
{% block content %} |
|
18 |
<div class="pk-information"> |
|
19 |
<p>{% trans "Define here pricing models used in events agendas." %}</p> |
|
20 |
</div> |
|
21 |
{% if object_list %} |
|
22 |
<div> |
|
23 |
<ul class="objects-list single-links"> |
|
24 |
{% for object in object_list %} |
|
25 |
<li> |
|
26 |
<a href="{% url 'chrono-manager-pricing-detail' pk=object.pk %}">{{ object.label }} ({{ object.slug }})</a> |
|
27 |
</li> |
|
28 |
{% endfor %} |
|
29 |
</ul> |
|
30 |
</div> |
|
31 |
{% else %} |
|
32 |
<div class="big-msg-info"> |
|
33 |
{% blocktrans %} |
|
34 |
This site doesn't have any pricing model yet. Click on the "New" button in the top |
|
35 |
right of the page to add a first one. |
|
36 |
{% endblocktrans %} |
|
37 |
</div> |
|
38 |
{% endif %} |
|
39 |
{% endblock %} |
chrono/pricing/templates/chrono/pricing/manager_pricing_variable_form.html | ||
---|---|---|
1 |
{% extends "chrono/pricing/manager_pricing_detail.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'chrono-manager-pricing-variable-edit' object.pk %}">{% trans "Variable definition" %}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2>{% trans "Variable definition" %}</h2> |
|
11 |
{% endblock %} |
|
12 | ||
13 |
{% block content %} |
|
14 |
<form method="post" enctype="multipart/form-data"> |
|
15 |
{% csrf_token %} |
|
16 |
{{ form.management_form }} |
|
17 |
<table id="pricing-variable-forms"> |
|
18 |
<thead> |
|
19 |
<tr> |
|
20 |
{% for field in form.0 %} |
|
21 |
<th class="column-{{ field.name }}{% if field.required %} required{% endif %}">{{ field.label }}</th> |
|
22 |
{% endfor %} |
|
23 |
</tr> |
|
24 |
</thead> |
|
25 |
<tbody> |
|
26 |
{% for sub_form in form %} |
|
27 |
<tr class='pricing-variable-form'> |
|
28 |
{% for field in sub_form %} |
|
29 |
<td class="field-{{ field.name }}"> |
|
30 |
{{ field.errors.as_ul }} |
|
31 |
{{ field }} |
|
32 |
</td> |
|
33 |
{% endfor %} |
|
34 |
</tr> |
|
35 |
{% endfor %} |
|
36 |
</tbody> |
|
37 |
</table> |
|
38 |
<button id="add-pricing-variable-form" type="button">{% trans "Add another variable" %}</button> |
|
39 |
<div class="buttons"> |
|
40 |
<button class="submit-button">{% trans "Save" %}</button> |
|
41 |
<a class="cancel" href="{% url 'chrono-manager-pricing-detail' object.pk %}">{% trans 'Cancel' %}</a> |
|
42 |
</div> |
|
43 |
</form> |
|
44 |
{% endblock %} |
chrono/pricing/urls.py | ||
---|---|---|
1 |
# chrono - agendas system |
|
2 |
# Copyright (C) 2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from django.conf.urls import url |
|
18 | ||
19 |
from . import views |
|
20 | ||
21 |
urlpatterns = [ |
|
22 |
url(r'^$', views.pricing_list, name='chrono-manager-pricing-list'), |
|
23 |
url( |
|
24 |
r'^add/$', |
|
25 |
views.pricing_add, |
|
26 |
name='chrono-manager-pricing-add', |
|
27 |
), |
|
28 |
url( |
|
29 |
r'^(?P<pk>\d+)/$', |
|
30 |
views.pricing_detail, |
|
31 |
name='chrono-manager-pricing-detail', |
|
32 |
), |
|
33 |
url( |
|
34 |
r'^(?P<pk>\d+)/edit/$', |
|
35 |
views.pricing_edit, |
|
36 |
name='chrono-manager-pricing-edit', |
|
37 |
), |
|
38 |
url( |
|
39 |
r'^(?P<pk>\d+)/delete/$', |
|
40 |
views.pricing_delete, |
|
41 |
name='chrono-manager-pricing-delete', |
|
42 |
), |
|
43 |
url( |
|
44 |
r'^(?P<pk>\d+)/duplicate/$', |
|
45 |
views.pricing_duplicate, |
|
46 |
name='chrono-manager-pricing-duplicate', |
|
47 |
), |
|
48 |
url( |
|
49 |
r'^(?P<pk>\d+)/export/$', |
|
50 |
views.pricing_export, |
|
51 |
name='chrono-manager-pricing-export', |
|
52 |
), |
|
53 |
url( |
|
54 |
r'^(?P<pk>\d+)/variable/$', |
|
55 |
views.pricing_variable_edit, |
|
56 |
name='chrono-manager-pricing-variable-edit', |
|
57 |
), |
|
58 |
url( |
|
59 |
r'^(?P<pk>\d+)/category/add/$', |
|
60 |
views.pricing_criteria_category_add, |
|
61 |
name='chrono-manager-pricing-criteria-category-add', |
|
62 |
), |
|
63 |
url( |
|
64 |
r'^(?P<pk>\d+)/category/(?P<category_pk>\d+)/edit/$', |
|
65 |
views.pricing_criteria_category_edit, |
|
66 |
name='chrono-manager-pricing-criteria-category-edit', |
|
67 |
), |
|
68 |
url( |
|
69 |
r'^(?P<pk>\d+)/category/(?P<category_pk>\d+)/delete/$', |
|
70 |
views.pricing_criteria_category_delete, |
|
71 |
name='chrono-manager-pricing-criteria-category-delete', |
|
72 |
), |
|
73 |
url( |
|
74 |
r'^(?P<pk>\d+)/order/$', |
|
75 |
views.pricing_criteria_category_order, |
|
76 |
name='chrono-manager-pricing-criteria-category-order', |
|
77 |
), |
|
78 |
url(r'^criterias/$', views.criteria_list, name='chrono-manager-pricing-criteria-list'), |
|
79 |
url( |
|
80 |
r'^criteria/category/add/$', |
|
81 |
views.criteria_category_add, |
|
82 |
name='chrono-manager-pricing-criteria-category-add', |
|
83 |
), |
|
84 |
url( |
|
85 |
r'^criteria/category/(?P<pk>\d+)/edit/$', |
|
86 |
views.criteria_category_edit, |
|
87 |
name='chrono-manager-pricing-criteria-category-edit', |
|
88 |
), |
|
89 |
url( |
|
90 |
r'^criteria/category/(?P<pk>\d+)/delete/$', |
|
91 |
views.criteria_category_delete, |
|
92 |
name='chrono-manager-pricing-criteria-category-delete', |
|
93 |
), |
|
94 |
url( |
|
95 |
r'^criteria/category/(?P<pk>\d+)/export/$', |
|
96 |
views.criteria_category_export, |
|
97 |
name='chrono-manager-pricing-criteria-category-export', |
|
98 |
), |
|
99 |
url( |
|
100 |
r'^criteria/category/(?P<pk>\d+)/order/$', |
|
101 |
views.criteria_order, |
|
102 |
name='chrono-manager-pricing-criteria-order', |
|
103 |
), |
|
104 |
url( |
|
105 |
r'^criteria/category/(?P<category_pk>\d+)/add/$', |
|
106 |
views.criteria_add, |
|
107 |
name='chrono-manager-pricing-criteria-add', |
|
108 |
), |
|
109 |
url( |
|
110 |
r'^criteria/category/(?P<category_pk>\d+)/(?P<pk>\d+)/edit/$', |
|
111 |
views.criteria_edit, |
|
112 |
name='chrono-manager-pricing-criteria-edit', |
|
113 |
), |
|
114 |
url( |
|
115 |
r'^criteria/category/(?P<category_pk>\d+)/(?P<pk>\d+)/delete/$', |
|
116 |
views.criteria_delete, |
|
117 |
name='chrono-manager-pricing-criteria-delete', |
|
118 |
), |
|
119 |
url( |
|
120 |
r'^agenda/(?P<pk>\d+)/pricing/add/$', |
|
121 |
views.agenda_pricing_add, |
|
122 |
name='chrono-manager-agenda-pricing-add', |
|
123 |
), |
|
124 |
url( |
|
125 |
r'^agenda/(?P<pk>\d+)/pricing/(?P<pricing_pk>\d+)/$', |
|
126 |
views.agenda_pricing_detail, |
|
127 |
name='chrono-manager-agenda-pricing-detail', |
|
128 |
), |
|
129 |
url( |
|
130 |
r'^agenda/(?P<pk>\d+)/pricing/(?P<pricing_pk>\d+)/edit/$', |
|
131 |
views.agenda_pricing_edit, |
|
132 |
name='chrono-manager-agenda-pricing-edit', |
|
133 |
), |
|
134 |
url( |
|
135 |
r'^agenda/(?P<pk>\d+)/pricing/(?P<pricing_pk>\d+)/delete/$', |
|
136 |
views.agenda_pricing_delete, |
|
137 |
name='chrono-manager-agenda-pricing-delete', |
|
138 |
), |
|
139 |
url( |
|
140 |
r'^agenda/(?P<pk>\d+)/pricing/(?P<pricing_pk>\d+)/matrix/edit/$', |
|
141 |
views.agenda_pricing_matrix_edit, |
|
142 |
name='chrono-manager-agenda-pricing-matrix-edit', |
|
143 |
), |
|
144 |
url( |
|
145 |
r'^agenda/(?P<pk>\d+)/pricing/(?P<pricing_pk>\d+)/matrix/(?P<slug>[-_a-zA-Z0-9]+)/edit/$', |
|
146 |
views.agenda_pricing_matrix_edit, |
|
147 |
name='chrono-manager-agenda-pricing-matrix-slug-edit', |
|
148 |
), |
|
149 |
] |
chrono/pricing/views.py | ||
---|---|---|
1 |
# chrono - agendas system |
|
2 |
# Copyright (C) 2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import datetime |
|
18 |
import json |
|
19 |
from collections import defaultdict |
|
20 |
from operator import itemgetter |
|
21 | ||
22 |
from django import forms |
|
23 |
from django.core.exceptions import PermissionDenied |
|
24 |
from django.db.models import Prefetch |
|
25 |
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect |
|
26 |
from django.shortcuts import get_object_or_404 |
|
27 |
from django.urls import reverse |
|
28 |
from django.views.generic import CreateView, DeleteView, DetailView, FormView, ListView, UpdateView |
|
29 |
from django.views.generic.detail import SingleObjectMixin |
|
30 | ||
31 |
from chrono.agendas.models import Agenda |
|
32 |
from chrono.manager.views import ManagedAgendaMixin, ViewableAgendaMixin |
|
33 |
from chrono.pricing.forms import ( |
|
34 |
AgendaPricingForm, |
|
35 |
CriteriaForm, |
|
36 |
NewCriteriaForm, |
|
37 |
PricingCriteriaCategoryAddForm, |
|
38 |
PricingCriteriaCategoryEditForm, |
|
39 |
PricingDuplicateForm, |
|
40 |
PricingMatrixForm, |
|
41 |
PricingVariableFormSet, |
|
42 |
) |
|
43 |
from chrono.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingCriteriaCategory |
|
44 | ||
45 | ||
46 |
class PricingListView(ListView): |
|
47 |
template_name = 'chrono/pricing/manager_pricing_list.html' |
|
48 |
model = Pricing |
|
49 | ||
50 |
def dispatch(self, request, *args, **kwargs): |
|
51 |
if not request.user.is_staff: |
|
52 |
raise PermissionDenied() |
|
53 |
return super().dispatch(request, *args, **kwargs) |
|
54 | ||
55 | ||
56 |
pricing_list = PricingListView.as_view() |
|
57 | ||
58 | ||
59 |
class CriteriaListView(ListView): |
|
60 |
template_name = 'chrono/pricing/manager_criteria_list.html' |
|
61 |
model = CriteriaCategory |
|
62 | ||
63 |
def dispatch(self, request, *args, **kwargs): |
|
64 |
if not request.user.is_staff: |
|
65 |
raise PermissionDenied() |
|
66 |
return super().dispatch(request, *args, **kwargs) |
|
67 | ||
68 |
def get_queryset(self): |
|
69 |
return CriteriaCategory.objects.prefetch_related('criterias') |
|
70 | ||
71 | ||
72 |
criteria_list = CriteriaListView.as_view() |
|
73 | ||
74 | ||
75 |
class PricingAddView(CreateView): |
|
76 |
template_name = 'chrono/pricing/manager_pricing_form.html' |
|
77 |
model = Pricing |
|
78 |
fields = ['label'] |
|
79 | ||
80 |
def dispatch(self, request, *args, **kwargs): |
|
81 |
if not request.user.is_staff: |
|
82 |
raise PermissionDenied() |
|
83 |
return super().dispatch(request, *args, **kwargs) |
|
84 | ||
85 |
def get_success_url(self): |
|
86 |
return reverse('chrono-manager-pricing-detail', args=[self.object.pk]) |
|
87 | ||
88 | ||
89 |
pricing_add = PricingAddView.as_view() |
|
90 | ||
91 | ||
92 |
class PricingDetailView(DetailView): |
|
93 |
template_name = 'chrono/pricing/manager_pricing_detail.html' |
|
94 |
model = Pricing |
|
95 | ||
96 |
def dispatch(self, request, *args, **kwargs): |
|
97 |
if not request.user.is_staff: |
|
98 |
raise PermissionDenied() |
|
99 |
return super().dispatch(request, *args, **kwargs) |
|
100 | ||
101 |
def get_queryset(self): |
|
102 |
return ( |
|
103 |
super() |
|
104 |
.get_queryset() |
|
105 |
.prefetch_related( |
|
106 |
Prefetch( |
|
107 |
'categories', queryset=CriteriaCategory.objects.order_by('pricingcriteriacategory__order') |
|
108 |
) |
|
109 |
) |
|
110 |
) |
|
111 | ||
112 | ||
113 |
pricing_detail = PricingDetailView.as_view() |
|
114 | ||
115 | ||
116 |
class PricingEditView(UpdateView): |
|
117 |
template_name = 'chrono/pricing/manager_pricing_form.html' |
|
118 |
model = Pricing |
|
119 |
fields = ['label', 'slug'] |
|
120 | ||
121 |
def dispatch(self, request, *args, **kwargs): |
|
122 |
if not request.user.is_staff: |
|
123 |
raise PermissionDenied() |
|
124 |
return super().dispatch(request, *args, **kwargs) |
|
125 | ||
126 |
def get_success_url(self): |
|
127 |
return reverse('chrono-manager-pricing-detail', args=[self.object.pk]) |
|
128 | ||
129 | ||
130 |
pricing_edit = PricingEditView.as_view() |
|
131 | ||
132 | ||
133 |
class PricingDeleteView(DeleteView): |
|
134 |
template_name = 'chrono/manager_confirm_delete.html' |
|
135 |
model = Pricing |
|
136 | ||
137 |
def dispatch(self, request, *args, **kwargs): |
|
138 |
if not request.user.is_staff: |
|
139 |
raise PermissionDenied() |
|
140 |
return super().dispatch(request, *args, **kwargs) |
|
141 | ||
142 |
def get_success_url(self): |
|
143 |
return reverse('chrono-manager-pricing-list') |
|
144 | ||
145 | ||
146 |
pricing_delete = PricingDeleteView.as_view() |
|
147 | ||
148 | ||
149 |
class PricingDuplicate(SingleObjectMixin, FormView): |
|
150 |
template_name = 'chrono/pricing/manager_pricing_duplicate_form.html' |
|
151 |
model = Pricing |
|
152 |
form_class = PricingDuplicateForm |
|
153 | ||
154 |
def dispatch(self, request, *args, **kwargs): |
|
155 |
self.object = self.get_object() |
|
156 |
if not request.user.is_staff: |
|
157 |
raise PermissionDenied() |
|
158 |
return super().dispatch(request, *args, **kwargs) |
|
159 | ||
160 |
def get_success_url(self): |
|
161 |
return reverse('chrono-manager-pricing-detail', kwargs={'pk': self.new_pricing.pk}) |
|
162 | ||
163 |
def form_valid(self, form): |
|
164 |
self.new_pricing = self.object.duplicate(label=form.cleaned_data['label']) |
|
165 |
return super().form_valid(form) |
|
166 | ||
167 | ||
168 |
pricing_duplicate = PricingDuplicate.as_view() |
|
169 | ||
170 | ||
171 |
class PricingExport(DetailView): |
|
172 |
model = Pricing |
|
173 | ||
174 |
def dispatch(self, request, *args, **kwargs): |
|
175 |
if not request.user.is_staff: |
|
176 |
raise PermissionDenied() |
|
177 |
return super().dispatch(request, *args, **kwargs) |
|
178 | ||
179 |
def get(self, request, *args, **kwargs): |
|
180 |
response = HttpResponse(content_type='application/json') |
|
181 |
today = datetime.date.today() |
|
182 |
attachment = 'attachment; filename="export_pricing_{}_{}.json"'.format( |
|
183 |
self.get_object().slug, today.strftime('%Y%m%d') |
|
184 |
) |
|
185 |
response['Content-Disposition'] = attachment |
|
186 |
json.dump({'pricing_models': [self.get_object().export_json()]}, response, indent=2) |
|
187 |
return response |
|
188 | ||
189 | ||
190 |
pricing_export = PricingExport.as_view() |
|
191 | ||
192 | ||
193 |
class PricingVariableEdit(FormView): |
|
194 |
template_name = 'chrono/pricing/manager_pricing_variable_form.html' |
|
195 |
model = Pricing |
|
196 |
form_class = PricingVariableFormSet |
|
197 | ||
198 |
def dispatch(self, request, *args, **kwargs): |
|
199 |
self.object = get_object_or_404(Pricing, pk=kwargs['pk']) |
|
200 |
if not request.user.is_staff: |
|
201 |
raise PermissionDenied() |
|
202 |
return super().dispatch(request, *args, **kwargs) |
|
203 | ||
204 |
def get_context_data(self, **kwargs): |
|
205 |
kwargs['object'] = self.object |
|
206 |
return super().get_context_data(**kwargs) |
|
207 | ||
208 |
def get_initial(self): |
|
209 |
return sorted( |
|
210 |
({'key': k, 'value': v} for k, v in self.object.extra_variables.items()), |
|
211 |
key=itemgetter('key'), |
|
212 |
) |
|
213 | ||
214 |
def form_valid(self, form): |
|
215 |
self.object.extra_variables = {} |
|
216 |
for sub_data in form.cleaned_data: |
|
217 |
if not sub_data.get('key'): |
|
218 |
continue |
|
219 |
self.object.extra_variables[sub_data['key']] = sub_data['value'] |
|
220 |
self.object.save() |
|
221 |
return HttpResponseRedirect(self.get_success_url()) |
|
222 | ||
223 |
def get_success_url(self): |
|
224 |
return reverse('chrono-manager-pricing-detail', args=[self.object.pk]) |
|
225 | ||
226 | ||
227 |
pricing_variable_edit = PricingVariableEdit.as_view() |
|
228 | ||
229 | ||
230 |
class PricingCriteriaCategoryAddView(FormView): |
|
231 |
template_name = 'chrono/pricing/manager_pricing_criteria_category_form.html' |
|
232 |
model = Pricing |
|
233 |
form_class = PricingCriteriaCategoryAddForm |
|
234 | ||
235 |
def dispatch(self, request, *args, **kwargs): |
|
236 |
self.object = get_object_or_404(Pricing, pk=kwargs['pk']) |
|
237 |
if self.object.categories.count() >= 3: |
|
238 |
raise Http404 |
|
239 |
if not request.user.is_staff: |
|
240 |
raise PermissionDenied() |
|
241 |
return super().dispatch(request, *args, **kwargs) |
|
242 | ||
243 |
def get_form_kwargs(self): |
|
244 |
kwargs = super().get_form_kwargs() |
|
245 |
kwargs['pricing'] = self.object |
|
246 |
return kwargs |
|
247 | ||
248 |
def get_context_data(self, **kwargs): |
|
249 |
kwargs['object'] = self.object |
|
250 |
return super().get_context_data(**kwargs) |
|
251 | ||
252 |
def form_valid(self, form): |
|
253 |
PricingCriteriaCategory.objects.create(pricing=self.object, category=form.cleaned_data['category']) |
|
254 |
return super().form_valid(form) |
|
255 | ||
256 |
def get_success_url(self): |
|
257 |
return reverse('chrono-manager-pricing-detail', args=[self.object.pk]) |
|
258 | ||
259 | ||
260 |
pricing_criteria_category_add = PricingCriteriaCategoryAddView.as_view() |
|
261 | ||
262 | ||
263 |
class PricingCriteriaCategoryEditView(FormView): |
|
264 |
template_name = 'chrono/pricing/manager_pricing_criteria_category_form.html' |
|
265 |
model = Pricing |
|
266 |
form_class = PricingCriteriaCategoryEditForm |
|
267 | ||
268 |
def dispatch(self, request, *args, **kwargs): |
|
269 |
self.object = get_object_or_404(Pricing, pk=kwargs['pk']) |
|
270 |
self.category = get_object_or_404(self.object.categories, pk=kwargs['category_pk']) |
|
271 |
if not request.user.is_staff: |
|
272 |
raise PermissionDenied() |
|
273 |
return super().dispatch(request, *args, **kwargs) |
|
274 | ||
275 |
def get_form_kwargs(self): |
|
276 |
kwargs = super().get_form_kwargs() |
|
277 |
kwargs['pricing'] = self.object |
|
278 |
kwargs['category'] = self.category |
|
279 |
return kwargs |
|
280 | ||
281 |
def get_context_data(self, **kwargs): |
|
282 |
kwargs['object'] = self.object |
|
283 |
kwargs['category'] = self.category |
|
284 |
return super().get_context_data(**kwargs) |
|
285 | ||
286 |
def form_valid(self, form): |
|
287 |
old_criterias = self.object.criterias.filter(category=self.category) |
|
288 |
new_criterias = form.cleaned_data['criterias'] |
|
289 |
removed_criterias = set(old_criterias) - set(new_criterias) |
|
290 |
self.object.criterias.remove(*removed_criterias) |
|
291 |
self.object.criterias.add(*new_criterias) |
|
292 |
return super().form_valid(form) |
|
293 | ||
294 |
def get_success_url(self): |
|
295 |
return reverse('chrono-manager-pricing-detail', args=[self.object.pk]) |
|
296 | ||
297 | ||
298 |
pricing_criteria_category_edit = PricingCriteriaCategoryEditView.as_view() |
|
299 | ||
300 | ||
301 |
class PricingCriteriaCategoryDeleteView(DeleteView): |
|
302 |
template_name = 'chrono/manager_confirm_delete.html' |
|
303 |
model = CriteriaCategory |
|
304 |
pk_url_kwarg = 'category_pk' |
|
305 | ||
306 |
def dispatch(self, request, *args, **kwargs): |
|
307 |
self.pricing = get_object_or_404(Pricing, pk=kwargs['pk']) |
|
308 |
if not request.user.is_staff: |
|
309 |
raise PermissionDenied() |
|
310 |
return super().dispatch(request, *args, **kwargs) |
|
311 | ||
312 |
def get_queryset(self): |
|
313 |
return self.pricing.categories.all() |
|
314 | ||
315 |
def delete(self, request, *args, **kwargs): |
|
316 |
self.object = self.get_object() |
|
317 |
self.pricing.categories.remove(self.object) |
|
318 |
self.pricing.criterias.remove(*self.pricing.criterias.filter(category=self.object)) |
|
319 |
return HttpResponseRedirect(self.get_success_url()) |
|
320 | ||
321 |
def get_success_url(self): |
|
322 |
return reverse('chrono-manager-pricing-detail', args=[self.pricing.pk]) |
|
323 | ||
324 | ||
325 |
pricing_criteria_category_delete = PricingCriteriaCategoryDeleteView.as_view() |
|
326 | ||
327 | ||
328 |
class PricingCriteriaCategoryOrder(DetailView): |
|
329 |
model = Pricing |
|
330 | ||
331 |
def dispatch(self, request, *args, **kwargs): |
|
332 |
if not request.user.is_staff: |
|
333 |
raise PermissionDenied() |
|
334 |
return super().dispatch(request, *args, **kwargs) |
|
335 | ||
336 |
def get(self, request, *args, **kwargs): |
|
337 |
if 'new-order' not in request.GET: |
|
338 |
return HttpResponseBadRequest('missing new-order parameter') |
|
339 |
pricing = self.get_object() |
|
340 |
try: |
|
341 |
new_order = [int(x) for x in request.GET['new-order'].split(',')] |
|
342 |
except ValueError: |
|
343 |
return HttpResponseBadRequest('incorrect new-order parameter') |
|
344 |
categories = pricing.categories.all() |
|
345 |
if set(new_order) != {x.pk for x in categories} or len(new_order) != len(categories): |
|
346 |
return HttpResponseBadRequest('incorrect new-order parameter') |
|
347 |
for i, c_id in enumerate(new_order): |
|
348 |
PricingCriteriaCategory.objects.filter(pricing=pricing, category=c_id).update(order=i + 1) |
|
349 |
return HttpResponse(status=204) |
|
350 | ||
351 | ||
352 |
pricing_criteria_category_order = PricingCriteriaCategoryOrder.as_view() |
|
353 | ||
354 | ||
355 |
class CriteriaCategoryAddView(CreateView): |
|
356 |
template_name = 'chrono/pricing/manager_criteria_category_form.html' |
|
357 |
model = CriteriaCategory |
|
358 |
fields = ['label'] |
|
359 | ||
360 |
def dispatch(self, request, *args, **kwargs): |
|
361 |
if not request.user.is_staff: |
|
362 |
raise PermissionDenied() |
|
363 |
return super().dispatch(request, *args, **kwargs) |
|
364 | ||
365 |
def get_success_url(self): |
|
366 |
return reverse('chrono-manager-pricing-criteria-list') |
|
367 | ||
368 | ||
369 |
criteria_category_add = CriteriaCategoryAddView.as_view() |
|
370 | ||
371 | ||
372 |
class CriteriaCategoryEditView(UpdateView): |
|
373 |
template_name = 'chrono/pricing/manager_criteria_category_form.html' |
|
374 |
model = CriteriaCategory |
|
375 |
fields = ['label', 'slug'] |
|
376 | ||
377 |
def dispatch(self, request, *args, **kwargs): |
|
378 |
if not request.user.is_staff: |
|
379 |
raise PermissionDenied() |
|
380 |
return super().dispatch(request, *args, **kwargs) |
|
381 | ||
382 |
def get_success_url(self): |
|
383 |
return reverse('chrono-manager-pricing-criteria-list') |
|
384 | ||
385 | ||
386 |
criteria_category_edit = CriteriaCategoryEditView.as_view() |
|
387 | ||
388 | ||
389 |
class CriteriaCategoryDeleteView(DeleteView): |
|
390 |
template_name = 'chrono/manager_confirm_delete.html' |
|
391 |
model = CriteriaCategory |
|
392 | ||
393 |
def dispatch(self, request, *args, **kwargs): |
|
394 |
if not request.user.is_staff: |
|
395 |
raise PermissionDenied() |
|
396 |
return super().dispatch(request, *args, **kwargs) |
|
397 | ||
398 |
def get_success_url(self): |
|
399 |
return reverse('chrono-manager-pricing-criteria-list') |
|
400 | ||
401 | ||
402 |
criteria_category_delete = CriteriaCategoryDeleteView.as_view() |
|
403 | ||
404 | ||
405 |
class CriteriaCategoryExport(DetailView): |
|
406 |
model = CriteriaCategory |
|
407 | ||
408 |
def dispatch(self, request, *args, **kwargs): |
|
409 |
if not request.user.is_staff: |
|
410 |
raise PermissionDenied() |
|
411 |
return super().dispatch(request, *args, **kwargs) |
|
412 | ||
413 |
def get(self, request, *args, **kwargs): |
|
414 |
response = HttpResponse(content_type='application/json') |
|
415 |
today = datetime.date.today() |
|
416 |
attachment = 'attachment; filename="export_pricing_category_{}_{}.json"'.format( |
|
417 |
self.get_object().slug, today.strftime('%Y%m%d') |
|
418 |
) |
|
419 |
response['Content-Disposition'] = attachment |
|
420 |
json.dump({'pricing_categories': [self.get_object().export_json()]}, response, indent=2) |
|
421 |
return response |
|
422 | ||
423 | ||
424 |
criteria_category_export = CriteriaCategoryExport.as_view() |
|
425 | ||
426 | ||
427 |
class CriteriaOrder(DetailView): |
|
428 |
model = CriteriaCategory |
|
429 | ||
430 |
def dispatch(self, request, *args, **kwargs): |
|
431 |
if not request.user.is_staff: |
|
432 |
raise PermissionDenied() |
|
433 |
return super().dispatch(request, *args, **kwargs) |
|
434 | ||
435 |
def get(self, request, *args, **kwargs): |
|
436 |
if 'new-order' not in request.GET: |
|
437 |
return HttpResponseBadRequest('missing new-order parameter') |
|
438 |
category = self.get_object() |
|
439 |
try: |
|
440 |
new_order = [int(x) for x in request.GET['new-order'].split(',')] |
|
441 |
except ValueError: |
|
442 |
return HttpResponseBadRequest('incorrect new-order parameter') |
|
443 |
criterias = category.criterias.all() |
|
444 |
if set(new_order) != {x.pk for x in criterias} or len(new_order) != len(criterias): |
|
445 |
return HttpResponseBadRequest('incorrect new-order parameter') |
|
446 |
criterias_by_id = {c.pk: c for c in criterias} |
|
447 |
for i, c_id in enumerate(new_order): |
|
448 |
criterias_by_id[c_id].order = i + 1 |
|
449 |
criterias_by_id[c_id].save() |
|
450 |
return HttpResponse(status=204) |
|
451 | ||
452 | ||
453 |
criteria_order = CriteriaOrder.as_view() |
|
454 | ||
455 | ||
456 |
class CriteriaAddView(CreateView): |
|
457 |
template_name = 'chrono/pricing/manager_criteria_form.html' |
|
458 |
model = Criteria |
|
459 |
form_class = NewCriteriaForm |
|
460 | ||
461 |
def dispatch(self, request, *args, **kwargs): |
|
462 |
self.category_pk = kwargs.pop('category_pk') |
|
463 |
if not request.user.is_staff: |
|
464 |
raise PermissionDenied() |
|
465 |
return super().dispatch(request, *args, **kwargs) |
|
466 | ||
467 |
def get_form_kwargs(self): |
|
468 |
kwargs = super().get_form_kwargs() |
|
469 |
if not kwargs.get('instance'): |
|
470 |
kwargs['instance'] = self.model() |
|
471 |
kwargs['instance'].category_id = self.category_pk |
|
472 |
return kwargs |
|
473 | ||
474 |
def get_success_url(self): |
|
475 |
return reverse('chrono-manager-pricing-criteria-list') |
|
476 | ||
477 | ||
478 |
criteria_add = CriteriaAddView.as_view() |
|
479 | ||
480 | ||
481 |
class CriteriaEditView(UpdateView): |
|
482 |
template_name = 'chrono/pricing/manager_criteria_form.html' |
|
483 |
model = Criteria |
|
484 |
form_class = CriteriaForm |
|
485 | ||
486 |
def dispatch(self, request, *args, **kwargs): |
|
487 |
self.category_pk = kwargs.pop('category_pk') |
|
488 |
if not request.user.is_staff: |
|
489 |
raise PermissionDenied() |
|
490 |
return super().dispatch(request, *args, **kwargs) |
|
491 | ||
492 |
def get_queryset(self): |
|
493 |
return Criteria.objects.filter(category=self.category_pk) |
|
494 | ||
495 |
def get_success_url(self): |
|
496 |
return reverse('chrono-manager-pricing-criteria-list') |
|
497 | ||
498 | ||
499 |
criteria_edit = CriteriaEditView.as_view() |
|
500 | ||
501 | ||
502 |
class CriteriaDeleteView(DeleteView): |
|
503 |
template_name = 'chrono/manager_confirm_delete.html' |
|
504 |
model = Criteria |
|
505 | ||
506 |
def dispatch(self, request, *args, **kwargs): |
|
507 |
self.category_pk = kwargs.pop('category_pk') |
|
508 |
if not request.user.is_staff: |
|
509 |
raise PermissionDenied() |
|
510 |
return super().dispatch(request, *args, **kwargs) |
|
511 | ||
512 |
def get_queryset(self): |
|
513 |
return Criteria.objects.filter(category=self.category_pk) |
|
514 | ||
515 |
def get_success_url(self): |
|
516 |
return reverse('chrono-manager-pricing-criteria-list') |
|
517 | ||
518 | ||
519 |
criteria_delete = CriteriaDeleteView.as_view() |
|
520 | ||
521 | ||
522 |
class AgendaPricingAddView(ManagedAgendaMixin, CreateView): |
|
523 |
template_name = 'chrono/pricing/manager_agenda_pricing_form.html' |
|
524 |
model = AgendaPricing |
|
525 |
form_class = AgendaPricingForm |
|
526 | ||
527 |
def get_success_url(self): |
|
528 |
return reverse('chrono-manager-agenda-pricing-detail', args=[self.agenda.pk, self.object.pk]) |
|
529 | ||
530 | ||
531 |
agenda_pricing_add = AgendaPricingAddView.as_view() |
|
532 | ||
533 | ||
534 |
class AgendaPricingDetailView(ViewableAgendaMixin, DetailView): |
|
535 |
model = AgendaPricing |
|
536 |
pk_url_kwarg = 'pricing_pk' |
|
537 |
template_name = 'chrono/pricing/manager_agenda_pricing_detail.html' |
|
538 | ||
539 |
def set_agenda(self, **kwargs): |
|
540 |
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events') |
|
541 | ||
542 |
def get_queryset(self): |
|
543 |
return AgendaPricing.objects.filter(agenda=self.agenda).prefetch_related( |
|
544 |
'pricing__criterias__category' |
|
545 |
) |
|
546 | ||
547 |
def get_context_data(self, **kwargs): |
|
548 |
kwargs['user_can_manage'] = self.agenda.can_be_managed(self.request.user) |
|
549 |
return super().get_context_data(**kwargs) |
|
550 | ||
551 | ||
552 |
agenda_pricing_detail = AgendaPricingDetailView.as_view() |
|
553 | ||
554 | ||
555 |
class AgendaPricingEditView(ManagedAgendaMixin, UpdateView): |
|
556 |
template_name = 'chrono/pricing/manager_agenda_pricing_form.html' |
|
557 |
model = AgendaPricing |
|
558 |
pk_url_kwarg = 'pricing_pk' |
|
559 |
form_class = AgendaPricingForm |
|
560 | ||
561 |
def set_agenda(self, **kwargs): |
|
562 |
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events') |
|
563 | ||
564 |
def get_queryset(self): |
|
565 |
return AgendaPricing.objects.filter(agenda=self.agenda) |
|
566 | ||
567 |
def get_success_url(self): |
|
568 |
return reverse('chrono-manager-agenda-pricing-detail', args=[self.agenda.pk, self.object.pk]) |
|
569 | ||
570 | ||
571 |
agenda_pricing_edit = AgendaPricingEditView.as_view() |
|
572 | ||
573 | ||
574 |
class AgendaPricingDeleteView(ManagedAgendaMixin, DeleteView): |
|
575 |
template_name = 'chrono/manager_confirm_delete.html' |
|
576 |
model = AgendaPricing |
|
577 |
pk_url_kwarg = 'pricing_pk' |
|
578 | ||
579 |
def set_agenda(self, **kwargs): |
|
580 |
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events') |
|
581 | ||
582 |
def get_queryset(self): |
|
583 |
return AgendaPricing.objects.filter(agenda=self.agenda) |
|
584 | ||
585 | ||
586 |
agenda_pricing_delete = AgendaPricingDeleteView.as_view() |
|
587 | ||
588 | ||
589 |
class AgendaPricingMatrixEdit(ManagedAgendaMixin, FormView): |
|
590 |
template_name = 'chrono/pricing/manager_agenda_pricing_matrix_form.html' |
|
591 | ||
592 |
def set_agenda(self, **kwargs): |
|
593 |
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events') |
|
594 |
self.object = get_object_or_404( |
|
595 |
AgendaPricing.objects.filter(agenda=self.agenda), pk=kwargs['pricing_pk'] |
|
596 |
) |
|
597 |
matrix_list = list(self.object.iter_pricing_matrix()) |
|
598 |
if not matrix_list: |
|
599 |
raise Http404 |
|
600 |
self.matrix = None |
|
601 |
if kwargs.get('slug'): |
|
602 |
for matrix in matrix_list: |
|
603 |
if matrix.criteria is None: |
|
604 |
continue |
|
605 |
if matrix.criteria.slug == kwargs['slug']: |
|
606 |
self.matrix = matrix |
|
607 |
break |
|
608 |
else: |
|
609 |
if matrix_list[0].criteria is None: |
|
610 |
self.matrix = matrix_list[0] |
|
611 |
if self.matrix is None: |
|
612 |
raise Http404 |
|
613 | ||
614 |
def get_context_data(self, **kwargs): |
|
615 |
kwargs['object'] = self.object |
|
616 |
kwargs['matrix'] = self.matrix |
|
617 |
return super().get_context_data(**kwargs) |
|
618 | ||
619 |
def get_form(self): |
|
620 |
count = len(self.matrix.rows) |
|
621 |
PricingMatrixFormSet = forms.formset_factory( |
|
622 |
PricingMatrixForm, min_num=count, max_num=count, extra=0, can_delete=False |
|
623 |
) |
|
624 |
kwargs = { |
|
625 |
'initial': [ |
|
626 |
{'crit_%i' % i: cell.value for i, cell in enumerate(row.cells)} for row in self.matrix.rows |
|
627 |
] |
|
628 |
} |
|
629 |
if self.request.method == 'POST': |
|
630 |
kwargs.update( |
|
631 |
{ |
|
632 |
'data': self.request.POST, |
|
633 |
} |
|
634 |
) |
|
635 |
return PricingMatrixFormSet(form_kwargs={'matrix': self.matrix}, **kwargs) |
|
636 | ||
637 |
def post(self, *args, **kwargs): |
|
638 |
form = self.get_form() |
|
639 |
if form.is_valid(): |
|
640 |
# build prixing_data for this matrix |
|
641 |
matrix_pricing_data = defaultdict(dict) |
|
642 |
for i, sub_data in enumerate(form.cleaned_data): |
|
643 |
row = self.matrix.rows[i] |
|
644 |
for j, cell in enumerate(row.cells): |
|
645 |
value = sub_data['crit_%s' % j] |
|
646 |
key = cell.criteria.identifier if cell.criteria else None |
|
647 |
matrix_pricing_data[key][row.criteria.identifier] = float(value) |
|
648 |
if self.matrix.criteria: |
|
649 |
# full pricing model with 3 categories |
|
650 |
self.object.pricing_data = self.object.pricing_data or {} |
|
651 |
self.object.pricing_data[self.matrix.criteria.identifier] = matrix_pricing_data |
|
652 |
elif list(matrix_pricing_data.keys()) == [None]: |
|
653 |
# only one category |
|
654 |
self.object.pricing_data = matrix_pricing_data[None] |
|
655 |
else: |
|
656 |
# 2 categories |
|
657 |
self.object.pricing_data = matrix_pricing_data |
|
658 |
self.object.save() |
|
659 |
return self.form_valid(form) |
|
660 |
else: |
|
661 |
return self.form_invalid(form) |
|
662 | ||
663 |
def get_success_url(self): |
|
664 |
return reverse('chrono-manager-agenda-pricing-detail', args=[self.agenda.pk, self.object.pk]) |
|
665 | ||
666 | ||
667 |
agenda_pricing_matrix_edit = AgendaPricingMatrixEdit.as_view() |
chrono/settings.py | ||
---|---|---|
190 | 190 |
WORKING_DAY_CALENDAR = None |
191 | 191 |
EXCEPTIONS_SOURCES = {} |
192 | 192 | |
193 |
# feature flag for publik-famille & pricing developements |
|
194 |
CHRONO_ENABLE_PRICING = False |
|
195 | ||
196 | 193 |
TEMPLATE_VARS = {} |
197 | 194 |
SMS_URL = '' |
198 | 195 |
SMS_SENDER = '' |
chrono/urls.py | ||
---|---|---|
22 | 22 | |
23 | 23 |
from .api.urls import urlpatterns as chrono_api_urls |
24 | 24 |
from .manager.urls import urlpatterns as chrono_manager_urls |
25 |
from .pricing.urls import urlpatterns as chrono_pricing_urls |
|
26 | 25 |
from .urls_utils import decorated_includes |
27 | 26 |
from .views import LoginView, LogoutView, homepage |
28 | 27 | |
29 | 28 |
urlpatterns = [ |
30 | 29 |
url(r'^$', homepage, name='home'), |
31 | 30 |
url(r'^manage/', decorated_includes(login_required, include(chrono_manager_urls))), |
32 |
url(r'^manage/pricing/', decorated_includes(login_required, include(chrono_pricing_urls))), |
|
33 | 31 |
url(r'^api/', include(chrono_api_urls)), |
34 | 32 |
url(r'^logout/$', LogoutView.as_view(), name='auth_logout'), |
35 | 33 |
url(r'^login/$', LoginView.as_view(), name='auth_login'), |
tests/manager/test_all.py | ||
---|---|---|
25 | 25 |
UnavailabilityCalendar, |
26 | 26 |
VirtualMember, |
27 | 27 |
) |
28 |
from chrono.pricing.models import AgendaPricing, Pricing |
|
29 | 28 |
from chrono.utils.signature import check_query |
30 | 29 |
from tests.utils import login |
31 | 30 | |
... | ... | |
445 | 444 |
assert 'events_type' not in resp.context['form'].fields |
446 | 445 | |
447 | 446 | |
448 |
def test_options_events_agenda_pricing(settings, app, admin_user): |
|
449 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
450 |
pricing1 = Pricing.objects.create(label='Model 1') |
|
451 |
pricing2 = Pricing.objects.create(label='Model 2') |
|
452 | ||
453 |
app = login(app) |
|
454 |
resp = app.get('/manage/agendas/%s/settings' % agenda.pk) |
|
455 |
assert 'Pricing' in resp |
|
456 |
assert 'Model 1' not in resp |
|
457 |
assert 'Model 2' not in resp |
|
458 |
AgendaPricing.objects.create( |
|
459 |
agenda=agenda, |
|
460 |
pricing=pricing1, |
|
461 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
462 |
date_end=datetime.date(year=2022, month=9, day=1), |
|
463 |
) |
|
464 |
AgendaPricing.objects.create( |
|
465 |
agenda=agenda, |
|
466 |
pricing=pricing1, |
|
467 |
date_start=datetime.date(year=2022, month=9, day=1), |
|
468 |
date_end=datetime.date(year=2023, month=9, day=1), |
|
469 |
) |
|
470 |
AgendaPricing.objects.create( |
|
471 |
agenda=agenda, |
|
472 |
pricing=pricing2, |
|
473 |
date_start=datetime.date(year=2022, month=9, day=1), |
|
474 |
date_end=datetime.date(year=2023, month=9, day=1), |
|
475 |
) |
|
476 | ||
477 |
resp = app.get('/manage/agendas/%s/settings' % agenda.pk) |
|
478 |
assert 'Model 1 (01/09/2021 - 01/09/2022)' in resp |
|
479 |
assert 'Model 1 (01/09/2022 - 01/09/2023)' in resp |
|
480 |
assert 'Model 2 (01/09/2022 - 01/09/2023)' in resp |
|
481 | ||
482 |
settings.CHRONO_ENABLE_PRICING = False |
|
483 |
resp = app.get('/manage/agendas/%s/settings' % agenda.pk) |
|
484 |
assert 'Pricing' not in resp |
|
485 |
assert 'Model 1' not in resp |
|
486 |
assert 'Model 2' not in resp |
|
487 |
settings.CHRONO_ENABLE_PRICING = True |
|
488 | ||
489 |
# check kind |
|
490 |
agenda.kind = 'meetings' |
|
491 |
agenda.save() |
|
492 |
resp = app.get('/manage/agendas/%s/settings' % agenda.pk) |
|
493 |
assert 'Pricing' not in resp |
|
494 |
assert 'Model 1' not in resp |
|
495 |
assert 'Model 2' not in resp |
|
496 | ||
497 | ||
498 | 447 |
def test_options_events_agenda_delays(settings, app, admin_user): |
499 | 448 |
settings.WORKING_DAY_CALENDAR = None |
500 | 449 |
agenda = Agenda.objects.create(label='Foo bar') |
tests/manager/test_check_type.py | ||
---|---|---|
119 | 119 |
assert check_type.group == group |
120 | 120 |
assert check_type.slug == 'foo-reason' |
121 | 121 |
assert check_type.kind == 'absence' |
122 |
assert check_type.pricing is None |
|
123 |
assert check_type.pricing_rate is None |
|
124 | 122 | |
125 | 123 |
resp = app.get('/manage/check-type/group/%s/add/' % group.pk) |
126 | 124 |
resp.form['label'] = 'Foo reason' |
... | ... | |
133 | 131 |
assert check_type.kind == 'presence' |
134 | 132 | |
135 | 133 | |
136 |
def test_add_check_type_pricing(settings, app, admin_user): |
|
137 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
138 | ||
139 |
app = login(app) |
|
140 |
resp = app.get('/manage/check-type/group/%s/add/' % group.pk) |
|
141 |
assert 'pricing' in resp.context['form'].fields |
|
142 |
assert 'pricing_rate' in resp.context['form'].fields |
|
143 |
resp.form['label'] = 'Foo reason' |
|
144 |
resp.form['pricing'] = 42 |
|
145 |
resp.form['pricing_rate'] = 150 |
|
146 |
resp = resp.form.submit() |
|
147 |
assert resp.context['form'].errors['__all__'] == ['Please choose between pricing and pricing rate.'] |
|
148 |
resp.form['pricing'] = 0 |
|
149 |
resp.form['pricing_rate'] = 0 |
|
150 |
resp = resp.form.submit() |
|
151 |
assert resp.context['form'].errors['__all__'] == ['Please choose between pricing and pricing rate.'] |
|
152 |
resp.form['pricing'] = 42 |
|
153 |
resp.form['pricing_rate'] = None |
|
154 |
resp = resp.form.submit() |
|
155 |
check_type = CheckType.objects.latest('pk') |
|
156 |
assert check_type.pricing == 42 |
|
157 |
assert check_type.pricing_rate is None |
|
158 | ||
159 |
resp = app.get('/manage/check-type/group/%s/add/' % group.pk) |
|
160 |
resp.form['label'] = 'Foo reason' |
|
161 |
resp.form['pricing_rate'] = 150 |
|
162 |
resp = resp.form.submit() |
|
163 |
check_type = CheckType.objects.latest('pk') |
|
164 |
assert check_type.pricing is None |
|
165 |
assert check_type.pricing_rate == 150 |
|
166 | ||
167 |
settings.CHRONO_ENABLE_PRICING = False |
|
168 |
resp = app.get('/manage/check-type/group/%s/add/' % group.pk) |
|
169 |
assert 'pricing' not in resp.context['form'].fields |
|
170 |
assert 'pricing_rate' not in resp.context['form'].fields |
|
171 | ||
172 | ||
173 | 134 |
def test_add_check_type_as_manager(app, manager_user, agenda_with_restrictions): |
174 | 135 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
175 | 136 | |
... | ... | |
205 | 166 |
assert check_type.label == 'Foo bar reason' |
206 | 167 |
assert check_type.slug == 'foo-bar-reason' |
207 | 168 |
assert check_type.kind == 'presence' |
208 |
assert check_type.pricing is None |
|
209 |
assert check_type.pricing_rate is None |
|
210 | 169 |
assert check_type.disabled is True |
211 | 170 | |
212 | 171 |
# check_type is used |
... | ... | |
225 | 184 |
app.get('/manage/check-type/group/%s/%s/edit/' % (group.pk, check_type.pk), status=403) |
226 | 185 | |
227 | 186 | |
228 |
def test_edit_check_type_pricing(settings, app, admin_user): |
|
229 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
230 |
check_type = CheckType.objects.create(label='Foo reason', group=group) |
|
231 | ||
232 |
app = login(app) |
|
233 |
resp = app.get('/manage/check-type/group/%s/%s/edit/' % (group.pk, check_type.pk)) |
|
234 |
assert 'pricing' in resp.context['form'].fields |
|
235 |
assert 'pricing_rate' in resp.context['form'].fields |
|
236 |
resp.form['pricing'] = 42 |
|
237 |
resp.form['pricing_rate'] = 150 |
|
238 |
resp = resp.form.submit() |
|
239 |
assert resp.context['form'].errors['__all__'] == ['Please choose between pricing and pricing rate.'] |
|
240 |
resp.form['pricing'] = 42 |
|
241 |
resp.form['pricing_rate'] = None |
|
242 |
resp = resp.form.submit() |
|
243 |
check_type.refresh_from_db() |
|
244 |
assert check_type.pricing == 42 |
|
245 |
assert check_type.pricing_rate is None |
|
246 | ||
247 |
resp = app.get('/manage/check-type/group/%s/%s/edit/' % (group.pk, check_type.pk)) |
|
248 |
resp.form['pricing'] = None |
|
249 |
resp.form['pricing_rate'] = 150 |
|
250 |
resp = resp.form.submit() |
|
251 |
check_type.refresh_from_db() |
|
252 |
assert check_type.pricing is None |
|
253 |
assert check_type.pricing_rate == 150 |
|
254 | ||
255 |
settings.CHRONO_ENABLE_PRICING = False |
|
256 |
resp = app.get('/manage/check-type/group/%s/%s/edit/' % (group.pk, check_type.pk)) |
|
257 |
assert 'pricing' not in resp.context['form'].fields |
|
258 |
assert 'pricing_rate' not in resp.context['form'].fields |
|
259 | ||
260 | ||
261 | 187 |
def test_delete_check_type(app, admin_user): |
262 | 188 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
263 | 189 |
check_type = CheckType.objects.create(label='Foo reason', group=group) |
tests/manager/test_import_export.py | ||
---|---|---|
22 | 22 |
pytestmark = pytest.mark.django_db |
23 | 23 | |
24 | 24 | |
25 |
def test_export_site(settings, app, admin_user):
|
|
25 |
def test_export_site(app, admin_user): |
|
26 | 26 |
login(app) |
27 | 27 |
resp = app.get('/manage/') |
28 | 28 |
resp = resp.click('Export') |
... | ... | |
40 | 40 |
'events_types': [], |
41 | 41 |
'resources': [], |
42 | 42 |
'categories': [], |
43 |
'pricing_categories': [], |
|
44 |
'pricing_models': [], |
|
45 | 43 |
} |
46 | 44 | |
47 | 45 |
agenda = Agenda.objects.create(label='Foo Bar', kind='events') |
... | ... | |
57 | 55 |
assert len(site_json['events_types']) == 0 |
58 | 56 |
assert len(site_json['resources']) == 0 |
59 | 57 |
assert len(site_json['categories']) == 0 |
60 |
assert len(site_json['pricing_categories']) == 0 |
|
61 |
assert len(site_json['pricing_models']) == 0 |
|
62 | 58 | |
63 | 59 |
resp = app.get('/manage/agendas/export/') |
64 | 60 |
resp.form['agendas'] = False |
... | ... | |
66 | 62 |
resp.form['events_types'] = False |
67 | 63 |
resp.form['resources'] = False |
68 | 64 |
resp.form['categories'] = False |
69 |
resp.form['pricing_categories'] = False |
|
70 |
resp.form['pricing_models'] = False |
|
71 | 65 |
resp = resp.form.submit() |
72 | 66 | |
73 | 67 |
site_json = json.loads(resp.text) |
... | ... | |
77 | 71 |
assert 'events_types' not in site_json |
78 | 72 |
assert 'resources' not in site_json |
79 | 73 |
assert 'categories' not in site_json |
80 |
assert 'pricing_categories' not in site_json |
|
81 |
assert 'pricing_models' not in site_json |
|
82 | ||
83 |
settings.CHRONO_ENABLE_PRICING = False |
|
84 |
resp = app.get('/manage/agendas/export/') |
|
85 |
assert 'pricing_categories' not in resp.context['form'].fields |
|
86 |
assert 'pricing_models' not in resp.context['form'].fields |
|
87 | 74 | |
88 | 75 | |
89 | 76 |
def test_import_agenda_as_manager(app, manager_user): |
tests/pricing/conftest.py | ||
---|---|---|
1 |
import pytest |
|
2 |
from django.contrib.auth.models import Group, User |
|
3 | ||
4 | ||
5 |
@pytest.fixture |
|
6 |
def simple_user(): |
|
7 |
try: |
|
8 |
user = User.objects.get(username='user') |
|
9 |
except User.DoesNotExist: |
|
10 |
user = User.objects.create_user('user', password='user') |
|
11 |
return user |
|
12 | ||
13 | ||
14 |
@pytest.fixture |
|
15 |
def managers_group(): |
|
16 |
group, _ = Group.objects.get_or_create(name='Managers') |
|
17 |
return group |
|
18 | ||
19 | ||
20 |
@pytest.fixture |
|
21 |
def manager_user(managers_group): |
|
22 |
try: |
|
23 |
user = User.objects.get(username='manager') |
|
24 |
except User.DoesNotExist: |
|
25 |
user = User.objects.create_user('manager', password='manager') |
|
26 |
user.groups.set([managers_group]) |
|
27 |
return user |
|
28 | ||
29 | ||
30 |
@pytest.fixture |
|
31 |
def admin_user(): |
|
32 |
try: |
|
33 |
user = User.objects.get(username='admin') |
|
34 |
except User.DoesNotExist: |
|
35 |
user = User.objects.create_superuser('admin', email=None, password='admin') |
|
36 |
return user |
tests/pricing/test_manager.py | ||
---|---|---|
1 |
import copy |
|
2 |
import datetime |
|
3 |
import json |
|
4 | ||
5 |
import pytest |
|
6 |
from webtest import Upload |
|
7 | ||
8 |
from chrono.agendas.models import Agenda |
|
9 |
from chrono.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingCriteriaCategory |
|
10 |
from tests.utils import login |
|
11 | ||
12 |
pytestmark = pytest.mark.django_db |
|
13 | ||
14 | ||
15 |
@pytest.fixture |
|
16 |
def agenda_with_restrictions(manager_user): |
|
17 |
agenda = Agenda(label='Foo Bar') |
|
18 |
agenda.view_role = manager_user.groups.all()[0] |
|
19 |
agenda.save() |
|
20 |
return agenda |
|
21 | ||
22 | ||
23 |
def test_list_pricings_as_manager(app, manager_user, agenda_with_restrictions): |
|
24 |
app = login(app, username='manager', password='manager') |
|
25 |
app.get('/manage/pricing/', status=403) |
|
26 | ||
27 |
resp = app.get('/manage/') |
|
28 |
assert 'Pricing' not in resp.text |
|
29 | ||
30 | ||
31 |
def test_add_pricing(settings, app, admin_user): |
|
32 |
app = login(app) |
|
33 |
resp = app.get('/manage/') |
|
34 |
resp = resp.click('Pricing') |
|
35 |
resp = resp.click('New pricing model') |
|
36 |
resp.form['label'] = 'Pricing model for lunch' |
|
37 |
resp = resp.form.submit() |
|
38 |
pricing = Pricing.objects.latest('pk') |
|
39 |
assert resp.location.endswith('/manage/pricing/%s/' % pricing.pk) |
|
40 |
assert pricing.label == 'Pricing model for lunch' |
|
41 |
assert pricing.slug == 'pricing-model-for-lunch' |
|
42 | ||
43 |
settings.CHRONO_ENABLE_PRICING = False |
|
44 |
resp = app.get('/manage/') |
|
45 |
assert 'Pricing' not in resp.text |
|
46 | ||
47 | ||
48 |
def test_add_pricing_as_manager(app, manager_user): |
|
49 |
app = login(app, username='manager', password='manager') |
|
50 |
app.get('/manage/pricing/add/', status=403) |
|
51 | ||
52 | ||
53 |
def test_detail_pricing(app, admin_user): |
|
54 |
pricing = Pricing.objects.create(label='Model') |
|
55 | ||
56 |
app = login(app) |
|
57 |
resp = app.get('/manage/pricing/') |
|
58 |
resp = resp.click(href='/manage/pricing/%s/' % pricing.pk) |
|
59 |
assert '/manage/pricing/%s/edit/' % pricing.pk in resp |
|
60 |
assert '/manage/pricing/%s/delete/' % pricing.pk in resp |
|
61 | ||
62 | ||
63 |
def test_detail_pricing_as_manager(app, manager_user): |
|
64 |
pricing = Pricing.objects.create(label='Model') |
|
65 | ||
66 |
app = login(app, username='manager', password='manager') |
|
67 |
app.get('/manage/pricing/%s/' % pricing.pk, status=403) |
|
68 | ||
69 | ||
70 |
def test_edit_pricing(app, admin_user): |
|
71 |
pricing = Pricing.objects.create(label='Model 1') |
|
72 |
pricing2 = Pricing.objects.create(label='Model 2') |
|
73 | ||
74 |
app = login(app) |
|
75 |
resp = app.get('/manage/pricing/%s/' % pricing.pk) |
|
76 |
resp = resp.click(href='/manage/pricing/%s/edit/' % pricing.pk) |
|
77 |
resp.form['label'] = 'Model Foo' |
|
78 |
resp.form['slug'] = pricing2.slug |
|
79 |
resp = resp.form.submit() |
|
80 |
assert resp.context['form'].errors['slug'] == ['Pricing with this Identifier already exists.'] |
|
81 | ||
82 |
resp.form['slug'] = 'foo-bar' |
|
83 |
resp = resp.form.submit() |
|
84 |
assert resp.location.endswith('/manage/pricing/%s/' % pricing.pk) |
|
85 |
pricing.refresh_from_db() |
|
86 |
assert pricing.label == 'Model Foo' |
|
87 |
assert pricing.slug == 'foo-bar' |
|
88 | ||
89 | ||
90 |
def test_edit_pricing_as_manager(app, manager_user): |
|
91 |
pricing = Pricing.objects.create(label='Model') |
|
92 | ||
93 |
app = login(app, username='manager', password='manager') |
|
94 |
app.get('/manage/pricing/%s/edit/' % pricing.pk, status=403) |
|
95 | ||
96 | ||
97 |
def test_delete_pricing(app, admin_user): |
|
98 |
pricing = Pricing.objects.create(label='Model') |
|
99 | ||
100 |
app = login(app) |
|
101 |
resp = app.get('/manage/pricing/%s/' % pricing.pk) |
|
102 |
resp = resp.click(href='/manage/pricing/%s/delete/' % pricing.pk) |
|
103 |
resp = resp.form.submit() |
|
104 |
assert resp.location.endswith('/manage/pricing/') |
|
105 |
assert Pricing.objects.exists() is False |
|
106 | ||
107 | ||
108 |
def test_delete_pricing_as_manager(app, manager_user): |
|
109 |
pricing = Pricing.objects.create(label='Model') |
|
110 | ||
111 |
app = login(app, username='manager', password='manager') |
|
112 |
app.get('/manage/pricing/%s/delete/' % pricing.pk, status=403) |
|
113 | ||
114 | ||
115 |
def test_duplicate_pricing(app, admin_user): |
|
116 |
pricing = Pricing.objects.create(label='Model') |
|
117 |
assert Pricing.objects.count() == 1 |
|
118 | ||
119 |
app = login(app) |
|
120 |
resp = app.get('/manage/pricing/%s/' % pricing.pk) |
|
121 |
resp = resp.click(href='/manage/pricing/%s/duplicate/' % pricing.pk) |
|
122 |
resp = resp.form.submit() |
|
123 |
assert Pricing.objects.count() == 2 |
|
124 | ||
125 |
new_pricing = Pricing.objects.latest('pk') |
|
126 |
assert resp.location == '/manage/pricing/%s/' % new_pricing.pk |
|
127 |
assert new_pricing.pk != pricing.pk |
|
128 | ||
129 |
resp = resp.follow() |
|
130 |
assert 'copy-of-model' in resp.text |
|
131 | ||
132 |
resp = resp.click(href='/manage/pricing/%s/duplicate/' % new_pricing.pk) |
|
133 |
resp.form['label'] = 'hop' |
|
134 |
resp = resp.form.submit().follow() |
|
135 |
assert 'hop' in resp.text |
|
136 | ||
137 | ||
138 |
def test_duplicate_pricing_as_manager(app, manager_user): |
|
139 |
pricing = Pricing.objects.create(label='Model') |
|
140 | ||
141 |
app = login(app, username='manager', password='manager') |
|
142 |
app.get('/manage/pricing/%s/duplicate/' % pricing.pk, status=403) |
|
143 | ||
144 | ||
145 |
@pytest.mark.freeze_time('2021-07-08') |
|
146 |
def test_import_pricing(app, admin_user): |
|
147 |
pricing = Pricing.objects.create(label='Model') |
|
148 | ||
149 |
app = login(app) |
|
150 |
resp = app.get('/manage/pricing/%s/export/' % pricing.id) |
|
151 |
assert resp.headers['content-type'] == 'application/json' |
|
152 |
assert resp.headers['content-disposition'] == 'attachment; filename="export_pricing_model_20210708.json"' |
|
153 |
pricing_export = resp.text |
|
154 | ||
155 |
# existing pricing |
|
156 |
resp = app.get('/manage/', status=200) |
|
157 |
resp = resp.click('Import') |
|
158 |
resp.form['agendas_json'] = Upload('export.json', pricing_export.encode('utf-8'), 'application/json') |
|
159 |
resp = resp.form.submit() |
|
160 |
assert resp.location.endswith('/manage/pricing/%s/' % pricing.pk) |
|
161 |
resp = resp.follow() |
|
162 |
assert 'No pricing model created. A pricing model has been updated.' not in resp.text |
|
163 |
assert Pricing.objects.count() == 1 |
|
164 | ||
165 |
# new pricing |
|
166 |
Pricing.objects.all().delete() |
|
167 |
resp = app.get('/manage/', status=200) |
|
168 |
resp = resp.click('Import') |
|
169 |
resp.form['agendas_json'] = Upload('export.json', pricing_export.encode('utf-8'), 'application/json') |
|
170 |
resp = resp.form.submit() |
|
171 |
pricing = Pricing.objects.latest('pk') |
|
172 |
assert resp.location.endswith('/manage/pricing/%s/' % pricing.pk) |
|
173 |
resp = resp.follow() |
|
174 |
assert 'A pricing model has been created. No pricing model updated.' not in resp.text |
|
175 |
assert Pricing.objects.count() == 1 |
|
176 | ||
177 |
# multiple pricing |
|
178 |
pricings = json.loads(pricing_export) |
|
179 |
pricings['pricing_models'].append(copy.copy(pricings['pricing_models'][0])) |
|
180 |
pricings['pricing_models'].append(copy.copy(pricings['pricing_models'][0])) |
|
181 |
pricings['pricing_models'][1]['label'] = 'Foo bar 2' |
|
182 |
pricings['pricing_models'][1]['slug'] = 'foo-bar-2' |
|
183 |
pricings['pricing_models'][2]['label'] = 'Foo bar 3' |
|
184 |
pricings['pricing_models'][2]['slug'] = 'foo-bar-3' |
|
185 | ||
186 |
resp = app.get('/manage/', status=200) |
|
187 |
resp = resp.click('Import') |
|
188 |
resp.form['agendas_json'] = Upload( |
|
189 |
'export.json', json.dumps(pricings).encode('utf-8'), 'application/json' |
|
190 |
) |
|
191 |
resp = resp.form.submit() |
|
192 |
assert resp.location.endswith('/manage/') |
|
193 |
resp = resp.follow() |
|
194 |
assert '2 pricing models have been created. A pricing model has been updated.' in resp.text |
|
195 |
assert Pricing.objects.count() == 3 |
|
196 | ||
197 |
Pricing.objects.all().delete() |
|
198 |
resp = app.get('/manage/', status=200) |
|
199 |
resp = resp.click('Import') |
|
200 |
resp.form['agendas_json'] = Upload( |
|
201 |
'export.json', json.dumps(pricings).encode('utf-8'), 'application/json' |
|
202 |
) |
|
203 |
resp = resp.form.submit().follow() |
|
204 |
assert '3 pricing models have been created. No pricing model updated.' in resp.text |
|
205 |
assert Pricing.objects.count() == 3 |
|
206 | ||
207 | ||
208 |
def test_pricing_edit_extra_variables(app, admin_user): |
|
209 |
pricing = Pricing.objects.create(label='Model') |
|
210 |
assert pricing.extra_variables == {} |
|
211 | ||
212 |
app = login(app) |
|
213 |
resp = app.get('/manage/pricing/%s/' % pricing.id) |
|
214 |
assert '<label>Extra variables:</label>' not in resp.text |
|
215 |
resp = resp.click(href='/manage/pricing/%s/variable/' % pricing.id) |
|
216 |
resp.form['form-0-key'] = 'foo' |
|
217 |
resp.form['form-0-value'] = 'bar' |
|
218 |
resp = resp.form.submit().follow() |
|
219 |
pricing.refresh_from_db() |
|
220 |
assert pricing.extra_variables == {'foo': 'bar'} |
|
221 |
assert '<label>Extra variables:</label>' in resp.text |
|
222 |
assert '<i>foo</i>' in resp |
|
223 | ||
224 |
resp = resp.click(href='/manage/pricing/%s/variable/' % pricing.id) |
|
225 |
assert resp.form['form-TOTAL_FORMS'].value == '2' |
|
226 |
assert resp.form['form-0-key'].value == 'foo' |
|
227 |
assert resp.form['form-0-value'].value == 'bar' |
|
228 |
assert resp.form['form-1-key'].value == '' |
|
229 |
assert resp.form['form-1-value'].value == '' |
|
230 |
resp.form['form-0-value'] = 'bar-bis' |
|
231 |
resp.form['form-1-key'] = 'blah' |
|
232 |
resp.form['form-1-value'] = 'baz' |
|
233 |
resp = resp.form.submit().follow() |
|
234 |
pricing.refresh_from_db() |
|
235 |
assert pricing.extra_variables == { |
|
236 |
'foo': 'bar-bis', |
|
237 |
'blah': 'baz', |
|
238 |
} |
|
239 |
assert '<i>blah</i>, <i>foo</i>' in resp |
|
240 | ||
241 |
resp = resp.click(href='/manage/pricing/%s/variable/' % pricing.id) |
|
242 |
assert resp.form['form-TOTAL_FORMS'].value == '3' |
|
243 |
assert resp.form['form-0-key'].value == 'blah' |
|
244 |
assert resp.form['form-0-value'].value == 'baz' |
|
245 |
assert resp.form['form-1-key'].value == 'foo' |
|
246 |
assert resp.form['form-1-value'].value == 'bar-bis' |
|
247 |
assert resp.form['form-2-key'].value == '' |
|
248 |
assert resp.form['form-2-value'].value == '' |
|
249 |
resp.form['form-1-key'] = 'foo' |
|
250 |
resp.form['form-1-value'] = 'bar' |
|
251 |
resp.form['form-0-key'] = '' |
|
252 |
resp = resp.form.submit().follow() |
|
253 |
pricing.refresh_from_db() |
|
254 |
assert pricing.extra_variables == { |
|
255 |
'foo': 'bar', |
|
256 |
} |
|
257 |
assert '<i>foo</i>' in resp |
|
258 | ||
259 | ||
260 |
def test_pricing_edit_extra_variables_as_manager(app, manager_user): |
|
261 |
pricing = Pricing.objects.create(label='Model') |
|
262 | ||
263 |
app = login(app, username='manager', password='manager') |
|
264 |
app.get('/manage/pricing/%s/variable/' % pricing.pk, status=403) |
|
265 | ||
266 | ||
267 |
def test_pricing_add_category(app, admin_user): |
|
268 |
pricing = Pricing.objects.create(label='Model') |
|
269 |
category1 = CriteriaCategory.objects.create(label='Cat 1') |
|
270 |
category2 = CriteriaCategory.objects.create(label='Cat 2') |
|
271 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
272 |
category4 = CriteriaCategory.objects.create(label='Cat 4') |
|
273 | ||
274 |
app = login(app) |
|
275 |
resp = app.get('/manage/pricing/%s/' % pricing.pk) |
|
276 |
resp = resp.click(href='/manage/pricing/%s/category/add/' % pricing.pk) |
|
277 |
assert list(resp.context['form'].fields['category'].queryset) == [ |
|
278 |
category1, |
|
279 |
category2, |
|
280 |
category3, |
|
281 |
category4, |
|
282 |
] |
|
283 |
resp.form['category'] = category1.pk |
|
284 |
resp = resp.form.submit() |
|
285 |
assert resp.location.endswith('/manage/pricing/%s/' % pricing.pk) |
|
286 |
resp = resp.follow() |
|
287 |
assert '/manage/pricing/%s/category/add/' % pricing.pk in resp |
|
288 |
assert list( |
|
289 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) |
|
290 |
) == [category1.pk] |
|
291 |
assert list(PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('order', flat=True)) == [ |
|
292 |
1 |
|
293 |
] |
|
294 | ||
295 |
resp = app.get('/manage/pricing/%s/category/add/' % pricing.pk) |
|
296 |
assert list(resp.context['form'].fields['category'].queryset) == [category2, category3, category4] |
|
297 |
resp.form['category'] = category4.pk |
|
298 |
resp = resp.form.submit().follow() |
|
299 |
assert list( |
|
300 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) |
|
301 |
) == [category1.pk, category4.pk] |
|
302 |
assert list(PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('order', flat=True)) == [ |
|
303 |
1, |
|
304 |
2, |
|
305 |
] |
|
306 | ||
307 |
resp = app.get('/manage/pricing/%s/category/add/' % pricing.pk) |
|
308 |
assert list(resp.context['form'].fields['category'].queryset) == [category2, category3] |
|
309 |
resp.form['category'] = category2.pk |
|
310 |
resp = resp.form.submit().follow() |
|
311 |
assert '/manage/pricing/%s/category/add/' % pricing.pk not in resp |
|
312 |
assert list( |
|
313 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) |
|
314 |
) == [category1.pk, category4.pk, category2.pk] |
|
315 |
assert list(PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('order', flat=True)) == [ |
|
316 |
1, |
|
317 |
2, |
|
318 |
3, |
|
319 |
] |
|
320 | ||
321 |
app.get('/manage/pricing/%s/category/add/' % pricing.pk, status=404) |
|
322 | ||
323 | ||
324 |
def test_pricing_add_category_as_manager(app, manager_user): |
|
325 |
pricing = Pricing.objects.create(label='Model') |
|
326 | ||
327 |
app = login(app, username='manager', password='manager') |
|
328 |
app.get('/manage/pricing/%s/category/add/' % pricing.pk, status=403) |
|
329 | ||
330 | ||
331 |
def test_pricing_edit_category(app, admin_user): |
|
332 |
category1 = CriteriaCategory.objects.create(label='Cat 1') |
|
333 |
criteria1 = Criteria.objects.create(label='Crit 1', category=category1) |
|
334 |
criteria2 = Criteria.objects.create(label='Crit 2', category=category1) |
|
335 |
criteria3 = Criteria.objects.create(label='Crit 3', category=category1) |
|
336 |
criteria4 = Criteria.objects.create(label='Crit 4', category=category1) |
|
337 |
category2 = CriteriaCategory.objects.create(label='cat 2') |
|
338 |
criteria5 = Criteria.objects.create(label='Crit 5', category=category2) |
|
339 |
pricing = Pricing.objects.create(label='Model') |
|
340 |
pricing.categories.add(category1, through_defaults={'order': 1}) |
|
341 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
342 | ||
343 |
app = login(app) |
|
344 |
resp = app.get('/manage/pricing/%s/' % pricing.pk) |
|
345 |
resp = resp.click(href='/manage/pricing/%s/category/%s/edit/' % (pricing.pk, category1.pk)) |
|
346 |
assert list(resp.context['form'].fields['criterias'].queryset) == [ |
|
347 |
criteria1, |
|
348 |
criteria2, |
|
349 |
criteria3, |
|
350 |
criteria4, |
|
351 |
] |
|
352 |
assert list(resp.context['form'].initial['criterias']) == [] |
|
353 |
resp.form['criterias'] = [criteria1.pk, criteria3.pk] |
|
354 |
resp = resp.form.submit() |
|
355 |
assert resp.location.endswith('/manage/pricing/%s/' % pricing.pk) |
|
356 |
resp = resp.follow() |
|
357 |
assert list(pricing.criterias.order_by('pk')) == [criteria1, criteria3] |
|
358 | ||
359 |
resp = app.get('/manage/pricing/%s/category/%s/edit/' % (pricing.pk, category1.pk)) |
|
360 |
assert list(resp.context['form'].initial['criterias']) == [criteria1, criteria3] |
|
361 |
resp.form['criterias'] = [criteria1.pk, criteria4.pk] |
|
362 |
resp = resp.form.submit().follow() |
|
363 |
assert list(pricing.criterias.order_by('pk')) == [criteria1, criteria4] |
|
364 | ||
365 |
resp = app.get('/manage/pricing/%s/category/%s/edit/' % (pricing.pk, category2.pk)) |
|
366 |
assert list(resp.context['form'].fields['criterias'].queryset) == [criteria5] |
|
367 |
assert list(resp.context['form'].initial['criterias']) == [] |
|
368 |
resp.form['criterias'] = [criteria5.pk] |
|
369 |
resp = resp.form.submit().follow() |
|
370 |
assert list(pricing.criterias.order_by('pk')) == [criteria1, criteria4, criteria5] |
|
371 | ||
372 | ||
373 |
def test_pricing_edit_category_as_manager(app, manager_user): |
|
374 |
pricing = Pricing.objects.create(label='Model') |
|
375 |
category = CriteriaCategory.objects.create(label='Cat') |
|
376 |
pricing.categories.add(category, through_defaults={'order': 1}) |
|
377 | ||
378 |
app = login(app, username='manager', password='manager') |
|
379 |
app.get('/manage/pricing/%s/category/%s/edit/' % (pricing.pk, category.pk), status=403) |
|
380 | ||
381 | ||
382 |
def test_pricing_delete_category(app, admin_user): |
|
383 |
category1 = CriteriaCategory.objects.create(label='Cat 1') |
|
384 |
criteria1 = Criteria.objects.create(label='Crit 1', category=category1) |
|
385 |
category2 = CriteriaCategory.objects.create(label='Cat 2') |
|
386 |
criteria2 = Criteria.objects.create(label='Crit 2', category=category2) |
|
387 |
pricing = Pricing.objects.create(label='Model') |
|
388 |
pricing.categories.add(category1, through_defaults={'order': 1}) |
|
389 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
390 |
pricing.criterias.add(criteria1, criteria2) |
|
391 | ||
392 |
app = login(app) |
|
393 |
resp = app.get('/manage/pricing/%s/' % pricing.pk) |
|
394 |
resp = resp.click(href='/manage/pricing/%s/category/%s/delete/' % (pricing.pk, category1.pk)) |
|
395 |
resp = resp.form.submit() |
|
396 |
assert resp.location.endswith('/manage/pricing/%s/' % pricing.pk) |
|
397 |
resp = resp.follow() |
|
398 |
assert list(pricing.categories.all()) == [category2] |
|
399 |
assert list(pricing.criterias.all()) == [criteria2] |
|
400 | ||
401 |
# not linked |
|
402 |
app.get('/manage/pricing/%s/category/%s/delete/' % (pricing.pk, category1.pk), status=404) |
|
403 |
# unknown |
|
404 |
app.get('/manage/pricing/%s/category/%s/delete/' % (pricing.pk, 0), status=404) |
|
405 | ||
406 | ||
407 |
def test_pricing_delete_category_as_manager(app, manager_user): |
|
408 |
pricing = Pricing.objects.create(label='Model') |
|
409 |
category = CriteriaCategory.objects.create(label='Cat') |
|
410 | ||
411 |
app = login(app, username='manager', password='manager') |
|
412 |
app.get('/manage/pricing/%s/category/%s/delete/' % (pricing.pk, category.pk), status=403) |
|
413 | ||
414 | ||
415 |
def test_pricing_reorder_categories(app, admin_user): |
|
416 |
category1 = CriteriaCategory.objects.create(label='Cat 1') |
|
417 |
category2 = CriteriaCategory.objects.create(label='Cat 2') |
|
418 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
419 |
category4 = CriteriaCategory.objects.create(label='Cat 4') |
|
420 |
pricing = Pricing.objects.create(label='Model') |
|
421 |
pricing.categories.add(category1, through_defaults={'order': 1}) |
|
422 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
423 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
424 |
assert list( |
|
425 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) |
|
426 |
) == [category1.pk, category2.pk, category3.pk] |
|
427 |
assert list(PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('order', flat=True)) == [ |
|
428 |
1, |
|
429 |
2, |
|
430 |
3, |
|
431 |
] |
|
432 | ||
433 |
app = login(app) |
|
434 |
# missing get params |
|
435 |
app.get('/manage/pricing/%s/order/' % (pricing.pk), status=400) |
|
436 | ||
437 |
# bad new-order param |
|
438 |
bad_params = [ |
|
439 |
# missing category3 in order |
|
440 |
','.join(str(x) for x in [category1.pk, category2.pk]), |
|
441 |
# category1 mentionned twice |
|
442 |
','.join(str(x) for x in [category1.pk, category2.pk, category3.pk, category1.pk]), |
|
443 |
# category4 not in pricing categories |
|
444 |
','.join(str(x) for x in [category1.pk, category2.pk, category3.pk, category4.pk]), |
|
445 |
# not an id |
|
446 |
'foo,1,2,3', |
|
447 |
' 1 ,2,3', |
|
448 |
] |
|
449 |
for bad_param in bad_params: |
|
450 |
app.get( |
|
451 |
'/manage/pricing/%s/order/' % (pricing.pk), |
|
452 |
params={'new-order': bad_param}, |
|
453 |
status=400, |
|
454 |
) |
|
455 |
# not changed |
|
456 |
assert list( |
|
457 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) |
|
458 |
) == [category1.pk, category2.pk, category3.pk] |
|
459 |
assert list(PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('order', flat=True)) == [ |
|
460 |
1, |
|
461 |
2, |
|
462 |
3, |
|
463 |
] |
|
464 | ||
465 |
# change order |
|
466 |
app.get( |
|
467 |
'/manage/pricing/%s/order/' % (pricing.pk), |
|
468 |
params={'new-order': ','.join(str(x) for x in [category3.pk, category1.pk, category2.pk])}, |
|
469 |
) |
|
470 |
assert list( |
|
471 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) |
|
472 |
) == [category3.pk, category1.pk, category2.pk] |
|
473 |
assert list(PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('order', flat=True)) == [ |
|
474 |
1, |
|
475 |
2, |
|
476 |
3, |
|
477 |
] |
|
478 | ||
479 | ||
480 |
def test_pricing_reorder_categories_as_manager(app, manager_user): |
|
481 |
pricing = Pricing.objects.create(label='Model') |
|
482 | ||
483 |
app = login(app, username='manager', password='manager') |
|
484 |
app.get('/manage/pricing/%s/order/' % (pricing.pk), status=403) |
|
485 | ||
486 | ||
487 |
def test_list_criterias_as_manager(app, manager_user): |
|
488 |
app = login(app, username='manager', password='manager') |
|
489 |
app.get('/manage/pricing/criterias/', status=403) |
|
490 | ||
491 | ||
492 |
def test_add_category(settings, app, admin_user): |
|
493 |
app = login(app) |
|
494 |
resp = app.get('/manage/') |
|
495 |
resp = resp.click('Pricing') |
|
496 |
resp = resp.click('Criterias') |
|
497 |
resp = resp.click('New category') |
|
498 |
resp.form['label'] = 'QF' |
|
499 |
resp = resp.form.submit() |
|
500 |
category = CriteriaCategory.objects.latest('pk') |
|
501 |
assert resp.location.endswith('/manage/pricing/criterias/') |
|
502 |
assert category.label == 'QF' |
|
503 |
assert category.slug == 'qf' |
|
504 | ||
505 | ||
506 |
def test_add_category_as_manager(app, manager_user): |
|
507 |
app = login(app, username='manager', password='manager') |
|
508 |
app.get('/manage/pricing/criteria/category/add/', status=403) |
|
509 | ||
510 | ||
511 |
def test_edit_category(app, admin_user): |
|
512 |
category = CriteriaCategory.objects.create(label='QF') |
|
513 |
category2 = CriteriaCategory.objects.create(label='Domicile') |
|
514 | ||
515 |
app = login(app) |
|
516 |
resp = app.get('/manage/pricing/criterias/') |
|
517 |
resp = resp.click(href='/manage/pricing/criteria/category/%s/edit/' % category.pk) |
|
518 |
resp.form['label'] = 'QF Foo' |
|
519 |
resp.form['slug'] = category2.slug |
|
520 |
resp = resp.form.submit() |
|
521 |
assert resp.context['form'].errors['slug'] == ['Criteria category with this Identifier already exists.'] |
|
522 | ||
523 |
resp.form['slug'] = 'baz2' |
|
524 |
resp = resp.form.submit() |
|
525 |
assert resp.location.endswith('/manage/pricing/criterias/') |
|
526 |
category.refresh_from_db() |
|
527 |
assert category.label == 'QF Foo' |
|
528 |
assert category.slug == 'baz2' |
|
529 | ||
530 | ||
531 |
def test_edit_category_as_manager(app, manager_user): |
|
532 |
category = CriteriaCategory.objects.create(label='Foo bar') |
|
533 | ||
534 |
app = login(app, username='manager', password='manager') |
|
535 |
app.get('/manage/pricing/criteria/category/%s/edit/' % category.pk, status=403) |
|
536 | ||
537 | ||
538 |
def test_delete_category(app, admin_user): |
|
539 |
category = CriteriaCategory.objects.create(label='QF') |
|
540 |
Criteria.objects.create(label='QF 1', category=category) |
|
541 | ||
542 |
app = login(app) |
|
543 |
resp = app.get('/manage/pricing/criterias/') |
|
544 |
resp = resp.click(href='/manage/pricing/criteria/category/%s/delete/' % category.pk) |
|
545 |
resp = resp.form.submit() |
|
546 |
assert resp.location.endswith('/manage/pricing/criterias/') |
|
547 |
assert CriteriaCategory.objects.exists() is False |
|
548 |
assert Criteria.objects.exists() is False |
|
549 | ||
550 | ||
551 |
def test_delete_category_as_manager(app, manager_user): |
|
552 |
category = CriteriaCategory.objects.create(label='Foo bar') |
|
553 | ||
554 |
app = login(app, username='manager', password='manager') |
|
555 |
app.get('/manage/pricing/criteria/category/%s/delete/' % category.pk, status=403) |
|
556 | ||
557 | ||
558 |
def test_add_criteria(app, admin_user): |
|
559 |
category = CriteriaCategory.objects.create(label='QF') |
|
560 | ||
561 |
app = login(app) |
|
562 |
resp = app.get('/manage/pricing/criterias/') |
|
563 |
resp = resp.click('Add a criteria') |
|
564 |
resp.form['label'] = 'QF < 1' |
|
565 |
resp.form['condition'] = 'qf < 1 #' |
|
566 |
assert 'slug' not in resp.context['form'].fields |
|
567 |
resp = resp.form.submit() |
|
568 |
assert resp.context['form'].errors['condition'] == ['Invalid syntax.'] |
|
569 |
resp.form['condition'] = 'qf < 1' |
|
570 |
resp = resp.form.submit() |
|
571 |
criteria = Criteria.objects.latest('pk') |
|
572 |
assert resp.location.endswith('/manage/pricing/criterias/') |
|
573 |
assert criteria.label == 'QF < 1' |
|
574 |
assert criteria.category == category |
|
575 |
assert criteria.slug == 'qf-1' |
|
576 |
assert criteria.condition == 'qf < 1' |
|
577 |
assert criteria.order == 1 |
|
578 | ||
579 |
resp = app.get('/manage/pricing/criteria/category/%s/add/' % category.pk) |
|
580 |
resp.form['label'] = 'QF < 1' |
|
581 |
resp.form['condition'] = 'qf < 1' |
|
582 |
resp = resp.form.submit() |
|
583 |
assert resp.location.endswith('/manage/pricing/criterias/') |
|
584 |
criteria = Criteria.objects.latest('pk') |
|
585 |
assert criteria.label == 'QF < 1' |
|
586 |
assert criteria.category == category |
|
587 |
assert criteria.slug == 'qf-1-1' |
|
588 |
assert criteria.condition == 'qf < 1' |
|
589 |
assert criteria.order == 2 |
|
590 | ||
591 | ||
592 |
def test_add_criteria_as_manager(app, manager_user): |
|
593 |
category = CriteriaCategory.objects.create(label='Foo bar') |
|
594 | ||
595 |
app = login(app, username='manager', password='manager') |
|
596 |
app.get('/manage/pricing/criteria/category/%s/add/' % category.pk, status=403) |
|
597 | ||
598 | ||
599 |
def test_edit_criteria(app, admin_user): |
|
600 |
category = CriteriaCategory.objects.create(label='QF') |
|
601 |
criteria = Criteria.objects.create(label='QF 1', category=category) |
|
602 |
criteria2 = Criteria.objects.create(label='QF 2', category=category) |
|
603 |
category2 = CriteriaCategory.objects.create(label='Foo') |
|
604 |
criteria3 = Criteria.objects.create(label='foo-bar', category=category2) |
|
605 | ||
606 |
app = login(app) |
|
607 |
resp = app.get('/manage/pricing/criterias/') |
|
608 |
resp = resp.click(href='/manage/pricing/criteria/category/%s/%s/edit/' % (category.pk, criteria.pk)) |
|
609 |
resp.form['label'] = 'QF 1 bis' |
|
610 |
resp.form['slug'] = criteria2.slug |
|
611 |
resp.form['condition'] = 'qf <= 1 #' |
|
612 |
resp = resp.form.submit() |
|
613 |
assert resp.context['form'].errors['slug'] == ['Another criteria exists with the same identifier.'] |
|
614 |
assert resp.context['form'].errors['condition'] == ['Invalid syntax.'] |
|
615 | ||
616 |
resp.form['condition'] = 'qf <= 1' |
|
617 |
resp.form['slug'] = criteria3.slug |
|
618 |
resp = resp.form.submit() |
|
619 |
assert resp.location.endswith('/manage/pricing/criterias/') |
|
620 |
criteria.refresh_from_db() |
|
621 |
assert criteria.label == 'QF 1 bis' |
|
622 |
assert criteria.slug == 'foo-bar' |
|
623 |
assert criteria.condition == 'qf <= 1' |
|
624 | ||
625 | ||
626 |
def test_edit_criteria_as_manager(app, manager_user): |
|
627 |
category = CriteriaCategory.objects.create(label='QF') |
|
628 |
criteria = Criteria.objects.create(label='QF 1', category=category) |
|
629 | ||
630 |
app = login(app, username='manager', password='manager') |
|
631 |
app.get('/manage/pricing/criteria/category/%s/%s/edit/' % (category.pk, criteria.pk), status=403) |
|
632 | ||
633 | ||
634 |
def test_delete_criteria(app, admin_user): |
|
635 |
category = CriteriaCategory.objects.create(label='QF') |
|
636 |
criteria = Criteria.objects.create(label='QF 1', category=category) |
|
637 | ||
638 |
app = login(app) |
|
639 |
resp = app.get('/manage/pricing/criterias/') |
|
640 |
resp = resp.click(href='/manage/pricing/criteria/category/%s/%s/delete/' % (category.pk, criteria.pk)) |
|
641 |
resp = resp.form.submit() |
|
642 |
assert resp.location.endswith('/manage/pricing/criterias/') |
|
643 |
assert CriteriaCategory.objects.exists() is True |
|
644 |
assert Criteria.objects.exists() is False |
|
645 | ||
646 | ||
647 |
def test_delete_criteria_as_manager(app, manager_user): |
|
648 |
category = CriteriaCategory.objects.create(label='QF') |
|
649 |
criteria = Criteria.objects.create(label='QF 1', category=category) |
|
650 | ||
651 |
app = login(app, username='manager', password='manager') |
|
652 |
app.get('/manage/pricing/criteria/category/%s/%s/delete/' % (category.pk, criteria.pk), status=403) |
|
653 | ||
654 | ||
655 |
def test_reorder_criterias(app, admin_user): |
|
656 |
category = CriteriaCategory.objects.create(label='QF') |
|
657 |
criteria1 = Criteria.objects.create(label='QF 1', category=category) |
|
658 |
criteria2 = Criteria.objects.create(label='QF 2', category=category) |
|
659 |
criteria3 = Criteria.objects.create(label='QF 3', category=category) |
|
660 |
criteria4 = Criteria.objects.create(label='QF 4', category=category) |
|
661 |
assert list(category.criterias.values_list('pk', flat=True).order_by('order')) == [ |
|
662 |
criteria1.pk, |
|
663 |
criteria2.pk, |
|
664 |
criteria3.pk, |
|
665 |
criteria4.pk, |
|
666 |
] |
|
667 |
assert list(category.criterias.values_list('order', flat=True).order_by('order')) == [1, 2, 3, 4] |
|
668 | ||
669 |
app = login(app) |
|
670 |
# missing get params |
|
671 |
app.get('/manage/pricing/criteria/category/%s/order/' % (category.pk), status=400) |
|
672 | ||
673 |
# bad new-order param |
|
674 |
bad_params = [ |
|
675 |
# missing criteria3 in order |
|
676 |
','.join(str(x) for x in [criteria1.pk, criteria2.pk, criteria4.pk]), |
|
677 |
# criteria1 mentionned twice |
|
678 |
','.join(str(x) for x in [criteria1.pk, criteria2.pk, criteria3.pk, criteria4.pk, criteria1.pk]), |
|
679 |
# not an id |
|
680 |
'foo,1,2,3,4', |
|
681 |
' 1 ,2,3,4', |
|
682 |
] |
|
683 |
for bad_param in bad_params: |
|
684 |
app.get( |
|
685 |
'/manage/pricing/criteria/category/%s/order/' % (category.pk), |
|
686 |
params={'new-order': bad_param}, |
|
687 |
status=400, |
|
688 |
) |
|
689 |
# not changed |
|
690 |
assert list(category.criterias.values_list('pk', flat=True).order_by('order')) == [ |
|
691 |
criteria1.pk, |
|
692 |
criteria2.pk, |
|
693 |
criteria3.pk, |
|
694 |
criteria4.pk, |
|
695 |
] |
|
696 |
assert list(category.criterias.values_list('order', flat=True).order_by('order')) == [1, 2, 3, 4] |
|
697 | ||
698 |
# change order |
|
699 |
app.get( |
|
700 |
'/manage/pricing/criteria/category/%s/order/' % (category.pk), |
|
701 |
params={ |
|
702 |
'new-order': ','.join(str(x) for x in [criteria3.pk, criteria1.pk, criteria4.pk, criteria2.pk]) |
|
703 |
}, |
|
704 |
) |
|
705 |
assert list(category.criterias.values_list('pk', flat=True).order_by('order')) == [ |
|
706 |
criteria3.pk, |
|
707 |
criteria1.pk, |
|
708 |
criteria4.pk, |
|
709 |
criteria2.pk, |
|
710 |
] |
|
711 |
assert list(category.criterias.values_list('order', flat=True).order_by('order')) == [1, 2, 3, 4] |
|
712 | ||
713 | ||
714 |
def test_reorder_criterias_as_manager(app, manager_user): |
|
715 |
category = CriteriaCategory.objects.create(label='QF') |
|
716 | ||
717 |
app = login(app, username='manager', password='manager') |
|
718 |
app.get('/manage/pricing/criteria/category/%s/order/' % (category.pk), status=403) |
|
719 | ||
720 | ||
721 |
@pytest.mark.freeze_time('2021-07-08') |
|
722 |
def test_import_criteria_category(app, admin_user): |
|
723 |
category = CriteriaCategory.objects.create(label='Foo bar') |
|
724 |
Criteria.objects.create(label='Foo', category=category) |
|
725 |
Criteria.objects.create(label='Baz', category=category) |
|
726 | ||
727 |
app = login(app) |
|
728 |
resp = app.get('/manage/pricing/criteria/category/%s/export/' % category.id) |
|
729 |
assert resp.headers['content-type'] == 'application/json' |
|
730 |
assert ( |
|
731 |
resp.headers['content-disposition'] |
|
732 |
== 'attachment; filename="export_pricing_category_foo-bar_20210708.json"' |
|
733 |
) |
|
734 |
category_export = resp.text |
|
735 | ||
736 |
# existing category |
|
737 |
resp = app.get('/manage/', status=200) |
|
738 |
resp = resp.click('Import') |
|
739 |
resp.form['agendas_json'] = Upload('export.json', category_export.encode('utf-8'), 'application/json') |
|
740 |
resp = resp.form.submit() |
|
741 |
assert resp.location.endswith('/manage/pricing/criterias/') |
|
742 |
resp = resp.follow() |
|
743 |
assert ( |
|
744 |
'No pricing criteria category created. A pricing criteria category has been updated.' not in resp.text |
|
745 |
) |
|
746 |
assert CriteriaCategory.objects.count() == 1 |
|
747 |
assert Criteria.objects.count() == 2 |
|
748 | ||
749 |
# new category |
|
750 |
CriteriaCategory.objects.all().delete() |
|
751 |
resp = app.get('/manage/', status=200) |
|
752 |
resp = resp.click('Import') |
|
753 |
resp.form['agendas_json'] = Upload('export.json', category_export.encode('utf-8'), 'application/json') |
|
754 |
resp = resp.form.submit() |
|
755 |
assert resp.location.endswith('/manage/pricing/criterias/') |
|
756 |
resp = resp.follow() |
|
757 |
assert ( |
|
758 |
'A pricing criteria category has been created. No pricing criteria category updated.' not in resp.text |
|
759 |
) |
|
760 |
assert CriteriaCategory.objects.count() == 1 |
|
761 |
assert Criteria.objects.count() == 2 |
|
762 | ||
763 |
# multiple categories |
|
764 |
categories = json.loads(category_export) |
|
765 |
categories['pricing_categories'].append(copy.copy(categories['pricing_categories'][0])) |
|
766 |
categories['pricing_categories'].append(copy.copy(categories['pricing_categories'][0])) |
|
767 |
categories['pricing_categories'][1]['label'] = 'Foo bar 2' |
|
768 |
categories['pricing_categories'][1]['slug'] = 'foo-bar-2' |
|
769 |
categories['pricing_categories'][2]['label'] = 'Foo bar 3' |
|
770 |
categories['pricing_categories'][2]['slug'] = 'foo-bar-3' |
|
771 | ||
772 |
resp = app.get('/manage/', status=200) |
|
773 |
resp = resp.click('Import') |
|
774 |
resp.form['agendas_json'] = Upload( |
|
775 |
'export.json', json.dumps(categories).encode('utf-8'), 'application/json' |
|
776 |
) |
|
777 |
resp = resp.form.submit() |
|
778 |
assert resp.location.endswith('/manage/') |
|
779 |
resp = resp.follow() |
|
780 |
assert ( |
|
781 |
'2 pricing criteria categories have been created. A pricing criteria category has been updated.' |
|
782 |
in resp.text |
|
783 |
) |
|
784 |
assert CriteriaCategory.objects.count() == 3 |
|
785 |
assert Criteria.objects.count() == 6 |
|
786 | ||
787 |
CriteriaCategory.objects.all().delete() |
|
788 |
resp = app.get('/manage/', status=200) |
|
789 |
resp = resp.click('Import') |
|
790 |
resp.form['agendas_json'] = Upload( |
|
791 |
'export.json', json.dumps(categories).encode('utf-8'), 'application/json' |
|
792 |
) |
|
793 |
resp = resp.form.submit().follow() |
|
794 |
assert ( |
|
795 |
'3 pricing criteria categories have been created. No pricing criteria category updated.' in resp.text |
|
796 |
) |
|
797 |
assert CriteriaCategory.objects.count() == 3 |
|
798 |
assert Criteria.objects.count() == 6 |
|
799 | ||
800 | ||
801 |
def test_add_agenda_pricing(settings, app, admin_user): |
|
802 |
agenda = Agenda.objects.create(label='Foo Bar') |
|
803 |
pricing = Pricing.objects.create(label='Model') |
|
804 | ||
805 |
app = login(app) |
|
806 |
resp = app.get('/manage/agendas/%s/settings' % agenda.pk) |
|
807 |
resp = resp.click('New pricing') |
|
808 |
resp.form['pricing'] = pricing.pk |
|
809 |
resp.form['date_start'] = '2021-09-01' |
|
810 |
resp.form['date_end'] = '2021-09-01' |
|
811 |
resp = resp.form.submit() |
|
812 |
assert resp.context['form'].errors['date_end'] == ['End date must be greater than start date.'] |
|
813 |
resp.form['date_end'] = '2022-09-01' |
|
814 |
resp = resp.form.submit() |
|
815 |
agenda_pricing = AgendaPricing.objects.latest('pk') |
|
816 |
assert resp.location.endswith('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
817 |
assert agenda_pricing.pricing == pricing |
|
818 |
assert agenda_pricing.agenda == agenda |
|
819 |
assert agenda_pricing.date_start == datetime.date(2021, 9, 1) |
|
820 |
assert agenda_pricing.date_end == datetime.date(2022, 9, 1) |
|
821 | ||
822 |
resp = app.get('/manage/pricing/agenda/%s/pricing/add/' % agenda.pk) |
|
823 |
resp.form['pricing'] = pricing.pk |
|
824 |
resp.form['date_start'] = '2021-11-01' |
|
825 |
resp.form['date_end'] = '2022-11-01' |
|
826 |
resp = resp.form.submit() |
|
827 |
assert resp.context['form'].errors['__all__'] == ['Pricing overlaps existing pricings.'] |
|
828 |
resp.form['date_start'] = '2022-09-01' |
|
829 |
resp.form['date_end'] = '2023-09-01' |
|
830 |
resp = resp.form.submit() |
|
831 |
agenda_pricing = AgendaPricing.objects.latest('pk') |
|
832 |
assert agenda_pricing.pricing == pricing |
|
833 |
assert agenda_pricing.agenda == agenda |
|
834 |
assert agenda_pricing.date_start == datetime.date(2022, 9, 1) |
|
835 |
assert agenda_pricing.date_end == datetime.date(2023, 9, 1) |
|
836 | ||
837 |
settings.CHRONO_ENABLE_PRICING = False |
|
838 |
resp = app.get('/manage/') |
|
839 |
resp = app.get('/manage/agendas/%s/settings' % agenda.pk) |
|
840 |
assert 'New pricing' not in resp |
|
841 | ||
842 | ||
843 |
def test_add_agenda_pricing_as_manager(app, manager_user, agenda_with_restrictions): |
|
844 |
app = login(app, username='manager', password='manager') |
|
845 |
app.get('/manage/pricing/agenda/%s/pricing/add/' % agenda_with_restrictions.pk, status=403) |
|
846 | ||
847 | ||
848 |
def test_edit_agenda_pricing(app, admin_user): |
|
849 |
agenda = Agenda.objects.create(label='Foo Bar') |
|
850 |
pricing = Pricing.objects.create(label='Model') |
|
851 |
pricing2 = Pricing.objects.create(label='Model 2') |
|
852 |
agenda_pricing = AgendaPricing.objects.create( |
|
853 |
agenda=agenda, |
|
854 |
pricing=pricing, |
|
855 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
856 |
date_end=datetime.date(year=2022, month=9, day=1), |
|
857 |
) |
|
858 |
agenda2 = Agenda.objects.create(label='Foo Bar') |
|
859 |
agenda_pricing2 = AgendaPricing.objects.create( |
|
860 |
agenda=agenda2, |
|
861 |
pricing=pricing, |
|
862 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
863 |
date_end=datetime.date(year=2022, month=9, day=1), |
|
864 |
) |
|
865 |
AgendaPricing.objects.create( |
|
866 |
agenda=agenda, |
|
867 |
pricing=pricing, |
|
868 |
date_start=datetime.date(year=2022, month=9, day=1), |
|
869 |
date_end=datetime.date(year=2023, month=9, day=1), |
|
870 |
) |
|
871 | ||
872 |
app = login(app) |
|
873 |
resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
874 |
resp = resp.click(href='/manage/pricing/agenda/%s/pricing/%s/edit/' % (agenda.pk, agenda_pricing.pk)) |
|
875 |
resp.form['pricing'] = pricing2.pk |
|
876 |
resp.form['date_start'] = '2021-09-01' |
|
877 |
resp.form['date_end'] = '2021-09-01' |
|
878 |
resp = resp.form.submit() |
|
879 |
assert resp.context['form'].errors['date_end'] == ['End date must be greater than start date.'] |
|
880 |
resp.form['date_start'] = '2021-11-01' |
|
881 |
resp.form['date_end'] = '2022-11-01' |
|
882 |
resp = resp.form.submit() |
|
883 |
assert resp.context['form'].errors['__all__'] == ['Pricing overlaps existing pricings.'] |
|
884 |
resp.form['date_start'] = '2021-08-01' |
|
885 |
resp.form['date_end'] = '2022-09-01' |
|
886 |
resp = resp.form.submit() |
|
887 |
assert resp.location.endswith('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
888 |
agenda_pricing.refresh_from_db() |
|
889 |
assert agenda_pricing.pricing == pricing2 |
|
890 |
assert agenda_pricing.agenda == agenda |
|
891 |
assert agenda_pricing.date_start == datetime.date(2021, 8, 1) |
|
892 |
assert agenda_pricing.date_end == datetime.date(2022, 9, 1) |
|
893 | ||
894 |
app.get('/manage/pricing/agenda/%s/pricing/%s/edit/' % (agenda.pk, agenda_pricing2.pk), status=404) |
|
895 |
app.get('/manage/pricing/agenda/%s/pricing/%s/edit/' % (0, agenda_pricing.pk), status=404) |
|
896 |
app.get('/manage/pricing/agenda/%s/pricing/%s/edit/' % (agenda.pk, 0), status=404) |
|
897 |
# wrong kind |
|
898 |
for kind in ['meetings', 'virtual']: |
|
899 |
agenda.kind = kind |
|
900 |
agenda.save() |
|
901 |
app.get('/manage/pricing/agenda/%s/pricing/%s/edit/' % (agenda.pk, agenda_pricing.pk), status=404) |
|
902 | ||
903 | ||
904 |
def test_edit_agenda_pricing_as_manager(app, manager_user, agenda_with_restrictions): |
|
905 |
pricing = Pricing.objects.create(label='Model') |
|
906 |
agenda_pricing = AgendaPricing.objects.create( |
|
907 |
agenda=agenda_with_restrictions, |
|
908 |
pricing=pricing, |
|
909 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
910 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
911 |
) |
|
912 |
app = login(app, username='manager', password='manager') |
|
913 |
app.get( |
|
914 |
'/manage/pricing/agenda/%s/pricing/%s/edit/' % (agenda_with_restrictions.pk, agenda_pricing.pk), |
|
915 |
status=403, |
|
916 |
) |
|
917 | ||
918 |
resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda_with_restrictions.pk, agenda_pricing.pk)) |
|
919 |
assert ( |
|
920 |
'/manage/pricing/agenda/%s/pricing/%s/edit/' % (agenda_with_restrictions.pk, agenda_pricing.pk) |
|
921 |
not in resp |
|
922 |
) |
|
923 | ||
924 | ||
925 |
def test_delete_agenda_pricing_as_manager(app, manager_user, agenda_with_restrictions): |
|
926 |
pricing = Pricing.objects.create(label='Model') |
|
927 |
agenda_pricing = AgendaPricing.objects.create( |
|
928 |
agenda=agenda_with_restrictions, |
|
929 |
pricing=pricing, |
|
930 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
931 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
932 |
) |
|
933 |
app = login(app, username='manager', password='manager') |
|
934 |
app.get( |
|
935 |
'/manage/pricing/agenda/%s/pricing/%s/delete/' % (agenda_with_restrictions.pk, agenda_pricing.pk), |
|
936 |
status=403, |
|
937 |
) |
|
938 | ||
939 |
resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda_with_restrictions.pk, agenda_pricing.pk)) |
|
940 |
assert ( |
|
941 |
'/manage/pricing/agenda/%s/pricing/%s/delete/' % (agenda_with_restrictions.pk, agenda_pricing.pk) |
|
942 |
not in resp |
|
943 |
) |
|
944 | ||
945 | ||
946 |
def test_delete_agenda_pricing(app, admin_user): |
|
947 |
agenda = Agenda.objects.create(label='Foo Bar') |
|
948 |
pricing = Pricing.objects.create(label='Model') |
|
949 |
agenda_pricing = AgendaPricing.objects.create( |
|
950 |
agenda=agenda, |
|
951 |
pricing=pricing, |
|
952 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
953 |
date_end=datetime.date(year=2022, month=9, day=1), |
|
954 |
) |
|
955 |
agenda2 = Agenda.objects.create(label='Foo Bar') |
|
956 |
agenda_pricing2 = AgendaPricing.objects.create( |
|
957 |
agenda=agenda2, |
|
958 |
pricing=pricing, |
|
959 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
960 |
date_end=datetime.date(year=2022, month=9, day=1), |
|
961 |
) |
|
962 | ||
963 |
app = login(app) |
|
964 |
app.get('/manage/pricing/agenda/%s/pricing/%s/delete/' % (agenda.pk, agenda_pricing2.pk), status=404) |
|
965 |
app.get('/manage/pricing/agenda/%s/pricing/%s/delete/' % (0, agenda_pricing.pk), status=404) |
|
966 |
app.get('/manage/pricing/agenda/%s/pricing/%s/delete/' % (agenda.pk, 0), status=404) |
|
967 |
# wrong kind |
|
968 |
for kind in ['meetings', 'virtual']: |
|
969 |
agenda.kind = kind |
|
970 |
agenda.save() |
|
971 |
app.get('/manage/pricing/agenda/%s/pricing/%s/delete/' % (agenda.pk, agenda_pricing.pk), status=404) |
|
972 | ||
973 |
agenda.kind = 'events' |
|
974 |
agenda.save() |
|
975 |
resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
976 |
resp = resp.click(href='/manage/pricing/agenda/%s/pricing/%s/delete/' % (agenda.pk, agenda_pricing.pk)) |
|
977 |
resp = resp.form.submit() |
|
978 |
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk) |
|
979 |
assert AgendaPricing.objects.filter(pk=agenda_pricing.pk).exists() is False |
|
980 | ||
981 | ||
982 |
def test_detail_agenda_pricing(app, admin_user): |
|
983 |
agenda = Agenda.objects.create(label='Foo Bar') |
|
984 |
pricing = Pricing.objects.create(label='Model') |
|
985 |
agenda_pricing = AgendaPricing.objects.create( |
|
986 |
agenda=agenda, |
|
987 |
pricing=pricing, |
|
988 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
989 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
990 |
) |
|
991 |
agenda2 = Agenda.objects.create(label='Foo Bar') |
|
992 |
agenda_pricing2 = AgendaPricing.objects.create( |
|
993 |
agenda=agenda2, |
|
994 |
pricing=pricing, |
|
995 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
996 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
997 |
) |
|
998 | ||
999 |
app = login(app) |
|
1000 |
resp = app.get('/manage/agendas/%s/settings' % agenda.pk) |
|
1001 |
resp = resp.click(href='/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
1002 | ||
1003 |
app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing2.pk), status=404) |
|
1004 |
app.get('/manage/pricing/agenda/%s/pricing/%s/' % (0, agenda_pricing.pk), status=404) |
|
1005 |
app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, 0), status=404) |
|
1006 |
# wrong kind |
|
1007 |
for kind in ['meetings', 'virtual']: |
|
1008 |
agenda.kind = kind |
|
1009 |
agenda.save() |
|
1010 |
app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk), status=404) |
|
1011 | ||
1012 | ||
1013 |
def test_detail_agenda_pricing_3_categories(app, admin_user): |
|
1014 |
category1 = CriteriaCategory.objects.create(label='Cat 1') |
|
1015 |
Criteria.objects.create(label='Crit 1-1', slug='crit-1-1', category=category1, order=1) |
|
1016 |
Criteria.objects.create(label='Crit 1-2', slug='crit-1-2', category=category1, order=2) |
|
1017 |
category2 = CriteriaCategory.objects.create(label='Cat 2') |
|
1018 |
Criteria.objects.create(label='Crit 2-1', slug='crit-2-1', category=category2, order=1) |
|
1019 |
Criteria.objects.create(label='Crit 2-2', slug='crit-2-2', category=category2, order=2) |
|
1020 |
Criteria.objects.create(label='Crit 2-3', slug='crit-2-3', category=category2, order=3) |
|
1021 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
1022 |
Criteria.objects.create(label='Crit 3-1', slug='crit-3-1', category=category3, order=1) |
|
1023 |
Criteria.objects.create(label='Crit 3-3', slug='crit-3-3', category=category3, order=3) |
|
1024 |
Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4) |
|
1025 |
Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2) |
|
1026 | ||
1027 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1028 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1029 |
pricing.categories.add(category1, through_defaults={'order': 1}) |
|
1030 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
1031 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
1032 |
pricing.criterias.set(Criteria.objects.all()) |
|
1033 |
agenda_pricing = AgendaPricing.objects.create( |
|
1034 |
agenda=agenda, |
|
1035 |
pricing=pricing, |
|
1036 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1037 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1038 |
pricing_data={ |
|
1039 |
'cat-1:crit-1-1': { |
|
1040 |
'cat-2:crit-2-1': { |
|
1041 |
'cat-3:crit-3-1': 111, |
|
1042 |
'cat-3:crit-3-3': 'not-a-decimal', |
|
1043 |
'cat-3:crit-3-4': 114, |
|
1044 |
}, |
|
1045 |
'cat-2:crit-2-3': { |
|
1046 |
'cat-3:crit-3-2': 132, |
|
1047 |
}, |
|
1048 |
}, |
|
1049 |
'cat-1:crit-1-2': { |
|
1050 |
'cat-2:crit-2-2': { |
|
1051 |
'cat-3:crit-3-3': 223, |
|
1052 |
}, |
|
1053 |
}, |
|
1054 |
}, |
|
1055 |
) |
|
1056 | ||
1057 |
app = login(app) |
|
1058 |
resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
1059 |
assert '<h3>Crit 1-1</h3>' in resp |
|
1060 |
ths = resp.pyquery.find('table.pricing-matrix-crit-1-1 thead th') |
|
1061 |
assert len(ths) == 4 |
|
1062 |
assert ths[0].text is None |
|
1063 |
assert ths[1].text == 'Crit 2-1' |
|
1064 |
assert ths[2].text == 'Crit 2-2' |
|
1065 |
assert ths[3].text == 'Crit 2-3' |
|
1066 |
assert resp.pyquery.find('table.pricing-matrix-crit-1-1 tr.pricing-row-crit-3-1 th')[0].text == 'Crit 3-1' |
|
1067 |
assert resp.pyquery.find('table.pricing-matrix-crit-1-1 tr.pricing-row-crit-3-2 th')[0].text == 'Crit 3-2' |
|
1068 |
assert resp.pyquery.find('table.pricing-matrix-crit-1-1 tr.pricing-row-crit-3-3 th')[0].text == 'Crit 3-3' |
|
1069 |
assert resp.pyquery.find('table.pricing-matrix-crit-1-1 tr.pricing-row-crit-3-4 th')[0].text == 'Crit 3-4' |
|
1070 |
assert ( |
|
1071 |
resp.pyquery.find('table.pricing-matrix-crit-1-1 tr.pricing-row-crit-3-1 td.pricing-cell-crit-2-1')[ |
|
1072 |
0 |
|
1073 |
].text |
|
1074 |
== '111.00' |
|
1075 |
) |
|
1076 |
assert ( |
|
1077 |
resp.pyquery.find('table.pricing-matrix-crit-1-1 tr.pricing-row-crit-3-2 td.pricing-cell-crit-2-1')[ |
|
1078 |
0 |
|
1079 |
].text |
|
1080 |
is None |
|
1081 |
) # not defined |
|
1082 |
assert ( |
|
1083 |
resp.pyquery.find('table.pricing-matrix-crit-1-1 tr.pricing-row-crit-3-3 td.pricing-cell-crit-2-1')[ |
|
1084 |
0 |
|
1085 |
].text |
|
1086 |
is None |
|
1087 |
) # wrong value |
|
1088 |
assert ( |
|
1089 |
resp.pyquery.find('table.pricing-matrix-crit-1-1 tr.pricing-row-crit-3-4 td.pricing-cell-crit-2-1')[ |
|
1090 |
0 |
|
1091 |
].text |
|
1092 |
== '114.00' |
|
1093 |
) |
|
1094 |
assert ( |
|
1095 |
resp.pyquery.find('table.pricing-matrix-crit-1-1 tr.pricing-row-crit-3-2 td.pricing-cell-crit-2-3')[ |
|
1096 |
0 |
|
1097 |
].text |
|
1098 |
== '132.00' |
|
1099 |
) |
|
1100 |
assert '<h3>Crit 1-2</h3>' in resp |
|
1101 |
ths = resp.pyquery.find('table.pricing-matrix-crit-1-2 thead th') |
|
1102 |
assert len(ths) == 4 |
|
1103 |
assert ths[0].text is None |
|
1104 |
assert ths[1].text == 'Crit 2-1' |
|
1105 |
assert ths[2].text == 'Crit 2-2' |
|
1106 |
assert ths[3].text == 'Crit 2-3' |
|
1107 |
assert resp.pyquery.find('table.pricing-matrix-crit-1-2 tr.pricing-row-crit-3-1 th')[0].text == 'Crit 3-1' |
|
1108 |
assert resp.pyquery.find('table.pricing-matrix-crit-1-2 tr.pricing-row-crit-3-2 th')[0].text == 'Crit 3-2' |
|
1109 |
assert resp.pyquery.find('table.pricing-matrix-crit-1-2 tr.pricing-row-crit-3-3 th')[0].text == 'Crit 3-3' |
|
1110 |
assert resp.pyquery.find('table.pricing-matrix-crit-1-2 tr.pricing-row-crit-3-4 th')[0].text == 'Crit 3-4' |
|
1111 |
assert ( |
|
1112 |
resp.pyquery.find('table.pricing-matrix-crit-1-2 tr.pricing-row-crit-3-2 td.pricing-cell-crit-2-2')[ |
|
1113 |
0 |
|
1114 |
].text |
|
1115 |
is None |
|
1116 |
) # not defined |
|
1117 |
assert ( |
|
1118 |
resp.pyquery.find('table.pricing-matrix-crit-1-2 tr.pricing-row-crit-3-3 td.pricing-cell-crit-2-2')[ |
|
1119 |
0 |
|
1120 |
].text |
|
1121 |
== '223.00' |
|
1122 |
) |
|
1123 | ||
1124 | ||
1125 |
def test_detail_agenda_pricing_2_categories(app, admin_user): |
|
1126 |
category2 = CriteriaCategory.objects.create(label='Cat 2') |
|
1127 |
Criteria.objects.create(label='Crit 2-1', slug='crit-2-1', category=category2, order=1) |
|
1128 |
Criteria.objects.create(label='Crit 2-2', slug='crit-2-2', category=category2, order=2) |
|
1129 |
Criteria.objects.create(label='Crit 2-3', slug='crit-2-3', category=category2, order=3) |
|
1130 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
1131 |
Criteria.objects.create(label='Crit 3-1', slug='crit-3-1', category=category3, order=1) |
|
1132 |
Criteria.objects.create(label='Crit 3-3', slug='crit-3-3', category=category3, order=3) |
|
1133 |
Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4) |
|
1134 |
Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2) |
|
1135 | ||
1136 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1137 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1138 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
1139 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
1140 |
pricing.criterias.set(Criteria.objects.all()) |
|
1141 |
agenda_pricing = AgendaPricing.objects.create( |
|
1142 |
agenda=agenda, |
|
1143 |
pricing=pricing, |
|
1144 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1145 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1146 |
pricing_data={ |
|
1147 |
'cat-2:crit-2-1': { |
|
1148 |
'cat-3:crit-3-1': 111, |
|
1149 |
'cat-3:crit-3-3': 'not-a-decimal', |
|
1150 |
'cat-3:crit-3-4': 114, |
|
1151 |
}, |
|
1152 |
'cat-2:crit-2-3': { |
|
1153 |
'cat-3:crit-3-2': 132, |
|
1154 |
}, |
|
1155 |
}, |
|
1156 |
) |
|
1157 | ||
1158 |
app = login(app) |
|
1159 |
resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
1160 |
assert '<h3>' not in resp |
|
1161 |
ths = resp.pyquery.find('table thead th') |
|
1162 |
assert len(ths) == 4 |
|
1163 |
assert ths[0].text is None |
|
1164 |
assert ths[1].text == 'Crit 2-1' |
|
1165 |
assert ths[2].text == 'Crit 2-2' |
|
1166 |
assert ths[3].text == 'Crit 2-3' |
|
1167 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-1 th')[0].text == 'Crit 3-1' |
|
1168 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-2 th')[0].text == 'Crit 3-2' |
|
1169 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-3 th')[0].text == 'Crit 3-3' |
|
1170 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-4 th')[0].text == 'Crit 3-4' |
|
1171 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-1 td.pricing-cell-crit-2-1')[0].text == '111.00' |
|
1172 |
assert ( |
|
1173 |
resp.pyquery.find('table tr.pricing-row-crit-3-2 td.pricing-cell-crit-2-1')[0].text is None |
|
1174 |
) # not defined |
|
1175 |
assert ( |
|
1176 |
resp.pyquery.find('table tr.pricing-row-crit-3-3 td.pricing-cell-crit-2-1')[0].text is None |
|
1177 |
) # wrong value |
|
1178 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-4 td.pricing-cell-crit-2-1')[0].text == '114.00' |
|
1179 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-2 td.pricing-cell-crit-2-3')[0].text == '132.00' |
|
1180 | ||
1181 | ||
1182 |
def test_detail_agenda_pricing_1_category(app, admin_user): |
|
1183 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
1184 |
Criteria.objects.create(label='Crit 3-1', slug='crit-3-1', category=category3, order=1) |
|
1185 |
Criteria.objects.create(label='Crit 3-3', slug='crit-3-3', category=category3, order=3) |
|
1186 |
Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4) |
|
1187 |
Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2) |
|
1188 | ||
1189 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1190 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1191 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
1192 |
pricing.criterias.set(Criteria.objects.all()) |
|
1193 |
agenda_pricing = AgendaPricing.objects.create( |
|
1194 |
agenda=agenda, |
|
1195 |
pricing=pricing, |
|
1196 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1197 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1198 |
pricing_data={ |
|
1199 |
'cat-3:crit-3-1': 111, |
|
1200 |
'cat-3:crit-3-3': 'not-a-decimal', |
|
1201 |
'cat-3:crit-3-4': 114, |
|
1202 |
}, |
|
1203 |
) |
|
1204 | ||
1205 |
app = login(app) |
|
1206 |
resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
1207 |
assert '<h3>' not in resp |
|
1208 |
ths = resp.pyquery.find('table thead') |
|
1209 |
assert len(ths) == 0 |
|
1210 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-1 th')[0].text == 'Crit 3-1' |
|
1211 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-2 th')[0].text == 'Crit 3-2' |
|
1212 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-3 th')[0].text == 'Crit 3-3' |
|
1213 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-4 th')[0].text == 'Crit 3-4' |
|
1214 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-1 td')[0].text == '111.00' |
|
1215 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-2 td')[0].text is None # not defined |
|
1216 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-3 td')[0].text is None # wrong value |
|
1217 |
assert resp.pyquery.find('table tr.pricing-row-crit-3-4 td')[0].text == '114.00' |
|
1218 | ||
1219 | ||
1220 |
def test_edit_agenda_pricing_matrix_3_categories(app, admin_user): |
|
1221 |
category1 = CriteriaCategory.objects.create(label='Cat 1') |
|
1222 |
criteria11 = Criteria.objects.create(label='Crit 1-1', slug='crit-1-1', category=category1, order=1) |
|
1223 |
criteria12 = Criteria.objects.create(label='Crit 1-2', slug='crit-1-2', category=category1, order=2) |
|
1224 |
category2 = CriteriaCategory.objects.create(label='Cat 2') |
|
1225 |
criteria21 = Criteria.objects.create(label='Crit 2-1', slug='crit-2-1', category=category2, order=1) |
|
1226 |
Criteria.objects.create(label='Crit 2-2', slug='crit-2-2', category=category2, order=2) |
|
1227 |
Criteria.objects.create(label='Crit 2-3', slug='crit-2-3', category=category2, order=3) |
|
1228 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
1229 |
criteria31 = Criteria.objects.create(label='Crit 3-1', slug='crit-3-1', category=category3, order=1) |
|
1230 |
Criteria.objects.create(label='Crit 3-3', slug='crit-3-3', category=category3, order=3) |
|
1231 |
Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4) |
|
1232 |
Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2) |
|
1233 | ||
1234 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1235 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1236 |
pricing.categories.add(category1, through_defaults={'order': 1}) |
|
1237 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
1238 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
1239 |
pricing.criterias.set(Criteria.objects.all()) |
|
1240 |
agenda_pricing = AgendaPricing.objects.create( |
|
1241 |
agenda=agenda, |
|
1242 |
pricing=pricing, |
|
1243 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1244 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1245 |
pricing_data={ |
|
1246 |
'cat-1:crit-1-1': { |
|
1247 |
'cat-2:crit-2-1': { |
|
1248 |
'cat-3:crit-3-1': 111, |
|
1249 |
'cat-3:crit-3-3': 'not-a-decimal', |
|
1250 |
'cat-3:crit-3-4': 114, |
|
1251 |
}, |
|
1252 |
'cat-2:crit-2-3': { |
|
1253 |
'cat-3:crit-3-2': 132, |
|
1254 |
}, |
|
1255 |
}, |
|
1256 |
'cat-1:crit-1-2': { |
|
1257 |
'cat-2:crit-2-2': { |
|
1258 |
'cat-3:crit-3-3': 223, |
|
1259 |
}, |
|
1260 |
}, |
|
1261 |
}, |
|
1262 |
) |
|
1263 | ||
1264 |
app = login(app) |
|
1265 |
resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
1266 |
resp = resp.click( |
|
1267 |
href='/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' |
|
1268 |
% (agenda.pk, agenda_pricing.pk, criteria11.slug) |
|
1269 |
) |
|
1270 |
assert resp.form['form-0-crit_0'].value == '111' |
|
1271 |
assert resp.form['form-0-crit_1'].value == '' |
|
1272 |
assert resp.form['form-0-crit_2'].value == '' |
|
1273 |
assert resp.form['form-1-crit_0'].value == '' |
|
1274 |
assert resp.form['form-1-crit_1'].value == '' |
|
1275 |
assert resp.form['form-1-crit_2'].value == '132' |
|
1276 |
assert resp.form['form-2-crit_0'].value == '' |
|
1277 |
assert resp.form['form-2-crit_1'].value == '' |
|
1278 |
assert resp.form['form-2-crit_2'].value == '' |
|
1279 |
assert resp.form['form-3-crit_0'].value == '114' |
|
1280 |
assert resp.form['form-3-crit_1'].value == '' |
|
1281 |
assert resp.form['form-3-crit_2'].value == '' |
|
1282 |
resp.form['form-0-crit_1'] = '121' |
|
1283 |
resp.form['form-0-crit_2'] = '131' |
|
1284 |
resp.form['form-1-crit_0'] = '112' |
|
1285 |
resp.form['form-1-crit_1'] = '122' |
|
1286 |
resp.form['form-1-crit_2'] = '132.5' |
|
1287 |
resp.form['form-2-crit_0'] = '113' |
|
1288 |
resp.form['form-2-crit_1'] = '123' |
|
1289 |
resp.form['form-2-crit_2'] = '133' |
|
1290 |
resp.form['form-3-crit_0'] = '914' |
|
1291 |
resp.form['form-3-crit_1'] = '124' |
|
1292 |
resp.form['form-3-crit_2'] = '134' |
|
1293 |
resp = resp.form.submit() |
|
1294 |
assert resp.location.endswith('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
1295 |
agenda_pricing.refresh_from_db() |
|
1296 |
assert agenda_pricing.pricing_data == { |
|
1297 |
'cat-1:crit-1-1': { |
|
1298 |
'cat-2:crit-2-1': { |
|
1299 |
'cat-3:crit-3-1': 111, |
|
1300 |
'cat-3:crit-3-2': 112, |
|
1301 |
'cat-3:crit-3-3': 113, |
|
1302 |
'cat-3:crit-3-4': 914, |
|
1303 |
}, |
|
1304 |
'cat-2:crit-2-2': { |
|
1305 |
'cat-3:crit-3-1': 121, |
|
1306 |
'cat-3:crit-3-2': 122, |
|
1307 |
'cat-3:crit-3-3': 123, |
|
1308 |
'cat-3:crit-3-4': 124, |
|
1309 |
}, |
|
1310 |
'cat-2:crit-2-3': { |
|
1311 |
'cat-3:crit-3-1': 131, |
|
1312 |
'cat-3:crit-3-2': 132.5, |
|
1313 |
'cat-3:crit-3-3': 133, |
|
1314 |
'cat-3:crit-3-4': 134, |
|
1315 |
}, |
|
1316 |
}, |
|
1317 |
'cat-1:crit-1-2': { |
|
1318 |
'cat-2:crit-2-2': { |
|
1319 |
'cat-3:crit-3-3': 223, |
|
1320 |
}, |
|
1321 |
}, |
|
1322 |
} |
|
1323 |
app.get( |
|
1324 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' |
|
1325 |
% (agenda.pk, agenda_pricing.pk, criteria12.slug), |
|
1326 |
status=200, |
|
1327 |
) |
|
1328 | ||
1329 |
agenda2 = Agenda.objects.create(label='Foo Bar') |
|
1330 |
agenda_pricing2 = AgendaPricing.objects.create( |
|
1331 |
agenda=agenda2, |
|
1332 |
pricing=pricing, |
|
1333 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1334 |
date_end=datetime.date(year=2022, month=9, day=1), |
|
1335 |
) |
|
1336 |
app.get( |
|
1337 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' |
|
1338 |
% (agenda.pk, agenda_pricing2.pk, criteria11.slug), |
|
1339 |
status=404, |
|
1340 |
) |
|
1341 |
app.get( |
|
1342 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' % (0, agenda_pricing.pk, criteria11.slug), |
|
1343 |
status=404, |
|
1344 |
) |
|
1345 |
app.get( |
|
1346 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' % (agenda.pk, 0, criteria11.slug), status=404 |
|
1347 |
) |
|
1348 |
app.get( |
|
1349 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' % (agenda.pk, agenda_pricing.pk, 'unknown'), |
|
1350 |
status=404, |
|
1351 |
) |
|
1352 |
app.get( |
|
1353 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' |
|
1354 |
% (agenda.pk, agenda_pricing.pk, criteria21.slug), |
|
1355 |
status=404, |
|
1356 |
) |
|
1357 |
app.get( |
|
1358 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' |
|
1359 |
% (agenda.pk, agenda_pricing.pk, criteria31.slug), |
|
1360 |
status=404, |
|
1361 |
) |
|
1362 |
app.get('/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (agenda.pk, agenda_pricing.pk), status=404) |
|
1363 |
# wrong kind |
|
1364 |
for kind in ['meetings', 'virtual']: |
|
1365 |
agenda.kind = kind |
|
1366 |
agenda.save() |
|
1367 |
app.get( |
|
1368 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' |
|
1369 |
% (agenda.pk, agenda_pricing.pk, criteria11.slug), |
|
1370 |
status=404, |
|
1371 |
) |
|
1372 | ||
1373 | ||
1374 |
def test_edit_agenda_pricing_matrix_2_categories(app, admin_user): |
|
1375 |
category2 = CriteriaCategory.objects.create(label='Cat 2') |
|
1376 |
criteria21 = Criteria.objects.create(label='Crit 2-1', slug='crit-2-1', category=category2, order=1) |
|
1377 |
Criteria.objects.create(label='Crit 2-2', slug='crit-2-2', category=category2, order=2) |
|
1378 |
Criteria.objects.create(label='Crit 2-3', slug='crit-2-3', category=category2, order=3) |
|
1379 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
1380 |
criteria31 = Criteria.objects.create(label='Crit 3-1', slug='crit-3-1', category=category3, order=1) |
|
1381 |
Criteria.objects.create(label='Crit 3-3', slug='crit-3-3', category=category3, order=3) |
|
1382 |
Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4) |
|
1383 |
Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2) |
|
1384 | ||
1385 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1386 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1387 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
1388 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
1389 |
pricing.criterias.set(Criteria.objects.all()) |
|
1390 |
agenda_pricing = AgendaPricing.objects.create( |
|
1391 |
agenda=agenda, |
|
1392 |
pricing=pricing, |
|
1393 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1394 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1395 |
pricing_data={ |
|
1396 |
'cat-2:crit-2-1': { |
|
1397 |
'cat-3:crit-3-1': 111, |
|
1398 |
'cat-3:crit-3-3': 'not-a-decimal', |
|
1399 |
'cat-3:crit-3-4': 114, |
|
1400 |
}, |
|
1401 |
'cat-2:crit-2-3': { |
|
1402 |
'cat-3:crit-3-2': 132, |
|
1403 |
}, |
|
1404 |
}, |
|
1405 |
) |
|
1406 | ||
1407 |
app = login(app) |
|
1408 |
resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
1409 |
resp = resp.click( |
|
1410 |
href='/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (agenda.pk, agenda_pricing.pk) |
|
1411 |
) |
|
1412 |
assert resp.form['form-0-crit_0'].value == '111' |
|
1413 |
assert resp.form['form-0-crit_1'].value == '' |
|
1414 |
assert resp.form['form-0-crit_2'].value == '' |
|
1415 |
assert resp.form['form-1-crit_0'].value == '' |
|
1416 |
assert resp.form['form-1-crit_1'].value == '' |
|
1417 |
assert resp.form['form-1-crit_2'].value == '132' |
|
1418 |
assert resp.form['form-2-crit_0'].value == '' |
|
1419 |
assert resp.form['form-2-crit_1'].value == '' |
|
1420 |
assert resp.form['form-2-crit_2'].value == '' |
|
1421 |
assert resp.form['form-3-crit_0'].value == '114' |
|
1422 |
assert resp.form['form-3-crit_1'].value == '' |
|
1423 |
assert resp.form['form-3-crit_2'].value == '' |
|
1424 |
resp.form['form-0-crit_1'] = '121' |
|
1425 |
resp.form['form-0-crit_2'] = '131' |
|
1426 |
resp.form['form-1-crit_0'] = '112' |
|
1427 |
resp.form['form-1-crit_1'] = '122' |
|
1428 |
resp.form['form-1-crit_2'] = '132.5' |
|
1429 |
resp.form['form-2-crit_0'] = '113' |
|
1430 |
resp.form['form-2-crit_1'] = '123' |
|
1431 |
resp.form['form-2-crit_2'] = '133' |
|
1432 |
resp.form['form-3-crit_0'] = '914' |
|
1433 |
resp.form['form-3-crit_1'] = '124' |
|
1434 |
resp.form['form-3-crit_2'] = '134' |
|
1435 |
resp = resp.form.submit() |
|
1436 |
assert resp.location.endswith('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
1437 |
agenda_pricing.refresh_from_db() |
|
1438 |
assert agenda_pricing.pricing_data == { |
|
1439 |
'cat-2:crit-2-1': { |
|
1440 |
'cat-3:crit-3-1': 111, |
|
1441 |
'cat-3:crit-3-2': 112, |
|
1442 |
'cat-3:crit-3-3': 113, |
|
1443 |
'cat-3:crit-3-4': 914, |
|
1444 |
}, |
|
1445 |
'cat-2:crit-2-2': { |
|
1446 |
'cat-3:crit-3-1': 121, |
|
1447 |
'cat-3:crit-3-2': 122, |
|
1448 |
'cat-3:crit-3-3': 123, |
|
1449 |
'cat-3:crit-3-4': 124, |
|
1450 |
}, |
|
1451 |
'cat-2:crit-2-3': { |
|
1452 |
'cat-3:crit-3-1': 131, |
|
1453 |
'cat-3:crit-3-2': 132.5, |
|
1454 |
'cat-3:crit-3-3': 133, |
|
1455 |
'cat-3:crit-3-4': 134, |
|
1456 |
}, |
|
1457 |
} |
|
1458 | ||
1459 |
agenda2 = Agenda.objects.create(label='Foo Bar') |
|
1460 |
agenda_pricing2 = AgendaPricing.objects.create( |
|
1461 |
agenda=agenda2, |
|
1462 |
pricing=pricing, |
|
1463 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1464 |
date_end=datetime.date(year=2022, month=9, day=1), |
|
1465 |
) |
|
1466 |
app.get('/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (agenda.pk, agenda_pricing2.pk), status=404) |
|
1467 |
app.get('/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (0, agenda_pricing.pk), status=404) |
|
1468 |
app.get('/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (agenda.pk, 0), status=404) |
|
1469 |
app.get( |
|
1470 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' |
|
1471 |
% (agenda.pk, agenda_pricing.pk, criteria21.slug), |
|
1472 |
status=404, |
|
1473 |
) |
|
1474 |
app.get( |
|
1475 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' |
|
1476 |
% (agenda.pk, agenda_pricing.pk, criteria31.slug), |
|
1477 |
status=404, |
|
1478 |
) |
|
1479 |
# wrong kind |
|
1480 |
for kind in ['meetings', 'virtual']: |
|
1481 |
agenda.kind = kind |
|
1482 |
agenda.save() |
|
1483 |
app.get( |
|
1484 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (agenda.pk, agenda_pricing.pk), status=404 |
|
1485 |
) |
|
1486 | ||
1487 | ||
1488 |
def test_edit_agenda_pricing_matrix_1_category(app, admin_user): |
|
1489 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
1490 |
criteria31 = Criteria.objects.create(label='Crit 3-1', slug='crit-3-1', category=category3, order=1) |
|
1491 |
Criteria.objects.create(label='Crit 3-3', slug='crit-3-3', category=category3, order=3) |
|
1492 |
Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4) |
|
1493 |
Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2) |
|
1494 | ||
1495 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1496 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1497 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
1498 |
pricing.criterias.set(Criteria.objects.all()) |
|
1499 |
agenda_pricing = AgendaPricing.objects.create( |
|
1500 |
agenda=agenda, |
|
1501 |
pricing=pricing, |
|
1502 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1503 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1504 |
pricing_data={ |
|
1505 |
'cat-3:crit-3-1': 111, |
|
1506 |
'cat-3:crit-3-3': 'not-a-decimal', |
|
1507 |
'cat-3:crit-3-4': 114, |
|
1508 |
}, |
|
1509 |
) |
|
1510 | ||
1511 |
app = login(app) |
|
1512 |
resp = app.get('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
1513 |
resp = resp.click( |
|
1514 |
href='/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (agenda.pk, agenda_pricing.pk) |
|
1515 |
) |
|
1516 |
assert resp.form['form-0-crit_0'].value == '111' |
|
1517 |
assert resp.form['form-1-crit_0'].value == '' |
|
1518 |
assert resp.form['form-2-crit_0'].value == '' |
|
1519 |
assert resp.form['form-3-crit_0'].value == '114' |
|
1520 |
resp.form['form-1-crit_0'] = '112.5' |
|
1521 |
resp.form['form-2-crit_0'] = '113' |
|
1522 |
resp.form['form-3-crit_0'] = '914' |
|
1523 |
resp = resp.form.submit() |
|
1524 |
assert resp.location.endswith('/manage/pricing/agenda/%s/pricing/%s/' % (agenda.pk, agenda_pricing.pk)) |
|
1525 |
agenda_pricing.refresh_from_db() |
|
1526 |
assert agenda_pricing.pricing_data == { |
|
1527 |
'cat-3:crit-3-1': 111, |
|
1528 |
'cat-3:crit-3-2': 112.5, |
|
1529 |
'cat-3:crit-3-3': 113, |
|
1530 |
'cat-3:crit-3-4': 914, |
|
1531 |
} |
|
1532 | ||
1533 |
agenda2 = Agenda.objects.create(label='Foo Bar') |
|
1534 |
agenda_pricing2 = AgendaPricing.objects.create( |
|
1535 |
agenda=agenda2, |
|
1536 |
pricing=pricing, |
|
1537 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1538 |
date_end=datetime.date(year=2022, month=9, day=1), |
|
1539 |
) |
|
1540 |
app.get('/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (agenda.pk, agenda_pricing2.pk), status=404) |
|
1541 |
app.get('/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (0, agenda_pricing.pk), status=404) |
|
1542 |
app.get('/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (agenda.pk, 0), status=404) |
|
1543 |
app.get( |
|
1544 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' |
|
1545 |
% (agenda.pk, agenda_pricing.pk, criteria31.slug), |
|
1546 |
status=404, |
|
1547 |
) |
|
1548 |
# wrong kind |
|
1549 |
for kind in ['meetings', 'virtual']: |
|
1550 |
agenda.kind = kind |
|
1551 |
agenda.save() |
|
1552 |
app.get( |
|
1553 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (agenda.pk, agenda_pricing.pk), status=404 |
|
1554 |
) |
|
1555 | ||
1556 | ||
1557 |
def test_edit_agenda_pricing_matrix_empty(app, admin_user): |
|
1558 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1559 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1560 |
agenda_pricing = AgendaPricing.objects.create( |
|
1561 |
agenda=agenda, |
|
1562 |
pricing=pricing, |
|
1563 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1564 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1565 |
) |
|
1566 | ||
1567 |
app = login(app) |
|
1568 |
app.get('/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (agenda.pk, agenda_pricing.pk), status=404) |
|
1569 | ||
1570 | ||
1571 |
def test_edit_agenda_pricing_matrix_as_manager(app, manager_user, agenda_with_restrictions): |
|
1572 |
category1 = CriteriaCategory.objects.create(label='Cat 1') |
|
1573 |
Criteria.objects.create(label='Crit 1-1', slug='crit-1-1', category=category1, order=1) |
|
1574 |
Criteria.objects.create(label='Crit 1-2', slug='crit-1-2', category=category1, order=2) |
|
1575 |
category2 = CriteriaCategory.objects.create(label='Cat 2') |
|
1576 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
1577 |
pricing = Pricing.objects.create(label='Model') |
|
1578 |
agenda_pricing = AgendaPricing.objects.create( |
|
1579 |
agenda=agenda_with_restrictions, |
|
1580 |
pricing=pricing, |
|
1581 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1582 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1583 |
) |
|
1584 |
pricing.categories.add(category1, through_defaults={'order': 1}) |
|
1585 |
pricing.criterias.set(Criteria.objects.all()) |
|
1586 | ||
1587 |
def check(): |
|
1588 |
resp = app.get( |
|
1589 |
'/manage/pricing/agenda/%s/pricing/%s/' % (agenda_with_restrictions.pk, agenda_pricing.pk) |
|
1590 |
) |
|
1591 |
assert ( |
|
1592 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' |
|
1593 |
% (agenda_with_restrictions.pk, agenda_pricing.pk) |
|
1594 |
not in resp |
|
1595 |
) |
|
1596 |
for slug in ['crit-1-1', 'crit-1-2']: |
|
1597 |
assert ( |
|
1598 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/%s/edit/' |
|
1599 |
% (agenda_with_restrictions.pk, agenda_pricing.pk, slug) |
|
1600 |
not in resp |
|
1601 |
) |
|
1602 | ||
1603 |
app = login(app, username='manager', password='manager') |
|
1604 |
app.get( |
|
1605 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' |
|
1606 |
% (agenda_with_restrictions.pk, agenda_pricing.pk), |
|
1607 |
status=403, |
|
1608 |
) |
|
1609 |
check() |
|
1610 | ||
1611 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
1612 |
app.get( |
|
1613 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' |
|
1614 |
% (agenda_with_restrictions.pk, agenda_pricing.pk), |
|
1615 |
status=403, |
|
1616 |
) |
|
1617 |
check() |
|
1618 | ||
1619 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
1620 |
app.get( |
|
1621 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/crit-1-1/edit/' |
|
1622 |
% (agenda_with_restrictions.pk, agenda_pricing.pk), |
|
1623 |
status=403, |
|
1624 |
) |
|
1625 |
app.get( |
|
1626 |
'/manage/pricing/agenda/%s/pricing/%s/matrix/crit-1-2/edit/' |
|
1627 |
% (agenda_with_restrictions.pk, agenda_pricing.pk), |
|
1628 |
status=403, |
|
1629 |
) |
|
1630 |
check() |
tests/pricing/test_models.py | ||
---|---|---|
1 |
import datetime |
|
2 |
import json |
|
3 |
from unittest import mock |
|
4 | ||
5 |
import pytest |
|
6 |
from django.template import Context |
|
7 |
from django.test.client import RequestFactory |
|
8 |
from django.utils.timezone import make_aware, now |
|
9 |
from publik_django_templatetags.wcs.context_processors import Cards |
|
10 | ||
11 |
from chrono.agendas.models import Agenda, Booking, CheckType, CheckTypeGroup, Event, Subscription |
|
12 |
from chrono.pricing.models import ( |
|
13 |
AgendaPricing, |
|
14 |
AgendaPricingNotFound, |
|
15 |
Criteria, |
|
16 |
CriteriaCategory, |
|
17 |
CriteriaConditionNotFound, |
|
18 |
Pricing, |
|
19 |
PricingBookingCheckTypeError, |
|
20 |
PricingBookingNotCheckedError, |
|
21 |
PricingCriteriaCategory, |
|
22 |
PricingDataError, |
|
23 |
PricingDataFormatError, |
|
24 |
PricingEventNotCheckedError, |
|
25 |
PricingMatrix, |
|
26 |
PricingMatrixCell, |
|
27 |
PricingMatrixRow, |
|
28 |
PricingMultipleBookingError, |
|
29 |
PricingSubscriptionError, |
|
30 |
) |
|
31 | ||
32 |
pytestmark = pytest.mark.django_db |
|
33 | ||
34 | ||
35 |
@pytest.fixture |
|
36 |
def context(): |
|
37 |
return Context( |
|
38 |
{ |
|
39 |
'cards': Cards(), |
|
40 |
'request': RequestFactory().get('/'), |
|
41 |
} |
|
42 |
) |
|
43 | ||
44 | ||
45 |
class MockedRequestResponse(mock.Mock): |
|
46 |
status_code = 200 |
|
47 | ||
48 |
def json(self): |
|
49 |
return json.loads(self.content) |
|
50 | ||
51 | ||
52 |
def mocked_requests_send(request, **kwargs): |
|
53 |
data = [{'id': 1, 'fields': {'foo': 'bar'}}, {'id': 2, 'fields': {'foo': 'baz'}}] # fake result |
|
54 |
return MockedRequestResponse(content=json.dumps({'data': data})) |
|
55 | ||
56 | ||
57 |
def test_criteria_category_slug(): |
|
58 |
category = CriteriaCategory.objects.create(label='Foo bar') |
|
59 |
assert category.slug == 'foo-bar' |
|
60 | ||
61 | ||
62 |
def test_criteria_category_existing_slug(): |
|
63 |
category = CriteriaCategory.objects.create(label='Foo bar', slug='bar') |
|
64 |
assert category.slug == 'bar' |
|
65 | ||
66 | ||
67 |
def test_criteria_category_duplicate_slugs(): |
|
68 |
category = CriteriaCategory.objects.create(label='Foo baz') |
|
69 |
assert category.slug == 'foo-baz' |
|
70 |
category = CriteriaCategory.objects.create(label='Foo baz') |
|
71 |
assert category.slug == 'foo-baz-1' |
|
72 |
category = CriteriaCategory.objects.create(label='Foo baz') |
|
73 |
assert category.slug == 'foo-baz-2' |
|
74 | ||
75 | ||
76 |
def test_criteria_slug(): |
|
77 |
category = CriteriaCategory.objects.create(label='Foo') |
|
78 |
criteria = Criteria.objects.create(label='Foo bar', category=category) |
|
79 |
assert criteria.slug == 'foo-bar' |
|
80 | ||
81 | ||
82 |
def test_criteria_existing_slug(): |
|
83 |
category = CriteriaCategory.objects.create(label='Foo') |
|
84 |
criteria = Criteria.objects.create(label='Foo bar', slug='bar', category=category) |
|
85 |
assert criteria.slug == 'bar' |
|
86 | ||
87 | ||
88 |
def test_criteria_duplicate_slugs(): |
|
89 |
category = CriteriaCategory.objects.create(label='Foo') |
|
90 |
category2 = CriteriaCategory.objects.create(label='Bar') |
|
91 |
Criteria.objects.create(label='Foo baz', slug='foo-baz', category=category2) |
|
92 |
criteria = Criteria.objects.create(label='Foo baz', category=category) |
|
93 |
assert criteria.slug == 'foo-baz' |
|
94 |
criteria = Criteria.objects.create(label='Foo baz', category=category) |
|
95 |
assert criteria.slug == 'foo-baz-1' |
|
96 |
criteria = Criteria.objects.create(label='Foo baz', category=category) |
|
97 |
assert criteria.slug == 'foo-baz-2' |
|
98 | ||
99 | ||
100 |
def test_criteria_order(): |
|
101 |
category = CriteriaCategory.objects.create(label='Foo') |
|
102 |
criteria = Criteria.objects.create(label='Foo bar', category=category) |
|
103 |
assert criteria.order == 1 |
|
104 | ||
105 | ||
106 |
def test_criteria_existing_order(): |
|
107 |
category = CriteriaCategory.objects.create(label='Foo') |
|
108 |
criteria = Criteria.objects.create(label='Foo bar', order=42, category=category) |
|
109 |
assert criteria.order == 42 |
|
110 | ||
111 | ||
112 |
def test_criteria_duplicate_orders(): |
|
113 |
category = CriteriaCategory.objects.create(label='Foo') |
|
114 |
category2 = CriteriaCategory.objects.create(label='Bar') |
|
115 |
Criteria.objects.create(label='Foo baz', order=1, category=category2) |
|
116 |
criteria = Criteria.objects.create(label='Foo baz', category=category) |
|
117 |
assert criteria.order == 1 |
|
118 |
criteria = Criteria.objects.create(label='Foo baz', category=category) |
|
119 |
assert criteria.order == 2 |
|
120 |
criteria = Criteria.objects.create(label='Foo baz', category=category) |
|
121 |
assert criteria.order == 3 |
|
122 | ||
123 | ||
124 |
def test_pricing_slug(): |
|
125 |
pricing = Pricing.objects.create(label='Foo bar') |
|
126 |
assert pricing.slug == 'foo-bar' |
|
127 | ||
128 | ||
129 |
def test_pricing_existing_slug(): |
|
130 |
pricing = Pricing.objects.create(label='Foo bar', slug='bar') |
|
131 |
assert pricing.slug == 'bar' |
|
132 | ||
133 | ||
134 |
def test_pricing_duplicate_slugs(): |
|
135 |
pricing = Pricing.objects.create(label='Foo baz') |
|
136 |
assert pricing.slug == 'foo-baz' |
|
137 |
pricing = Pricing.objects.create(label='Foo baz') |
|
138 |
assert pricing.slug == 'foo-baz-1' |
|
139 |
pricing = Pricing.objects.create(label='Foo baz') |
|
140 |
assert pricing.slug == 'foo-baz-2' |
|
141 | ||
142 | ||
143 |
def test_pricing_category_criteria_order(): |
|
144 |
category = CriteriaCategory.objects.create(label='Foo') |
|
145 |
pricing = Pricing.objects.create(label='Foo bar') |
|
146 |
pcc = PricingCriteriaCategory.objects.create(pricing=pricing, category=category) |
|
147 |
assert pcc.order == 1 |
|
148 | ||
149 | ||
150 |
def test_pricing_category_criteria_existing_order(): |
|
151 |
category = CriteriaCategory.objects.create(label='Foo') |
|
152 |
pricing = Pricing.objects.create(label='Foo bar') |
|
153 |
pcc = PricingCriteriaCategory.objects.create(order=42, pricing=pricing, category=category) |
|
154 |
assert pcc.order == 42 |
|
155 | ||
156 | ||
157 |
def test_pricing_category_criteria_duplicate_orders(): |
|
158 |
category1 = CriteriaCategory.objects.create(label='Foo') |
|
159 |
category2 = CriteriaCategory.objects.create(label='Bar') |
|
160 |
category3 = CriteriaCategory.objects.create(label='Baz') |
|
161 |
pricing = Pricing.objects.create(label='Foo bar') |
|
162 |
pricing2 = Pricing.objects.create(label='Foo baz') |
|
163 |
PricingCriteriaCategory.objects.create(order=1, pricing=pricing2, category=category1) |
|
164 |
PricingCriteriaCategory.objects.create(order=2, pricing=pricing2, category=category2) |
|
165 |
PricingCriteriaCategory.objects.create(order=3, pricing=pricing2, category=category3) |
|
166 |
pcc = PricingCriteriaCategory.objects.create(pricing=pricing, category=category1) |
|
167 |
assert pcc.order == 1 |
|
168 |
pcc = PricingCriteriaCategory.objects.create(pricing=pricing, category=category2) |
|
169 |
assert pcc.order == 2 |
|
170 |
pcc = PricingCriteriaCategory.objects.create(pricing=pricing, category=category3) |
|
171 |
assert pcc.order == 3 |
|
172 | ||
173 | ||
174 |
def test_pricing_duplicate(): |
|
175 |
category1 = CriteriaCategory.objects.create(label='Cat 1') |
|
176 |
Criteria.objects.create(label='Crit 1-1', slug='crit-1-1', category=category1, order=1) |
|
177 |
Criteria.objects.create(label='Crit 1-2', slug='crit-1-2', category=category1, order=2) |
|
178 |
category2 = CriteriaCategory.objects.create(label='Cat 2') |
|
179 |
Criteria.objects.create(label='Crit 2-1', slug='crit-2-1', category=category2, order=1) |
|
180 |
Criteria.objects.create(label='Crit 2-2', slug='crit-2-2', category=category2, order=2) |
|
181 |
Criteria.objects.create(label='Crit 2-3', slug='crit-2-3', category=category2, order=3) |
|
182 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
183 |
Criteria.objects.create(label='Crit 3-1', slug='crit-3-1', category=category3, order=1) |
|
184 |
Criteria.objects.create(label='Crit 3-3', slug='crit-3-3', category=category3, order=3) |
|
185 |
Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4) |
|
186 |
Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2) |
|
187 |
not_used = Criteria.objects.create(label='Not used', slug='crit-3-notused', category=category3, order=5) |
|
188 | ||
189 |
pricing = Pricing.objects.create( |
|
190 |
label='Foo', |
|
191 |
extra_variables={ |
|
192 |
'foo': 'bar', |
|
193 |
}, |
|
194 |
) |
|
195 |
pricing.categories.add(category1, through_defaults={'order': 1}) |
|
196 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
197 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
198 |
pricing.criterias.set(Criteria.objects.exclude(pk=not_used.pk)) |
|
199 | ||
200 |
new_pricing = pricing.duplicate() |
|
201 |
assert new_pricing.label == 'Copy of Foo' |
|
202 |
assert new_pricing.slug == 'copy-of-foo' |
|
203 |
assert new_pricing.extra_variables == pricing.extra_variables |
|
204 |
assert list(new_pricing.criterias.all()) == list(pricing.criterias.all()) |
|
205 |
original_pcc_categories = list( |
|
206 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) |
|
207 |
) |
|
208 |
new_pcc_categories = list( |
|
209 |
PricingCriteriaCategory.objects.filter(pricing=new_pricing).values_list('category', flat=True) |
|
210 |
) |
|
211 |
assert new_pcc_categories == original_pcc_categories |
|
212 |
original_pcc_orders = list( |
|
213 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('order', flat=True) |
|
214 |
) |
|
215 |
new_pcc_orders = list( |
|
216 |
PricingCriteriaCategory.objects.filter(pricing=new_pricing).values_list('order', flat=True) |
|
217 |
) |
|
218 |
assert new_pcc_orders == original_pcc_orders |
|
219 | ||
220 |
new_pricing = pricing.duplicate(label='Bar') |
|
221 |
assert new_pricing.label == 'Bar' |
|
222 |
assert new_pricing.slug == 'bar' |
|
223 | ||
224 | ||
225 |
def test_get_agenda_pricing(): |
|
226 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
227 |
pricing = Pricing.objects.create(label='Foo bar') |
|
228 |
event = Event.objects.create( |
|
229 |
agenda=agenda, start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), places=10 |
|
230 |
) |
|
231 | ||
232 |
# not found |
|
233 |
with pytest.raises(AgendaPricingNotFound): |
|
234 |
AgendaPricing.get_agenda_pricing(event) |
|
235 | ||
236 |
# ok |
|
237 |
agenda_pricing = AgendaPricing.objects.create( |
|
238 |
agenda=agenda, |
|
239 |
pricing=pricing, |
|
240 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
241 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
242 |
) |
|
243 |
assert AgendaPricing.get_agenda_pricing(event) == agenda_pricing |
|
244 | ||
245 |
# more than one matching |
|
246 |
AgendaPricing.objects.create( |
|
247 |
agenda=agenda, |
|
248 |
pricing=pricing, |
|
249 |
date_start=datetime.date(year=2021, month=9, day=14), |
|
250 |
date_end=datetime.date(year=2021, month=9, day=16), |
|
251 |
) |
|
252 |
with pytest.raises(AgendaPricingNotFound): |
|
253 |
AgendaPricing.get_agenda_pricing(event) |
|
254 | ||
255 | ||
256 |
@pytest.mark.parametrize( |
|
257 |
'event_date, found', |
|
258 |
[ |
|
259 |
# just before first day |
|
260 |
((2021, 8, 31, 12, 00), False), |
|
261 |
# first day |
|
262 |
((2021, 9, 1, 12, 00), True), |
|
263 |
# last day |
|
264 |
((2021, 9, 30, 12, 00), True), |
|
265 |
# just after last day |
|
266 |
((2021, 10, 1, 12, 00), False), |
|
267 |
], |
|
268 |
) |
|
269 |
def test_get_agenda_pricing_event_date(event_date, found): |
|
270 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
271 |
pricing = Pricing.objects.create(label='Foo bar') |
|
272 |
event = Event.objects.create( |
|
273 |
agenda=agenda, start_datetime=make_aware(datetime.datetime(*event_date)), places=10 |
|
274 |
) |
|
275 |
agenda_pricing = AgendaPricing.objects.create( |
|
276 |
agenda=agenda, |
|
277 |
pricing=pricing, |
|
278 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
279 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
280 |
) |
|
281 |
if found: |
|
282 |
assert AgendaPricing.get_agenda_pricing(event) == agenda_pricing |
|
283 |
else: |
|
284 |
with pytest.raises(AgendaPricingNotFound): |
|
285 |
AgendaPricing.get_agenda_pricing(event) |
|
286 | ||
287 | ||
288 |
@mock.patch('requests.Session.send', side_effect=mocked_requests_send) |
|
289 |
def test_get_pricing_context(mock_send, context, nocache): |
|
290 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
291 |
event = Event.objects.create( |
|
292 |
agenda=agenda, start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), places=10 |
|
293 |
) |
|
294 |
pricing = Pricing.objects.create(label='Foo bar') |
|
295 |
agenda_pricing = AgendaPricing.objects.create( |
|
296 |
agenda=agenda, |
|
297 |
pricing=pricing, |
|
298 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
299 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
300 |
) |
|
301 |
assert agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35') == {} |
|
302 |
pricing.extra_variables = { |
|
303 |
'foo': 'bar', |
|
304 |
'qf': '{{ 40|add:2 }}', |
|
305 |
'domicile': 'commune', |
|
306 |
'ids': '{{ cards|objects:"foo"|getlist:"id"|join:"," }}', |
|
307 |
'bad': '{% if foo %}', |
|
308 |
} |
|
309 |
pricing.save() |
|
310 |
assert agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35') == { |
|
311 |
'foo': 'bar', |
|
312 |
'qf': '42', |
|
313 |
'domicile': 'commune', |
|
314 |
'ids': '1,2', |
|
315 |
} |
|
316 | ||
317 |
# user_external_id and adult_external_id can be used in variables |
|
318 |
pricing.extra_variables = { |
|
319 |
'qf': '{{ cards|objects:"qf"|filter_by:"foo"|filter_value:user_external_id|filter_by:"bar"|filter_value:adult_external_id|list }}', |
|
320 |
} |
|
321 |
pricing.save() |
|
322 |
mock_send.reset_mock() |
|
323 |
agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35') |
|
324 |
assert 'filter-foo=child%3A42&' in mock_send.call_args_list[0][0][0].url |
|
325 |
assert 'filter-bar=parent%3A35&' in mock_send.call_args_list[0][0][0].url |
|
326 |
pricing.extra_variables = { |
|
327 |
'qf': '{{ cards|objects:"qf"|filter_by:"foo"|filter_value:user_external_raw_id|filter_by:"bar"|filter_value:adult_external_raw_id|list }}', |
|
328 |
} |
|
329 |
pricing.save() |
|
330 |
mock_send.reset_mock() |
|
331 |
agenda_pricing.get_pricing_context(event, 'child:42', 'parent:35') |
|
332 |
assert 'filter-foo=42&' in mock_send.call_args_list[0][0][0].url |
|
333 |
assert 'filter-bar=35&' in mock_send.call_args_list[0][0][0].url |
|
334 | ||
335 | ||
336 |
@pytest.mark.parametrize( |
|
337 |
'condition, context, result', |
|
338 |
[ |
|
339 |
('qf < 1', {}, False), |
|
340 |
('qf < 1', {'qf': 'foo'}, False), |
|
341 |
('qf < 1', {'qf': 1}, False), |
|
342 |
('qf < 1', {'qf': 0.9}, True), |
|
343 |
('1 <= qf and qf < 2', {'qf': 0}, False), |
|
344 |
('1 <= qf and qf < 2', {'qf': 2}, False), |
|
345 |
('1 <= qf and qf < 2', {'qf': 10}, False), |
|
346 |
('1 <= qf and qf < 2', {'qf': 1}, True), |
|
347 |
('1 <= qf and qf < 2', {'qf': 1.5}, True), |
|
348 |
], |
|
349 |
) |
|
350 |
def test_compute_condition(condition, context, result): |
|
351 |
category = CriteriaCategory.objects.create(label='QF', slug='qf') |
|
352 |
criteria = Criteria.objects.create(label='FOO', condition=condition, category=category) |
|
353 |
assert criteria.compute_condition(context) == result |
|
354 | ||
355 | ||
356 |
def test_compute_pricing(): |
|
357 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
358 |
category = CriteriaCategory.objects.create(label='QF', slug='qf') |
|
359 |
pricing = Pricing.objects.create(label='Foo bar') |
|
360 |
pricing.categories.add(category, through_defaults={'order': 1}) |
|
361 |
agenda_pricing = AgendaPricing.objects.create( |
|
362 |
agenda=agenda, |
|
363 |
pricing=pricing, |
|
364 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
365 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
366 |
) |
|
367 |
# no criteria defined on agenda_pricing |
|
368 |
with pytest.raises(CriteriaConditionNotFound) as e: |
|
369 |
agenda_pricing.compute_pricing(context={'qf': 2}) |
|
370 |
assert e.value.details == {'category': 'qf'} |
|
371 | ||
372 |
# conditions are not set |
|
373 |
criteria1 = Criteria.objects.create(label='QF < 1', slug='qf-0', category=category) |
|
374 |
criteria2 = Criteria.objects.create(label='QF >= 1', slug='qf-1', category=category) |
|
375 |
pricing.criterias.add(criteria1) |
|
376 |
pricing.criterias.add(criteria2) |
|
377 |
with pytest.raises(CriteriaConditionNotFound) as e: |
|
378 |
agenda_pricing.compute_pricing(context={'qf': 2}) |
|
379 |
assert e.value.details == {'category': 'qf'} |
|
380 | ||
381 |
# conditions set, but no match |
|
382 |
criteria1.condition = 'qf < 1' |
|
383 |
criteria1.save() |
|
384 |
criteria2.condition = 'False' |
|
385 |
criteria2.save() |
|
386 |
with pytest.raises(CriteriaConditionNotFound) as e: |
|
387 |
agenda_pricing.compute_pricing(context={'qf': 2}) |
|
388 |
assert e.value.details == {'category': 'qf'} |
|
389 | ||
390 |
# criteria found, but agenda_pricing.pricing_data is not defined |
|
391 |
criteria1.condition = 'qf < 1' |
|
392 |
criteria1.save() |
|
393 |
criteria2.condition = 'qf >= 1' |
|
394 |
criteria2.save() |
|
395 |
with pytest.raises(PricingDataFormatError) as e: |
|
396 |
agenda_pricing.compute_pricing(context={'qf': 2}) |
|
397 |
assert e.value.details == {'category': 'qf', 'pricing': None, 'wanted': 'dict'} |
|
398 | ||
399 |
# criteria not found in pricing_data |
|
400 |
agenda_pricing.pricing_data = { |
|
401 |
'qf:qf-0': 42, |
|
402 |
} |
|
403 |
agenda_pricing.save() |
|
404 |
with pytest.raises(PricingDataError) as e: |
|
405 |
agenda_pricing.compute_pricing(context={'qf': 2}) |
|
406 |
assert e.value.details == {'category': 'qf', 'criteria': 'qf-1'} |
|
407 | ||
408 |
# criteria found, but value is wrong |
|
409 |
for value in ['foo', [], {}]: |
|
410 |
agenda_pricing.pricing_data = { |
|
411 |
'qf:qf-0': 42, |
|
412 |
'qf:qf-1': value, |
|
413 |
} |
|
414 |
agenda_pricing.save() |
|
415 |
with pytest.raises(PricingDataFormatError) as e: |
|
416 |
agenda_pricing.compute_pricing(context={'qf': 2}) |
|
417 |
assert e.value.details == {'pricing': value, 'wanted': 'decimal'} |
|
418 | ||
419 |
# correct value (decimal) |
|
420 |
agenda_pricing.pricing_data = { |
|
421 |
'qf:qf-0': 42, |
|
422 |
'qf:qf-1': 52, |
|
423 |
} |
|
424 |
agenda_pricing.save() |
|
425 |
assert agenda_pricing.compute_pricing(context={'qf': 2}) == (52, {'qf': 'qf-1'}) |
|
426 | ||
427 |
# more complexe pricing model |
|
428 |
category2 = CriteriaCategory.objects.create(label='Domicile', slug='domicile') |
|
429 |
criteria1 = Criteria.objects.create( |
|
430 |
label='Commune', slug='dom-0', condition='domicile == "commune"', category=category2 |
|
431 |
) |
|
432 |
criteria2 = Criteria.objects.create( |
|
433 |
label='Hors commune', slug='dom-1', condition='domicile != "commune"', category=category2 |
|
434 |
) |
|
435 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
436 |
pricing.criterias.add(criteria1) |
|
437 |
pricing.criterias.add(criteria2) |
|
438 | ||
439 |
# wrong definition |
|
440 |
agenda_pricing.pricing_data = { |
|
441 |
'domicile:dom-0': { |
|
442 |
'qf:qf-0': 3, |
|
443 |
'qf:qf-1': 5, |
|
444 |
}, |
|
445 |
'domicile:dom-1': { |
|
446 |
'qf:qf-0': 7, |
|
447 |
'qf:qf-1': 10, |
|
448 |
}, |
|
449 |
} |
|
450 |
agenda_pricing.save() |
|
451 |
with pytest.raises(PricingDataError) as e: |
|
452 |
agenda_pricing.compute_pricing(context={'qf': 2, 'domicile': 'commune'}) |
|
453 | ||
454 |
# reorder categories, so the definition is correct |
|
455 |
PricingCriteriaCategory.objects.filter(pricing=pricing, category=category).update(order=2) |
|
456 |
PricingCriteriaCategory.objects.filter(pricing=pricing, category=category2).update(order=1) |
|
457 |
assert agenda_pricing.compute_pricing(context={'qf': 2, 'domicile': 'commune'}) == ( |
|
458 |
5, |
|
459 |
{'domicile': 'dom-0', 'qf': 'qf-1'}, |
|
460 |
) |
|
461 |
assert agenda_pricing.compute_pricing(context={'qf': 0, 'domicile': 'commune'}) == ( |
|
462 |
3, |
|
463 |
{'domicile': 'dom-0', 'qf': 'qf-0'}, |
|
464 |
) |
|
465 |
assert agenda_pricing.compute_pricing(context={'qf': 2, 'domicile': 'ext'}) == ( |
|
466 |
10, |
|
467 |
{'domicile': 'dom-1', 'qf': 'qf-1'}, |
|
468 |
) |
|
469 |
assert agenda_pricing.compute_pricing(context={'qf': 0, 'domicile': 'ext'}) == ( |
|
470 |
7, |
|
471 |
{'domicile': 'dom-1', 'qf': 'qf-0'}, |
|
472 |
) |
|
473 | ||
474 | ||
475 |
def test_get_booking_modifier_event_not_checked(): |
|
476 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
477 |
event = Event.objects.create( |
|
478 |
agenda=agenda, start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), places=10 |
|
479 |
) |
|
480 |
pricing = Pricing.objects.create(label='Foo bar') |
|
481 |
agenda_pricing = AgendaPricing.objects.create( |
|
482 |
agenda=agenda, |
|
483 |
pricing=pricing, |
|
484 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
485 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
486 |
) |
|
487 |
with pytest.raises(PricingEventNotCheckedError): |
|
488 |
agenda_pricing.get_booking_modifier(event, 'child:42') |
|
489 | ||
490 | ||
491 |
def test_get_booking_modifier_no_subscription(): |
|
492 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
493 |
event = Event.objects.create( |
|
494 |
agenda=agenda, |
|
495 |
start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), |
|
496 |
places=10, |
|
497 |
checked=True, |
|
498 |
) |
|
499 |
Subscription.objects.create( |
|
500 |
agenda=agenda, |
|
501 |
user_external_id='child:35', # another user |
|
502 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
503 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
504 |
) |
|
505 |
pricing = Pricing.objects.create(label='Foo bar') |
|
506 |
agenda_pricing = AgendaPricing.objects.create( |
|
507 |
agenda=agenda, |
|
508 |
pricing=pricing, |
|
509 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
510 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
511 |
) |
|
512 |
with pytest.raises(PricingSubscriptionError): |
|
513 |
agenda_pricing.get_booking_modifier(event, 'child:42') |
|
514 | ||
515 |
# more than one subscription found ! |
|
516 |
Subscription.objects.create( |
|
517 |
agenda=agenda, |
|
518 |
user_external_id='child:42', |
|
519 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
520 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
521 |
) |
|
522 |
Subscription.objects.create( |
|
523 |
agenda=agenda, |
|
524 |
user_external_id='child:42', |
|
525 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
526 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
527 |
) |
|
528 |
with pytest.raises(PricingSubscriptionError): |
|
529 |
agenda_pricing.get_booking_modifier(event, 'child:42') |
|
530 | ||
531 | ||
532 |
@pytest.mark.parametrize( |
|
533 |
'event_date, success', |
|
534 |
[ |
|
535 |
# just before first day |
|
536 |
((2021, 8, 31, 12, 00), False), |
|
537 |
# first day |
|
538 |
((2021, 9, 1, 12, 00), True), |
|
539 |
# last day |
|
540 |
((2021, 9, 30, 12, 00), True), |
|
541 |
# just after last day |
|
542 |
((2021, 10, 1, 12, 00), False), |
|
543 |
], |
|
544 |
) |
|
545 |
def test_get_booking_modifier_subscription_date(event_date, success): |
|
546 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
547 |
Subscription.objects.create( |
|
548 |
agenda=agenda, |
|
549 |
user_external_id='child:42', |
|
550 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
551 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
552 |
) |
|
553 |
event = Event.objects.create( |
|
554 |
agenda=agenda, start_datetime=make_aware(datetime.datetime(*event_date)), places=10, checked=True |
|
555 |
) |
|
556 |
pricing = Pricing.objects.create(label='Foo bar') |
|
557 |
agenda_pricing = AgendaPricing.objects.create( |
|
558 |
agenda=agenda, |
|
559 |
pricing=pricing, |
|
560 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
561 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
562 |
) |
|
563 |
if success: |
|
564 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
565 |
'status': 'not-booked', |
|
566 |
'modifier_type': 'rate', |
|
567 |
'modifier_rate': 0, |
|
568 |
} |
|
569 |
else: |
|
570 |
with pytest.raises(PricingSubscriptionError): |
|
571 |
agenda_pricing.get_booking_modifier(event, 'child:42') |
|
572 | ||
573 | ||
574 |
def test_get_booking_modifier_no_booking(): |
|
575 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
576 |
Subscription.objects.create( |
|
577 |
agenda=agenda, |
|
578 |
user_external_id='child:42', |
|
579 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
580 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
581 |
) |
|
582 |
event = Event.objects.create( |
|
583 |
agenda=agenda, |
|
584 |
start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), |
|
585 |
places=10, |
|
586 |
checked=True, |
|
587 |
) |
|
588 |
pricing = Pricing.objects.create(label='Foo bar') |
|
589 |
agenda_pricing = AgendaPricing.objects.create( |
|
590 |
agenda=agenda, |
|
591 |
pricing=pricing, |
|
592 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
593 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
594 |
) |
|
595 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
596 |
'status': 'not-booked', |
|
597 |
'modifier_type': 'rate', |
|
598 |
'modifier_rate': 0, |
|
599 |
} |
|
600 | ||
601 |
# more than one booking found ! |
|
602 |
Booking.objects.create(event=event, user_external_id='child:42') |
|
603 |
Booking.objects.create(event=event, user_external_id='child:42') |
|
604 |
with pytest.raises(PricingMultipleBookingError): |
|
605 |
agenda_pricing.get_booking_modifier(event, 'child:42') |
|
606 | ||
607 | ||
608 |
def test_get_booking_modifier_booking_cancelled(): |
|
609 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
610 |
Subscription.objects.create( |
|
611 |
agenda=agenda, |
|
612 |
user_external_id='child:42', |
|
613 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
614 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
615 |
) |
|
616 |
event = Event.objects.create( |
|
617 |
agenda=agenda, |
|
618 |
start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), |
|
619 |
places=10, |
|
620 |
checked=True, |
|
621 |
) |
|
622 |
Booking.objects.create(event=event, user_external_id='child:42', cancellation_datetime=now()) |
|
623 |
pricing = Pricing.objects.create(label='Foo bar') |
|
624 |
agenda_pricing = AgendaPricing.objects.create( |
|
625 |
agenda=agenda, |
|
626 |
pricing=pricing, |
|
627 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
628 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
629 |
) |
|
630 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
631 |
'status': 'cancelled', |
|
632 |
'modifier_type': 'rate', |
|
633 |
'modifier_rate': 0, |
|
634 |
} |
|
635 | ||
636 | ||
637 |
def test_get_booking_modifier_booking_not_checked(): |
|
638 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
639 |
Subscription.objects.create( |
|
640 |
agenda=agenda, |
|
641 |
user_external_id='child:42', |
|
642 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
643 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
644 |
) |
|
645 |
event = Event.objects.create( |
|
646 |
agenda=agenda, |
|
647 |
start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), |
|
648 |
places=10, |
|
649 |
checked=True, |
|
650 |
) |
|
651 |
Booking.objects.create(event=event, user_external_id='child:42') |
|
652 |
pricing = Pricing.objects.create(label='Foo bar') |
|
653 |
agenda_pricing = AgendaPricing.objects.create( |
|
654 |
agenda=agenda, |
|
655 |
pricing=pricing, |
|
656 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
657 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
658 |
) |
|
659 |
with pytest.raises(PricingBookingNotCheckedError): |
|
660 |
agenda_pricing.get_booking_modifier(event, 'child:42') |
|
661 | ||
662 | ||
663 |
def test_get_booking_modifier_booking_absence(): |
|
664 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
665 |
Subscription.objects.create( |
|
666 |
agenda=agenda, |
|
667 |
user_external_id='child:42', |
|
668 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
669 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
670 |
) |
|
671 |
event = Event.objects.create( |
|
672 |
agenda=agenda, |
|
673 |
start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), |
|
674 |
places=10, |
|
675 |
checked=True, |
|
676 |
) |
|
677 |
booking = Booking.objects.create(event=event, user_external_id='child:42', user_was_present=False) |
|
678 |
pricing = Pricing.objects.create(label='Foo bar') |
|
679 |
agenda_pricing = AgendaPricing.objects.create( |
|
680 |
agenda=agenda, |
|
681 |
pricing=pricing, |
|
682 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
683 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
684 |
) |
|
685 | ||
686 |
# no check type |
|
687 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
688 |
'status': 'absence', |
|
689 |
'check_type_group': None, |
|
690 |
'check_type': None, |
|
691 |
'modifier_type': 'rate', |
|
692 |
'modifier_rate': 0, |
|
693 |
} |
|
694 | ||
695 |
# check_type but incomplete configuration |
|
696 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
697 |
check_type = CheckType.objects.create(label='Foo reason', group=group, kind='absence') |
|
698 |
booking.user_check_type = check_type |
|
699 |
booking.save() |
|
700 |
with pytest.raises(PricingBookingCheckTypeError) as e: |
|
701 |
agenda_pricing.get_booking_modifier(event, 'child:42') |
|
702 |
assert e.value.details == { |
|
703 |
'check_type_group': 'foo-bar', |
|
704 |
'check_type': 'foo-reason', |
|
705 |
'reason': 'not-configured', |
|
706 |
} |
|
707 | ||
708 |
check_type.pricing = 42 |
|
709 |
check_type.save() |
|
710 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
711 |
'status': 'absence', |
|
712 |
'check_type_group': 'foo-bar', |
|
713 |
'check_type': 'foo-reason', |
|
714 |
'modifier_type': 'fixed', |
|
715 |
'modifier_fixed': 42, |
|
716 |
} |
|
717 | ||
718 |
check_type.pricing = 0 |
|
719 |
check_type.save() |
|
720 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
721 |
'status': 'absence', |
|
722 |
'check_type_group': 'foo-bar', |
|
723 |
'check_type': 'foo-reason', |
|
724 |
'modifier_type': 'fixed', |
|
725 |
'modifier_fixed': 0, |
|
726 |
} |
|
727 | ||
728 |
check_type.pricing = None |
|
729 |
check_type.pricing_rate = 20 |
|
730 |
check_type.save() |
|
731 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
732 |
'status': 'absence', |
|
733 |
'check_type_group': 'foo-bar', |
|
734 |
'check_type': 'foo-reason', |
|
735 |
'modifier_type': 'rate', |
|
736 |
'modifier_rate': 20, |
|
737 |
} |
|
738 | ||
739 |
check_type.pricing_rate = 0 |
|
740 |
check_type.save() |
|
741 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
742 |
'status': 'absence', |
|
743 |
'check_type_group': 'foo-bar', |
|
744 |
'check_type': 'foo-reason', |
|
745 |
'modifier_type': 'rate', |
|
746 |
'modifier_rate': 0, |
|
747 |
} |
|
748 | ||
749 |
# bad check type kind |
|
750 |
check_type.kind = 'presence' |
|
751 |
check_type.save() |
|
752 |
with pytest.raises(PricingBookingCheckTypeError) as e: |
|
753 |
agenda_pricing.get_booking_modifier(event, 'child:42') |
|
754 |
assert e.value.details == { |
|
755 |
'check_type_group': 'foo-bar', |
|
756 |
'check_type': 'foo-reason', |
|
757 |
'reason': 'wrong-kind', |
|
758 |
} |
|
759 | ||
760 | ||
761 |
def test_get_booking_modifier_booking_presence(): |
|
762 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
763 |
Subscription.objects.create( |
|
764 |
agenda=agenda, |
|
765 |
user_external_id='child:42', |
|
766 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
767 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
768 |
) |
|
769 |
event = Event.objects.create( |
|
770 |
agenda=agenda, |
|
771 |
start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), |
|
772 |
places=10, |
|
773 |
checked=True, |
|
774 |
) |
|
775 |
booking = Booking.objects.create(event=event, user_external_id='child:42', user_was_present=True) |
|
776 |
pricing = Pricing.objects.create(label='Foo bar') |
|
777 |
agenda_pricing = AgendaPricing.objects.create( |
|
778 |
agenda=agenda, |
|
779 |
pricing=pricing, |
|
780 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
781 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
782 |
) |
|
783 | ||
784 |
# no check type |
|
785 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
786 |
'status': 'presence', |
|
787 |
'check_type_group': None, |
|
788 |
'check_type': None, |
|
789 |
'modifier_type': 'rate', |
|
790 |
'modifier_rate': 100, |
|
791 |
} |
|
792 | ||
793 |
# check_type but incomplete configuration |
|
794 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
795 |
check_type = CheckType.objects.create(label='Foo reason', group=group, kind='presence') |
|
796 |
booking.user_check_type = check_type |
|
797 |
booking.save() |
|
798 |
with pytest.raises(PricingBookingCheckTypeError) as e: |
|
799 |
agenda_pricing.get_booking_modifier(event, 'child:42') |
|
800 |
assert e.value.details == { |
|
801 |
'check_type_group': 'foo-bar', |
|
802 |
'check_type': 'foo-reason', |
|
803 |
'reason': 'not-configured', |
|
804 |
} |
|
805 | ||
806 |
check_type.pricing = 42 |
|
807 |
check_type.save() |
|
808 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
809 |
'status': 'presence', |
|
810 |
'check_type_group': 'foo-bar', |
|
811 |
'check_type': 'foo-reason', |
|
812 |
'modifier_type': 'fixed', |
|
813 |
'modifier_fixed': 42, |
|
814 |
} |
|
815 | ||
816 |
check_type.pricing = 0 |
|
817 |
check_type.save() |
|
818 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
819 |
'status': 'presence', |
|
820 |
'check_type_group': 'foo-bar', |
|
821 |
'check_type': 'foo-reason', |
|
822 |
'modifier_type': 'fixed', |
|
823 |
'modifier_fixed': 0, |
|
824 |
} |
|
825 | ||
826 |
check_type.pricing = None |
|
827 |
check_type.pricing_rate = 150 |
|
828 |
check_type.save() |
|
829 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
830 |
'status': 'presence', |
|
831 |
'check_type_group': 'foo-bar', |
|
832 |
'check_type': 'foo-reason', |
|
833 |
'modifier_type': 'rate', |
|
834 |
'modifier_rate': 150, |
|
835 |
} |
|
836 | ||
837 |
check_type.pricing_rate = 0 |
|
838 |
check_type.save() |
|
839 |
assert agenda_pricing.get_booking_modifier(event, 'child:42') == { |
|
840 |
'status': 'presence', |
|
841 |
'check_type_group': 'foo-bar', |
|
842 |
'check_type': 'foo-reason', |
|
843 |
'modifier_type': 'rate', |
|
844 |
'modifier_rate': 0, |
|
845 |
} |
|
846 | ||
847 |
# bad check type kind |
|
848 |
check_type.kind = 'absence' |
|
849 |
check_type.save() |
|
850 |
with pytest.raises(PricingBookingCheckTypeError) as e: |
|
851 |
agenda_pricing.get_booking_modifier(event, 'child:42') |
|
852 |
assert e.value.details == { |
|
853 |
'check_type_group': 'foo-bar', |
|
854 |
'check_type': 'foo-reason', |
|
855 |
'reason': 'wrong-kind', |
|
856 |
} |
|
857 | ||
858 | ||
859 |
def test_get_pricing_data(context): |
|
860 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
861 |
event = Event.objects.create( |
|
862 |
agenda=agenda, |
|
863 |
start_datetime=make_aware(datetime.datetime(2021, 9, 15, 12, 00)), |
|
864 |
places=10, |
|
865 |
checked=True, |
|
866 |
) |
|
867 |
Subscription.objects.create( |
|
868 |
agenda=agenda, |
|
869 |
user_external_id='child:42', |
|
870 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
871 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
872 |
) |
|
873 |
category = CriteriaCategory.objects.create(label='Foo', slug='foo') |
|
874 |
criteria = Criteria.objects.create(label='Bar', slug='bar', condition='True', category=category) |
|
875 |
pricing = Pricing.objects.create( |
|
876 |
label='Foo bar', |
|
877 |
extra_variables={ |
|
878 |
'domicile': 'commune', |
|
879 |
'qf': '2', |
|
880 |
}, |
|
881 |
) |
|
882 |
pricing.criterias.add(criteria) |
|
883 |
pricing.categories.add(category, through_defaults={'order': 1}) |
|
884 |
AgendaPricing.objects.create( |
|
885 |
agenda=agenda, |
|
886 |
pricing=pricing, |
|
887 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
888 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
889 |
pricing_data={ |
|
890 |
'foo:bar': 42, |
|
891 |
}, |
|
892 |
) |
|
893 |
assert AgendaPricing.get_pricing_data(context['request'], event, 'child:42', 'parent:35') == { |
|
894 |
'pricing': 0, |
|
895 |
'calculation_details': { |
|
896 |
'pricing': 42, |
|
897 |
'criterias': {'foo': 'bar'}, |
|
898 |
'context': {'domicile': 'commune', 'qf': '2'}, |
|
899 |
}, |
|
900 |
'booking_details': { |
|
901 |
'status': 'not-booked', |
|
902 |
'modifier_type': 'rate', |
|
903 |
'modifier_rate': 0, |
|
904 |
}, |
|
905 |
} |
|
906 | ||
907 | ||
908 |
@pytest.mark.parametrize( |
|
909 |
'modifier, pricing_amount', |
|
910 |
[ |
|
911 |
# not booked |
|
912 |
( |
|
913 |
{ |
|
914 |
'status': 'not-booked', |
|
915 |
'modifier_type': 'rate', |
|
916 |
'modifier_rate': 0, |
|
917 |
}, |
|
918 |
0, |
|
919 |
), |
|
920 |
# cancelled |
|
921 |
( |
|
922 |
{ |
|
923 |
'status': 'cancelled', |
|
924 |
'modifier_type': 'rate', |
|
925 |
'modifier_rate': 0, |
|
926 |
}, |
|
927 |
0, |
|
928 |
), |
|
929 |
# absence |
|
930 |
( |
|
931 |
{ |
|
932 |
'status': 'absence', |
|
933 |
'check_type_group': None, |
|
934 |
'check_type': None, |
|
935 |
'modifier_type': 'rate', |
|
936 |
'modifier_rate': 0, |
|
937 |
}, |
|
938 |
0, |
|
939 |
), |
|
940 |
( |
|
941 |
{ |
|
942 |
'status': 'absence', |
|
943 |
'check_type_group': 'foo-bar', |
|
944 |
'check_type': 'foo-reason', |
|
945 |
'modifier_type': 'fixed', |
|
946 |
'modifier_fixed': 35, |
|
947 |
}, |
|
948 |
35, |
|
949 |
), |
|
950 |
( |
|
951 |
{ |
|
952 |
'status': 'absence', |
|
953 |
'check_type_group': 'foo-bar', |
|
954 |
'check_type': 'foo-reason', |
|
955 |
'modifier_type': 'fixed', |
|
956 |
'modifier_fixed': 0, |
|
957 |
}, |
|
958 |
0, |
|
959 |
), |
|
960 |
( |
|
961 |
{ |
|
962 |
'status': 'absence', |
|
963 |
'check_type_group': 'foo-bar', |
|
964 |
'check_type': 'foo-reason', |
|
965 |
'modifier_type': 'rate', |
|
966 |
'modifier_rate': 20, |
|
967 |
}, |
|
968 |
8.4, |
|
969 |
), |
|
970 |
( |
|
971 |
{ |
|
972 |
'status': 'absence', |
|
973 |
'check_type_group': 'foo-bar', |
|
974 |
'check_type': 'foo-reason', |
|
975 |
'modifier_type': 'rate', |
|
976 |
'modifier_rate': 0, |
|
977 |
}, |
|
978 |
0, |
|
979 |
), |
|
980 |
# presence |
|
981 |
( |
|
982 |
{ |
|
983 |
'status': 'presence', |
|
984 |
'check_type_group': None, |
|
985 |
'check_type': None, |
|
986 |
'modifier_type': 'rate', |
|
987 |
'modifier_rate': 100, |
|
988 |
}, |
|
989 |
42, |
|
990 |
), |
|
991 |
( |
|
992 |
{ |
|
993 |
'status': 'presence', |
|
994 |
'check_type_group': 'foo-bar', |
|
995 |
'check_type': 'foo-reason', |
|
996 |
'modifier_type': 'fixed', |
|
997 |
'modifier_fixed': 35, |
|
998 |
}, |
|
999 |
35, |
|
1000 |
), |
|
1001 |
( |
|
1002 |
{ |
|
1003 |
'status': 'presence', |
|
1004 |
'check_type_group': 'foo-bar', |
|
1005 |
'check_type': 'foo-reason', |
|
1006 |
'modifier_type': 'fixed', |
|
1007 |
'modifier_fixed': 0, |
|
1008 |
}, |
|
1009 |
0, |
|
1010 |
), |
|
1011 |
( |
|
1012 |
{ |
|
1013 |
'status': 'presence', |
|
1014 |
'check_type_group': 'foo-bar', |
|
1015 |
'check_type': 'foo-reason', |
|
1016 |
'modifier_type': 'rate', |
|
1017 |
'modifier_rate': 150, |
|
1018 |
}, |
|
1019 |
63, |
|
1020 |
), |
|
1021 |
( |
|
1022 |
{ |
|
1023 |
'status': 'presence', |
|
1024 |
'check_type_group': 'foo-bar', |
|
1025 |
'check_type': 'foo-reason', |
|
1026 |
'modifier_type': 'rate', |
|
1027 |
'modifier_rate': 0, |
|
1028 |
}, |
|
1029 |
0, |
|
1030 |
), |
|
1031 |
], |
|
1032 |
) |
|
1033 |
def test_aggregate_pricing_data(modifier, pricing_amount): |
|
1034 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1035 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1036 |
agenda_pricing = AgendaPricing.objects.create( |
|
1037 |
agenda=agenda, |
|
1038 |
pricing=pricing, |
|
1039 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1040 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1041 |
) |
|
1042 | ||
1043 |
assert agenda_pricing.aggregate_pricing_data( |
|
1044 |
pricing=42, criterias={'foo': 'bar'}, context={'domicile': 'commune', 'qf': 2}, modifier=modifier |
|
1045 |
) == { |
|
1046 |
'pricing': pricing_amount, |
|
1047 |
'calculation_details': { |
|
1048 |
'pricing': 42, |
|
1049 |
'criterias': {'foo': 'bar'}, |
|
1050 |
'context': {'domicile': 'commune', 'qf': 2}, |
|
1051 |
}, |
|
1052 |
'booking_details': modifier, |
|
1053 |
} |
|
1054 | ||
1055 | ||
1056 |
def test_agenda_pricing_iter_pricing_matrix_3_categories(): |
|
1057 |
category1 = CriteriaCategory.objects.create(label='Cat 1') |
|
1058 |
criteria11 = Criteria.objects.create(label='Crit 1-1', slug='crit-1-1', category=category1, order=1) |
|
1059 |
criteria12 = Criteria.objects.create(label='Crit 1-2', slug='crit-1-2', category=category1, order=2) |
|
1060 |
category2 = CriteriaCategory.objects.create(label='Cat 2') |
|
1061 |
criteria21 = Criteria.objects.create(label='Crit 2-1', slug='crit-2-1', category=category2, order=1) |
|
1062 |
criteria22 = Criteria.objects.create(label='Crit 2-2', slug='crit-2-2', category=category2, order=2) |
|
1063 |
criteria23 = Criteria.objects.create(label='Crit 2-3', slug='crit-2-3', category=category2, order=3) |
|
1064 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
1065 |
criteria31 = Criteria.objects.create(label='Crit 3-1', slug='crit-3-1', category=category3, order=1) |
|
1066 |
criteria33 = Criteria.objects.create(label='Crit 3-3', slug='crit-3-3', category=category3, order=3) |
|
1067 |
criteria34 = Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4) |
|
1068 |
criteria32 = Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2) |
|
1069 |
not_used = Criteria.objects.create(label='Not used', slug='crit-3-notused', category=category3, order=5) |
|
1070 |
category4 = CriteriaCategory.objects.create(label='Cat 4') # ignored |
|
1071 | ||
1072 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1073 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1074 |
pricing.categories.add(category1, through_defaults={'order': 1}) |
|
1075 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
1076 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
1077 |
pricing.categories.add(category4, through_defaults={'order': 4}) |
|
1078 |
pricing.criterias.set(Criteria.objects.exclude(pk=not_used.pk)) |
|
1079 |
agenda_pricing = AgendaPricing.objects.create( |
|
1080 |
agenda=agenda, |
|
1081 |
pricing=pricing, |
|
1082 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1083 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1084 |
) |
|
1085 |
assert list(agenda_pricing.iter_pricing_matrix()) == [ |
|
1086 |
PricingMatrix( |
|
1087 |
criteria=criteria11, |
|
1088 |
rows=[ |
|
1089 |
PricingMatrixRow( |
|
1090 |
criteria=criteria31, |
|
1091 |
cells=[ |
|
1092 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1093 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1094 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1095 |
], |
|
1096 |
), |
|
1097 |
PricingMatrixRow( |
|
1098 |
criteria=criteria32, |
|
1099 |
cells=[ |
|
1100 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1101 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1102 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1103 |
], |
|
1104 |
), |
|
1105 |
PricingMatrixRow( |
|
1106 |
criteria=criteria33, |
|
1107 |
cells=[ |
|
1108 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1109 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1110 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1111 |
], |
|
1112 |
), |
|
1113 |
PricingMatrixRow( |
|
1114 |
criteria=criteria34, |
|
1115 |
cells=[ |
|
1116 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1117 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1118 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1119 |
], |
|
1120 |
), |
|
1121 |
], |
|
1122 |
), |
|
1123 |
PricingMatrix( |
|
1124 |
criteria=criteria12, |
|
1125 |
rows=[ |
|
1126 |
PricingMatrixRow( |
|
1127 |
criteria=criteria31, |
|
1128 |
cells=[ |
|
1129 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1130 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1131 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1132 |
], |
|
1133 |
), |
|
1134 |
PricingMatrixRow( |
|
1135 |
criteria=criteria32, |
|
1136 |
cells=[ |
|
1137 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1138 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1139 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1140 |
], |
|
1141 |
), |
|
1142 |
PricingMatrixRow( |
|
1143 |
criteria=criteria33, |
|
1144 |
cells=[ |
|
1145 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1146 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1147 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1148 |
], |
|
1149 |
), |
|
1150 |
PricingMatrixRow( |
|
1151 |
criteria=criteria34, |
|
1152 |
cells=[ |
|
1153 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1154 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1155 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1156 |
], |
|
1157 |
), |
|
1158 |
], |
|
1159 |
), |
|
1160 |
] |
|
1161 | ||
1162 |
# some data defined |
|
1163 |
agenda_pricing.pricing_data = { |
|
1164 |
'cat-1:crit-1-1': { |
|
1165 |
'cat-2:crit-2-1': { |
|
1166 |
'cat-3:crit-3-1': 111, |
|
1167 |
'cat-3:crit-3-3': 'not-a-decimal', |
|
1168 |
'cat-3:crit-3-4': 114, |
|
1169 |
}, |
|
1170 |
'cat-2:crit-2-3': { |
|
1171 |
'cat-3:crit-3-2': 132, |
|
1172 |
}, |
|
1173 |
}, |
|
1174 |
'cat-1:crit-1-2': { |
|
1175 |
'cat-2:crit-2-2': { |
|
1176 |
'cat-3:crit-3-3': 223, |
|
1177 |
}, |
|
1178 |
}, |
|
1179 |
} |
|
1180 |
agenda_pricing.save() |
|
1181 |
assert list(agenda_pricing.iter_pricing_matrix()) == [ |
|
1182 |
PricingMatrix( |
|
1183 |
criteria=criteria11, |
|
1184 |
rows=[ |
|
1185 |
PricingMatrixRow( |
|
1186 |
criteria=criteria31, |
|
1187 |
cells=[ |
|
1188 |
PricingMatrixCell(criteria=criteria21, value=111), |
|
1189 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1190 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1191 |
], |
|
1192 |
), |
|
1193 |
PricingMatrixRow( |
|
1194 |
criteria=criteria32, |
|
1195 |
cells=[ |
|
1196 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1197 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1198 |
PricingMatrixCell(criteria=criteria23, value=132), |
|
1199 |
], |
|
1200 |
), |
|
1201 |
PricingMatrixRow( |
|
1202 |
criteria=criteria33, |
|
1203 |
cells=[ |
|
1204 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1205 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1206 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1207 |
], |
|
1208 |
), |
|
1209 |
PricingMatrixRow( |
|
1210 |
criteria=criteria34, |
|
1211 |
cells=[ |
|
1212 |
PricingMatrixCell(criteria=criteria21, value=114), |
|
1213 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1214 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1215 |
], |
|
1216 |
), |
|
1217 |
], |
|
1218 |
), |
|
1219 |
PricingMatrix( |
|
1220 |
criteria=criteria12, |
|
1221 |
rows=[ |
|
1222 |
PricingMatrixRow( |
|
1223 |
criteria=criteria31, |
|
1224 |
cells=[ |
|
1225 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1226 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1227 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1228 |
], |
|
1229 |
), |
|
1230 |
PricingMatrixRow( |
|
1231 |
criteria=criteria32, |
|
1232 |
cells=[ |
|
1233 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1234 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1235 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1236 |
], |
|
1237 |
), |
|
1238 |
PricingMatrixRow( |
|
1239 |
criteria=criteria33, |
|
1240 |
cells=[ |
|
1241 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1242 |
PricingMatrixCell(criteria=criteria22, value=223), |
|
1243 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1244 |
], |
|
1245 |
), |
|
1246 |
PricingMatrixRow( |
|
1247 |
criteria=criteria34, |
|
1248 |
cells=[ |
|
1249 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1250 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1251 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1252 |
], |
|
1253 |
), |
|
1254 |
], |
|
1255 |
), |
|
1256 |
] |
|
1257 | ||
1258 | ||
1259 |
def test_agenda_pricing_iter_pricing_matrix_2_categories(): |
|
1260 |
category2 = CriteriaCategory.objects.create(label='Cat 2') |
|
1261 |
criteria21 = Criteria.objects.create(label='Crit 2-1', slug='crit-2-1', category=category2, order=1) |
|
1262 |
criteria22 = Criteria.objects.create(label='Crit 2-2', slug='crit-2-2', category=category2, order=2) |
|
1263 |
criteria23 = Criteria.objects.create(label='Crit 2-3', slug='crit-2-3', category=category2, order=3) |
|
1264 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
1265 |
criteria31 = Criteria.objects.create(label='Crit 3-1', slug='crit-3-1', category=category3, order=1) |
|
1266 |
criteria33 = Criteria.objects.create(label='Crit 3-3', slug='crit-3-3', category=category3, order=3) |
|
1267 |
criteria34 = Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4) |
|
1268 |
criteria32 = Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2) |
|
1269 |
not_used = Criteria.objects.create(label='Not used', slug='crit-3-notused', category=category3, order=5) |
|
1270 | ||
1271 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1272 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1273 |
pricing.categories.add(category2, through_defaults={'order': 2}) |
|
1274 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
1275 |
pricing.criterias.set(Criteria.objects.exclude(pk=not_used.pk)) |
|
1276 |
agenda_pricing = AgendaPricing.objects.create( |
|
1277 |
agenda=agenda, |
|
1278 |
pricing=pricing, |
|
1279 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1280 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1281 |
) |
|
1282 | ||
1283 |
assert list(agenda_pricing.iter_pricing_matrix()) == [ |
|
1284 |
PricingMatrix( |
|
1285 |
criteria=None, |
|
1286 |
rows=[ |
|
1287 |
PricingMatrixRow( |
|
1288 |
criteria=criteria31, |
|
1289 |
cells=[ |
|
1290 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1291 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1292 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1293 |
], |
|
1294 |
), |
|
1295 |
PricingMatrixRow( |
|
1296 |
criteria=criteria32, |
|
1297 |
cells=[ |
|
1298 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1299 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1300 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1301 |
], |
|
1302 |
), |
|
1303 |
PricingMatrixRow( |
|
1304 |
criteria=criteria33, |
|
1305 |
cells=[ |
|
1306 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1307 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1308 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1309 |
], |
|
1310 |
), |
|
1311 |
PricingMatrixRow( |
|
1312 |
criteria=criteria34, |
|
1313 |
cells=[ |
|
1314 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1315 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1316 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1317 |
], |
|
1318 |
), |
|
1319 |
], |
|
1320 |
), |
|
1321 |
] |
|
1322 | ||
1323 |
# some data defined |
|
1324 |
agenda_pricing.pricing_data = { |
|
1325 |
'cat-2:crit-2-1': { |
|
1326 |
'cat-3:crit-3-1': 11, |
|
1327 |
'cat-3:crit-3-3': 'not-a-decimal', |
|
1328 |
'cat-3:crit-3-4': 14, |
|
1329 |
}, |
|
1330 |
'cat-2:crit-2-3': { |
|
1331 |
'cat-3:crit-3-2': 32, |
|
1332 |
}, |
|
1333 |
} |
|
1334 |
agenda_pricing.save() |
|
1335 |
assert list(agenda_pricing.iter_pricing_matrix()) == [ |
|
1336 |
PricingMatrix( |
|
1337 |
criteria=None, |
|
1338 |
rows=[ |
|
1339 |
PricingMatrixRow( |
|
1340 |
criteria=criteria31, |
|
1341 |
cells=[ |
|
1342 |
PricingMatrixCell(criteria=criteria21, value=11), |
|
1343 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1344 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1345 |
], |
|
1346 |
), |
|
1347 |
PricingMatrixRow( |
|
1348 |
criteria=criteria32, |
|
1349 |
cells=[ |
|
1350 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1351 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1352 |
PricingMatrixCell(criteria=criteria23, value=32), |
|
1353 |
], |
|
1354 |
), |
|
1355 |
PricingMatrixRow( |
|
1356 |
criteria=criteria33, |
|
1357 |
cells=[ |
|
1358 |
PricingMatrixCell(criteria=criteria21, value=None), |
|
1359 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1360 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1361 |
], |
|
1362 |
), |
|
1363 |
PricingMatrixRow( |
|
1364 |
criteria=criteria34, |
|
1365 |
cells=[ |
|
1366 |
PricingMatrixCell(criteria=criteria21, value=14), |
|
1367 |
PricingMatrixCell(criteria=criteria22, value=None), |
|
1368 |
PricingMatrixCell(criteria=criteria23, value=None), |
|
1369 |
], |
|
1370 |
), |
|
1371 |
], |
|
1372 |
), |
|
1373 |
] |
|
1374 | ||
1375 | ||
1376 |
def test_agenda_pricing_iter_pricing_matrix_1_category(): |
|
1377 |
category3 = CriteriaCategory.objects.create(label='Cat 3') |
|
1378 |
criteria31 = Criteria.objects.create(label='Crit 3-1', slug='crit-3-1', category=category3, order=1) |
|
1379 |
criteria33 = Criteria.objects.create(label='Crit 3-3', slug='crit-3-3', category=category3, order=3) |
|
1380 |
criteria34 = Criteria.objects.create(label='Crit 3-4', slug='crit-3-4', category=category3, order=4) |
|
1381 |
criteria32 = Criteria.objects.create(label='Crit 3-2', slug='crit-3-2', category=category3, order=2) |
|
1382 |
not_used = Criteria.objects.create(label='Not used', slug='crit-3-notused', category=category3, order=5) |
|
1383 | ||
1384 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1385 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1386 |
pricing.categories.add(category3, through_defaults={'order': 3}) |
|
1387 |
pricing.criterias.set(Criteria.objects.exclude(pk=not_used.pk)) |
|
1388 |
agenda_pricing = AgendaPricing.objects.create( |
|
1389 |
agenda=agenda, |
|
1390 |
pricing=pricing, |
|
1391 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1392 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1393 |
) |
|
1394 | ||
1395 |
assert list(agenda_pricing.iter_pricing_matrix()) == [ |
|
1396 |
PricingMatrix( |
|
1397 |
criteria=None, |
|
1398 |
rows=[ |
|
1399 |
PricingMatrixRow( |
|
1400 |
criteria=criteria31, |
|
1401 |
cells=[ |
|
1402 |
PricingMatrixCell(criteria=None, value=None), |
|
1403 |
], |
|
1404 |
), |
|
1405 |
PricingMatrixRow( |
|
1406 |
criteria=criteria32, |
|
1407 |
cells=[ |
|
1408 |
PricingMatrixCell(criteria=None, value=None), |
|
1409 |
], |
|
1410 |
), |
|
1411 |
PricingMatrixRow( |
|
1412 |
criteria=criteria33, |
|
1413 |
cells=[ |
|
1414 |
PricingMatrixCell(criteria=None, value=None), |
|
1415 |
], |
|
1416 |
), |
|
1417 |
PricingMatrixRow( |
|
1418 |
criteria=criteria34, |
|
1419 |
cells=[ |
|
1420 |
PricingMatrixCell(criteria=None, value=None), |
|
1421 |
], |
|
1422 |
), |
|
1423 |
], |
|
1424 |
), |
|
1425 |
] |
|
1426 | ||
1427 |
# some data defined |
|
1428 |
agenda_pricing.pricing_data = { |
|
1429 |
'cat-3:crit-3-1': 1, |
|
1430 |
'cat-3:crit-3-3': 'not-a-decimal', |
|
1431 |
'cat-3:crit-3-4': 4, |
|
1432 |
} |
|
1433 |
agenda_pricing.save() |
|
1434 |
assert list(agenda_pricing.iter_pricing_matrix()) == [ |
|
1435 |
PricingMatrix( |
|
1436 |
criteria=None, |
|
1437 |
rows=[ |
|
1438 |
PricingMatrixRow( |
|
1439 |
criteria=criteria31, |
|
1440 |
cells=[ |
|
1441 |
PricingMatrixCell(criteria=None, value=1), |
|
1442 |
], |
|
1443 |
), |
|
1444 |
PricingMatrixRow( |
|
1445 |
criteria=criteria32, |
|
1446 |
cells=[ |
|
1447 |
PricingMatrixCell(criteria=None, value=None), |
|
1448 |
], |
|
1449 |
), |
|
1450 |
PricingMatrixRow( |
|
1451 |
criteria=criteria33, |
|
1452 |
cells=[ |
|
1453 |
PricingMatrixCell(criteria=None, value=None), |
|
1454 |
], |
|
1455 |
), |
|
1456 |
PricingMatrixRow( |
|
1457 |
criteria=criteria34, |
|
1458 |
cells=[ |
|
1459 |
PricingMatrixCell(criteria=None, value=4), |
|
1460 |
], |
|
1461 |
), |
|
1462 |
], |
|
1463 |
), |
|
1464 |
] |
|
1465 | ||
1466 | ||
1467 |
def test_agenda_pricing_iter_pricing_matrix_empty(): |
|
1468 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
1469 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1470 |
agenda_pricing = AgendaPricing.objects.create( |
|
1471 |
agenda=agenda, |
|
1472 |
pricing=pricing, |
|
1473 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
1474 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
1475 |
) |
|
1476 | ||
1477 |
assert list(agenda_pricing.iter_pricing_matrix()) == [] |
tests/settings.py | ||
---|---|---|
34 | 34 |
EXCEPTIONS_SOURCES = {} |
35 | 35 | |
36 | 36 |
SITE_BASE_URL = 'https://example.com' |
37 |
CHRONO_ENABLE_PRICING = True |
tests/test_import_export.py | ||
---|---|---|
35 | 35 |
VirtualMember, |
36 | 36 |
) |
37 | 37 |
from chrono.manager.utils import import_site |
38 |
from chrono.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingCriteriaCategory |
|
39 | 38 | |
40 | 39 |
pytestmark = pytest.mark.django_db |
41 | 40 | |
... | ... | |
486 | 485 |
assert agenda.events_type == events_type |
487 | 486 | |
488 | 487 | |
489 |
def test_import_export_agenda_with_pricing(app): |
|
490 |
pricing = Pricing.objects.create(label='Foo') |
|
491 |
agenda = Agenda.objects.create(label='Foo Bar', kind='events') |
|
492 |
Desk.objects.create(agenda=agenda, slug='_exceptions_holder') |
|
493 |
agenda_pricing = AgendaPricing.objects.create( |
|
494 |
agenda=agenda, |
|
495 |
pricing=pricing, |
|
496 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
497 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
498 |
pricing_data={ |
|
499 |
'foo': 'bar', |
|
500 |
}, |
|
501 |
) |
|
502 |
output = get_output_of_command('export_site') |
|
503 | ||
504 |
import_site(data={}, clean=True) |
|
505 |
assert Agenda.objects.count() == 0 |
|
506 |
assert Pricing.objects.count() == 0 |
|
507 |
data = json.loads(output) |
|
508 |
del data['pricing_models'] |
|
509 |
print(data) |
|
510 | ||
511 |
with pytest.raises(AgendaImportError) as excinfo: |
|
512 |
import_site(data, overwrite=True) |
|
513 |
assert str(excinfo.value) == 'Missing "foo" pricing model' |
|
514 | ||
515 |
Pricing.objects.create(label='foobar') |
|
516 |
with pytest.raises(AgendaImportError) as excinfo: |
|
517 |
import_site(data, overwrite=True) |
|
518 |
assert str(excinfo.value) == 'Missing "foo" pricing model' |
|
519 | ||
520 |
pricing = Pricing.objects.create(label='Foo') |
|
521 |
import_site(data, overwrite=True) |
|
522 |
agenda = Agenda.objects.get(slug=agenda.slug) |
|
523 |
assert agenda.agendapricing_set.count() == 1 |
|
524 |
agenda_pricing = agenda.agendapricing_set.get() |
|
525 |
assert agenda_pricing.agenda == agenda |
|
526 |
assert agenda_pricing.pricing == pricing |
|
527 |
assert agenda_pricing.date_start == datetime.date(year=2021, month=9, day=1) |
|
528 |
assert agenda_pricing.date_end == datetime.date(year=2021, month=10, day=1) |
|
529 | ||
530 |
# again |
|
531 |
import_site(data) |
|
532 |
assert agenda.agendapricing_set.count() == 1 |
|
533 |
agenda_pricing = AgendaPricing.objects.get(pk=agenda_pricing.pk) |
|
534 |
assert agenda_pricing.agenda == agenda |
|
535 |
assert agenda_pricing.pricing == pricing |
|
536 | ||
537 |
data['agendas'][0]['pricings'].append( |
|
538 |
{ |
|
539 |
'pricing': 'foo', |
|
540 |
'date_start': '2022-09-01', |
|
541 |
'date_end': '2022-10-01', |
|
542 |
'pricing_data': {'foo': 'bar'}, |
|
543 |
} |
|
544 |
) |
|
545 |
import_site(data) |
|
546 |
assert agenda.agendapricing_set.count() == 2 |
|
547 |
agenda_pricing = AgendaPricing.objects.latest('pk') |
|
548 |
assert agenda_pricing.agenda == agenda |
|
549 |
assert agenda_pricing.pricing == pricing |
|
550 |
assert agenda_pricing.date_start == datetime.date(year=2022, month=9, day=1) |
|
551 |
assert agenda_pricing.date_end == datetime.date(year=2022, month=10, day=1) |
|
552 | ||
553 | ||
554 | 488 |
def test_import_export_virtual_agenda(app): |
555 | 489 |
virtual_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual') |
556 | 490 |
output = get_output_of_command('export_site') |
... | ... | |
1166 | 1100 |
assert resource.slug == 'foo-bar' |
1167 | 1101 | |
1168 | 1102 | |
1169 |
def test_import_export_pricing_criteria_category(app): |
|
1170 |
output = get_output_of_command('export_site') |
|
1171 |
payload = json.loads(output) |
|
1172 |
assert len(payload['pricing_categories']) == 0 |
|
1173 | ||
1174 |
category = CriteriaCategory.objects.create(label='Foo bar') |
|
1175 |
Criteria.objects.create(label='Foo reason', category=category) |
|
1176 |
Criteria.objects.create(label='Baz', category=category) |
|
1177 | ||
1178 |
output = get_output_of_command('export_site') |
|
1179 |
payload = json.loads(output) |
|
1180 |
assert len(payload['pricing_categories']) == 1 |
|
1181 | ||
1182 |
category.delete() |
|
1183 |
assert not CriteriaCategory.objects.exists() |
|
1184 |
assert not Criteria.objects.exists() |
|
1185 | ||
1186 |
import_site(copy.deepcopy(payload)) |
|
1187 |
assert CriteriaCategory.objects.count() == 1 |
|
1188 |
category = CriteriaCategory.objects.first() |
|
1189 |
assert category.label == 'Foo bar' |
|
1190 |
assert category.slug == 'foo-bar' |
|
1191 |
assert category.criterias.count() == 2 |
|
1192 |
assert Criteria.objects.get(category=category, label='Foo reason', slug='foo-reason') |
|
1193 |
assert Criteria.objects.get(category=category, label='Baz', slug='baz') |
|
1194 | ||
1195 |
# update |
|
1196 |
update_payload = copy.deepcopy(payload) |
|
1197 |
update_payload['pricing_categories'][0]['label'] = 'Foo bar Updated' |
|
1198 |
import_site(update_payload) |
|
1199 |
category.refresh_from_db() |
|
1200 |
assert category.label == 'Foo bar Updated' |
|
1201 | ||
1202 |
# insert another category |
|
1203 |
category.slug = 'foo-bar-updated' |
|
1204 |
category.save() |
|
1205 |
import_site(copy.deepcopy(payload)) |
|
1206 |
assert CriteriaCategory.objects.count() == 2 |
|
1207 |
category = CriteriaCategory.objects.latest('pk') |
|
1208 |
assert category.label == 'Foo bar' |
|
1209 |
assert category.slug == 'foo-bar' |
|
1210 |
assert category.criterias.count() == 2 |
|
1211 |
assert Criteria.objects.get(category=category, label='Foo reason', slug='foo-reason') |
|
1212 |
assert Criteria.objects.get(category=category, label='Baz', slug='baz') |
|
1213 | ||
1214 |
# with overwrite |
|
1215 |
Criteria.objects.create(category=category, label='Baz2') |
|
1216 |
import_site(copy.deepcopy(payload), overwrite=True) |
|
1217 |
assert CriteriaCategory.objects.count() == 2 |
|
1218 |
category = CriteriaCategory.objects.latest('pk') |
|
1219 |
assert category.label == 'Foo bar' |
|
1220 |
assert category.slug == 'foo-bar' |
|
1221 |
assert category.criterias.count() == 2 |
|
1222 |
assert Criteria.objects.get(category=category, label='Foo reason', slug='foo-reason') |
|
1223 |
assert Criteria.objects.get(category=category, label='Baz', slug='baz') |
|
1224 | ||
1225 | ||
1226 |
def test_import_export_pricing(app): |
|
1227 |
output = get_output_of_command('export_site') |
|
1228 |
payload = json.loads(output) |
|
1229 |
assert len(payload['pricing_models']) == 0 |
|
1230 | ||
1231 |
pricing = Pricing.objects.create(label='Foo bar', extra_variables={'foo': 'bar'}) |
|
1232 | ||
1233 |
output = get_output_of_command('export_site') |
|
1234 |
payload = json.loads(output) |
|
1235 |
assert len(payload['pricing_models']) == 1 |
|
1236 | ||
1237 |
pricing.delete() |
|
1238 |
assert not Pricing.objects.exists() |
|
1239 | ||
1240 |
import_site(copy.deepcopy(payload)) |
|
1241 |
assert Pricing.objects.count() == 1 |
|
1242 |
pricing = Pricing.objects.first() |
|
1243 |
assert pricing.label == 'Foo bar' |
|
1244 |
assert pricing.slug == 'foo-bar' |
|
1245 |
assert pricing.extra_variables == {'foo': 'bar'} |
|
1246 | ||
1247 |
# update |
|
1248 |
update_payload = copy.deepcopy(payload) |
|
1249 |
update_payload['pricing_models'][0]['label'] = 'Foo bar Updated' |
|
1250 |
import_site(update_payload) |
|
1251 |
pricing.refresh_from_db() |
|
1252 |
assert pricing.label == 'Foo bar Updated' |
|
1253 | ||
1254 |
# insert another pricing |
|
1255 |
pricing.slug = 'foo-bar-updated' |
|
1256 |
pricing.save() |
|
1257 |
import_site(copy.deepcopy(payload)) |
|
1258 |
assert Pricing.objects.count() == 2 |
|
1259 |
pricing = Pricing.objects.latest('pk') |
|
1260 |
assert pricing.label == 'Foo bar' |
|
1261 |
assert pricing.slug == 'foo-bar' |
|
1262 |
assert pricing.extra_variables == {'foo': 'bar'} |
|
1263 | ||
1264 | ||
1265 |
def test_import_export_pricing_with_categories(app): |
|
1266 |
pricing = Pricing.objects.create(label='Foo bar') |
|
1267 |
category = CriteriaCategory.objects.create(label='Foo bar') |
|
1268 |
pricing.categories.add(category, through_defaults={'order': 42}) |
|
1269 | ||
1270 |
output = get_output_of_command('export_site') |
|
1271 | ||
1272 |
import_site(data={}, clean=True) |
|
1273 |
assert Pricing.objects.count() == 0 |
|
1274 |
assert CriteriaCategory.objects.count() == 0 |
|
1275 |
data = json.loads(output) |
|
1276 |
del data['pricing_categories'] |
|
1277 | ||
1278 |
with pytest.raises(AgendaImportError) as excinfo: |
|
1279 |
import_site(data, overwrite=True) |
|
1280 |
assert str(excinfo.value) == 'Missing "foo-bar" pricing category' |
|
1281 | ||
1282 |
CriteriaCategory.objects.create(label='Foobar') |
|
1283 |
with pytest.raises(AgendaImportError) as excinfo: |
|
1284 |
import_site(data, overwrite=True) |
|
1285 |
assert str(excinfo.value) == 'Missing "foo-bar" pricing category' |
|
1286 | ||
1287 |
category = CriteriaCategory.objects.create(label='Foo bar') |
|
1288 |
import_site(data, overwrite=True) |
|
1289 |
pricing = Pricing.objects.get(slug=pricing.slug) |
|
1290 |
assert list(pricing.categories.all()) == [category] |
|
1291 |
assert PricingCriteriaCategory.objects.first().order == 42 |
|
1292 | ||
1293 |
category2 = CriteriaCategory.objects.create(label='Foo bar 2') |
|
1294 |
category3 = CriteriaCategory.objects.create(label='Foo bar 3') |
|
1295 |
pricing.categories.add(category2, through_defaults={'order': 1}) |
|
1296 |
output = get_output_of_command('export_site') |
|
1297 |
data = json.loads(output) |
|
1298 |
del data['pricing_categories'] |
|
1299 |
data['pricing_models'][0]['categories'] = [ |
|
1300 |
{ |
|
1301 |
'category': 'foo-bar-3', |
|
1302 |
'order': 1, |
|
1303 |
'criterias': [], |
|
1304 |
}, |
|
1305 |
{ |
|
1306 |
'category': 'foo-bar', |
|
1307 |
'order': 35, |
|
1308 |
'criterias': [], |
|
1309 |
}, |
|
1310 |
] |
|
1311 |
import_site(data, overwrite=True) |
|
1312 |
assert list(pricing.categories.all()) == [category, category3] |
|
1313 |
assert list( |
|
1314 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) |
|
1315 |
) == [category3.pk, category.pk] |
|
1316 |
assert list(PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('order', flat=True)) == [ |
|
1317 |
1, |
|
1318 |
35, |
|
1319 |
] |
|
1320 |
assert list(pricing.criterias.all()) == [] |
|
1321 | ||
1322 |
criteria1 = Criteria.objects.create(label='Crit 1', category=category) |
|
1323 |
Criteria.objects.create(label='Crit 2', category=category) |
|
1324 |
criteria3 = Criteria.objects.create(label='Crit 3', category=category) |
|
1325 | ||
1326 |
# unknown criteria |
|
1327 |
data['pricing_models'][0]['categories'] = [ |
|
1328 |
{ |
|
1329 |
'category': 'foo-bar-3', |
|
1330 |
'order': 1, |
|
1331 |
'criterias': ['unknown'], |
|
1332 |
}, |
|
1333 |
{ |
|
1334 |
'category': 'foo-bar', |
|
1335 |
'order': 35, |
|
1336 |
'criterias': [], |
|
1337 |
}, |
|
1338 |
] |
|
1339 |
with pytest.raises(AgendaImportError) as excinfo: |
|
1340 |
import_site(data, overwrite=True) |
|
1341 |
assert str(excinfo.value) == 'Missing "unknown" pricing criteria for "foo-bar-3" category' |
|
1342 | ||
1343 |
# wrong criteria (from another category) |
|
1344 |
data['pricing_models'][0]['categories'] = [ |
|
1345 |
{ |
|
1346 |
'category': 'foo-bar-3', |
|
1347 |
'order': 1, |
|
1348 |
'criterias': ['crit-1'], |
|
1349 |
}, |
|
1350 |
{ |
|
1351 |
'category': 'foo-bar', |
|
1352 |
'order': 35, |
|
1353 |
'criterias': [], |
|
1354 |
}, |
|
1355 |
] |
|
1356 |
with pytest.raises(AgendaImportError) as excinfo: |
|
1357 |
import_site(data, overwrite=True) |
|
1358 |
assert str(excinfo.value) == 'Missing "crit-1" pricing criteria for "foo-bar-3" category' |
|
1359 | ||
1360 |
data['pricing_models'][0]['categories'] = [ |
|
1361 |
{ |
|
1362 |
'category': 'foo-bar-3', |
|
1363 |
'order': 1, |
|
1364 |
'criterias': [], |
|
1365 |
}, |
|
1366 |
{ |
|
1367 |
'category': 'foo-bar', |
|
1368 |
'order': 35, |
|
1369 |
'criterias': ['crit-1', 'crit-3'], |
|
1370 |
}, |
|
1371 |
] |
|
1372 |
import_site(data, overwrite=True) |
|
1373 |
assert list(pricing.categories.all()) == [category, category3] |
|
1374 |
assert list( |
|
1375 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) |
|
1376 |
) == [category3.pk, category.pk] |
|
1377 |
assert list(PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('order', flat=True)) == [ |
|
1378 |
1, |
|
1379 |
35, |
|
1380 |
] |
|
1381 |
assert set(pricing.criterias.all()) == {criteria1, criteria3} |
|
1382 | ||
1383 | ||
1384 | 1103 |
@mock.patch('chrono.agendas.models.Agenda.is_available_for_simple_management') |
1385 | 1104 |
def test_import_export_desk_simple_management(available_mock): |
1386 | 1105 |
agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True) |
1387 |
- |