Add support for synchronizing bootstrap setups available with the official repository
This commit is contained in:
@@ -6,7 +6,7 @@ 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_BOOTSTRAP_IMPORT)
|
||||
PERMISSION_BOOTSTRAP_IMPORT, PERMISSION_BOOTSTRAP_REPOSITORY_SYNC)
|
||||
|
||||
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]}
|
||||
@@ -19,4 +19,5 @@ link_bootstrap_setup_dump = {'text': _(u'dump current setup'), 'view': 'bootstra
|
||||
link_bootstrap_setup_export = {'text': _(u'export'), 'view': 'bootstrap_setup_export', 'args': 'object.pk', 'famfam': 'disk', 'permissions': [PERMISSION_BOOTSTRAP_EXPORT]}
|
||||
link_bootstrap_setup_import_from_file = {'text': _(u'import from file'), 'view': 'bootstrap_setup_import_from_file', 'famfam': 'folder', 'permissions': [PERMISSION_BOOTSTRAP_IMPORT]}
|
||||
link_bootstrap_setup_import_from_url = {'text': _(u'import from URL'), 'view': 'bootstrap_setup_import_from_url', 'famfam': 'world', 'permissions': [PERMISSION_BOOTSTRAP_IMPORT]}
|
||||
link_bootstrap_setup_repository_sync = {'text': _(u'sync with repository'), 'view': 'bootstrap_setup_repository_sync', 'famfam': 'world', 'permissions': [PERMISSION_BOOTSTRAP_REPOSITORY_SYNC]}
|
||||
link_erase_database = {'text': _(u'erase database'), 'view': 'erase_database_view', 'icon': 'radioactivity.png', 'permissions': [PERMISSION_NUKE_DATABASE]}
|
||||
|
||||
@@ -76,3 +76,6 @@ FIXTURE_METADATA_DESCRIPTION = 'description'
|
||||
BOOTSTRAP_EXTENSION = 'txt'
|
||||
|
||||
BOOTSTRAP_SETUP_MAGIC_NUMBER = 'bootstrap setup'
|
||||
|
||||
BOOTSTRAP_REPOSITORY_URL = 'http://bootstrap.mayan-edms.com'
|
||||
BOOTSTRAP_REPOSITORY_INDEX_FILE = '_repo_index.txt'
|
||||
|
||||
@@ -6,9 +6,13 @@ import requests
|
||||
|
||||
from django.db import models
|
||||
from django.core import serializers
|
||||
from django.utils.simplejson import loads
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Q
|
||||
|
||||
from .classes import BootstrapModel, FixtureMetadata
|
||||
from .literals import (FIXTURE_TYPE_FIXTURE_PROCESS, FIXTURE_TYPE_EMPTY_FIXTURE)
|
||||
from .literals import (FIXTURE_TYPE_FIXTURE_PROCESS, FIXTURE_TYPE_EMPTY_FIXTURE,
|
||||
BOOTSTRAP_REPOSITORY_URL, BOOTSTRAP_REPOSITORY_INDEX_FILE)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,19 +32,44 @@ class BootstrapSetupManager(models.Manager):
|
||||
result.append(model_fixture)
|
||||
return FIXTURE_TYPE_FIXTURE_PROCESS[serialization_format]('\n'.join(result))
|
||||
|
||||
def import_setup(self, file_data):
|
||||
def import_setup(self, file_data, overwrite=False):
|
||||
BootstrapModel.check_magic_number(file_data)
|
||||
metadata = FixtureMetadata.read_all(file_data)
|
||||
instance = self.model(fixture=file_data, **metadata)
|
||||
try:
|
||||
instance.save(update_metadata=False)
|
||||
except IntegrityError:
|
||||
if not overwrite:
|
||||
raise
|
||||
else:
|
||||
# Delete conflicting bootstrap setups
|
||||
query = Q()
|
||||
if 'slug' in metadata:
|
||||
query = query | Q(slug=metadata['slug'])
|
||||
|
||||
if 'name' in metadata:
|
||||
query = query | Q(name=metadata['name'])
|
||||
|
||||
self.model.objects.filter(query).delete()
|
||||
self.import_setup(file_data)
|
||||
|
||||
def import_from_file(self, files):
|
||||
file_data = files.read()
|
||||
self.import_setup(file_data)
|
||||
|
||||
def import_from_url(self, url):
|
||||
def import_from_url(self, url, **kwargs):
|
||||
response = requests.get(url)
|
||||
if response.status_code == requests.codes.ok:
|
||||
self.import_setup(response.text)
|
||||
self.import_setup(response.text, **kwargs)
|
||||
else:
|
||||
response.raise_for_status()
|
||||
|
||||
def repository_sync(self):
|
||||
response = requests.get('%s/%s' % (BOOTSTRAP_REPOSITORY_URL, BOOTSTRAP_REPOSITORY_INDEX_FILE))
|
||||
if response.status_code == requests.codes.ok:
|
||||
for entry in loads(response.text):
|
||||
bootstrap_setup_url = '%s/%s' % (BOOTSTRAP_REPOSITORY_URL, entry['filename'])
|
||||
self.import_from_url(bootstrap_setup_url, overwrite=True)
|
||||
else:
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
@@ -14,4 +14,5 @@ PERMISSION_BOOTSTRAP_EXECUTE = Permission.objects.register(namespace, 'bootstrap
|
||||
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 bootstrap setups as files'))
|
||||
PERMISSION_BOOTSTRAP_IMPORT = Permission.objects.register(namespace, 'bootstrap_import', _(u'Import new bootstrap setups'))
|
||||
PERMISSION_BOOTSTRAP_REPOSITORY_SYNC = Permission.objects.register(namespace, 'bootstrap_repo_sync', _(u'Sync the local bootstrap setups with a published repository'))
|
||||
PERMISSION_NUKE_DATABASE = Permission.objects.register(namespace, 'nuke_database', _(u'Erase the entire database and document storage'))
|
||||
|
||||
@@ -8,7 +8,8 @@ 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_import_from_url, link_bootstrap_setup_import_from_file)
|
||||
link_bootstrap_setup_import_from_url, link_bootstrap_setup_import_from_file,
|
||||
link_bootstrap_setup_repository_sync)
|
||||
from .models import BootstrapSetup
|
||||
from .classes import FixtureMetadata
|
||||
from .literals import (FIXTURE_METADATA_CREATED, FIXTURE_METADATA_EDITED,
|
||||
@@ -16,8 +17,8 @@ from .literals import (FIXTURE_METADATA_CREATED, FIXTURE_METADATA_EDITED,
|
||||
FIXTURE_METADATA_DESCRIPTION, DATETIME_STRING_FORMAT, FIXTURE_METADATA_SLUG)
|
||||
|
||||
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, link_bootstrap_setup_import_from_file, link_bootstrap_setup_import_from_url], menu_name='secondary_menu')
|
||||
register_links(['bootstrap_setup_list', 'bootstrap_setup_create', 'bootstrap_setup_dump', 'bootstrap_setup_import_from_file', 'bootstrap_setup_import_from_url'], [link_bootstrap_setup_list, link_bootstrap_setup_create, link_bootstrap_setup_dump, link_bootstrap_setup_import_from_file, link_bootstrap_setup_import_from_url], menu_name='secondary_menu')
|
||||
register_links([BootstrapSetup], [link_bootstrap_setup_list, link_bootstrap_setup_create, link_bootstrap_setup_dump, link_bootstrap_setup_import_from_file, link_bootstrap_setup_import_from_url, link_bootstrap_setup_repository_sync], menu_name='secondary_menu')
|
||||
register_links(['bootstrap_setup_list', 'bootstrap_setup_create', 'bootstrap_setup_dump', 'bootstrap_setup_import_from_file', 'bootstrap_setup_import_from_url', 'bootstrap_setup_repository_sync'], [link_bootstrap_setup_list, link_bootstrap_setup_create, link_bootstrap_setup_dump, link_bootstrap_setup_import_from_file, link_bootstrap_setup_import_from_url, link_bootstrap_setup_repository_sync], menu_name='secondary_menu')
|
||||
|
||||
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))
|
||||
|
||||
BIN
apps/bootstrap/static/images/icons/world.png
Executable file
BIN
apps/bootstrap/static/images/icons/world.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
@@ -11,5 +11,6 @@ urlpatterns = patterns('bootstrap.views',
|
||||
url(r'^setup/dump/$', 'bootstrap_setup_dump', (), 'bootstrap_setup_dump'),
|
||||
url(r'^setup/import/file/$', 'bootstrap_setup_import_from_file', (), 'bootstrap_setup_import_from_file'),
|
||||
url(r'^setup/import/url/$', 'bootstrap_setup_import_from_url', (), 'bootstrap_setup_import_from_url'),
|
||||
url(r'^setup/repository/sync/$', 'bootstrap_setup_repository_sync', (), 'bootstrap_setup_repository_sync'),
|
||||
url(r'^nuke/$', 'erase_database_view', (), 'erase_database_view'),
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ 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_IMPORT)
|
||||
PERMISSION_BOOTSTRAP_EXPORT, PERMISSION_BOOTSTRAP_IMPORT, PERMISSION_BOOTSTRAP_REPOSITORY_SYNC)
|
||||
from .forms import (BootstrapSetupForm, BootstrapSetupForm_view, BootstrapSetupForm_dump,
|
||||
BootstrapSetupForm_edit, BootstrapFileImportForm, BootstrapURLImportForm)
|
||||
from .exceptions import ExistingData, NotABootstrapSetup
|
||||
@@ -242,7 +242,7 @@ def bootstrap_setup_import_from_file(request):
|
||||
except NotABootstrapSetup:
|
||||
messages.error(request, _(u'File is not a bootstrap setup.'))
|
||||
except Exception as exception:
|
||||
messages.error(request, exception)
|
||||
messages.error(request, _(u'Error importing bootstrap setup from file; %s.') % exception)
|
||||
return HttpResponseRedirect(previous)
|
||||
else:
|
||||
form = BootstrapFileImportForm()
|
||||
@@ -270,7 +270,7 @@ def bootstrap_setup_import_from_url(request):
|
||||
except NotABootstrapSetup:
|
||||
messages.error(request, _(u'Data from URL is not a bootstrap setup.'))
|
||||
except Exception as exception:
|
||||
messages.error(request, exception)
|
||||
messages.error(request, _(u'Error importing bootstrap setup from URL; %s.') % exception)
|
||||
return HttpResponseRedirect(previous)
|
||||
else:
|
||||
form = BootstrapURLImportForm()
|
||||
@@ -312,3 +312,31 @@ def erase_database_view(request):
|
||||
|
||||
return render_to_response('generic_confirm.html', context,
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
def bootstrap_setup_repository_sync(request):
|
||||
Permission.objects.check_permissions(request.user, [PERMISSION_BOOTSTRAP_REPOSITORY_SYNC])
|
||||
|
||||
post_action_redirect = reverse('bootstrap_setup_list')
|
||||
|
||||
previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', '/')))
|
||||
next = request.POST.get('next', request.GET.get('next', post_action_redirect if post_action_redirect else request.META.get('HTTP_REFERER', '/')))
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
BootstrapSetup.objects.repository_sync()
|
||||
messages.success(request, _(u'Bootstrap repository successfully synchronized.'))
|
||||
except Exception, e:
|
||||
messages.error(request, _(u'Bootstrap repository synchronization error: %(error)s') % {'error': e})
|
||||
|
||||
return HttpResponseRedirect(reverse('bootstrap_setup_list'))
|
||||
|
||||
context = {
|
||||
'previous': previous,
|
||||
'next': next,
|
||||
'title': _(u'Are you sure you wish to synchronize with the bootstrap repository?'),
|
||||
'form_icon': 'world.png',
|
||||
}
|
||||
|
||||
return render_to_response('generic_confirm.html', context,
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
Reference in New Issue
Block a user