From b1546bcc20f737bfedf5079a179ec0b638b19ce7 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 25 Sep 2018 16:37:14 +0200 Subject: [PATCH] manage resource from command line (fixes #26582) --- .../base/management/commands/resource.py | 189 ++++++++++++++++++ passerelle/forms.py | 17 +- 2 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 passerelle/base/management/commands/resource.py diff --git a/passerelle/base/management/commands/resource.py b/passerelle/base/management/commands/resource.py new file mode 100644 index 0000000..525c51b --- /dev/null +++ b/passerelle/base/management/commands/resource.py @@ -0,0 +1,189 @@ +# passerelle - uniform access to multiple data sources and services +# Copyright (C) 2017 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 . + +from __future__ import print_function +import os +import argparse + +from django.core.files import File +from django.core.management.base import BaseCommand, CommandError +from django.apps import apps +from django import forms +from django.forms.models import modelform_factory + +from passerelle.base.models import BaseResource +from passerelle.forms import GenericConnectorForm + + +class Command(BaseCommand): + help = 'manage resources' + + def add_arguments(self, parser): + subparsers = parser.add_subparsers(parser_class=argparse.ArgumentParser) + + new_parser = subparsers.add_parser('list-type') + new_parser.add_argument('--resource-type') + new_parser.set_defaults(subcommand=self.list_type) + + new_parser = subparsers.add_parser('list') + new_parser.add_argument('resource_type') + new_parser.set_defaults(subcommand=self.list) + + new_parser = subparsers.add_parser('new') + new_parser.add_argument('resource_type') + new_parser.add_argument('--string-field', + nargs=2, + action='append', + default=[], + type=str) + new_parser.add_argument('--file-field', + nargs=2, + action='append', + default=[], + type=str) + new_parser.set_defaults(subcommand=self.new) + + new_parser = subparsers.add_parser('update') + new_parser.add_argument('resource_type') + new_parser.add_argument('slug') + new_parser.add_argument('--string-field', + nargs=2, + action='append', + default=[], + type=str) + new_parser.add_argument('--file-field', + nargs=2, + action='append', + default=[], + type=str) + new_parser.set_defaults(subcommand=self.update) + + new_parser = subparsers.add_parser('delete') + new_parser.add_argument('resource_type') + new_parser.add_argument('slug') + new_parser.set_defaults(subcommand=self.delete) + + def get_models(self): + for app_config in apps.get_app_configs(): + for model in app_config.get_models(): + if issubclass(model, BaseResource): + yield app_config, model + + def get_model(self, resource_type): + for app_config, model in self.get_models(): + if model._meta.label_lower == resource_type: + return app_config, model + raise CommandError('unknown resource_type %s' % resource_type) + + def get_form_class(self, app_config, model, fields=None): + if hasattr(app_config, 'get_form_class'): + form_class = app_config.get_form_class() + else: + form_class = modelform_factory( + model, + form=GenericConnectorForm, + fields=fields, + exclude=('slug', 'users')) + return form_class + + def get_fields(self, options): + for key, value in options.get('string_field', []): + yield key + for key, value in options.get('file_field', []): + yield key + + def get_model_and_form_class(self, options): + app_config, model = self.get_model(options['resource_type']) + fields = list(self.get_fields(options)) + form_class = self.get_form_class(app_config, model, fields=fields) + return model, form_class + + def list_type(self, **options): + models = self.get_models() + resource_type = options.get('resource_type') + if resource_type: + models = [self.get_model(resource_type)] + for app_config, model in models: + if not resource_type: + print('-', model._meta.label_lower) + form_class = self.get_form_class(app_config, model) + for key in form_class.base_fields: + field = form_class.base_fields[key] + print(' -', key) + print(' ', 'type:', 'file' if isinstance(field, forms.FileField) else 'string') + if hasattr(field, 'choices') and field.choices: + print(' ', 'choices:') + for a, b in field.choices: + print(' %s: %s' % (a, b)) + if field.help_text: + print(' ', 'help text:', field.help_text) + + def list(self, **options): + model, form_class = self.get_model_and_form_class(options) + for instance in model.objects.all(): + for field in model._meta.get_fields(): + try: + attribute = field.attname + except AttributeError: + continue + value = getattr(instance, attribute) + if field.is_relation: + value = list(value.all()) + print('%-20s: %s' % (field.name, value)) + + def new(self, **options): + model, form_class = self.get_model_and_form_class(options) + self.do_form(form_class, options) + + def update(self, **options): + model, form_class = self.get_model_and_form_class(options) + try: + instance = model.objects.get(slug=options['slug']) + except model.DoesNotExist: + raise CommandError('object %s with slug %s does not exist' % (options['resource_type'], options['slug'])) + self.do_form(form_class, options, instance=instance) + + def do_form(self, form_class, options, **kwargs): + data = {} + files = {} + for key, value in options['string_field']: + data[key] = value + for key, value in options['file_field']: + files[key] = File(open(value), name=os.path.basename(value)) + form = form_class(data, files, **kwargs) + if not form.is_valid(): + for msg in form.errors.get('__all__', []): + print('ERROR:', msg) + for key in form.errors: + if key == '__all__': + continue + for msg in form.errors[key]: + print('ERROR:', key, ';', msg) + raise CommandError('could not create %s' % options['resource_type']) + form.save() + + def handle(self, *args, **options): + method = options.get('subcommand') + if method: + method(**options) + + def delete(self, **options): + model, form_class = self.get_model_and_form_class(options) + try: + instance = model.objects.get(slug=options['slug']) + except model.DoesNotExist: + raise CommandError('object %s with slug %s does not exist' % (options['resource_type'], options['slug'])) + instance.delete() diff --git a/passerelle/forms.py b/passerelle/forms.py index cea7719..0485248 100644 --- a/passerelle/forms.py +++ b/passerelle/forms.py @@ -17,8 +17,17 @@ from django.utils.text import slugify from django import forms + class GenericConnectorForm(forms.ModelForm): - def save(self, commit=True): - if not self.instance.slug: - self.instance.slug = slugify(self.instance.title) - return super(GenericConnectorForm, self).save(commit=commit) + def clean(self): + super(GenericConnectorForm, self).clean() + title = self.cleaned_data.get('title') or self.instance.title + if not self.instance.slug and title: + self.instance.slug = slugify(title) + exclude = self._get_validation_exclusions() + exclude.remove('slug') + try: + self.instance.validate_unique(exclude=exclude) + except forms.ValidationError: + raise forms.ValidationError('generated slug is not unique') + -- 2.18.0