0001-pricing-import-export-65442.patch
lingo/agendas/models.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import copy |
|
18 | ||
17 | 19 |
from django.db import models |
18 | 20 |
from django.utils.text import slugify |
19 | 21 |
from django.utils.translation import ugettext_lazy as _ |
20 | 22 | |
21 |
from lingo.utils.misc import clean_import_data, generate_slug |
|
23 |
from lingo.utils.misc import AgendaImportError, clean_import_data, generate_slug
|
|
22 | 24 | |
23 | 25 | |
24 | 26 |
class Agenda(models.Model): |
... | ... | |
37 | 39 |
def base_slug(self): |
38 | 40 |
return slugify(self.label) |
39 | 41 | |
42 |
def export_json(self): |
|
43 |
return { |
|
44 |
'slug': self.slug, |
|
45 |
'pricings': [x.export_json() for x in self.agendapricing_set.all()], |
|
46 |
} |
|
47 | ||
48 |
@classmethod |
|
49 |
def import_json(cls, data, overwrite=False): |
|
50 |
from lingo.pricing.models import AgendaPricing, Pricing |
|
51 | ||
52 |
data = copy.deepcopy(data) |
|
53 |
try: |
|
54 |
agenda = Agenda.objects.get(slug=data['slug']) |
|
55 |
except Agenda.DoesNotExist: |
|
56 |
raise AgendaImportError(_('Missing "%s" agenda') % data['slug']) |
|
57 |
pricings = data.pop('pricings', None) or [] |
|
58 |
for pricing_data in pricings: |
|
59 |
try: |
|
60 |
pricing_data['pricing'] = Pricing.objects.get(slug=pricing_data['pricing']) |
|
61 |
except Pricing.DoesNotExist: |
|
62 |
raise AgendaImportError(_('Missing "%s" pricing model') % pricing_data['pricing']) |
|
63 | ||
64 |
for pricing_data in pricings: |
|
65 |
pricing_data['agenda'] = agenda |
|
66 |
AgendaPricing.import_json(pricing_data) |
|
67 | ||
68 |
return agenda, False |
|
69 | ||
40 | 70 |
def can_be_managed(self, user): |
41 | 71 |
return True |
42 | 72 |
lingo/pricing/forms.py | ||
---|---|---|
22 | 22 |
from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory |
23 | 23 | |
24 | 24 | |
25 |
class ExportForm(forms.Form): |
|
26 |
agendas = forms.BooleanField(label=_('Agendas'), required=False, initial=True) |
|
27 |
pricing_categories = forms.BooleanField( |
|
28 |
label=_('Pricing criteria categories'), required=False, initial=True |
|
29 |
) |
|
30 |
pricing_models = forms.BooleanField(label=_('Pricing models'), required=False, initial=True) |
|
31 | ||
32 | ||
33 |
class ImportForm(forms.Form): |
|
34 |
config_json = forms.FileField(label=_('Export File')) |
|
35 | ||
36 | ||
25 | 37 |
class NewCriteriaForm(forms.ModelForm): |
26 | 38 |
class Meta: |
27 | 39 |
model = Criteria |
lingo/pricing/management/commands/export_site.py | ||
---|---|---|
1 |
# lingo - payment and billing 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 json |
|
18 |
import sys |
|
19 | ||
20 |
from django.core.management.base import BaseCommand |
|
21 | ||
22 |
from lingo.pricing.utils import export_site |
|
23 | ||
24 | ||
25 |
class Command(BaseCommand): |
|
26 |
help = 'Export the site' |
|
27 | ||
28 |
def add_arguments(self, parser): |
|
29 |
parser.add_argument( |
|
30 |
'--output', metavar='FILE', default=None, help='name of a file to write output to' |
|
31 |
) |
|
32 | ||
33 |
def handle(self, *args, **options): |
|
34 |
if options['output']: |
|
35 |
with open(options['output'], 'w') as output: |
|
36 |
json.dump(export_site(), output, indent=4) |
|
37 |
else: |
|
38 |
output = sys.stdout |
|
39 |
json.dump(export_site(), output, indent=4) |
lingo/pricing/management/commands/import_site.py | ||
---|---|---|
1 |
# lingo - payment and billing 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 json |
|
18 |
import sys |
|
19 | ||
20 |
from django.core.management.base import BaseCommand, CommandError |
|
21 | ||
22 |
from lingo.pricing.utils import import_site |
|
23 |
from lingo.utils.misc import AgendaImportError |
|
24 | ||
25 | ||
26 |
class Command(BaseCommand): |
|
27 |
help = 'Import an exported site' |
|
28 | ||
29 |
def add_arguments(self, parser): |
|
30 |
parser.add_argument('filename', metavar='FILENAME', type=str, help='name of file to import') |
|
31 |
parser.add_argument('--clean', action='store_true', default=False, help='Clean site before importing') |
|
32 |
parser.add_argument( |
|
33 |
'--if-empty', action='store_true', default=False, help='Import only if site is empty' |
|
34 |
) |
|
35 |
parser.add_argument('--overwrite', action='store_true', default=False, help='Overwrite existing data') |
|
36 | ||
37 |
def handle(self, filename, **options): |
|
38 |
def do_import(fd): |
|
39 |
try: |
|
40 |
import_site( |
|
41 |
json.load(fd), |
|
42 |
if_empty=options['if_empty'], |
|
43 |
clean=options['clean'], |
|
44 |
overwrite=options['overwrite'], |
|
45 |
) |
|
46 |
except AgendaImportError as exc: |
|
47 |
raise CommandError('%s' % exc) |
|
48 | ||
49 |
if filename == '-': |
|
50 |
fd = sys.stdin |
|
51 |
do_import(fd) |
|
52 |
else: |
|
53 |
with open(filename) as fd: |
|
54 |
do_import(fd) |
lingo/pricing/templates/lingo/pricing/export.html | ||
---|---|---|
1 |
{% extends "lingo/pricing/manager_pricing_list.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'lingo-manager-config-export' %}">{% trans 'Export' %}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2>{% trans "Export" %}</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 "Export" %}</button> |
|
19 |
<a class="cancel" href="{% url 'lingo-manager-pricing-list' %}">{% trans 'Cancel' %}</a> |
|
20 |
</div> |
|
21 |
</form> |
|
22 |
{% endblock %} |
lingo/pricing/templates/lingo/pricing/import.html | ||
---|---|---|
1 |
{% extends "lingo/pricing/manager_pricing_list.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'lingo-manager-config-import' %}">{% trans 'Import' %}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2>{% trans "Import" %}</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 "Import" %}</button> |
|
19 |
<a class="cancel" href="{% url 'lingo-manager-pricing-list' %}">{% trans 'Cancel' %}</a> |
|
20 |
</div> |
|
21 |
</form> |
|
22 |
{% endblock %} |
lingo/pricing/templates/lingo/pricing/manager_pricing_list.html | ||
---|---|---|
9 | 9 |
{% block appbar %} |
10 | 10 |
<h2>{% trans 'Pricing' context 'pricing' %}</h2> |
11 | 11 |
<span class="actions"> |
12 |
<a href="{% url 'lingo-manager-pricing-criteria-list' %}">{% trans 'Criterias' %}</a> |
|
13 |
<a rel="popup" href="{% url 'lingo-manager-pricing-add' %}">{% trans 'New pricing model' %}</a> |
|
12 |
<a class="extra-actions-menu-opener"></a> |
|
13 |
<ul class="extra-actions-menu"> |
|
14 |
<li><a rel="popup" href="{% url 'lingo-manager-config-import' %}">{% trans 'Import' %}</a></li> |
|
15 |
<li><a rel="popup" href="{% url 'lingo-manager-config-export' %}" data-autoclose-dialog="true">{% trans 'Export' %}</a></li> |
|
16 |
</ul> |
|
17 |
<a href="{% url 'lingo-manager-pricing-criteria-list' %}">{% trans 'Criterias' %}</a> |
|
18 |
<a rel="popup" href="{% url 'lingo-manager-pricing-add' %}">{% trans 'New pricing model' %}</a> |
|
14 | 19 |
</span> |
15 | 20 |
{% endblock %} |
16 | 21 |
lingo/pricing/urls.py | ||
---|---|---|
20 | 20 | |
21 | 21 |
urlpatterns = [ |
22 | 22 |
url(r'^$', views.pricing_list, name='lingo-manager-pricing-list'), |
23 |
url(r'^import/$', views.config_import, name='lingo-manager-config-import'), |
|
24 |
url(r'^export/$', views.config_export, name='lingo-manager-config-export'), |
|
23 | 25 |
url( |
24 | 26 |
r'^add/$', |
25 | 27 |
views.pricing_add, |
lingo/pricing/utils.py | ||
---|---|---|
1 |
# lingo - payment and billing 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 collections |
|
18 | ||
19 |
from django.db import transaction |
|
20 | ||
21 |
from lingo.agendas.models import Agenda |
|
22 |
from lingo.pricing.models import AgendaPricing, CriteriaCategory, Pricing |
|
23 | ||
24 | ||
25 |
def export_site( |
|
26 |
agendas=True, |
|
27 |
pricing_categories=True, |
|
28 |
pricing_models=True, |
|
29 |
): |
|
30 |
'''Dump site objects to JSON-dumpable dictionnary''' |
|
31 |
data = collections.OrderedDict() |
|
32 |
if pricing_models: |
|
33 |
data['pricing_models'] = [x.export_json() for x in Pricing.objects.all()] |
|
34 |
if pricing_categories: |
|
35 |
data['pricing_categories'] = [x.export_json() for x in CriteriaCategory.objects.all()] |
|
36 |
if agendas: |
|
37 |
data['agendas'] = [x.export_json() for x in Agenda.objects.all()] |
|
38 |
return data |
|
39 | ||
40 | ||
41 |
def import_site(data, if_empty=False, clean=False, overwrite=False): |
|
42 |
if if_empty and ( |
|
43 |
AgendaPricing.objects.exists() or CriteriaCategory.objects.exists() or Pricing.objects.exists() |
|
44 |
): |
|
45 |
return |
|
46 | ||
47 |
if clean: |
|
48 |
AgendaPricing.objects.all().delete() |
|
49 |
CriteriaCategory.objects.all().delete() |
|
50 |
Pricing.objects.all().delete() |
|
51 | ||
52 |
results = { |
|
53 |
key: collections.defaultdict(list) |
|
54 |
for key in [ |
|
55 |
'agendas', |
|
56 |
'pricing_categories', |
|
57 |
'pricing_models', |
|
58 |
] |
|
59 |
} |
|
60 | ||
61 |
with transaction.atomic(): |
|
62 |
for cls, key in ( |
|
63 |
(CriteriaCategory, 'pricing_categories'), |
|
64 |
(Pricing, 'pricing_models'), |
|
65 |
(Agenda, 'agendas'), |
|
66 |
): |
|
67 |
objs = data.get(key, []) |
|
68 |
for obj in objs: |
|
69 |
created, obj = cls.import_json(obj, overwrite=overwrite) |
|
70 |
results[key]['all'].append(obj) |
|
71 |
if created: |
|
72 |
results[key]['created'].append(obj) |
|
73 |
else: |
|
74 |
results[key]['updated'].append(obj) |
|
75 |
return results |
lingo/pricing/views.py | ||
---|---|---|
20 | 20 |
from operator import itemgetter |
21 | 21 | |
22 | 22 |
from django import forms |
23 |
from django.contrib import messages |
|
23 | 24 |
from django.core.exceptions import PermissionDenied |
24 | 25 |
from django.db.models import Prefetch |
25 | 26 |
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect |
26 | 27 |
from django.shortcuts import get_object_or_404 |
27 |
from django.urls import reverse |
|
28 |
from django.urls import reverse, reverse_lazy |
|
29 |
from django.utils.encoding import force_text |
|
30 |
from django.utils.translation import ugettext_lazy as _ |
|
31 |
from django.utils.translation import ungettext |
|
28 | 32 |
from django.views.generic import CreateView, DeleteView, DetailView, FormView, ListView, UpdateView |
29 | 33 |
from django.views.generic.detail import SingleObjectMixin |
30 | 34 | |
... | ... | |
33 | 37 |
from lingo.pricing.forms import ( |
34 | 38 |
AgendaPricingForm, |
35 | 39 |
CriteriaForm, |
40 |
ExportForm, |
|
41 |
ImportForm, |
|
36 | 42 |
NewCriteriaForm, |
37 | 43 |
PricingCriteriaCategoryAddForm, |
38 | 44 |
PricingCriteriaCategoryEditForm, |
... | ... | |
41 | 47 |
PricingVariableFormSet, |
42 | 48 |
) |
43 | 49 |
from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingCriteriaCategory |
50 |
from lingo.pricing.utils import export_site, import_site |
|
51 |
from lingo.utils.misc import AgendaImportError |
|
52 | ||
53 | ||
54 |
class ConfigExportView(FormView): |
|
55 |
form_class = ExportForm |
|
56 |
template_name = 'lingo/pricing/export.html' |
|
57 | ||
58 |
def dispatch(self, request, *args, **kwargs): |
|
59 |
if not request.user.is_staff: |
|
60 |
raise PermissionDenied() |
|
61 |
return super().dispatch(request, *args, **kwargs) |
|
62 | ||
63 |
def form_valid(self, form): |
|
64 |
response = HttpResponse(content_type='application/json') |
|
65 |
today = datetime.date.today() |
|
66 |
response['Content-Disposition'] = 'attachment; filename="export_pricing_config_{}.json"'.format( |
|
67 |
today.strftime('%Y%m%d') |
|
68 |
) |
|
69 |
json.dump(export_site(**form.cleaned_data), response, indent=2) |
|
70 |
return response |
|
71 | ||
72 | ||
73 |
config_export = ConfigExportView.as_view() |
|
74 | ||
75 | ||
76 |
class ConfigImportView(FormView): |
|
77 |
form_class = ImportForm |
|
78 |
template_name = 'lingo/pricing/import.html' |
|
79 |
success_url = reverse_lazy('lingo-manager-pricing-list') |
|
80 | ||
81 |
def dispatch(self, request, *args, **kwargs): |
|
82 |
if not request.user.is_staff: |
|
83 |
raise PermissionDenied() |
|
84 |
return super().dispatch(request, *args, **kwargs) |
|
85 | ||
86 |
def form_valid(self, form): |
|
87 |
try: |
|
88 |
config_json = json.loads(force_text(self.request.FILES['config_json'].read())) |
|
89 |
except ValueError: |
|
90 |
form.add_error('config_json', _('File is not in the expected JSON format.')) |
|
91 |
return self.form_invalid(form) |
|
92 | ||
93 |
try: |
|
94 |
results = import_site(config_json, overwrite=False) |
|
95 |
except AgendaImportError as exc: |
|
96 |
form.add_error('config_json', '%s' % exc) |
|
97 |
return self.form_invalid(form) |
|
98 |
except KeyError as exc: |
|
99 |
form.add_error('config_json', _('Key "%s" is missing.') % exc.args[0]) |
|
100 |
return self.form_invalid(form) |
|
101 | ||
102 |
import_messages = { |
|
103 |
'agendas': { |
|
104 |
'update_noop': _('No agenda updated.'), |
|
105 |
'update': lambda x: ungettext( |
|
106 |
'An agenda has been updated.', |
|
107 |
'%(count)d agendas have been updated.', |
|
108 |
x, |
|
109 |
), |
|
110 |
}, |
|
111 |
'pricing_categories': { |
|
112 |
'create_noop': _('No pricing criteria category created.'), |
|
113 |
'create': lambda x: ungettext( |
|
114 |
'A pricing criteria category has been created.', |
|
115 |
'%(count)d pricing criteria categories have been created.', |
|
116 |
x, |
|
117 |
), |
|
118 |
'update_noop': _('No pricing criteria category updated.'), |
|
119 |
'update': lambda x: ungettext( |
|
120 |
'A pricing criteria category has been updated.', |
|
121 |
'%(count)d pricing criteria categories have been updated.', |
|
122 |
x, |
|
123 |
), |
|
124 |
}, |
|
125 |
'pricing_models': { |
|
126 |
'create_noop': _('No pricing model created.'), |
|
127 |
'create': lambda x: ungettext( |
|
128 |
'A pricing model has been created.', |
|
129 |
'%(count)d pricing models have been created.', |
|
130 |
x, |
|
131 |
), |
|
132 |
'update_noop': _('No pricing model updated.'), |
|
133 |
'update': lambda x: ungettext( |
|
134 |
'A pricing model has been updated.', |
|
135 |
'%(count)d pricing models have been updated.', |
|
136 |
x, |
|
137 |
), |
|
138 |
}, |
|
139 |
} |
|
140 | ||
141 |
global_noop = True |
|
142 |
for obj_name, obj_results in results.items(): |
|
143 |
if obj_results['all']: |
|
144 |
global_noop = False |
|
145 |
count = len(obj_results['created']) |
|
146 |
if not count: |
|
147 |
message1 = import_messages[obj_name]['create_noop'] |
|
148 |
else: |
|
149 |
message1 = import_messages[obj_name]['create'](count) % {'count': count} |
|
150 | ||
151 |
count = len(obj_results['updated']) |
|
152 |
if not count: |
|
153 |
message2 = import_messages[obj_name]['update_noop'] |
|
154 |
else: |
|
155 |
message2 = import_messages[obj_name]['update'](count) % {'count': count} |
|
156 | ||
157 |
obj_results['messages'] = "%s %s" % (message1, message2) |
|
158 | ||
159 |
pc_count, pm_count = ( |
|
160 |
len(results['pricing_categories']['all']), |
|
161 |
len(results['pricing_models']['all']), |
|
162 |
) |
|
163 |
if (pc_count, pm_count) == (1, 0): |
|
164 |
# only one criteria category imported, redirect to criteria page |
|
165 |
return HttpResponseRedirect(reverse('lingo-manager-pricing-criteria-list')) |
|
166 |
if (pc_count, pm_count) == (0, 1): |
|
167 |
# only one pricing imported, redirect to pricing page |
|
168 |
return HttpResponseRedirect( |
|
169 |
reverse( |
|
170 |
'lingo-manager-pricing-detail', |
|
171 |
kwargs={'pk': results['pricing_models']['all'][0].pk}, |
|
172 |
) |
|
173 |
) |
|
174 | ||
175 |
if global_noop: |
|
176 |
messages.info(self.request, _('No data found.')) |
|
177 |
else: |
|
178 |
messages.info(self.request, results['agendas']['messages']) |
|
179 |
messages.info(self.request, results['pricing_categories']['messages']) |
|
180 |
messages.info(self.request, results['pricing_models']['messages']) |
|
181 | ||
182 |
return super().form_valid(form) |
|
183 | ||
184 | ||
185 |
config_import = ConfigImportView.as_view() |
|
44 | 186 | |
45 | 187 | |
46 | 188 |
class PricingListView(ListView): |
tests/pricing/test_import_export.py | ||
---|---|---|
1 |
import copy |
|
2 |
import datetime |
|
3 |
import json |
|
4 |
import os |
|
5 |
import shutil |
|
6 |
import sys |
|
7 |
import tempfile |
|
8 |
from io import StringIO |
|
9 | ||
10 |
import pytest |
|
11 |
from django.core.management import call_command |
|
12 |
from django.utils.encoding import force_bytes |
|
13 | ||
14 |
from lingo.agendas.models import Agenda |
|
15 |
from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingCriteriaCategory |
|
16 |
from lingo.pricing.utils import import_site |
|
17 |
from lingo.utils.misc import AgendaImportError |
|
18 | ||
19 |
pytestmark = pytest.mark.django_db |
|
20 | ||
21 | ||
22 |
def get_output_of_command(command, *args, **kwargs): |
|
23 |
old_stdout = sys.stdout |
|
24 |
output = sys.stdout = StringIO() |
|
25 |
call_command(command, *args, **kwargs) |
|
26 |
sys.stdout = old_stdout |
|
27 |
return output.getvalue() |
|
28 | ||
29 | ||
30 |
def test_import_export(app): |
|
31 |
agenda = Agenda.objects.create(label='Foo Bar') |
|
32 |
pricing = Pricing.objects.create(label='Foo') |
|
33 |
AgendaPricing.objects.create( |
|
34 |
agenda=agenda, |
|
35 |
pricing=pricing, |
|
36 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
37 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
38 |
) |
|
39 |
CriteriaCategory.objects.create(label='Foo bar') |
|
40 | ||
41 |
output = get_output_of_command('export_site') |
|
42 |
assert len(json.loads(output)['agendas']) == 1 |
|
43 |
assert len(json.loads(output)['pricing_models']) == 1 |
|
44 |
assert len(json.loads(output)['pricing_categories']) == 1 |
|
45 |
import_site(data={}, clean=True) |
|
46 |
empty_output = get_output_of_command('export_site') |
|
47 |
assert len(json.loads(empty_output)['agendas']) == 1 |
|
48 |
assert len(json.loads(empty_output)['agendas'][0]['pricings']) == 0 |
|
49 |
assert len(json.loads(empty_output)['pricing_models']) == 0 |
|
50 |
assert len(json.loads(empty_output)['pricing_categories']) == 0 |
|
51 | ||
52 |
old_stdin = sys.stdin |
|
53 |
sys.stdin = StringIO(json.dumps({})) |
|
54 |
agenda = Agenda.objects.create(label='Foo Bar') |
|
55 |
pricing = Pricing.objects.create(label='Foo') |
|
56 |
AgendaPricing.objects.create( |
|
57 |
agenda=agenda, |
|
58 |
pricing=pricing, |
|
59 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
60 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
61 |
) |
|
62 |
CriteriaCategory.objects.create(label='Foo bar') |
|
63 |
old_stdin = sys.stdin |
|
64 |
sys.stdin = StringIO(json.dumps({})) |
|
65 |
assert AgendaPricing.objects.count() == 1 |
|
66 |
assert Pricing.objects.count() == 1 |
|
67 |
assert CriteriaCategory.objects.count() == 1 |
|
68 |
try: |
|
69 |
call_command('import_site', '-', clean=True) |
|
70 |
finally: |
|
71 |
sys.stdin = old_stdin |
|
72 |
assert AgendaPricing.objects.count() == 0 |
|
73 |
assert Pricing.objects.count() == 0 |
|
74 |
assert CriteriaCategory.objects.count() == 0 |
|
75 | ||
76 |
with tempfile.NamedTemporaryFile() as f: |
|
77 |
f.write(force_bytes(output)) |
|
78 |
f.flush() |
|
79 |
call_command('import_site', f.name) |
|
80 |
assert AgendaPricing.objects.count() == 1 |
|
81 |
assert Pricing.objects.count() == 1 |
|
82 |
assert CriteriaCategory.objects.count() == 1 |
|
83 | ||
84 |
import_site(data={}, if_empty=True) |
|
85 |
assert AgendaPricing.objects.count() == 1 |
|
86 |
assert Pricing.objects.count() == 1 |
|
87 |
assert CriteriaCategory.objects.count() == 1 |
|
88 | ||
89 |
import_site(data={}, clean=True) |
|
90 |
tempdir = tempfile.mkdtemp('lingo-test') |
|
91 |
empty_output = get_output_of_command('export_site', output=os.path.join(tempdir, 't.json')) |
|
92 |
assert os.path.exists(os.path.join(tempdir, 't.json')) |
|
93 |
shutil.rmtree(tempdir) |
|
94 | ||
95 | ||
96 |
def test_import_export_agenda_with_pricing(app): |
|
97 |
pricing = Pricing.objects.create(label='Foo') |
|
98 |
agenda = Agenda.objects.create(label='Foo Bar') |
|
99 |
agenda_pricing = AgendaPricing.objects.create( |
|
100 |
agenda=agenda, |
|
101 |
pricing=pricing, |
|
102 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
103 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
104 |
pricing_data={ |
|
105 |
'foo': 'bar', |
|
106 |
}, |
|
107 |
) |
|
108 |
output = get_output_of_command('export_site') |
|
109 | ||
110 |
import_site(data={}, clean=True) |
|
111 |
assert Pricing.objects.count() == 0 |
|
112 |
data = json.loads(output) |
|
113 |
del data['pricing_models'] |
|
114 | ||
115 |
with pytest.raises(AgendaImportError) as excinfo: |
|
116 |
import_site(data, overwrite=True) |
|
117 |
assert str(excinfo.value) == 'Missing "foo" pricing model' |
|
118 | ||
119 |
Pricing.objects.create(label='foobar') |
|
120 |
with pytest.raises(AgendaImportError) as excinfo: |
|
121 |
import_site(data, overwrite=True) |
|
122 |
assert str(excinfo.value) == 'Missing "foo" pricing model' |
|
123 | ||
124 |
Agenda.objects.all().delete() |
|
125 |
pricing = Pricing.objects.create(label='Foo') |
|
126 |
with pytest.raises(AgendaImportError) as excinfo: |
|
127 |
import_site(data, overwrite=True) |
|
128 |
assert str(excinfo.value) == 'Missing "foo-bar" agenda' |
|
129 | ||
130 |
agenda = Agenda.objects.create(label='Foo Bar') |
|
131 |
import_site(data, overwrite=True) |
|
132 |
assert agenda.agendapricing_set.count() == 1 |
|
133 |
agenda_pricing = agenda.agendapricing_set.get() |
|
134 |
assert agenda_pricing.agenda == agenda |
|
135 |
assert agenda_pricing.pricing == pricing |
|
136 |
assert agenda_pricing.date_start == datetime.date(year=2021, month=9, day=1) |
|
137 |
assert agenda_pricing.date_end == datetime.date(year=2021, month=10, day=1) |
|
138 | ||
139 |
# again |
|
140 |
import_site(data) |
|
141 |
assert agenda.agendapricing_set.count() == 1 |
|
142 |
agenda_pricing = AgendaPricing.objects.get(pk=agenda_pricing.pk) |
|
143 |
assert agenda_pricing.agenda == agenda |
|
144 |
assert agenda_pricing.pricing == pricing |
|
145 | ||
146 |
data['agendas'][0]['pricings'].append( |
|
147 |
{ |
|
148 |
'pricing': 'foo', |
|
149 |
'date_start': '2022-09-01', |
|
150 |
'date_end': '2022-10-01', |
|
151 |
'pricing_data': {'foo': 'bar'}, |
|
152 |
} |
|
153 |
) |
|
154 |
import_site(data) |
|
155 |
assert agenda.agendapricing_set.count() == 2 |
|
156 |
agenda_pricing = AgendaPricing.objects.latest('pk') |
|
157 |
assert agenda_pricing.agenda == agenda |
|
158 |
assert agenda_pricing.pricing == pricing |
|
159 |
assert agenda_pricing.date_start == datetime.date(year=2022, month=9, day=1) |
|
160 |
assert agenda_pricing.date_end == datetime.date(year=2022, month=10, day=1) |
|
161 | ||
162 | ||
163 |
def test_import_export_site_criteria_category(app): |
|
164 |
output = get_output_of_command('export_site') |
|
165 |
payload = json.loads(output) |
|
166 |
assert len(payload['pricing_categories']) == 0 |
|
167 | ||
168 |
category = CriteriaCategory.objects.create(label='Foo bar') |
|
169 |
Criteria.objects.create(label='Foo reason', category=category) |
|
170 |
Criteria.objects.create(label='Baz', category=category) |
|
171 | ||
172 |
output = get_output_of_command('export_site') |
|
173 |
payload = json.loads(output) |
|
174 |
assert len(payload['pricing_categories']) == 1 |
|
175 | ||
176 |
category.delete() |
|
177 |
assert not CriteriaCategory.objects.exists() |
|
178 |
assert not Criteria.objects.exists() |
|
179 | ||
180 |
import_site(copy.deepcopy(payload)) |
|
181 |
assert CriteriaCategory.objects.count() == 1 |
|
182 |
category = CriteriaCategory.objects.first() |
|
183 |
assert category.label == 'Foo bar' |
|
184 |
assert category.slug == 'foo-bar' |
|
185 |
assert category.criterias.count() == 2 |
|
186 |
assert Criteria.objects.get(category=category, label='Foo reason', slug='foo-reason') |
|
187 |
assert Criteria.objects.get(category=category, label='Baz', slug='baz') |
|
188 | ||
189 |
# update |
|
190 |
update_payload = copy.deepcopy(payload) |
|
191 |
update_payload['pricing_categories'][0]['label'] = 'Foo bar Updated' |
|
192 |
import_site(update_payload) |
|
193 |
category.refresh_from_db() |
|
194 |
assert category.label == 'Foo bar Updated' |
|
195 | ||
196 |
# insert another category |
|
197 |
category.slug = 'foo-bar-updated' |
|
198 |
category.save() |
|
199 |
import_site(copy.deepcopy(payload)) |
|
200 |
assert CriteriaCategory.objects.count() == 2 |
|
201 |
category = CriteriaCategory.objects.latest('pk') |
|
202 |
assert category.label == 'Foo bar' |
|
203 |
assert category.slug == 'foo-bar' |
|
204 |
assert category.criterias.count() == 2 |
|
205 |
assert Criteria.objects.get(category=category, label='Foo reason', slug='foo-reason') |
|
206 |
assert Criteria.objects.get(category=category, label='Baz', slug='baz') |
|
207 | ||
208 |
# with overwrite |
|
209 |
Criteria.objects.create(category=category, label='Baz2') |
|
210 |
import_site(copy.deepcopy(payload), overwrite=True) |
|
211 |
assert CriteriaCategory.objects.count() == 2 |
|
212 |
category = CriteriaCategory.objects.latest('pk') |
|
213 |
assert category.label == 'Foo bar' |
|
214 |
assert category.slug == 'foo-bar' |
|
215 |
assert category.criterias.count() == 2 |
|
216 |
assert Criteria.objects.get(category=category, label='Foo reason', slug='foo-reason') |
|
217 |
assert Criteria.objects.get(category=category, label='Baz', slug='baz') |
|
218 | ||
219 | ||
220 |
def test_import_export_site(app): |
|
221 |
output = get_output_of_command('export_site') |
|
222 |
payload = json.loads(output) |
|
223 |
assert len(payload['pricing_models']) == 0 |
|
224 | ||
225 |
pricing = Pricing.objects.create(label='Foo bar', extra_variables={'foo': 'bar'}) |
|
226 | ||
227 |
output = get_output_of_command('export_site') |
|
228 |
payload = json.loads(output) |
|
229 |
assert len(payload['pricing_models']) == 1 |
|
230 | ||
231 |
pricing.delete() |
|
232 |
assert not Pricing.objects.exists() |
|
233 | ||
234 |
import_site(copy.deepcopy(payload)) |
|
235 |
assert Pricing.objects.count() == 1 |
|
236 |
pricing = Pricing.objects.first() |
|
237 |
assert pricing.label == 'Foo bar' |
|
238 |
assert pricing.slug == 'foo-bar' |
|
239 |
assert pricing.extra_variables == {'foo': 'bar'} |
|
240 | ||
241 |
# update |
|
242 |
update_payload = copy.deepcopy(payload) |
|
243 |
update_payload['pricing_models'][0]['label'] = 'Foo bar Updated' |
|
244 |
import_site(update_payload) |
|
245 |
pricing.refresh_from_db() |
|
246 |
assert pricing.label == 'Foo bar Updated' |
|
247 | ||
248 |
# insert another pricing |
|
249 |
pricing.slug = 'foo-bar-updated' |
|
250 |
pricing.save() |
|
251 |
import_site(copy.deepcopy(payload)) |
|
252 |
assert Pricing.objects.count() == 2 |
|
253 |
pricing = Pricing.objects.latest('pk') |
|
254 |
assert pricing.label == 'Foo bar' |
|
255 |
assert pricing.slug == 'foo-bar' |
|
256 |
assert pricing.extra_variables == {'foo': 'bar'} |
|
257 | ||
258 | ||
259 |
def test_import_export_site_with_categories(app): |
|
260 |
pricing = Pricing.objects.create(label='Foo bar') |
|
261 |
category = CriteriaCategory.objects.create(label='Foo bar') |
|
262 |
pricing.categories.add(category, through_defaults={'order': 42}) |
|
263 | ||
264 |
output = get_output_of_command('export_site') |
|
265 | ||
266 |
import_site(data={}, clean=True) |
|
267 |
assert Pricing.objects.count() == 0 |
|
268 |
assert CriteriaCategory.objects.count() == 0 |
|
269 |
data = json.loads(output) |
|
270 |
del data['pricing_categories'] |
|
271 | ||
272 |
with pytest.raises(AgendaImportError) as excinfo: |
|
273 |
import_site(data, overwrite=True) |
|
274 |
assert str(excinfo.value) == 'Missing "foo-bar" pricing category' |
|
275 | ||
276 |
CriteriaCategory.objects.create(label='Foobar') |
|
277 |
with pytest.raises(AgendaImportError) as excinfo: |
|
278 |
import_site(data, overwrite=True) |
|
279 |
assert str(excinfo.value) == 'Missing "foo-bar" pricing category' |
|
280 | ||
281 |
category = CriteriaCategory.objects.create(label='Foo bar') |
|
282 |
import_site(data, overwrite=True) |
|
283 |
pricing = Pricing.objects.get(slug=pricing.slug) |
|
284 |
assert list(pricing.categories.all()) == [category] |
|
285 |
assert PricingCriteriaCategory.objects.first().order == 42 |
|
286 | ||
287 |
category2 = CriteriaCategory.objects.create(label='Foo bar 2') |
|
288 |
category3 = CriteriaCategory.objects.create(label='Foo bar 3') |
|
289 |
pricing.categories.add(category2, through_defaults={'order': 1}) |
|
290 |
output = get_output_of_command('export_site') |
|
291 |
data = json.loads(output) |
|
292 |
del data['pricing_categories'] |
|
293 |
data['pricing_models'][0]['categories'] = [ |
|
294 |
{ |
|
295 |
'category': 'foo-bar-3', |
|
296 |
'order': 1, |
|
297 |
'criterias': [], |
|
298 |
}, |
|
299 |
{ |
|
300 |
'category': 'foo-bar', |
|
301 |
'order': 35, |
|
302 |
'criterias': [], |
|
303 |
}, |
|
304 |
] |
|
305 |
import_site(data, overwrite=True) |
|
306 |
assert list(pricing.categories.all()) == [category, category3] |
|
307 |
assert list( |
|
308 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) |
|
309 |
) == [category3.pk, category.pk] |
|
310 |
assert list(PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('order', flat=True)) == [ |
|
311 |
1, |
|
312 |
35, |
|
313 |
] |
|
314 |
assert list(pricing.criterias.all()) == [] |
|
315 | ||
316 |
criteria1 = Criteria.objects.create(label='Crit 1', category=category) |
|
317 |
Criteria.objects.create(label='Crit 2', category=category) |
|
318 |
criteria3 = Criteria.objects.create(label='Crit 3', category=category) |
|
319 | ||
320 |
# unknown criteria |
|
321 |
data['pricing_models'][0]['categories'] = [ |
|
322 |
{ |
|
323 |
'category': 'foo-bar-3', |
|
324 |
'order': 1, |
|
325 |
'criterias': ['unknown'], |
|
326 |
}, |
|
327 |
{ |
|
328 |
'category': 'foo-bar', |
|
329 |
'order': 35, |
|
330 |
'criterias': [], |
|
331 |
}, |
|
332 |
] |
|
333 |
with pytest.raises(AgendaImportError) as excinfo: |
|
334 |
import_site(data, overwrite=True) |
|
335 |
assert str(excinfo.value) == 'Missing "unknown" pricing criteria for "foo-bar-3" category' |
|
336 | ||
337 |
# wrong criteria (from another category) |
|
338 |
data['pricing_models'][0]['categories'] = [ |
|
339 |
{ |
|
340 |
'category': 'foo-bar-3', |
|
341 |
'order': 1, |
|
342 |
'criterias': ['crit-1'], |
|
343 |
}, |
|
344 |
{ |
|
345 |
'category': 'foo-bar', |
|
346 |
'order': 35, |
|
347 |
'criterias': [], |
|
348 |
}, |
|
349 |
] |
|
350 |
with pytest.raises(AgendaImportError) as excinfo: |
|
351 |
import_site(data, overwrite=True) |
|
352 |
assert str(excinfo.value) == 'Missing "crit-1" pricing criteria for "foo-bar-3" category' |
|
353 | ||
354 |
data['pricing_models'][0]['categories'] = [ |
|
355 |
{ |
|
356 |
'category': 'foo-bar-3', |
|
357 |
'order': 1, |
|
358 |
'criterias': [], |
|
359 |
}, |
|
360 |
{ |
|
361 |
'category': 'foo-bar', |
|
362 |
'order': 35, |
|
363 |
'criterias': ['crit-1', 'crit-3'], |
|
364 |
}, |
|
365 |
] |
|
366 |
import_site(data, overwrite=True) |
|
367 |
assert list(pricing.categories.all()) == [category, category3] |
|
368 |
assert list( |
|
369 |
PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) |
|
370 |
) == [category3.pk, category.pk] |
|
371 |
assert list(PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('order', flat=True)) == [ |
|
372 |
1, |
|
373 |
35, |
|
374 |
] |
|
375 |
assert set(pricing.criterias.all()) == {criteria1, criteria3} |
tests/pricing/test_manager.py | ||
---|---|---|
20 | 20 |
return agenda |
21 | 21 | |
22 | 22 | |
23 |
def test_export_site(settings, freezer, app, admin_user): |
|
24 |
freezer.move_to('2020-06-15') |
|
25 |
login(app) |
|
26 |
resp = app.get('/manage/pricing/') |
|
27 |
resp = resp.click('Export') |
|
28 | ||
29 |
resp = resp.form.submit() |
|
30 |
assert resp.headers['content-type'] == 'application/json' |
|
31 |
assert resp.headers['content-disposition'] == 'attachment; filename="export_pricing_config_20200615.json"' |
|
32 | ||
33 |
site_json = json.loads(resp.text) |
|
34 |
assert site_json == { |
|
35 |
'agendas': [], |
|
36 |
'pricing_categories': [], |
|
37 |
'pricing_models': [], |
|
38 |
} |
|
39 | ||
40 |
Agenda.objects.create(label='Foo Bar') |
|
41 |
resp = app.get('/manage/pricing/export/') |
|
42 |
resp = resp.form.submit() |
|
43 | ||
44 |
site_json = json.loads(resp.text) |
|
45 |
assert len(site_json['agendas']) == 1 |
|
46 | ||
47 |
resp = app.get('/manage/pricing/export/') |
|
48 |
resp.form['agendas'] = False |
|
49 |
resp.form['pricing_categories'] = False |
|
50 |
resp.form['pricing_models'] = False |
|
51 |
resp = resp.form.submit() |
|
52 | ||
53 |
site_json = json.loads(resp.text) |
|
54 |
assert 'agendas' not in site_json |
|
55 |
assert 'pricing_categories' not in site_json |
|
56 |
assert 'pricing_models' not in site_json |
|
57 | ||
58 | ||
23 | 59 |
@pytest.mark.xfail(reason='/manage/ limited to admin') |
24 | 60 |
def test_list_pricings_as_manager(app, manager_user, agenda_with_restrictions): |
25 | 61 |
app = login(app, username='manager', password='manager') |
... | ... | |
144 | 180 |
app.get('/manage/pricing/%s/duplicate/' % pricing.pk, status=403) |
145 | 181 | |
146 | 182 | |
147 |
@pytest.mark.xfail(reason='import not yet implemented') |
|
148 | 183 |
@pytest.mark.freeze_time('2021-07-08') |
149 | 184 |
def test_import_pricing(app, admin_user): |
150 | 185 |
pricing = Pricing.objects.create(label='Model') |
... | ... | |
156 | 191 |
pricing_export = resp.text |
157 | 192 | |
158 | 193 |
# existing pricing |
159 |
resp = app.get('/manage/', status=200) |
|
194 |
resp = app.get('/manage/pricing/', status=200)
|
|
160 | 195 |
resp = resp.click('Import') |
161 |
resp.form['agendas_json'] = Upload('export.json', pricing_export.encode('utf-8'), 'application/json')
|
|
196 |
resp.form['config_json'] = Upload('export.json', pricing_export.encode('utf-8'), 'application/json')
|
|
162 | 197 |
resp = resp.form.submit() |
163 | 198 |
assert resp.location.endswith('/manage/pricing/%s/' % pricing.pk) |
164 | 199 |
resp = resp.follow() |
... | ... | |
167 | 202 | |
168 | 203 |
# new pricing |
169 | 204 |
Pricing.objects.all().delete() |
170 |
resp = app.get('/manage/', status=200) |
|
205 |
resp = app.get('/manage/pricing/', status=200)
|
|
171 | 206 |
resp = resp.click('Import') |
172 |
resp.form['agendas_json'] = Upload('export.json', pricing_export.encode('utf-8'), 'application/json')
|
|
207 |
resp.form['config_json'] = Upload('export.json', pricing_export.encode('utf-8'), 'application/json')
|
|
173 | 208 |
resp = resp.form.submit() |
174 | 209 |
pricing = Pricing.objects.latest('pk') |
175 | 210 |
assert resp.location.endswith('/manage/pricing/%s/' % pricing.pk) |
... | ... | |
186 | 221 |
pricings['pricing_models'][2]['label'] = 'Foo bar 3' |
187 | 222 |
pricings['pricing_models'][2]['slug'] = 'foo-bar-3' |
188 | 223 | |
189 |
resp = app.get('/manage/', status=200) |
|
224 |
resp = app.get('/manage/pricing/', status=200)
|
|
190 | 225 |
resp = resp.click('Import') |
191 |
resp.form['agendas_json'] = Upload( |
|
192 |
'export.json', json.dumps(pricings).encode('utf-8'), 'application/json' |
|
193 |
) |
|
226 |
resp.form['config_json'] = Upload('export.json', json.dumps(pricings).encode('utf-8'), 'application/json') |
|
194 | 227 |
resp = resp.form.submit() |
195 |
assert resp.location.endswith('/manage/') |
|
228 |
assert resp.location.endswith('/manage/pricing/')
|
|
196 | 229 |
resp = resp.follow() |
197 | 230 |
assert '2 pricing models have been created. A pricing model has been updated.' in resp.text |
198 | 231 |
assert Pricing.objects.count() == 3 |
199 | 232 | |
200 | 233 |
Pricing.objects.all().delete() |
201 |
resp = app.get('/manage/', status=200) |
|
234 |
resp = app.get('/manage/pricing/', status=200)
|
|
202 | 235 |
resp = resp.click('Import') |
203 |
resp.form['agendas_json'] = Upload( |
|
204 |
'export.json', json.dumps(pricings).encode('utf-8'), 'application/json' |
|
205 |
) |
|
236 |
resp.form['config_json'] = Upload('export.json', json.dumps(pricings).encode('utf-8'), 'application/json') |
|
206 | 237 |
resp = resp.form.submit().follow() |
207 | 238 |
assert '3 pricing models have been created. No pricing model updated.' in resp.text |
208 | 239 |
assert Pricing.objects.count() == 3 |
... | ... | |
734 | 765 |
app.get('/manage/pricing/criteria/category/%s/order/' % (category.pk), status=403) |
735 | 766 | |
736 | 767 | |
737 |
@pytest.mark.xfail(reason='import not yet implemented') |
|
738 | 768 |
@pytest.mark.freeze_time('2021-07-08') |
739 | 769 |
def test_import_criteria_category(app, admin_user): |
740 | 770 |
category = CriteriaCategory.objects.create(label='Foo bar') |
... | ... | |
751 | 781 |
category_export = resp.text |
752 | 782 | |
753 | 783 |
# existing category |
754 |
resp = app.get('/manage/', status=200) |
|
784 |
resp = app.get('/manage/pricing/', status=200)
|
|
755 | 785 |
resp = resp.click('Import') |
756 |
resp.form['agendas_json'] = Upload('export.json', category_export.encode('utf-8'), 'application/json')
|
|
786 |
resp.form['config_json'] = Upload('export.json', category_export.encode('utf-8'), 'application/json')
|
|
757 | 787 |
resp = resp.form.submit() |
758 | 788 |
assert resp.location.endswith('/manage/pricing/criterias/') |
759 | 789 |
resp = resp.follow() |
... | ... | |
765 | 795 | |
766 | 796 |
# new category |
767 | 797 |
CriteriaCategory.objects.all().delete() |
768 |
resp = app.get('/manage/', status=200) |
|
798 |
resp = app.get('/manage/pricing/', status=200)
|
|
769 | 799 |
resp = resp.click('Import') |
770 |
resp.form['agendas_json'] = Upload('export.json', category_export.encode('utf-8'), 'application/json')
|
|
800 |
resp.form['config_json'] = Upload('export.json', category_export.encode('utf-8'), 'application/json')
|
|
771 | 801 |
resp = resp.form.submit() |
772 | 802 |
assert resp.location.endswith('/manage/pricing/criterias/') |
773 | 803 |
resp = resp.follow() |
... | ... | |
786 | 816 |
categories['pricing_categories'][2]['label'] = 'Foo bar 3' |
787 | 817 |
categories['pricing_categories'][2]['slug'] = 'foo-bar-3' |
788 | 818 | |
789 |
resp = app.get('/manage/', status=200) |
|
819 |
resp = app.get('/manage/pricing/', status=200)
|
|
790 | 820 |
resp = resp.click('Import') |
791 |
resp.form['agendas_json'] = Upload(
|
|
821 |
resp.form['config_json'] = Upload(
|
|
792 | 822 |
'export.json', json.dumps(categories).encode('utf-8'), 'application/json' |
793 | 823 |
) |
794 | 824 |
resp = resp.form.submit() |
795 |
assert resp.location.endswith('/manage/') |
|
825 |
assert resp.location.endswith('/manage/pricing/')
|
|
796 | 826 |
resp = resp.follow() |
797 | 827 |
assert ( |
798 | 828 |
'2 pricing criteria categories have been created. A pricing criteria category has been updated.' |
... | ... | |
802 | 832 |
assert Criteria.objects.count() == 6 |
803 | 833 | |
804 | 834 |
CriteriaCategory.objects.all().delete() |
805 |
resp = app.get('/manage/', status=200) |
|
835 |
resp = app.get('/manage/pricing/', status=200)
|
|
806 | 836 |
resp = resp.click('Import') |
807 |
resp.form['agendas_json'] = Upload(
|
|
837 |
resp.form['config_json'] = Upload(
|
|
808 | 838 |
'export.json', json.dumps(categories).encode('utf-8'), 'application/json' |
809 | 839 |
) |
810 | 840 |
resp = resp.form.submit().follow() |
811 |
- |