Add backup job list, create, edit, test views

This commit is contained in:
Roberto Rosario
2012-08-11 08:40:47 -04:00
parent 141db9dc34
commit 0e9cda6547
14 changed files with 506 additions and 26 deletions

View File

@@ -5,10 +5,12 @@ from django.utils.translation import ugettext_lazy as _
from job_processor.models import JobQueue, JobType
from job_processor.exceptions import JobQueuePushError
from navigation.api import bind_links
from navigation.api import bind_links, register_model_list_columns
from project_tools.api import register_tool
from project_setup.api import register_setup
from .links import backup_tool_link, restore_tool_link
from .links import backup_tool_link, restore_tool_link, backup_job_list, backup_job_create, backup_job_edit, backup_job_test
from .models import BackupJob
# TODO: move to literals
BACKUP_JOB_QUEUE_NAME = 'backups_queue'
@@ -26,5 +28,13 @@ def create_backups_job_queue():
create_backups_job_queue()
#backup_job_type = JobType('remote_backup', _(u'Remove backup'), do_backup)
register_tool(backup_tool_link)
register_setup(backup_tool_link)
register_tool(restore_tool_link)
bind_links([BackupJob, 'backup_job_list', 'backup_job_create'], [backup_job_list], menu_name='secondary_menu')
bind_links([BackupJob, 'backup_job_list', 'backup_job_create'], [backup_job_create], menu_name='sidebar')
bind_links([BackupJob], [backup_job_edit, backup_job_test])
register_model_list_columns(BackupJob, [
{'name':_(u'begin date time'), 'attribute': 'begin_datetime'},
{'name':_(u'storage module'), 'attribute': 'storage_module'},
])

View File

@@ -1,9 +1,47 @@
import logging
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.core.files.base import ContentFile
from django.core.management.commands.dumpdata import Command
from django.db import router, DEFAULT_DB_ALIAS
logger = logging.getLogger(__name__)
# Data types
class ElementDataBase(object):
"""
The basic unit of a backup, a data type
it is produced or consumed by the ElementBackup classes
"""
def save(self):
"""
Must return a file like object
"""
raise NotImplemented
def load(self, file_object):
"""
Must read a file like object and store content
"""
raise NotImplemented
class Fixture(ElementDataBase):
name = 'fixture'
def __init__(self, model_backup, content):
self.model_backup = model_backup
self.content = content
def save(self):
return ContentFile(name='%s_%s' % (self.__class__.__name__, self.model_backup.app_backup.name), content=self.content)
#def load(self):
# Element backup
class ElementBackupBase(object):
"""
Sub classes must provide at least:
@@ -28,7 +66,7 @@ class ElementBackupBase(object):
return unicode(self.__class__.label)
class ElementBackupModel(ElementBackupBase):
class ModelBackup(ElementBackupBase):
label = _(u'Model fixtures')
def __init__(self, models=None):
@@ -39,18 +77,23 @@ class ElementBackupModel(ElementBackupBase):
def backup(self):
"""
TODO: turn into a generator maybe?
"""
#TODO: turn into a generator
command = Command()
if not self.model_list:
result = [self.app_backup.name]
else:
result = [u'%s.%s' (self.app_backup.name, model) for model in self.model_list]
result = command.handle(u' '.join(result), format='json', indent=4, using=DEFAULT_DB_ALIAS, exclude=[], user_base_manager=False, use_natural_keys=False)
return result
#TODO: a single Fixture or a list of Fixtures for each model?
return Fixture(
model_backup=self,
content=command.handle(u' '.join(result), format='json', indent=4, using=DEFAULT_DB_ALIAS, exclude=[], user_base_manager=False, use_natural_keys=False)
)
class ElementBackupFile(ElementBackupBase):
class FileBackup(ElementBackupBase):
label = _(u'File copy')
def __init__(self, storage_class, filepath=None):
@@ -68,6 +111,7 @@ class ElementBackupFile(ElementBackupBase):
return None
# App config
class AppBackup(object):
_registry = {}
@@ -89,6 +133,10 @@ class AppBackup(object):
def get_all(cls):
return cls._registry.values()
@classmethod
def get_as_choices(cls):
return [(key, key.label) for key, values in cls._registry.items()]
def __init__(self, name, label, backup_managers):
self.label = label
self.name = name
@@ -102,14 +150,17 @@ class AppBackup(object):
results.append(u'%s - %s' % (manager, manager.info() or _(u'Nothing')))
return u', '.join(results)
def backup(self, storage_module, *args, **kwargs):
def backup(self, storage_module, dry_run=False):
logger.debug('starting')
self.state = self.__class__.STATE_BACKING_UP
for manager in self.backup_managers:
result = manager.backup()
storage_module.backup(result)
storage_module.backup(result, dry_run=dry_run)
self.state = self.__class__.STATE_IDLE
def restore(self, storage_module=None):
logger.debug('starting')
self.state = self.__class__.STATE_RESTORING
for manager in self.backup_managers:
manager.restore(storage_module.restore())
@@ -119,8 +170,9 @@ class AppBackup(object):
return unicode(self.label)
#Storage
class StorageModuleBase(object):
_registry = []
_registry = {}
# Local modules depend on hardware on a node and execute in the Scheduler
# of a particular node
@@ -135,16 +187,34 @@ class StorageModuleBase(object):
(REALM_REMOTE, _(u'remote')),
)
class UnknownStorageModule(Exception):
pass
@classmethod
def register(cls, klass):
"""
Register a subclass of StorageModuleBase to make it available to the
UI
"""
cls._registry.append(klass)
cls._registry[klass.name] = klass
def __init__(self, *args, **kwargs):
pass
@classmethod
def get_all(cls):
return cls._registry.values()
@classmethod
def get(cls, name):
try:
return cls._registry[name]
except KeyError:
raise cls.UnknownStorageModule
@classmethod
def get_as_choices(cls):
return cls._registry.items()
def get_arguments(self):
return []
def is_local_realm(self):
return self.realm == REALM_LOCAL
@@ -152,7 +222,7 @@ class StorageModuleBase(object):
def is_remote_realm(self):
return self.realm == REALM_REMOTE
def backup(self, data):
def backup(self, data, dry_run):
raise NotImplemented
def restore(self):
@@ -160,18 +230,24 @@ class StorageModuleBase(object):
Must return data or a file like object
"""
raise NotImplemented
def __unicode__(self):
return unicode(self.label)
class TestStorageModule(StorageModuleBase):
name = 'test_storage'
label = _(u'Test storage module')
realm = StorageModuleBase.REALM_LOCAL
def __init__(self, *args, **kwargs):
self.backup_path = kwargs.pop('backup_path', None)
self.restore_path = kwargs.pop('restore_path', None)
return super(TestStorageModule, self).__init__(*args, **kwargs)
def get_arguments(self):
return ['backup_path', 'restore_path']
def backup(self, data):
def backup(self, data, dry_run):
print '***** received data'
print data
print '***** saving to path: %s' % self.backup_path

23
apps/backups/forms.py Normal file
View File

@@ -0,0 +1,23 @@
from __future__ import absolute_import
from django import forms
from .models import BackupJob
class BackupJobForm(forms.ModelForm):
#expiration_datetime = SplitTimeDeltaField()
class Meta:
model = BackupJob
#exclude = ('checkout_datetime', 'user_content_type', 'user_object_id')
#widgets = {
# 'document': forms.widgets.HiddenInput(),
#}
#def clean_document(self):
# document = self.cleaned_data['document']
# if document.is_checked_out():
# raise DocumentAlreadyCheckedOut
# return document

View File

@@ -4,7 +4,13 @@ from django.utils.translation import ugettext_lazy as _
from navigation.api import Link
#from .permissions import
from .permissions import PERMISSION_BACKUP_JOB_VIEW, PERMISSION_BACKUP_JOB_CREATE, PERMISSION_BACKUP_JOB_EDIT, PERMISSION_BACKUP_JOB_DELETE
backup_tool_link = Link(text=_(u'backups'), view='backup_job_list', icon='cd_burn.png', permissions=[PERMISSION_BACKUP_JOB_VIEW])
backup_job_list = Link(text=_(u'backup job list'), view='backup_job_list', sprite='cd_burn', permissions=[PERMISSION_BACKUP_JOB_VIEW])
backup_job_create = Link(text=_(u'create'), view='backup_job_create', sprite='cd_add', permissions=[PERMISSION_BACKUP_JOB_CREATE])
backup_job_edit = Link(text=_(u'edit'), view='backup_job_edit', args='object.pk', sprite='cd_edit', permissions=[PERMISSION_BACKUP_JOB_EDIT])
backup_job_test = Link(text=_(u'test'), view='backup_job_test', args='object.pk', sprite='cd_go')#, permissions=[PERMISSION_BACKUP_JOB_TEST])
backup_job_delete = Link(text=_(u'delete'), view='backup_job_delete', args='object.pk', sprite='cd_delete', permissions=[PERMISSION_BACKUP_JOB_DELETE])
backup_tool_link = Link(text=_(u'backup'), view='backup_view', icon='cd_burn.png')#, permissions=[])
restore_tool_link = Link(text=_(u'restore'), view='restore_view', icon='cd_eject.png')#, permissions=[])

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'BackupJob'
db.create_table('backups_backupjob', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('label', self.gf('django.db.models.fields.CharField')(max_length=64)),
('begin_datetime', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2012, 8, 11, 0, 0))),
('storage_module', self.gf('django.db.models.fields.CharField')(max_length=16)),
('storage_arguments_json', self.gf('django.db.models.fields.TextField')(blank=True)),
))
db.send_create_signal('backups', ['BackupJob'])
# Adding model 'BackupJobApp'
db.create_table('backups_backupjobapp', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('backup_job', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['backups.BackupJob'])),
('app_backup', self.gf('django.db.models.fields.CharField')(max_length=64)),
))
db.send_create_signal('backups', ['BackupJobApp'])
def backwards(self, orm):
# Deleting model 'BackupJob'
db.delete_table('backups_backupjob')
# Deleting model 'BackupJobApp'
db.delete_table('backups_backupjobapp')
models = {
'backups.backupjob': {
'Meta': {'object_name': 'BackupJob'},
'begin_datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 8, 11, 0, 0)'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'label': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'storage_arguments_json': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'storage_module': ('django.db.models.fields.CharField', [], {'max_length': '16'})
},
'backups.backupjobapp': {
'Meta': {'object_name': 'BackupJobApp'},
'app_backup': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'backup_job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['backups.BackupJob']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
}
complete_apps = ['backups']

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'BackupJob.label'
db.delete_column('backups_backupjob', 'label')
# Adding field 'BackupJob.name'
db.add_column('backups_backupjob', 'name',
self.gf('django.db.models.fields.CharField')(default=' ', max_length=64),
keep_default=False)
def backwards(self, orm):
# User chose to not deal with backwards NULL issues for 'BackupJob.label'
raise RuntimeError("Cannot reverse this migration. 'BackupJob.label' and its values cannot be restored.")
# Deleting field 'BackupJob.name'
db.delete_column('backups_backupjob', 'name')
models = {
'backups.backupjob': {
'Meta': {'object_name': 'BackupJob'},
'begin_datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 8, 11, 0, 0)'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'storage_arguments_json': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'storage_module': ('django.db.models.fields.CharField', [], {'max_length': '16'})
},
'backups.backupjobapp': {
'Meta': {'object_name': 'BackupJobApp'},
'app_backup': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'backup_job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['backups.BackupJob']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
}
complete_apps = ['backups']

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'BackupJob.storage_module'
db.delete_column('backups_backupjob', 'storage_module')
# Adding field 'BackupJob.storage_module_name'
db.add_column('backups_backupjob', 'storage_module_name',
self.gf('django.db.models.fields.CharField')(default=' ', max_length=16),
keep_default=False)
def backwards(self, orm):
# User chose to not deal with backwards NULL issues for 'BackupJob.storage_module'
raise RuntimeError("Cannot reverse this migration. 'BackupJob.storage_module' and its values cannot be restored.")
# Deleting field 'BackupJob.storage_module_name'
db.delete_column('backups_backupjob', 'storage_module_name')
models = {
'backups.backupjob': {
'Meta': {'object_name': 'BackupJob'},
'begin_datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 8, 11, 0, 0)'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'storage_arguments_json': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'storage_module_name': ('django.db.models.fields.CharField', [], {'max_length': '16'})
},
'backups.backupjobapp': {
'Meta': {'object_name': 'BackupJobApp'},
'app_backup': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'backup_job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['backups.BackupJob']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
}
complete_apps = ['backups']

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'BackupJob.enabled'
db.add_column('backups_backupjob', 'enabled',
self.gf('django.db.models.fields.BooleanField')(default=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'BackupJob.enabled'
db.delete_column('backups_backupjob', 'enabled')
models = {
'backups.backupjob': {
'Meta': {'object_name': 'BackupJob'},
'begin_datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 8, 11, 0, 0)'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'storage_arguments_json': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'storage_module_name': ('django.db.models.fields.CharField', [], {'max_length': '16'})
},
'backups.backupjobapp': {
'Meta': {'object_name': 'BackupJobApp'},
'app_backup': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'backup_job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['backups.BackupJob']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
}
complete_apps = ['backups']

View File

View File

@@ -1,3 +1,74 @@
from django.db import models
from __future__ import absolute_import
# Create your models here.
import logging
import datetime
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from .api import AppBackup, StorageModuleBase
logger = logging.getLogger(__name__)
class BackupJob(models.Model):
name = models.CharField(max_length=64, verbose_name=_(u'name'))
enabled = models.BooleanField(default=True, verbose_name=_(u'enabled'))
begin_datetime = models.DateTimeField(verbose_name=_(u'begin date and time'), default=lambda: datetime.datetime.now())
# * repetition =
# day - 1 days
# weekly - days of week checkbox
# month - day of month, day of week
# * repetition option field
# * ends
# - never
# - After # ocurrences
# - On date
# * end option field
# * type
# - Full
# - Incremental
storage_module_name = models.CharField(max_length=16, choices=StorageModuleBase.get_as_choices(), verbose_name=_(u'storage module'))
storage_arguments_json = models.TextField(verbose_name=_(u'storage module arguments (in JSON)'), blank=True)
@property
def apps(self):
return self.backupjobapp_set
def __unicode__(self):
return self.name
@property
def storage_module(self):
return StorageModuleBase.get(self.storage_module_name)
def backup(self, dry_run=False):
logger.debug('starting: %s', self)
logger.debug('dry_run: %s' % dry_run)
storage_module = self.storage_module
#TODO: loads
for app in self.apps.all():
app.backup(storage_module(backup_path='/tmp', dry_run=dry_run), dry_run=dry_run)
def save(self, *args, **kwargs):
#dump
super(BackupJob, self).save(*args, **kwargs)
@models.permalink
def get_absolute_url(self):
return ('checkout_info', [self.document.pk])
class Meta:
verbose_name = _(u'document checkout')
verbose_name_plural = _(u'document checkouts')
class BackupJobApp(models.Model):
backup_job = models.ForeignKey(BackupJob)
app_backup = models.CharField(max_length=64, choices=AppBackup.get_as_choices())
#class BackupJobLog

View File

@@ -0,0 +1,12 @@
from __future__ import absolute_import
from django.utils.translation import ugettext_lazy as _
from permissions.models import PermissionNamespace, Permission
namespace = PermissionNamespace('backups', _(u'Backups'))
PERMISSION_BACKUP_JOB_VIEW = Permission.objects.register(namespace, 'backup_job_view', _(u'View a backup job'))
PERMISSION_BACKUP_JOB_CREATE = Permission.objects.register(namespace, 'backup_job_view', _(u'Create backup jobs'))
PERMISSION_BACKUP_JOB_EDIT = Permission.objects.register(namespace, 'backup_job_edit', _(u'Edit an existing backup jobs'))
PERMISSION_BACKUP_JOB_DELETE = Permission.objects.register(namespace, 'backup_job_delete', _(u'Delete an existing backup jobs'))

View File

@@ -1,5 +1,9 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('backups.views',
url(r'^backup/$', 'backup_view', (), 'backup_view'),
url(r'^jobs/list/$', 'backup_job_list', (), 'backup_job_list'),
url(r'^jobs/create/$', 'backup_job_create', (), 'backup_job_create'),
url(r'^jobs/(?P<backup_job_pk>\d+)/edit/$', 'backup_job_edit', (), 'backup_job_edit'),
url(r'^jobs/(?P<backup_job_pk>\d+)/test/$', 'backup_job_test', (), 'backup_job_test'),
#url(r'^jobs/(?P<backup_job_pk>\d+)/delete/$', 'backup_job_delete', (), 'backup_job_delete'),
)

View File

@@ -2,7 +2,7 @@ from __future__ import absolute_import
from django.utils.translation import ugettext_lazy as _
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.contrib import messages
from django.core.urlresolvers import reverse
@@ -10,7 +10,100 @@ from django.core.urlresolvers import reverse
from permissions.models import Permission
from .api import AppBackup, TestStorageModule
#from .permissions import
from .models import BackupJob
from .forms import BackupJobForm
from .permissions import PERMISSION_BACKUP_JOB_VIEW, PERMISSION_BACKUP_JOB_CREATE, PERMISSION_BACKUP_JOB_EDIT
def backup_job_list(request):
pre_object_list = BackupJob.objects.all()
try:
Permission.objects.check_permissions(request.user, [PERMISSION_BACKUP_JOB_VIEW])
except PermissionDenied:
# If user doesn't have global permission, get a list of backup jobs
# for which he/she does have access use it to filter the
# provided object_list
final_object_list = AccessEntry.objects.filter_objects_by_access(PERMISSION_BACKUP_JOB_VIEW, request.user, pre_object_list)
else:
final_object_list = pre_object_list
context = {
'object_list': final_object_list,
'title': _(u'backup jobs'),
'hide_link': True,
#'extra_columns': [
# {'name': _(u'info'), 'attribute': 'info'},
#],
}
return render_to_response('generic_list.html', context,
context_instance=RequestContext(request))
def backup_job_create(request):
Permission.objects.check_permissions(request.user, [PERMISSION_BACKUP_JOB_CREATE])
if request.method == 'POST':
form = BackupJobForm(data=request.POST)
if form.is_valid():
try:
backup_job = form.save()
except Exception, exc:
messages.error(request, _(u'Error creating backup job; %s') % exc)
else:
messages.success(request, _(u'Backup job "%s" created successfully.') % backup_job)
return HttpResponseRedirect(reverse('backup_job_list'))
else:
form = BackupJobForm()
return render_to_response('generic_form.html', {
'form': form,
'title': _(u'Create backup job')
}, context_instance=RequestContext(request))
def backup_job_edit(request, backup_job_pk):
backup_job = get_object_or_404(BackupJob, pk=backup_job_pk)
try:
Permission.objects.check_permissions(request.user, [PERMISSION_BACKUP_JOB_EDIT])
except PermissionDenied:
AccessEntry.objects.check_access(PERMISSION_BACKUP_JOB_EDIT, request.user, backup_job)
if request.method == 'POST':
form = BackupJobForm(data=request.POST, instance=backup_job)
if form.is_valid():
try:
backup_job = form.save()
except Exception, exc:
messages.error(request, _(u'Error editing backup job; %s') % exc)
else:
messages.success(request, _(u'Backup job "%s" edited successfully.') % backup_job)
return HttpResponseRedirect(reverse('backup_job_list'))
else:
form = BackupJobForm(instance=backup_job)
return render_to_response('generic_form.html', {
'form': form,
'object': backup_job,
'title': _(u'Edit backup job: %s') % backup_job
}, context_instance=RequestContext(request))
def backup_job_test(request, backup_job_pk):
backup_job = get_object_or_404(BackupJob, pk=backup_job_pk)
#try:
# Permission.objects.check_permissions(request.user, [PERMISSION_BACKUP_JOB_EDIT])
#except PermissionDenied:
# AccessEntry.objects.check_access(PERMISSION_BACKUP_JOB_EDIT, request.user, backup_job)
try:
backup_job.backup(dry_run=True)
except Exception, exc:
messages.error(request, _(u'Error testing backup job; %s') % exc)
return HttpResponseRedirect(reverse('backup_job_list'))
else:
messages.success(request, _(u'Test for backup job "%s" finished successfully.') % backup_job)
return HttpResponseRedirect(reverse('backup_job_list'))
def backup_view(request):

View File

@@ -14,7 +14,7 @@ from history.permissions import PERMISSION_HISTORY_VIEW
from project_setup.api import register_setup
from acls.api import class_permissions
from statistics.api import register_statistics
from backups.api import AppBackup, ElementBackupModel, ElementBackupFile
from backups.api import AppBackup, ModelBackup, FileBackup
from .models import (Document, DocumentPage,
DocumentPageTransformation, DocumentType, DocumentTypeFilename,
@@ -137,4 +137,4 @@ class_permissions(Document, [
])
register_statistics(get_statistics)
AppBackup('documents', _(u'Documents'), [ElementBackupModel(), ElementBackupFile(document_settings.STORAGE_BACKEND)])
AppBackup('documents', _(u'Documents'), [ModelBackup(), FileBackup(document_settings.STORAGE_BACKEND)])