diff --git a/apps/bootstrap/classes.py b/apps/bootstrap/classes.py index 5cb392c138..0a83bdfe2b 100644 --- a/apps/bootstrap/classes.py +++ b/apps/bootstrap/classes.py @@ -8,7 +8,8 @@ from django.core import serializers from django.utils.datastructures import SortedDict from .exceptions import ExistingData -from .literals import FIXTURE_TYPE_PK_NULLIFIER, FIXTURE_TYPE_MODEL_PROCESS +from .literals import (FIXTURE_TYPE_PK_NULLIFIER, FIXTURE_TYPE_MODEL_PROCESS, + FIXTURE_METADATA_REMARK_CHARACTER) from .utils import toposort2 logger = logging.getLogger(__name__) @@ -131,13 +132,32 @@ class FixtureMetadata(object): return '\n'.join(result) - def __init__(self, literal, generate_function): + @classmethod + def read_all(cls, data): + result = {} + for instance in cls.get_all(): + single_result = instance.read_value(data) + if single_result: + result[instance.property_name] = single_result + + return result + + def __init__(self, literal, generate_function, read_function=None, property_name=None): self.literal = literal self.generate_function = generate_function + self.property_name = property_name + self.read_function = read_function or (lambda x: x) self.__class__._registry[id(self)] = self + + def get_with_remark(self): + return '%s %s' % (FIXTURE_METADATA_REMARK_CHARACTER, self.literal) def generate(self, fixture_instance): - return '# %s: %s' % (self.literal, self.generate_function(fixture_instance)) + return '%s: %s' % (self.get_with_remark(), self.generate_function(fixture_instance)) def read_value(self, fixture_data): - return [line[line.find(self.literal) + len(self.literal) + 2:] for line in fixture_data.splitlines(False) if line.find(self.literal)] + if self.property_name: + for line in fixture_data.splitlines(False): + if line.startswith(self.get_with_remark()): + # TODO: replace the "+ 4" with a space and next character finding algo + return self.read_function(line[len(self.literal) + 4:]) diff --git a/apps/bootstrap/forms.py b/apps/bootstrap/forms.py index af8692554f..5314e8b6a5 100644 --- a/apps/bootstrap/forms.py +++ b/apps/bootstrap/forms.py @@ -43,3 +43,9 @@ class BootstrapSetupForm_dump(BootstrapSetupForm): class Meta(BootstrapSetupForm.Meta): model = BootstrapSetup exclude = ('fixture',) + + +class BootstrapUploadForm(forms.Form): + file = forms.FileField( + label=_(u'Bootstrap setup file'), + ) diff --git a/apps/bootstrap/links.py b/apps/bootstrap/links.py index 5a742bbaca..00bb4f57bf 100644 --- a/apps/bootstrap/links.py +++ b/apps/bootstrap/links.py @@ -5,7 +5,8 @@ from django.utils.translation import ugettext_lazy as _ from .permissions import (PERMISSION_BOOTSTRAP_VIEW, PERMISSION_BOOTSTRAP_CREATE, PERMISSION_BOOTSTRAP_EDIT, PERMISSION_BOOTSTRAP_DELETE, PERMISSION_BOOTSTRAP_EXECUTE, PERMISSION_BOOTSTRAP_DUMP, - PERMISSION_NUKE_DATABASE, PERMISSION_BOOTSTRAP_EXPORT) + PERMISSION_NUKE_DATABASE, PERMISSION_BOOTSTRAP_EXPORT, + PERMISSION_BOOTSTRAP_IMPORT) link_bootstrap_setup_tool = {'text': _(u'bootstrap'), 'view': 'bootstrap_setup_list', 'icon': 'lightning.png', 'permissions': [PERMISSION_BOOTSTRAP_VIEW]} link_bootstrap_setup_list = {'text': _(u'bootstrap setup list'), 'view': 'bootstrap_setup_list', 'famfam': 'lightning', 'permissions': [PERMISSION_BOOTSTRAP_VIEW]} @@ -16,4 +17,5 @@ link_bootstrap_setup_view = {'text': _(u'details'), 'view': 'bootstrap_setup_vie link_bootstrap_setup_execute = {'text': _(u'execute'), 'view': 'bootstrap_setup_execute', 'args': 'object.pk', 'famfam': 'lightning_go', 'permissions': [PERMISSION_BOOTSTRAP_EXECUTE]} link_bootstrap_setup_dump = {'text': _(u'dump current setup'), 'view': 'bootstrap_setup_dump', 'famfam': 'arrow_down', 'permissions': [PERMISSION_BOOTSTRAP_DUMP]} link_bootstrap_setup_export = {'text': _(u'export'), 'view': 'bootstrap_setup_export', 'args': 'object.pk', 'famfam': 'disk', 'permissions': [PERMISSION_BOOTSTRAP_EXPORT]} +link_bootstrap_setup_import = {'text': _(u'import'), 'view': 'bootstrap_setup_import', 'famfam': 'folder', 'permissions': [PERMISSION_BOOTSTRAP_IMPORT]} link_erase_database = {'text': _(u'erase database'), 'view': 'erase_database_view', 'icon': 'radioactivity.png', 'permissions': [PERMISSION_NUKE_DATABASE]} diff --git a/apps/bootstrap/literals.py b/apps/bootstrap/literals.py index d828c137f3..7362290c25 100644 --- a/apps/bootstrap/literals.py +++ b/apps/bootstrap/literals.py @@ -64,6 +64,7 @@ if YAML_AVAILABLE: FIXTURE_TYPES_CHOICES += (FIXTURE_TYPE_BETTER_YAML, _(u'Better YAML')), # better_yaml is not working with natural keys +FIXTURE_METADATA_REMARK_CHARACTER = '#' DATETIME_STRING_FORMAT = '%a, %d %b %Y %H:%M:%S +0000' FIXTURE_METADATA_CREATED = 'created' FIXTURE_METADATA_EDITED = 'edited' @@ -71,5 +72,4 @@ FIXTURE_METADATA_MAYAN_VERSION = 'mayan_edms_version' FIXTURE_METADATA_FORMAT = 'format' FIXTURE_METADATA_NAME = 'name' FIXTURE_METADATA_DESCRIPTION = 'description' - BOOTSTRAP_EXTENSION = 'txt' diff --git a/apps/bootstrap/managers.py b/apps/bootstrap/managers.py index d8e5029a14..bb6e191f0b 100644 --- a/apps/bootstrap/managers.py +++ b/apps/bootstrap/managers.py @@ -5,7 +5,7 @@ import logging from django.db import models from django.core import serializers -from .classes import BootstrapModel +from .classes import BootstrapModel, FixtureMetadata from .literals import FIXTURE_TYPE_FIXTURE_PROCESS, FIXTURE_TYPE_EMPTY_FIXTURE logger = logging.getLogger(__name__) @@ -32,3 +32,11 @@ class BootstrapSetupManager(models.Manager): if not FIXTURE_TYPE_EMPTY_FIXTURE[serialization_format](model_fixture): result.append(model_fixture) return FIXTURE_TYPE_FIXTURE_PROCESS[serialization_format]('\n'.join(result)) + + def import_setup(self, files): + file_data = files.read() + metadata = FixtureMetadata.read_all(file_data) + instance = self.model(fixture=file_data, **metadata) + instance.save(update_metadata=False) + + diff --git a/apps/bootstrap/models.py b/apps/bootstrap/models.py index 312841b52e..49d39305e9 100644 --- a/apps/bootstrap/models.py +++ b/apps/bootstrap/models.py @@ -16,7 +16,7 @@ from django.core import management from django.core.files.uploadedfile import SimpleUploadedFile from .literals import (FIXTURE_TYPES_CHOICES, FIXTURE_FILE_TYPE, COMMAND_LOADDATA, - BOOTSTRAP_EXTENSION) + BOOTSTRAP_EXTENSION, FIXTURE_METADATA_REMARK_CHARACTER) from .managers import BootstrapSetupManager from .classes import BootstrapModel, FixtureMetadata @@ -78,7 +78,7 @@ class BootstrapSetup(models.Model): """ Return the bootstrap setup's fixture without comments. """ - return re.sub(re.compile('#.*?\n'), '', self.fixture) + return re.sub(re.compile('%s.*?\n' % FIXTURE_METADATA_REMARK_CHARACTER), '', self.fixture) def get_metadata_string(self): """ @@ -93,10 +93,12 @@ class BootstrapSetup(models.Model): return SimpleUploadedFile(name=self.get_filename(), content=self.fixture) def save(self, *args, **kwargs): - self.fixture = '%s\n\n%s' % ( - self.get_metadata_string(), - self.cleaned_fixture - ) + update_metadata = kwargs.pop('update_metadata', True) + if update_metadata: + self.fixture = '%s\n%s' % ( + self.get_metadata_string(), + self.cleaned_fixture + ) return super(BootstrapSetup, self).save(*args, **kwargs) class Meta: diff --git a/apps/bootstrap/permissions.py b/apps/bootstrap/permissions.py index f69d9b746d..6102374e12 100644 --- a/apps/bootstrap/permissions.py +++ b/apps/bootstrap/permissions.py @@ -12,5 +12,6 @@ PERMISSION_BOOTSTRAP_EDIT = Permission.objects.register(namespace, 'bootstrap_ed PERMISSION_BOOTSTRAP_DELETE = Permission.objects.register(namespace, 'bootstrap_delete', _(u'Delete bootstrap setups')) PERMISSION_BOOTSTRAP_EXECUTE = Permission.objects.register(namespace, 'bootstrap_execute', _(u'Execute bootstrap setups')) PERMISSION_BOOTSTRAP_DUMP = Permission.objects.register(namespace, 'bootstrap_dump', _(u'Dump the current project\s setup into a bootstrap setup')) -PERMISSION_BOOTSTRAP_EXPORT = Permission.objects.register(namespace, 'bootstrap_export', _(u'Export the bootstrap setup as files')) +PERMISSION_BOOTSTRAP_EXPORT = Permission.objects.register(namespace, 'bootstrap_export', _(u'Export bootstrap setups as files')) +PERMISSION_BOOTSTRAP_IMPORT = Permission.objects.register(namespace, 'bootstrap_import', _(u'Import new bootstrap setups')) PERMISSION_NUKE_DATABASE = Permission.objects.register(namespace, 'nuke_database', _(u'Erase the entire database and document storage')) diff --git a/apps/bootstrap/post_init.py b/apps/bootstrap/post_init.py index 88d16e6441..d682b29eab 100644 --- a/apps/bootstrap/post_init.py +++ b/apps/bootstrap/post_init.py @@ -1,13 +1,14 @@ from __future__ import absolute_import -from time import gmtime, strftime +import datetime from navigation.api import register_links from main import __version__ from .links import (link_bootstrap_setup_create, link_bootstrap_setup_execute, link_bootstrap_setup_list, link_bootstrap_setup_edit, link_bootstrap_setup_delete, - link_bootstrap_setup_view, link_bootstrap_setup_dump, link_bootstrap_setup_export) + link_bootstrap_setup_view, link_bootstrap_setup_dump, link_bootstrap_setup_export, + link_bootstrap_setup_import) from .models import BootstrapSetup from .classes import FixtureMetadata from .literals import (FIXTURE_METADATA_CREATED, FIXTURE_METADATA_EDITED, @@ -15,12 +16,12 @@ from .literals import (FIXTURE_METADATA_CREATED, FIXTURE_METADATA_EDITED, FIXTURE_METADATA_DESCRIPTION, DATETIME_STRING_FORMAT) register_links([BootstrapSetup], [link_bootstrap_setup_view, link_bootstrap_setup_edit, link_bootstrap_setup_delete, link_bootstrap_setup_execute, link_bootstrap_setup_export]) -register_links([BootstrapSetup], [link_bootstrap_setup_list, link_bootstrap_setup_create, link_bootstrap_setup_dump], menu_name='secondary_menu') -register_links(['bootstrap_setup_list', 'bootstrap_setup_create', 'bootstrap_setup_dump'], [link_bootstrap_setup_list, link_bootstrap_setup_create, link_bootstrap_setup_dump], menu_name='secondary_menu') +register_links([BootstrapSetup], [link_bootstrap_setup_list, link_bootstrap_setup_create, link_bootstrap_setup_dump, link_bootstrap_setup_import], menu_name='secondary_menu') +register_links(['bootstrap_setup_list', 'bootstrap_setup_create', 'bootstrap_setup_dump', 'bootstrap_setup_import'], [link_bootstrap_setup_list, link_bootstrap_setup_create, link_bootstrap_setup_dump, link_bootstrap_setup_import], menu_name='secondary_menu') -FixtureMetadata(FIXTURE_METADATA_CREATED, generate_function=lambda fixture_instance: strftime(fixture_instance.created.strftime(DATETIME_STRING_FORMAT))) -FixtureMetadata(FIXTURE_METADATA_EDITED, generate_function=lambda fixture_instance: strftime(DATETIME_STRING_FORMAT, gmtime())) +FixtureMetadata(FIXTURE_METADATA_CREATED, generate_function=lambda fixture_instance: fixture_instance.created.strftime(DATETIME_STRING_FORMAT), read_function=lambda x: datetime.datetime.strptime(x, DATETIME_STRING_FORMAT), property_name='created') +FixtureMetadata(FIXTURE_METADATA_EDITED, generate_function=lambda fixture_instance: datetime.datetime.now().strftime(DATETIME_STRING_FORMAT)) FixtureMetadata(FIXTURE_METADATA_MAYAN_VERSION, generate_function=lambda fixture_instance: __version__) -FixtureMetadata(FIXTURE_METADATA_FORMAT, generate_function=lambda fixture_instance: fixture_instance.type) -FixtureMetadata(FIXTURE_METADATA_NAME, generate_function=lambda fixture_instance: fixture_instance.name) -FixtureMetadata(FIXTURE_METADATA_DESCRIPTION, generate_function=lambda fixture_instance: fixture_instance.description) +FixtureMetadata(FIXTURE_METADATA_FORMAT, generate_function=lambda fixture_instance: fixture_instance.type, property_name='type') +FixtureMetadata(FIXTURE_METADATA_NAME, generate_function=lambda fixture_instance: fixture_instance.name, property_name='name') +FixtureMetadata(FIXTURE_METADATA_DESCRIPTION, generate_function=lambda fixture_instance: fixture_instance.description, property_name='description') diff --git a/apps/bootstrap/urls.py b/apps/bootstrap/urls.py index 73a1ce013f..fb9a60890f 100644 --- a/apps/bootstrap/urls.py +++ b/apps/bootstrap/urls.py @@ -9,5 +9,6 @@ urlpatterns = patterns('bootstrap.views', url(r'^setup/(?P\d+)/execute/$', 'bootstrap_setup_execute', (), 'bootstrap_setup_execute'), url(r'^setup/(?P\d+)/export/$', 'bootstrap_setup_export', (), 'bootstrap_setup_export'), url(r'^setup/dump/$', 'bootstrap_setup_dump', (), 'bootstrap_setup_dump'), + url(r'^setup/import/$', 'bootstrap_setup_import', (), 'bootstrap_setup_import'), url(r'^nuke/$', 'erase_database_view', (), 'erase_database_view'), ) diff --git a/apps/bootstrap/views.py b/apps/bootstrap/views.py index 5e6c48dff9..d97a429ee0 100644 --- a/apps/bootstrap/views.py +++ b/apps/bootstrap/views.py @@ -17,9 +17,9 @@ from .classes import Cleanup, BootstrapModel from .permissions import (PERMISSION_BOOTSTRAP_VIEW, PERMISSION_BOOTSTRAP_CREATE, PERMISSION_BOOTSTRAP_EDIT, PERMISSION_BOOTSTRAP_DELETE, PERMISSION_BOOTSTRAP_EXECUTE, PERMISSION_NUKE_DATABASE, PERMISSION_BOOTSTRAP_DUMP, - PERMISSION_BOOTSTRAP_EXPORT) + PERMISSION_BOOTSTRAP_EXPORT, PERMISSION_BOOTSTRAP_IMPORT) from .forms import (BootstrapSetupForm, BootstrapSetupForm_view, BootstrapSetupForm_dump, - BootstrapSetupForm_edit) + BootstrapSetupForm_edit, BootstrapUploadForm) from .exceptions import ExistingData @@ -227,6 +227,59 @@ def bootstrap_setup_export(request, bootstrap_setup_pk): ) +def bootstrap_setup_import(request): + + Permission.objects.check_permissions(request.user, [PERMISSION_BOOTSTRAP_IMPORT]) + + previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', '/'))) + + if request.method == 'POST': + form = BootstrapUploadForm(request.POST, request.FILES) + if form.is_valid(): + try: + BootstrapSetup.objects.import_setup(request.FILES['file']) + messages.success(request, _(u'Bootstrap setup imported successfully.')) + return HttpResponseRedirect(reverse('bootstrap_setup_list')) + except Exception as exception: + messages.error(request, exception) + return HttpResponseRedirect(previous) + else: + form = BootstrapUploadForm() + + return render_to_response('generic_form.html', { + 'title': _(u'Import bootstrap setup'), + 'form_icon': 'folder.png', + 'form': form, + 'previous': previous, + }, context_instance=RequestContext(request)) + + +def bootstrap_setup_dump(request): + Permission.objects.check_permissions(request.user, [PERMISSION_BOOTSTRAP_DUMP]) + + if request.method == 'POST': + form = BootstrapSetupForm_dump(request.POST) + if form.is_valid(): + bootstrap = form.save(commit=False) + try: + bootstrap.fixture = BootstrapSetup.objects.dump(serialization_format=bootstrap.type) + except Exception as exception: + messages.error(request, _(u'Error dumping bootstrap setup; %s') % exception) + raise + else: + bootstrap.save() + messages.success(request, _(u'Bootstrap created successfully.')) + return HttpResponseRedirect(reverse('bootstrap_setup_list')) + else: + form = BootstrapSetupForm_dump() + + return render_to_response('generic_form.html', { + 'title': _(u'dump current setup into a bootstrap setup'), + 'form': form, + }, + context_instance=RequestContext(request)) + + def erase_database_view(request): Permission.objects.check_permissions(request.user, [PERMISSION_NUKE_DATABASE])