From e273b2fad7d2228916ee4476363de9264a6c12ba Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Fri, 3 Mar 2017 16:28:22 +0100 Subject: [PATCH 4/4] import_site: add --clean, --if-empty and --overwrite options --- passerelle/apps/csvdatasource/models.py | 6 ++-- passerelle/base/management/commands/import_site.py | 13 ++++++- passerelle/base/models.py | 27 +++++++++----- passerelle/utils/__init__.py | 29 +++++++++++++-- tests/test_import_export.py | 42 ++++++++++++++++++---- 5 files changed, 96 insertions(+), 21 deletions(-) diff --git a/passerelle/apps/csvdatasource/models.py b/passerelle/apps/csvdatasource/models.py index 7f7ca70..011e19f 100644 --- a/passerelle/apps/csvdatasource/models.py +++ b/passerelle/apps/csvdatasource/models.py @@ -383,10 +383,12 @@ class CsvDataSource(BaseResource): return d @classmethod - def import_json_real(cls, d, **kwargs): + def import_json_real(cls, overwrite, instance, d, **kwargs): queries = d.pop('queries', []) - instance = super(CsvDataSource, cls).import_json_real(d, **kwargs) + instance = super(CsvDataSource, cls).import_json_real(overwrite, instance, d, **kwargs) new = [] + if instance and overwrite: + Query.objects.filter(resource=instance).delete() for query in queries: q = Query.import_json(query) q.resource = instance diff --git a/passerelle/base/management/commands/import_site.py b/passerelle/base/management/commands/import_site.py index 612f5f0..e13e1e2 100644 --- a/passerelle/base/management/commands/import_site.py +++ b/passerelle/base/management/commands/import_site.py @@ -3,6 +3,7 @@ from optparse import make_option from django.core.management.base import BaseCommand +from passerelle.base.models import ApiUser, BaseResource from passerelle.utils import import_site @@ -10,9 +11,19 @@ class Command(BaseCommand): args = '' help = 'Import an exported site' option_list = BaseCommand.option_list + ( + make_option('--clean', action='store_true', default=False, + help='Clean site before importing'), make_option('--import-users', action='store_true', default=False, help='Import users and access rights'), + make_option('--if-empty', action='store_true', default=False, + help='Import only if passerelle is empty'), + make_option('--overwrite', action='store_true', default=False, + help='Overwirte existing resources'), ) def handle(self, filename, **options): - import_site(json.load(open(filename)), import_users=options['import_users']) + import_site(json.load(open(filename)), + if_empty=options['if_empty'], + clean=options['clean'], + overwrite=options['overwrite'], + import_users=options['import_users']) diff --git a/passerelle/base/models.py b/passerelle/base/models.py index 29132f7..012b98c 100644 --- a/passerelle/base/models.py +++ b/passerelle/base/models.py @@ -62,13 +62,16 @@ class ApiUser(models.Model): } @classmethod - def import_json(self, d): + def import_json(self, d, overwrite=False): if d.get('@type') != 'passerelle-user': raise ValueError('not a passerelle user export') d = d.copy() d.pop('@type') - return self.objects.get_or_create(username=d['username'], - defaults=d) + api_user, created = self.objects.get_or_create(username=d['username'], defaults=d) + if overwrite and not created: + for key in d: + setattr(api_user, key, d[key]) + api_user.save() class TemplateVar(models.Model): @@ -219,7 +222,7 @@ class BaseResource(models.Model): return d @staticmethod - def import_json(d, import_users=False): + def import_json(d, import_users=False, overwrite=False): if d.get('@type') != 'passerelle-resource': raise ValueError('not a passerelle resource export') @@ -228,12 +231,14 @@ class BaseResource(models.Model): app_label, model_name = d['resource_type'].split('.') model = apps.get_model(app_label, model_name) try: - return model.objects.get(slug=d['slug']) + instance = model.objects.get(slug=d['slug']) + if not overwrite: + return except model.DoesNotExist: - pass + instance = None with transaction.atomic(): # prevent semi-creation of ressources - instance = model.import_json_real(d) + instance = model.import_json_real(overwrite, instance, d) resource_type = ContentType.objects.get_for_model(instance) # We can only connect AccessRight objects to the new Resource after its creation if import_users: @@ -247,7 +252,7 @@ class BaseResource(models.Model): return instance @classmethod - def import_json_real(cls, d, **kwargs): + def import_json_real(cls, overwrite, instance, d, **kwargs): init_kwargs = { 'title': d['title'], 'slug': d['slug'], @@ -255,7 +260,11 @@ class BaseResource(models.Model): 'log_level': d['log_level'], } init_kwargs.update(kwargs) - instance = cls(**init_kwargs) + if instance: + for key in init_kwargs: + setattr(instance, key, init_kwargs[key]) + else: + instance = cls(**init_kwargs) for field, model in cls._meta.get_concrete_fields_with_model(): if field.name == 'id': continue diff --git a/passerelle/utils/__init__.py b/passerelle/utils/__init__.py index aab32b6..e1c91e8 100644 --- a/passerelle/utils/__init__.py +++ b/passerelle/utils/__init__.py @@ -192,16 +192,39 @@ def export_site(): return d -def import_site(d, import_users=False): +def import_site(d, if_empty=False, clean=False, overwrite=False, import_users=False): '''Load passerelle configuration (users, resources and ACLs) from a dictionnary loaded from JSON ''' d = d.copy() + def is_empty(): + if import_users: + if ApiUser.objects.count(): + return False + + for subclass in BaseResource.__subclasses__(): + if subclass._meta.abstract: + continue + if subclass.objects.count(): + return False + return True + + if if_empty and not is_empty(): + return + + if clean: + for subclass in BaseResource.__subclasses__(): + if subclass._meta.abstract: + continue + subclass.objects.all().delete() + if import_users: + ApiUser.objects.all().delete() + with transaction.atomic(): if import_users: for apiuser in d['apiusers']: - ApiUser.import_json(apiuser) + ApiUser.import_json(apiuser, overwrite=overwrite) for resource in d['resources']: - BaseResource.import_json(resource, import_users=import_users) + BaseResource.import_json(resource, overwrite=overwrite, import_users=import_users) diff --git a/tests/test_import_export.py b/tests/test_import_export.py index 12d71bf..51ba151 100644 --- a/tests/test_import_export.py +++ b/tests/test_import_export.py @@ -87,6 +87,14 @@ def filetype(request): return request.param +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_export_csvdatasource(app, setup, filetype): def clear(): Query.objects.all().delete() @@ -109,12 +117,7 @@ def test_export_csvdatasource(app, setup, filetype): second['resources'][0]['csv_file']['name'] = 'whocares' assert first == second - old_stdout = sys.stdout - output = sys.stdout = StringIO() - call_command('export_site') - sys.stdout = old_stdout - - output = output.getvalue() + output = get_output_of_command('export_site') third = json.loads(output) third['resources'][0]['csv_file']['name'] = 'whocares' assert first == third @@ -127,3 +130,30 @@ def test_export_csvdatasource(app, setup, filetype): fourth = export_site() fourth['resources'][0]['csv_file']['name'] = 'whocares' assert first == fourth + + Query.objects.all().delete() + + with tempfile.NamedTemporaryFile() as f: + f.write(output) + f.flush() + call_command('import_site', f.name, import_users=True) + assert Query.objects.count() == 0 + + with tempfile.NamedTemporaryFile() as f: + f.write(output) + f.flush() + call_command('import_site', f.name, overwrite=True, import_users=True) + assert Query.objects.count() == 1 + + query = Query(slug='query-2_', resource=CsvDataSource.objects.get(), structure='array') + query.projections = '\n'.join(['id:int(id)', 'prenom:prenom']) + query.save() + + assert Query.objects.count() == 2 + + with tempfile.NamedTemporaryFile() as f: + f.write(output) + f.flush() + call_command('import_site', f.name, clean=True, overwrite=True, import_users=True) + assert Query.objects.count() == 1 + assert Query.objects.filter(slug='query-1_').exists() -- 2.1.4