diskette 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diskette-0.3.0/LICENCE.txt +21 -0
- diskette-0.3.0/MANIFEST.in +13 -0
- diskette-0.3.0/PKG-INFO +92 -0
- diskette-0.3.0/README.rst +42 -0
- diskette-0.3.0/diskette/__init__.py +6 -0
- diskette-0.3.0/diskette/apps.py +7 -0
- diskette-0.3.0/diskette/contrib/__init__.py +0 -0
- diskette-0.3.0/diskette/contrib/composer/__init__.py +0 -0
- diskette-0.3.0/diskette/contrib/composer/collectors.py +9 -0
- diskette-0.3.0/diskette/contrib/composer/processors.py +23 -0
- diskette-0.3.0/diskette/contrib/django_configuration.py +42 -0
- diskette-0.3.0/diskette/core/__init__.py +0 -0
- diskette-0.3.0/diskette/core/applications/__init__.py +9 -0
- diskette-0.3.0/diskette/core/applications/definitions.py +333 -0
- diskette-0.3.0/diskette/core/applications/lookupstore.py +412 -0
- diskette-0.3.0/diskette/core/applications/store.py +16 -0
- diskette-0.3.0/diskette/core/defaults.py +9 -0
- diskette-0.3.0/diskette/core/dumper.py +496 -0
- diskette-0.3.0/diskette/core/handlers/__init__.py +8 -0
- diskette-0.3.0/diskette/core/handlers/dump.py +352 -0
- diskette-0.3.0/diskette/core/handlers/load.py +107 -0
- diskette-0.3.0/diskette/core/loader.py +248 -0
- diskette-0.3.0/diskette/core/serializers/__init__.py +10 -0
- diskette-0.3.0/diskette/core/serializers/dumpdata.py +175 -0
- diskette-0.3.0/diskette/core/serializers/loaddata.py +111 -0
- diskette-0.3.0/diskette/core/storages.py +137 -0
- diskette-0.3.0/diskette/exceptions.py +97 -0
- diskette-0.3.0/diskette/factories/__init__.py +6 -0
- diskette-0.3.0/diskette/factories/user.py +57 -0
- diskette-0.3.0/diskette/models/__init__.py +1 -0
- diskette-0.3.0/diskette/settings.py +43 -0
- diskette-0.3.0/diskette/urls.py +4 -0
- diskette-0.3.0/diskette/utils/__init__.py +0 -0
- diskette-0.3.0/diskette/utils/filesystem.py +23 -0
- diskette-0.3.0/diskette/utils/jsons.py +27 -0
- diskette-0.3.0/diskette/utils/lists.py +42 -0
- diskette-0.3.0/diskette/utils/loggers.py +111 -0
- diskette-0.3.0/diskette/utils/tests.py +265 -0
- diskette-0.3.0/diskette.egg-info/PKG-INFO +92 -0
- diskette-0.3.0/diskette.egg-info/SOURCES.txt +45 -0
- diskette-0.3.0/diskette.egg-info/dependency_links.txt +1 -0
- diskette-0.3.0/diskette.egg-info/requires.txt +27 -0
- diskette-0.3.0/diskette.egg-info/top_level.txt +2 -0
- diskette-0.3.0/diskette.egg-info/zip-safe +1 -0
- diskette-0.3.0/setup.cfg +104 -0
- diskette-0.3.0/setup.py +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2024, Emencia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
include MANIFEST.in
|
|
2
|
+
include LICENCE.txt
|
|
3
|
+
include README.rst
|
|
4
|
+
|
|
5
|
+
recursive-include diskette/templates *
|
|
6
|
+
recursive-include diskette/static *
|
|
7
|
+
recursive-include diskette/locale *
|
|
8
|
+
|
|
9
|
+
recursive-exclude docs *
|
|
10
|
+
recursive-exclude tests *
|
|
11
|
+
recursive-exclude sandbox *
|
|
12
|
+
recursive-exclude * __pycache__
|
|
13
|
+
recursive-exclude * *.py[co]
|
diskette-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: diskette
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Export and import Django application data and medias
|
|
5
|
+
Home-page: https://github.com/emencia/diskette
|
|
6
|
+
Author: Emencia
|
|
7
|
+
Author-email: support@emencia.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Source Code, https://github.com/emencia/diskette
|
|
10
|
+
Project-URL: Issue Tracker, https://github.com/emencia/diskette/issues
|
|
11
|
+
Project-URL: Changelog, https://diskette.readthedocs.io/en/latest/history.html
|
|
12
|
+
Project-URL: Documentation, https://diskette.readthedocs.io/
|
|
13
|
+
Keywords: Python,Django
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Natural Language :: English
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Framework :: Django
|
|
22
|
+
Classifier: Framework :: Django :: 4.0
|
|
23
|
+
Classifier: Framework :: Django :: 4.1
|
|
24
|
+
Classifier: Framework :: Django :: 4.2
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/x-rst
|
|
27
|
+
License-File: LICENCE.txt
|
|
28
|
+
Requires-Dist: Django<5.0,>=4.0
|
|
29
|
+
Requires-Dist: django-sendfile2>=0.7.0
|
|
30
|
+
Requires-Dist: datalookup>=1.0.0
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-django; extra == "dev"
|
|
34
|
+
Requires-Dist: factory-boy; extra == "dev"
|
|
35
|
+
Requires-Dist: pyquery; extra == "dev"
|
|
36
|
+
Requires-Dist: freezegun; extra == "dev"
|
|
37
|
+
Provides-Extra: quality
|
|
38
|
+
Requires-Dist: flake8; extra == "quality"
|
|
39
|
+
Requires-Dist: tox; extra == "quality"
|
|
40
|
+
Provides-Extra: doc
|
|
41
|
+
Requires-Dist: sphinx; extra == "doc"
|
|
42
|
+
Requires-Dist: furo==2023.7.26; extra == "doc"
|
|
43
|
+
Requires-Dist: sphinx-copybutton==0.5.2; extra == "doc"
|
|
44
|
+
Requires-Dist: tabulate; extra == "doc"
|
|
45
|
+
Requires-Dist: project-composer; extra == "doc"
|
|
46
|
+
Provides-Extra: doc-live
|
|
47
|
+
Requires-Dist: livereload; extra == "doc-live"
|
|
48
|
+
Provides-Extra: release
|
|
49
|
+
Requires-Dist: twine; extra == "release"
|
|
50
|
+
|
|
51
|
+
.. _Python: https://www.python.org/
|
|
52
|
+
.. _Django: https://www.djangoproject.com/
|
|
53
|
+
.. _django-sendfile2: https://github.com/moggers87/django-sendfile2
|
|
54
|
+
.. _datalookup: https://datalookup.readthedocs.io/
|
|
55
|
+
|
|
56
|
+
========
|
|
57
|
+
Diskette
|
|
58
|
+
========
|
|
59
|
+
|
|
60
|
+
Export and import Django application datas and medias.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
Features
|
|
64
|
+
********
|
|
65
|
+
|
|
66
|
+
* Based on *Django apps* to know about applications and their models;
|
|
67
|
+
* Application datas are dumped with Django ``dumpdata`` command as JSON fixtures, dumps
|
|
68
|
+
can be naturally loaded in any database type using Django command ``loaddata``;
|
|
69
|
+
* Define application to be dumped with multiple options;
|
|
70
|
+
* Advanced data drainage for undefined applications;
|
|
71
|
+
* Media archiving is done through *Storages* (not *Django storages*) that can be
|
|
72
|
+
whatever directory you need to backup;
|
|
73
|
+
* Many excluding rules for datas and storages to avoid useless content in archive;
|
|
74
|
+
* Build a complete archive that can be automatically loaded with Diskette or manually;
|
|
75
|
+
* Support models made with ``django-polymorphic``;
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
Dependancies
|
|
79
|
+
************
|
|
80
|
+
|
|
81
|
+
* `Python`_>=3.9;
|
|
82
|
+
* `Django`_>=4.0,<5.0;
|
|
83
|
+
* `django-sendfile2`_>=0.7.0;
|
|
84
|
+
* `datalookup`_>=1.0.0;
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
Links
|
|
88
|
+
*****
|
|
89
|
+
|
|
90
|
+
* Read the documentation on `Read the docs <https://diskette.readthedocs.io/>`_;
|
|
91
|
+
* Download its `PyPi package <https://pypi.python.org/pypi/diskette>`_;
|
|
92
|
+
* Clone it on its `Github repository <https://github.com/emencia/diskette>`_;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
.. _Python: https://www.python.org/
|
|
2
|
+
.. _Django: https://www.djangoproject.com/
|
|
3
|
+
.. _django-sendfile2: https://github.com/moggers87/django-sendfile2
|
|
4
|
+
.. _datalookup: https://datalookup.readthedocs.io/
|
|
5
|
+
|
|
6
|
+
========
|
|
7
|
+
Diskette
|
|
8
|
+
========
|
|
9
|
+
|
|
10
|
+
Export and import Django application datas and medias.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Features
|
|
14
|
+
********
|
|
15
|
+
|
|
16
|
+
* Based on *Django apps* to know about applications and their models;
|
|
17
|
+
* Application datas are dumped with Django ``dumpdata`` command as JSON fixtures, dumps
|
|
18
|
+
can be naturally loaded in any database type using Django command ``loaddata``;
|
|
19
|
+
* Define application to be dumped with multiple options;
|
|
20
|
+
* Advanced data drainage for undefined applications;
|
|
21
|
+
* Media archiving is done through *Storages* (not *Django storages*) that can be
|
|
22
|
+
whatever directory you need to backup;
|
|
23
|
+
* Many excluding rules for datas and storages to avoid useless content in archive;
|
|
24
|
+
* Build a complete archive that can be automatically loaded with Diskette or manually;
|
|
25
|
+
* Support models made with ``django-polymorphic``;
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
Dependancies
|
|
29
|
+
************
|
|
30
|
+
|
|
31
|
+
* `Python`_>=3.9;
|
|
32
|
+
* `Django`_>=4.0,<5.0;
|
|
33
|
+
* `django-sendfile2`_>=0.7.0;
|
|
34
|
+
* `datalookup`_>=1.0.0;
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Links
|
|
38
|
+
*****
|
|
39
|
+
|
|
40
|
+
* Read the documentation on `Read the docs <https://diskette.readthedocs.io/>`_;
|
|
41
|
+
* Download its `PyPi package <https://pypi.python.org/pypi/diskette>`_;
|
|
42
|
+
* Clone it on its `Github repository <https://github.com/emencia/diskette>`_;
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from project_composer.processors import ClassProcessor
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DisketteDefinitionsProcessor(ClassProcessor):
|
|
5
|
+
"""
|
|
6
|
+
'project-composer' Processor to collect Diskette application definitions from
|
|
7
|
+
enabled applications.
|
|
8
|
+
"""
|
|
9
|
+
def get_module_path(self, name):
|
|
10
|
+
"""
|
|
11
|
+
Return a Python path for a module name.
|
|
12
|
+
|
|
13
|
+
Arguments:
|
|
14
|
+
name (string): Module name.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
string: Module name prefixed with repository path if it is not empty else
|
|
18
|
+
returns just the module name.
|
|
19
|
+
"""
|
|
20
|
+
return "{base}.{part}".format(
|
|
21
|
+
base=self.composer.get_application_base_module_path(name),
|
|
22
|
+
part="disk",
|
|
23
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
.. _django-configuration: https://django-configurations.readthedocs.io/en/stable/
|
|
3
|
+
"""
|
|
4
|
+
from ..settings import (
|
|
5
|
+
DISKETTE_APPS,
|
|
6
|
+
DISKETTE_STORAGES,
|
|
7
|
+
DISKETTE_STORAGES_EXCLUDES,
|
|
8
|
+
DISKETTE_DUMP_PATH,
|
|
9
|
+
DISKETTE_DUMP_FILENAME,
|
|
10
|
+
DISKETTE_LOAD_STORAGES_PATH,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DisketteDefaultSettings:
|
|
15
|
+
"""
|
|
16
|
+
Default Diskette settings class to use with a `django-configuration`_ class.
|
|
17
|
+
|
|
18
|
+
You could use it like so: ::
|
|
19
|
+
|
|
20
|
+
from configurations import Configuration
|
|
21
|
+
from diskette.contrib.django_configuration import DisketteDefaultSettings
|
|
22
|
+
|
|
23
|
+
class Dev(DisketteDefaultSettings, Configuration):
|
|
24
|
+
DEBUG = True
|
|
25
|
+
|
|
26
|
+
DISKETTE_DUMP_FILENAME = "foo.tar.gz"
|
|
27
|
+
|
|
28
|
+
This will override only the setting ``DISKETTE_DUMP_FILENAME``, all other
|
|
29
|
+
Diskette settings will have the default values from ``diskette.settings``.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
DISKETTE_APPS = DISKETTE_APPS
|
|
33
|
+
|
|
34
|
+
DISKETTE_STORAGES = DISKETTE_STORAGES
|
|
35
|
+
|
|
36
|
+
DISKETTE_STORAGES_EXCLUDES = DISKETTE_STORAGES_EXCLUDES
|
|
37
|
+
|
|
38
|
+
DISKETTE_DUMP_PATH = DISKETTE_DUMP_PATH
|
|
39
|
+
|
|
40
|
+
DISKETTE_DUMP_FILENAME = DISKETTE_DUMP_FILENAME
|
|
41
|
+
|
|
42
|
+
DISKETTE_LOAD_STORAGES_PATH = DISKETTE_LOAD_STORAGES_PATH
|
|
File without changes
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from django.utils.text import slugify
|
|
4
|
+
|
|
5
|
+
from ...exceptions import ApplicationConfigError
|
|
6
|
+
from ..defaults import DEFAULT_FORMAT, AVAILABLE_FORMATS
|
|
7
|
+
|
|
8
|
+
from .store import get_appstore
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
appstore = get_appstore()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ApplicationConfig:
|
|
15
|
+
"""
|
|
16
|
+
Application model to validate and store application details.
|
|
17
|
+
|
|
18
|
+
TODO: Another name would better to avoid mental clash with Django "AppConfig".
|
|
19
|
+
ApplicationModel (+2) ? ApplicationDataDef (0)? ApplicationDefinition (+3) ?
|
|
20
|
+
DataDefinition (+3) ?
|
|
21
|
+
|
|
22
|
+
Arguments:
|
|
23
|
+
name (string): Application name, almost anything but it may be slugified for
|
|
24
|
+
internal usages so avoid too much longer text and special characters.
|
|
25
|
+
models (list): List of labels. A label is either an application label like
|
|
26
|
+
``auth`` or a full model label like ``auth.user``. Labels are validated,
|
|
27
|
+
they must exists in Django application registry.
|
|
28
|
+
|
|
29
|
+
Keyword Arguments:
|
|
30
|
+
filename (string): The filename to use if application dump is to be written in
|
|
31
|
+
a file. The filename also determine the format used to dump data. If you
|
|
32
|
+
want another format that the default one you will have to define
|
|
33
|
+
it from the filename even you don't plan to write dump to a file.
|
|
34
|
+
Finally if not given, the filename will be automatically defined with
|
|
35
|
+
slugified ``name`` with default format.
|
|
36
|
+
excludes (list): The list of excluded model labels that won't be collected
|
|
37
|
+
into the application dump. Currently, the excluded labels are not
|
|
38
|
+
validated.
|
|
39
|
+
natural_foreign (boolean): Enable usage of natural foreign key.
|
|
40
|
+
natural_primary (boolean): Enable usage of natural primary key.
|
|
41
|
+
comments (string): Free text not used internally.
|
|
42
|
+
allow_drain (boolean): Define if application allows its excluded models to be
|
|
43
|
+
drained. Default is ``False`` to avoid implicit draining of data that may
|
|
44
|
+
not be wanted.
|
|
45
|
+
dump_command (string): Custom dump command to use for this specific application
|
|
46
|
+
instead of default ``dumpdata``. If you have models that use
|
|
47
|
+
``django-polymorphic`` you should give value ``polymorphic_dumpdata`` here.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
is_drain (boolean): Declare application as a special drain application. This
|
|
51
|
+
should always be ``False`` for a common application, ``True`` value is
|
|
52
|
+
reserved to ``DrainApplicationConfig``.
|
|
53
|
+
"""
|
|
54
|
+
CONFIG_ATTRS = [
|
|
55
|
+
"name", "models", "excludes", "retention", "natural_foreign", "natural_primary",
|
|
56
|
+
"comments", "filename", "is_drain", "allow_drain", "dump_command",
|
|
57
|
+
]
|
|
58
|
+
OPTIONS_ATTRS = [
|
|
59
|
+
"models", "excludes", "natural_foreign", "natural_primary", "filename",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
def __init__(self, name, models=[], excludes=None, natural_foreign=False,
|
|
63
|
+
natural_primary=False, comments=None, filename=None,
|
|
64
|
+
is_drain=None, allow_drain=False, dump_command=None):
|
|
65
|
+
self.name = name
|
|
66
|
+
self._models = [models] if isinstance(models, str) else models
|
|
67
|
+
self._excludes = excludes or []
|
|
68
|
+
self.natural_foreign = natural_foreign
|
|
69
|
+
self.natural_primary = natural_primary
|
|
70
|
+
self.comments = comments
|
|
71
|
+
self.filename = filename or self.get_filename()
|
|
72
|
+
self.allow_drain = allow_drain
|
|
73
|
+
self.dump_command = dump_command
|
|
74
|
+
self.is_drain = False
|
|
75
|
+
|
|
76
|
+
def __repr__(self):
|
|
77
|
+
return "<{klass}: {name}>".format(
|
|
78
|
+
klass=self.__class__.__name__,
|
|
79
|
+
name=self.name
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def models(self):
|
|
84
|
+
"""
|
|
85
|
+
List all fully qualified model labels to include, either implicitely and
|
|
86
|
+
explicitely from given ``models`` argument.
|
|
87
|
+
|
|
88
|
+
NOTE: models, excludes and retention attributes may be cached since they have
|
|
89
|
+
no reason to change.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
list: Fully qualified model labels.
|
|
93
|
+
"""
|
|
94
|
+
return [
|
|
95
|
+
model.label
|
|
96
|
+
for model in appstore.get_models_inclusions(
|
|
97
|
+
self._models, excludes=self._excludes
|
|
98
|
+
)
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def excludes(self):
|
|
103
|
+
"""
|
|
104
|
+
List all fully qualified model labels to exclude, either implicitely and
|
|
105
|
+
explicitely from given ``_excludes`` argument.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
list: Fully qualified model labels.
|
|
109
|
+
"""
|
|
110
|
+
return [
|
|
111
|
+
model.label
|
|
112
|
+
for model in appstore.get_models_exclusions(
|
|
113
|
+
self._models, excludes=self._excludes
|
|
114
|
+
)
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def retention(self):
|
|
119
|
+
"""
|
|
120
|
+
List all fully qualified model labels that are not allowed to be drained from
|
|
121
|
+
this application.
|
|
122
|
+
|
|
123
|
+
Included models are never allowed to be drained and exclusions models may be
|
|
124
|
+
allowed if ``allow_drain`` is enabled.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
list: Fully qualified model labels that won't be allowed to be drained.
|
|
128
|
+
This means labels from ``models`` on defaut and possibly the
|
|
129
|
+
``excludes`` one also if application allows for drainage.
|
|
130
|
+
"""
|
|
131
|
+
if not self.allow_drain:
|
|
132
|
+
return self.models + self.excludes
|
|
133
|
+
|
|
134
|
+
return self.models
|
|
135
|
+
|
|
136
|
+
def get_filename(self, format_extension=None):
|
|
137
|
+
"""
|
|
138
|
+
Automatically determine filename from Application name and with a format
|
|
139
|
+
extension name.
|
|
140
|
+
|
|
141
|
+
Keyword Arguments:
|
|
142
|
+
format_extension (string): Custom extension to use if given.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
string: Filename.
|
|
146
|
+
"""
|
|
147
|
+
return slugify(self.name) + "." + (format_extension or DEFAULT_FORMAT)
|
|
148
|
+
|
|
149
|
+
def as_config(self):
|
|
150
|
+
"""
|
|
151
|
+
Returns Application configuration suitable for dump history.
|
|
152
|
+
|
|
153
|
+
Keyword Arguments:
|
|
154
|
+
name (boolean): To include or not the name into the dict.
|
|
155
|
+
commented (boolean): To include or not the comments into the dict.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
dict: Application data.
|
|
159
|
+
"""
|
|
160
|
+
return {
|
|
161
|
+
name: getattr(self, name)
|
|
162
|
+
for name in self.CONFIG_ATTRS
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
def as_options(self):
|
|
166
|
+
"""
|
|
167
|
+
Returns Application options suitable to pass to dumpdata command.
|
|
168
|
+
|
|
169
|
+
Keyword Arguments:
|
|
170
|
+
name (boolean): To include or not the name into the dict.
|
|
171
|
+
commented (boolean): To include or not the comments into the dict.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
dict: Application data.
|
|
175
|
+
"""
|
|
176
|
+
return {
|
|
177
|
+
name: getattr(self, name)
|
|
178
|
+
for name in self.OPTIONS_ATTRS
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
def validate_includes(self):
|
|
182
|
+
"""
|
|
183
|
+
Validate include labels from ``_models`` attribute.
|
|
184
|
+
"""
|
|
185
|
+
# Models are required but not for an application drain object
|
|
186
|
+
if not self._models and self.is_drain is False:
|
|
187
|
+
msg = "{obj}: 'models' must not be an empty value."
|
|
188
|
+
raise ApplicationConfigError(msg.format(
|
|
189
|
+
obj=self.__repr__(),
|
|
190
|
+
))
|
|
191
|
+
|
|
192
|
+
unknow_apps, unknow_models = appstore.check_unexisting_labels(self._models)
|
|
193
|
+
|
|
194
|
+
if len(unknow_apps) > 0:
|
|
195
|
+
msg = (
|
|
196
|
+
"{obj}: Some given app labels to include does not exists: {labels}"
|
|
197
|
+
)
|
|
198
|
+
raise ApplicationConfigError(msg.format(
|
|
199
|
+
obj=self.__repr__(),
|
|
200
|
+
labels=", ".join(unknow_apps),
|
|
201
|
+
))
|
|
202
|
+
|
|
203
|
+
if len(unknow_models) > 0:
|
|
204
|
+
msg = (
|
|
205
|
+
"{obj}: Some given models labels to include does not exists: {labels}"
|
|
206
|
+
)
|
|
207
|
+
raise ApplicationConfigError(msg.format(
|
|
208
|
+
obj=self.__repr__(),
|
|
209
|
+
labels=", ".join(unknow_models),
|
|
210
|
+
))
|
|
211
|
+
|
|
212
|
+
def validate_excludes(self):
|
|
213
|
+
"""
|
|
214
|
+
Validate exclude labels from ``excludes`` attribute.
|
|
215
|
+
"""
|
|
216
|
+
if not isinstance(self._excludes, list):
|
|
217
|
+
msg = "{obj}: 'excludes' argument must be a list."
|
|
218
|
+
raise ApplicationConfigError(msg.format(
|
|
219
|
+
obj=self.__repr__(),
|
|
220
|
+
))
|
|
221
|
+
else:
|
|
222
|
+
errors = appstore.is_fully_qualified_labels(self._excludes)
|
|
223
|
+
if errors:
|
|
224
|
+
msg = (
|
|
225
|
+
"{obj}: 'excludes' argument can only contains fully qualified "
|
|
226
|
+
"labels (like 'foo.bar') these ones are invalid: {labels}"
|
|
227
|
+
)
|
|
228
|
+
raise ApplicationConfigError(msg.format(
|
|
229
|
+
obj=self.__repr__(),
|
|
230
|
+
labels=", ".join(errors),
|
|
231
|
+
))
|
|
232
|
+
|
|
233
|
+
unknow_apps, unknow_models = appstore.check_unexisting_labels(self._excludes)
|
|
234
|
+
|
|
235
|
+
if len(unknow_apps) > 0:
|
|
236
|
+
msg = (
|
|
237
|
+
"{obj}: Some given app labels to exclude does not exists: {labels}"
|
|
238
|
+
)
|
|
239
|
+
raise ApplicationConfigError(msg.format(
|
|
240
|
+
obj=self.__repr__(),
|
|
241
|
+
labels=", ".join(unknow_apps),
|
|
242
|
+
))
|
|
243
|
+
|
|
244
|
+
if len(unknow_models) > 0:
|
|
245
|
+
msg = (
|
|
246
|
+
"{obj}: Some given models labels to exclude does not exists: {labels}"
|
|
247
|
+
)
|
|
248
|
+
raise ApplicationConfigError(msg.format(
|
|
249
|
+
obj=self.__repr__(),
|
|
250
|
+
labels=", ".join(unknow_models),
|
|
251
|
+
))
|
|
252
|
+
|
|
253
|
+
def validate_filename(self):
|
|
254
|
+
# Filename must have a file extension to discover serialization format
|
|
255
|
+
extension = Path(self.filename).suffix
|
|
256
|
+
if not extension:
|
|
257
|
+
msg = (
|
|
258
|
+
"{obj}: Given file name '{filename}' must have a file extension to "
|
|
259
|
+
"discover format."
|
|
260
|
+
)
|
|
261
|
+
raise ApplicationConfigError(msg.format(
|
|
262
|
+
obj=self.__repr__(),
|
|
263
|
+
filename=self.filename,
|
|
264
|
+
))
|
|
265
|
+
# File extension must correspond to an allowed format
|
|
266
|
+
else:
|
|
267
|
+
# Remove leading dot
|
|
268
|
+
extension = extension[1:]
|
|
269
|
+
if extension not in AVAILABLE_FORMATS:
|
|
270
|
+
msg = (
|
|
271
|
+
"{obj}: Given file name '{filename}' must use a file extension "
|
|
272
|
+
"from allowed formats: {formats}"
|
|
273
|
+
)
|
|
274
|
+
raise ApplicationConfigError(msg.format(
|
|
275
|
+
obj=self.__repr__(),
|
|
276
|
+
filename=self.filename,
|
|
277
|
+
formats=", ".join(AVAILABLE_FORMATS),
|
|
278
|
+
))
|
|
279
|
+
|
|
280
|
+
def validate(self):
|
|
281
|
+
"""
|
|
282
|
+
Validate Application options.
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
ApplicationConfigError: In case of invalid values from options.
|
|
286
|
+
"""
|
|
287
|
+
self.validate_filename()
|
|
288
|
+
self.validate_includes()
|
|
289
|
+
self.validate_excludes()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class DrainApplicationConfig(ApplicationConfig):
|
|
293
|
+
"""
|
|
294
|
+
Special application to drain remaining models from apps.
|
|
295
|
+
|
|
296
|
+
On default a drain will dump anything that have not been defined from apps. Its
|
|
297
|
+
base goal is to dump data from undefined applications.
|
|
298
|
+
|
|
299
|
+
Attributes:
|
|
300
|
+
drain_excluded (boolean): If enabled, the drain will accept to drain exclusion
|
|
301
|
+
from applications which allow it. Else the drain will exclude also the
|
|
302
|
+
application exclusion. Default is disabled.
|
|
303
|
+
"""
|
|
304
|
+
CONFIG_ATTRS = [
|
|
305
|
+
"name", "models", "excludes", "natural_foreign", "natural_primary", "comments",
|
|
306
|
+
"filename", "is_drain", "drain_excluded", "dump_command",
|
|
307
|
+
]
|
|
308
|
+
OPTIONS_ATTRS = [
|
|
309
|
+
"models", "excludes", "natural_foreign", "natural_primary", "filename",
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
def __init__(self, *args, **kwargs):
|
|
313
|
+
self.drain_excluded = kwargs.pop("drain_excluded", False)
|
|
314
|
+
|
|
315
|
+
super().__init__(*args, **kwargs)
|
|
316
|
+
|
|
317
|
+
# Drain never allow for any inclusion
|
|
318
|
+
self._models = []
|
|
319
|
+
# Force as a drain only
|
|
320
|
+
self.is_drain = True
|
|
321
|
+
# It is not allowed to be drained itself
|
|
322
|
+
self.allow_drain = False
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def excludes(self):
|
|
326
|
+
"""
|
|
327
|
+
Just returns exclude labels as given since drain excludes are meaningful
|
|
328
|
+
enough.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
list: Fully qualified model labels.
|
|
332
|
+
"""
|
|
333
|
+
return self._excludes
|