diff --git a/lingo/agendas/models.py b/lingo/agendas/models.py index 68fc822..e4918d1 100644 --- a/lingo/agendas/models.py +++ b/lingo/agendas/models.py @@ -66,7 +66,7 @@ class Agenda(models.Model): } @classmethod - def import_json(cls, data, overwrite=False): + def import_json(cls, data): data = copy.deepcopy(data) try: agenda = Agenda.objects.get(slug=data['slug']) @@ -111,14 +111,11 @@ class CheckTypeGroup(models.Model): return slugify(self.label) @classmethod - def import_json(cls, data, overwrite=False): + def import_json(cls, data): check_types = data.pop('check_types', []) data = clean_import_data(cls, data) group, created = cls.objects.update_or_create(slug=data['slug'], defaults=data) - if overwrite: - CheckType.objects.filter(group=group).delete() - for check_type in check_types: check_type['group'] = group CheckType.import_json(check_type) diff --git a/lingo/invoicing/utils.py b/lingo/invoicing/utils.py index 5e27003..a2d3851 100644 --- a/lingo/invoicing/utils.py +++ b/lingo/invoicing/utils.py @@ -317,19 +317,13 @@ def export_site( regies=True, ): '''Dump site objects to JSON-dumpable dictionnary''' - data = collections.OrderedDict() + data = {} if regies: data['regies'] = [x.export_json() for x in Regie.objects.all()] return data -def import_site(data, if_empty=False, clean=False): - if if_empty and (Regie.objects.exists()): - return - - if clean: - Regie.objects.all().delete() - +def import_site(data): results = { key: collections.defaultdict(list) for key in [ diff --git a/lingo/pricing/management/commands/__init__.py b/lingo/pricing/management/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lingo/pricing/management/commands/export_pricing_config.py b/lingo/pricing/management/commands/export_pricing_config.py deleted file mode 100644 index 21d4b8d..0000000 --- a/lingo/pricing/management/commands/export_pricing_config.py +++ /dev/null @@ -1,39 +0,0 @@ -# lingo - payment and billing system -# Copyright (C) 2022 Entr'ouvert -# -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU Affero General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import json -import sys - -from django.core.management.base import BaseCommand - -from lingo.pricing.utils import export_site - - -class Command(BaseCommand): - help = 'Export the site' - - def add_arguments(self, parser): - parser.add_argument( - '--output', metavar='FILE', default=None, help='name of a file to write output to' - ) - - def handle(self, *args, **options): - if options['output']: - with open(options['output'], 'w') as output: - json.dump(export_site(), output, indent=4) - else: - output = sys.stdout - json.dump(export_site(), output, indent=4) diff --git a/lingo/pricing/management/commands/import_pricing_config.py b/lingo/pricing/management/commands/import_pricing_config.py deleted file mode 100644 index 4b4f00e..0000000 --- a/lingo/pricing/management/commands/import_pricing_config.py +++ /dev/null @@ -1,54 +0,0 @@ -# lingo - payment and billing system -# Copyright (C) 2022 Entr'ouvert -# -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU Affero General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import json -import sys - -from django.core.management.base import BaseCommand, CommandError - -from lingo.pricing.utils import import_site -from lingo.utils.misc import LingoImportError - - -class Command(BaseCommand): - help = 'Import an exported site' - - def add_arguments(self, parser): - parser.add_argument('filename', metavar='FILENAME', type=str, help='name of file to import') - parser.add_argument('--clean', action='store_true', default=False, help='Clean site before importing') - parser.add_argument( - '--if-empty', action='store_true', default=False, help='Import only if site is empty' - ) - parser.add_argument('--overwrite', action='store_true', default=False, help='Overwrite existing data') - - def handle(self, filename, **options): - def do_import(fd): - try: - import_site( - json.load(fd), - if_empty=options['if_empty'], - clean=options['clean'], - overwrite=options['overwrite'], - ) - except LingoImportError as exc: - raise CommandError('%s' % exc) - - if filename == '-': - fd = sys.stdin - do_import(fd) - else: - with open(filename) as fd: - do_import(fd) diff --git a/lingo/pricing/models.py b/lingo/pricing/models.py index f6dac8c..6f49366 100644 --- a/lingo/pricing/models.py +++ b/lingo/pricing/models.py @@ -102,14 +102,11 @@ class CriteriaCategory(models.Model): return slugify(self.label) @classmethod - def import_json(cls, data, overwrite=False): + def import_json(cls, data): criterias = data.pop('criterias', []) data = clean_import_data(cls, data) category, created = cls.objects.update_or_create(slug=data['slug'], defaults=data) - if overwrite: - Criteria.objects.filter(category=category).delete() - for criteria in criterias: criteria['category'] = category Criteria.import_json(criteria) @@ -297,7 +294,7 @@ class Pricing(models.Model): return result @classmethod - def import_json(cls, data, overwrite=False): + def import_json(cls, data): data = data.copy() categories = data.pop('categories', []) categories_by_slug = {c.slug: c for c in CriteriaCategory.objects.all()} @@ -446,7 +443,7 @@ class AgendaPricing(models.Model): } @classmethod - def import_json(cls, data, overwrite=False): + def import_json(cls, data): data = copy.deepcopy(data) agenda_slugs = data.pop('agendas', None) or [] billing_dates = data.pop('billing_dates', None) or [] @@ -463,12 +460,8 @@ class AgendaPricing(models.Model): raise LingoImportError(_('Missing "%s" pricing model') % data['pricing']) agenda_pricing, created = cls.objects.update_or_create(slug=data['slug'], defaults=data) - if overwrite and not created: - agenda_pricing.agendas.clear() agenda_pricing.agendas.add(*agendas) - if overwrite and not created: - agenda_pricing.billingdates.all().delete() for billing_date in billing_dates: billing_date['agenda_pricing'] = agenda_pricing BillingDate.import_json(billing_date) diff --git a/lingo/pricing/utils.py b/lingo/pricing/utils.py index 7312a81..b27397c 100644 --- a/lingo/pricing/utils.py +++ b/lingo/pricing/utils.py @@ -30,7 +30,7 @@ def export_site( pricings=True, ): '''Dump site objects to JSON-dumpable dictionnary''' - data = collections.OrderedDict() + data = {} if pricings: data['pricings'] = [x.export_json() for x in AgendaPricing.objects.all()] if pricing_models: @@ -44,21 +44,7 @@ def export_site( return data -def import_site(data, if_empty=False, clean=False, overwrite=False): - if if_empty and ( - AgendaPricing.objects.exists() - or CheckTypeGroup.objects.exists() - or CriteriaCategory.objects.exists() - or Pricing.objects.exists() - ): - return - - if clean: - AgendaPricing.objects.all().delete() - CriteriaCategory.objects.all().delete() - Pricing.objects.all().delete() - CheckTypeGroup.objects.all().delete() - +def import_site(data): results = { key: collections.defaultdict(list) for key in [ @@ -80,7 +66,7 @@ def import_site(data, if_empty=False, clean=False, overwrite=False): ): objs = data.get(key, []) for obj in objs: - created, obj = cls.import_json(obj, overwrite=overwrite) + created, obj = cls.import_json(obj) results[key]['all'].append(obj) if created: results[key]['created'].append(obj) diff --git a/lingo/pricing/views.py b/lingo/pricing/views.py index 23515bd..3a67268 100644 --- a/lingo/pricing/views.py +++ b/lingo/pricing/views.py @@ -111,7 +111,7 @@ class ConfigImportView(FormView): return self.form_invalid(form) try: - results = import_site(config_json, overwrite=False) + results = import_site(config_json) except LingoImportError as exc: form.add_error('config_json', '%s' % exc) return self.form_invalid(form) diff --git a/tests/pricing/test_import_export.py b/tests/pricing/test_import_export.py index 0d872fd..f7de20e 100644 --- a/tests/pricing/test_import_export.py +++ b/tests/pricing/test_import_export.py @@ -1,15 +1,7 @@ import copy import datetime -import json -import os -import shutil -import sys -import tempfile -from io import StringIO import pytest -from django.core.management import call_command -from django.utils.encoding import force_bytes from lingo.agendas.models import Agenda, CheckType, CheckTypeGroup from lingo.pricing.models import ( @@ -20,20 +12,12 @@ from lingo.pricing.models import ( Pricing, PricingCriteriaCategory, ) -from lingo.pricing.utils import import_site +from lingo.pricing.utils import export_site, import_site from lingo.utils.misc import LingoImportError pytestmark = pytest.mark.django_db -def get_output_of_command(command, *args, **kwargs): - old_stdout = sys.stdout - output = sys.stdout = StringIO() - call_command(command, *args, **kwargs) - sys.stdout = old_stdout - return output.getvalue() - - def test_import_export(app): Agenda.objects.create(label='Foo Bar') pricing = Pricing.objects.create(label='Foo') @@ -44,59 +28,16 @@ def test_import_export(app): ) CriteriaCategory.objects.create(label='Foo bar') - output = get_output_of_command('export_pricing_config') - assert len(json.loads(output)['agendas']) == 1 - assert len(json.loads(output)['pricings']) == 1 - assert len(json.loads(output)['pricings'][0]['agendas']) == 0 - assert len(json.loads(output)['pricing_models']) == 1 - assert len(json.loads(output)['pricing_categories']) == 1 - import_site(data={}, clean=True) - empty_output = get_output_of_command('export_pricing_config') - assert len(json.loads(empty_output)['agendas']) == 1 - assert len(json.loads(empty_output)['pricings']) == 0 - assert len(json.loads(empty_output)['pricing_models']) == 0 - assert len(json.loads(empty_output)['pricing_categories']) == 0 - - old_stdin = sys.stdin - sys.stdin = StringIO(json.dumps({})) - pricing = Pricing.objects.create(label='Foo') - AgendaPricing.objects.create( - pricing=pricing, - date_start=datetime.date(year=2021, month=9, day=1), - date_end=datetime.date(year=2021, month=10, day=1), - ) - CriteriaCategory.objects.create(label='Foo bar') - old_stdin = sys.stdin - sys.stdin = StringIO(json.dumps({})) + data = export_site() + assert len(data['agendas']) == 1 + assert len(data['pricings']) == 1 + assert len(data['pricings'][0]['agendas']) == 0 + assert len(data['pricing_models']) == 1 + assert len(data['pricing_categories']) == 1 + import_site(data={}) assert AgendaPricing.objects.count() == 1 assert Pricing.objects.count() == 1 assert CriteriaCategory.objects.count() == 1 - try: - call_command('import_pricing_config', '-', clean=True) - finally: - sys.stdin = old_stdin - assert AgendaPricing.objects.count() == 0 - assert Pricing.objects.count() == 0 - assert CriteriaCategory.objects.count() == 0 - - with tempfile.NamedTemporaryFile() as f: - f.write(force_bytes(output)) - f.flush() - call_command('import_pricing_config', f.name) - assert AgendaPricing.objects.count() == 1 - assert Pricing.objects.count() == 1 - assert CriteriaCategory.objects.count() == 1 - - import_site(data={}, if_empty=True) - assert AgendaPricing.objects.count() == 1 - assert Pricing.objects.count() == 1 - assert CriteriaCategory.objects.count() == 1 - - import_site(data={}, clean=True) - tempdir = tempfile.mkdtemp('lingo-test') - empty_output = get_output_of_command('export_pricing_config', output=os.path.join(tempdir, 't.json')) - assert os.path.exists(os.path.join(tempdir, 't.json')) - shutil.rmtree(tempdir) def test_import_export_agenda_pricing(app): @@ -112,31 +53,27 @@ def test_import_export_agenda_pricing(app): }, ) agenda_pricing.agendas.set([agenda]) - output = get_output_of_command('export_pricing_config') - - import_site(data={}, clean=True) - assert Pricing.objects.count() == 0 - data = json.loads(output) + data = export_site() Agenda.objects.all().delete() with pytest.raises(LingoImportError) as excinfo: - import_site(data, overwrite=True) + import_site(data) assert str(excinfo.value) == 'Missing "foo-bar" agenda' agenda2 = Agenda.objects.create(label='Baz') with pytest.raises(LingoImportError) as excinfo: - import_site(data, overwrite=True) + import_site(data) assert str(excinfo.value) == 'Missing "foo-bar" agenda' del data['pricing_models'] Pricing.objects.all().delete() agenda = Agenda.objects.create(label='Foo Bar') with pytest.raises(LingoImportError) as excinfo: - import_site(data, overwrite=True) + import_site(data) assert str(excinfo.value) == 'Missing "foo" pricing model' pricing = Pricing.objects.create(label='Foo') - import_site(data, overwrite=True) + import_site(data) agenda_pricing = AgendaPricing.objects.latest('pk') assert list(agenda_pricing.agendas.all()) == [agenda] assert agenda_pricing.pricing == pricing @@ -189,15 +126,9 @@ def test_import_export_agenda_pricing_with_billing_dates(app): label='Period 2', ) - output = get_output_of_command('export_pricing_config') + data = export_site() - import_site(data={}, clean=True) - assert Pricing.objects.count() == 0 - assert AgendaPricing.objects.count() == 0 - assert BillingDate.objects.count() == 0 - data = json.loads(output) - - import_site(data, overwrite=True) + import_site(data) agenda_pricing = AgendaPricing.objects.latest('pk') assert agenda_pricing.billingdates.count() == 2 billing_date1 = agenda_pricing.billingdates.all()[0] @@ -211,41 +142,37 @@ def test_import_export_agenda_pricing_with_billing_dates(app): def test_import_export_agenda_with_check_types(app): group = CheckTypeGroup.objects.create(label='foo') agenda = Agenda.objects.create(label='Foo Bar', check_type_group=group) - output = get_output_of_command('export_pricing_config') + data = export_site() - import_site(data={}, clean=True) - assert CheckTypeGroup.objects.count() == 0 - data = json.loads(output) + group.delete() del data['check_type_groups'] agenda.check_type_group = None agenda.save() with pytest.raises(LingoImportError) as excinfo: - import_site(data, overwrite=True) + import_site(data) assert str(excinfo.value) == 'Missing "foo" check type group' CheckTypeGroup.objects.create(label='foobar') with pytest.raises(LingoImportError) as excinfo: - import_site(data, overwrite=True) + import_site(data) assert str(excinfo.value) == 'Missing "foo" check type group' group = CheckTypeGroup.objects.create(label='foo') - import_site(data, overwrite=True) + import_site(data) agenda.refresh_from_db() assert agenda.check_type_group == group def test_import_export_pricing_criteria_category(app): - output = get_output_of_command('export_pricing_config') - payload = json.loads(output) + payload = export_site() assert len(payload['pricing_categories']) == 0 category = CriteriaCategory.objects.create(label='Foo bar') Criteria.objects.create(label='Foo reason', category=category) Criteria.objects.create(label='Baz', category=category) - output = get_output_of_command('export_pricing_config') - payload = json.loads(output) + payload = export_site() assert len(payload['pricing_categories']) == 1 category.delete() @@ -280,27 +207,14 @@ def test_import_export_pricing_criteria_category(app): assert Criteria.objects.get(category=category, label='Foo reason', slug='foo-reason') assert Criteria.objects.get(category=category, label='Baz', slug='baz') - # with overwrite - Criteria.objects.create(category=category, label='Baz2') - import_site(copy.deepcopy(payload), overwrite=True) - assert CriteriaCategory.objects.count() == 2 - category = CriteriaCategory.objects.latest('pk') - assert category.label == 'Foo bar' - assert category.slug == 'foo-bar' - assert category.criterias.count() == 2 - assert Criteria.objects.get(category=category, label='Foo reason', slug='foo-reason') - assert Criteria.objects.get(category=category, label='Baz', slug='baz') - def test_import_export_pricing(app): - output = get_output_of_command('export_pricing_config') - payload = json.loads(output) + payload = export_site() assert len(payload['pricing_models']) == 0 pricing = Pricing.objects.create(label='Foo bar', extra_variables={'foo': 'bar'}) - output = get_output_of_command('export_pricing_config') - payload = json.loads(output) + payload = export_site() assert len(payload['pricing_models']) == 1 pricing.delete() @@ -336,25 +250,21 @@ def test_import_export_pricing_with_categories(app): category = CriteriaCategory.objects.create(label='Foo bar') pricing.categories.add(category, through_defaults={'order': 42}) - output = get_output_of_command('export_pricing_config') - - import_site(data={}, clean=True) - assert Pricing.objects.count() == 0 - assert CriteriaCategory.objects.count() == 0 - data = json.loads(output) + data = export_site() + category.delete() del data['pricing_categories'] with pytest.raises(LingoImportError) as excinfo: - import_site(data, overwrite=True) + import_site(data) assert str(excinfo.value) == 'Missing "foo-bar" pricing category' CriteriaCategory.objects.create(label='Foobar') with pytest.raises(LingoImportError) as excinfo: - import_site(data, overwrite=True) + import_site(data) assert str(excinfo.value) == 'Missing "foo-bar" pricing category' category = CriteriaCategory.objects.create(label='Foo bar') - import_site(data, overwrite=True) + import_site(data) pricing = Pricing.objects.get(slug=pricing.slug) assert list(pricing.categories.all()) == [category] assert PricingCriteriaCategory.objects.first().order == 42 @@ -362,8 +272,7 @@ def test_import_export_pricing_with_categories(app): category2 = CriteriaCategory.objects.create(label='Foo bar 2') category3 = CriteriaCategory.objects.create(label='Foo bar 3') pricing.categories.add(category2, through_defaults={'order': 1}) - output = get_output_of_command('export_pricing_config') - data = json.loads(output) + data = export_site() del data['pricing_categories'] data['pricing_models'][0]['categories'] = [ { @@ -377,7 +286,7 @@ def test_import_export_pricing_with_categories(app): 'criterias': [], }, ] - import_site(data, overwrite=True) + import_site(data) assert list(pricing.categories.all()) == [category, category3] assert list( PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) @@ -406,7 +315,7 @@ def test_import_export_pricing_with_categories(app): }, ] with pytest.raises(LingoImportError) as excinfo: - import_site(data, overwrite=True) + import_site(data) assert str(excinfo.value) == 'Missing "unknown" pricing criteria for "foo-bar-3" category' # wrong criteria (from another category) @@ -423,7 +332,7 @@ def test_import_export_pricing_with_categories(app): }, ] with pytest.raises(LingoImportError) as excinfo: - import_site(data, overwrite=True) + import_site(data) assert str(excinfo.value) == 'Missing "crit-1" pricing criteria for "foo-bar-3" category' data['pricing_models'][0]['categories'] = [ @@ -438,7 +347,7 @@ def test_import_export_pricing_with_categories(app): 'criterias': ['crit-1', 'crit-3'], }, ] - import_site(data, overwrite=True) + import_site(data) assert list(pricing.categories.all()) == [category, category3] assert list( PricingCriteriaCategory.objects.filter(pricing=pricing).values_list('category', flat=True) @@ -451,16 +360,14 @@ def test_import_export_pricing_with_categories(app): def test_import_export_check_type_group(app): - output = get_output_of_command('export_pricing_config') - payload = json.loads(output) + payload = export_site() assert len(payload['check_type_groups']) == 0 group = CheckTypeGroup.objects.create(label='Foo bar') CheckType.objects.create(label='Foo reason', group=group) CheckType.objects.create(label='Baz', group=group) - output = get_output_of_command('export_pricing_config') - payload = json.loads(output) + payload = export_site() assert len(payload['check_type_groups']) == 1 group.delete() @@ -494,14 +401,3 @@ def test_import_export_check_type_group(app): assert group.check_types.count() == 2 assert CheckType.objects.get(group=group, label='Foo reason', slug='foo-reason') assert CheckType.objects.get(group=group, label='Baz', slug='baz') - - # with overwrite - CheckType.objects.create(group=group, label='Baz2') - import_site(copy.deepcopy(payload), overwrite=True) - assert CheckTypeGroup.objects.count() == 2 - group = CheckTypeGroup.objects.latest('pk') - assert group.label == 'Foo bar' - assert group.slug == 'foo-bar' - assert group.check_types.count() == 2 - assert CheckType.objects.get(group=group, label='Foo reason', slug='foo-reason') - assert CheckType.objects.get(group=group, label='Baz', slug='baz')