bazis-uploadable 2.2.0__py3-none-any.whl
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.
- bazis/contrib/uploadable/__init__.py +19 -0
- bazis/contrib/uploadable/_version.py +34 -0
- bazis/contrib/uploadable/admin.py +25 -0
- bazis/contrib/uploadable/apps.py +22 -0
- bazis/contrib/uploadable/conf.py +26 -0
- bazis/contrib/uploadable/locale/en/LC_MESSAGES/django.mo +0 -0
- bazis/contrib/uploadable/locale/en/LC_MESSAGES/django.po +39 -0
- bazis/contrib/uploadable/locale/ru/LC_MESSAGES/django.mo +0 -0
- bazis/contrib/uploadable/locale/ru/LC_MESSAGES/django.po +41 -0
- bazis/contrib/uploadable/migrations/0001_initial.py +47 -0
- bazis/contrib/uploadable/migrations/0002_alter_fileupload_file.py +34 -0
- bazis/contrib/uploadable/migrations/__init__.py +14 -0
- bazis/contrib/uploadable/models.py +19 -0
- bazis/contrib/uploadable/models_abstract.py +63 -0
- bazis/contrib/uploadable/router.py +23 -0
- bazis/contrib/uploadable/routes.py +81 -0
- bazis_uploadable-2.2.0.dist-info/METADATA +682 -0
- bazis_uploadable-2.2.0.dist-info/RECORD +20 -0
- bazis_uploadable-2.2.0.dist-info/WHEEL +5 -0
- bazis_uploadable-2.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
17
|
+
__version__ = version('bazis-uploadable')
|
|
18
|
+
except PackageNotFoundError:
|
|
19
|
+
__version__ = 'dev'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '2.2.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 2, 0)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from django.contrib import admin
|
|
16
|
+
|
|
17
|
+
from . import models
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@admin.register(models.FileUpload)
|
|
21
|
+
class FileUploadAdmin(admin.ModelAdmin):
|
|
22
|
+
list_display = ('name',)
|
|
23
|
+
list_filter = ('extension',)
|
|
24
|
+
search_fields = ('name', 'extension')
|
|
25
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from django.utils.translation import gettext_lazy as _
|
|
16
|
+
|
|
17
|
+
from bazis.core.apps import BaseConfig
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UploadableConfig(BaseConfig):
|
|
21
|
+
name = 'bazis.contrib.uploadable'
|
|
22
|
+
verbose_name = _('Uploadable files')
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from django.utils.translation import gettext_lazy as _
|
|
16
|
+
|
|
17
|
+
from pydantic import Field
|
|
18
|
+
|
|
19
|
+
from bazis.core.utils.schemas import BazisSettings
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Settings(BazisSettings):
|
|
23
|
+
BAZIS_STORAGE_FILE_UPLOAD: str = Field('', title=_('Uploadable files storage engine'))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
settings = Settings()
|
|
Binary file
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
|
+
#
|
|
6
|
+
#, fuzzy
|
|
7
|
+
msgid ""
|
|
8
|
+
msgstr ""
|
|
9
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
|
+
"Report-Msgid-Bugs-To: \n"
|
|
11
|
+
"POT-Creation-Date: 2024-07-03 15:09+0000\n"
|
|
12
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
15
|
+
"Language: \n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
20
|
+
|
|
21
|
+
#: apps.py:8 models_abstract.py:45 router.py:8
|
|
22
|
+
msgid "Uploadable files"
|
|
23
|
+
msgstr ""
|
|
24
|
+
|
|
25
|
+
#: conf.py:9
|
|
26
|
+
msgid "Uploadable files storage engine"
|
|
27
|
+
msgstr ""
|
|
28
|
+
|
|
29
|
+
#: models_abstract.py:22 models_abstract.py:44
|
|
30
|
+
msgid "File"
|
|
31
|
+
msgstr ""
|
|
32
|
+
|
|
33
|
+
#: models_abstract.py:23
|
|
34
|
+
msgid "Name"
|
|
35
|
+
msgstr ""
|
|
36
|
+
|
|
37
|
+
#: models_abstract.py:24
|
|
38
|
+
msgid "Extension"
|
|
39
|
+
msgstr ""
|
|
Binary file
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
|
+
#
|
|
6
|
+
#, fuzzy
|
|
7
|
+
msgid ""
|
|
8
|
+
msgstr ""
|
|
9
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
|
+
"Report-Msgid-Bugs-To: \n"
|
|
11
|
+
"POT-Creation-Date: 2024-07-03 15:09+0000\n"
|
|
12
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
13
|
+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
14
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
15
|
+
"Language: \n"
|
|
16
|
+
"MIME-Version: 1.0\n"
|
|
17
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
18
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
19
|
+
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
|
20
|
+
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
|
|
21
|
+
"%100>=11 && n%100<=14)? 2 : 3);\n"
|
|
22
|
+
|
|
23
|
+
#: apps.py:8 models_abstract.py:45 router.py:8
|
|
24
|
+
msgid "Uploadable files"
|
|
25
|
+
msgstr "Загружаемые файлы"
|
|
26
|
+
|
|
27
|
+
#: conf.py:9
|
|
28
|
+
msgid "Uploadable files storage engine"
|
|
29
|
+
msgstr "Движок хранилища загружаемых файлов"
|
|
30
|
+
|
|
31
|
+
#: models_abstract.py:22 models_abstract.py:44
|
|
32
|
+
msgid "File"
|
|
33
|
+
msgstr "Файл"
|
|
34
|
+
|
|
35
|
+
#: models_abstract.py:23
|
|
36
|
+
msgid "Name"
|
|
37
|
+
msgstr "Наименование"
|
|
38
|
+
|
|
39
|
+
#: models_abstract.py:24
|
|
40
|
+
msgid "Extension"
|
|
41
|
+
msgstr "Расширение"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# Generated by Django 5.0.6 on 2024-07-03 13:40
|
|
16
|
+
|
|
17
|
+
import django.core.files.storage.filesystem
|
|
18
|
+
from django.db import migrations, models
|
|
19
|
+
|
|
20
|
+
import bazis.core.utils.orm
|
|
21
|
+
import model_clone.mixin
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Migration(migrations.Migration):
|
|
25
|
+
|
|
26
|
+
initial = True
|
|
27
|
+
|
|
28
|
+
dependencies = [
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
operations = [
|
|
32
|
+
migrations.CreateModel(
|
|
33
|
+
name='FileUpload',
|
|
34
|
+
fields=[
|
|
35
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
36
|
+
('file', models.FileField(storage=django.core.files.storage.filesystem.FileSystemStorage, upload_to=bazis.core.utils.orm.get_file_path, verbose_name='File')),
|
|
37
|
+
('name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Name')),
|
|
38
|
+
('extension', models.CharField(blank=True, max_length=50, null=True, verbose_name='Extension')),
|
|
39
|
+
],
|
|
40
|
+
options={
|
|
41
|
+
'verbose_name': 'File',
|
|
42
|
+
'verbose_name_plural': 'Uploadable files',
|
|
43
|
+
'abstract': False,
|
|
44
|
+
},
|
|
45
|
+
bases=(model_clone.mixin.CloneMixin, models.Model),
|
|
46
|
+
),
|
|
47
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# Generated by Django 5.0.1 on 2025-02-12 13:39
|
|
16
|
+
|
|
17
|
+
import bazis.core.utils.orm
|
|
18
|
+
import django.core.files.storage.filesystem
|
|
19
|
+
from django.db import migrations, models
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Migration(migrations.Migration):
|
|
23
|
+
|
|
24
|
+
dependencies = [
|
|
25
|
+
('uploadable', '0001_initial'),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
operations = [
|
|
29
|
+
migrations.AlterField(
|
|
30
|
+
model_name='fileupload',
|
|
31
|
+
name='file',
|
|
32
|
+
field=models.FileField(max_length=255, storage=django.core.files.storage.filesystem.FileSystemStorage, upload_to=bazis.core.utils.orm.get_file_path, verbose_name='File'),
|
|
33
|
+
),
|
|
34
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from bazis.contrib.uploadable.models_abstract import FileUploadAbstract
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FileUpload(FileUploadAbstract):
|
|
19
|
+
pass
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
from django.conf import settings
|
|
18
|
+
from django.core.files.storage import FileSystemStorage
|
|
19
|
+
from django.db import models
|
|
20
|
+
from django.utils.functional import cached_property
|
|
21
|
+
from django.utils.translation import gettext_lazy as _
|
|
22
|
+
|
|
23
|
+
from bazis.core.models_abstract import JsonApiMixin
|
|
24
|
+
from bazis.core.utils.imp import import_class
|
|
25
|
+
from bazis.core.utils.orm import get_file_path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if settings.BAZIS_STORAGE_FILE_UPLOAD:
|
|
29
|
+
FileStorage = import_class(settings.BAZIS_STORAGE_FILE_UPLOAD)
|
|
30
|
+
else:
|
|
31
|
+
FileStorage = FileSystemStorage
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FileUploadAbstract(JsonApiMixin):
|
|
35
|
+
file = models.FileField(_('File'), upload_to=get_file_path, max_length=255)
|
|
36
|
+
name = models.CharField(_('Name'), max_length=255, blank=True, null=True)
|
|
37
|
+
extension = models.CharField(_('Extension'), max_length=50, blank=True, null=True)
|
|
38
|
+
|
|
39
|
+
def __init__(self, *args, **kwargs):
|
|
40
|
+
super().__init__(*args, **kwargs)
|
|
41
|
+
self._meta.get_field('file').storage = FileStorage()
|
|
42
|
+
|
|
43
|
+
@cached_property
|
|
44
|
+
def size(self) -> int | None:
|
|
45
|
+
try:
|
|
46
|
+
return self.file.size
|
|
47
|
+
except FileNotFoundError:
|
|
48
|
+
return 0
|
|
49
|
+
|
|
50
|
+
def __str__(self):
|
|
51
|
+
return self.file.name
|
|
52
|
+
|
|
53
|
+
def save(self, *args, **kwargs):
|
|
54
|
+
if not self.name:
|
|
55
|
+
self.name = os.path.basename(self.file.name)
|
|
56
|
+
_, self.extension = (os.path.splitext(self.name) + ('',))[:2]
|
|
57
|
+
self.extension = self.extension[1:]
|
|
58
|
+
return super().save(*args, **kwargs)
|
|
59
|
+
|
|
60
|
+
class Meta:
|
|
61
|
+
verbose_name = _('File')
|
|
62
|
+
verbose_name_plural = _('Uploadable files')
|
|
63
|
+
abstract = True
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from django.utils.translation import gettext_lazy as _
|
|
16
|
+
|
|
17
|
+
from bazis.core.routing import BazisRouter
|
|
18
|
+
|
|
19
|
+
from .routes import FileUploadRouteSet
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
router = BazisRouter(tags=[_('Uploadable files')])
|
|
23
|
+
router.register(FileUploadRouteSet.as_router())
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Copyright 2026 EcoFuture Technology Services LLC and contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from functools import partial
|
|
16
|
+
|
|
17
|
+
from django.apps import apps
|
|
18
|
+
|
|
19
|
+
from fastapi import Form, Request
|
|
20
|
+
|
|
21
|
+
from bazis.core.routes_abstract.initial import http_post
|
|
22
|
+
from bazis.core.routes_abstract.jsonapi import (
|
|
23
|
+
JsonapiRouteBase,
|
|
24
|
+
api_action_init,
|
|
25
|
+
api_action_jsonapi_init,
|
|
26
|
+
api_action_response_init,
|
|
27
|
+
item_data_typing,
|
|
28
|
+
meta_fields_addition,
|
|
29
|
+
)
|
|
30
|
+
from bazis.core.schemas import CrudApiAction, SchemaFields
|
|
31
|
+
from bazis.core.utils.django_types import UploadFileDjango
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FileUploadRouteSet(JsonapiRouteBase):
|
|
35
|
+
model = apps.get_model('uploadable.FileUpload')
|
|
36
|
+
|
|
37
|
+
fields = {
|
|
38
|
+
None: SchemaFields(
|
|
39
|
+
include={
|
|
40
|
+
'size': None,
|
|
41
|
+
'extension': None,
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@http_post(
|
|
47
|
+
'/',
|
|
48
|
+
status_code=201,
|
|
49
|
+
endpoint_callbacks=[
|
|
50
|
+
partial(meta_fields_addition, api_action=CrudApiAction.CREATE),
|
|
51
|
+
partial(api_action_init, api_action=CrudApiAction.CREATE),
|
|
52
|
+
partial(api_action_response_init, api_action=CrudApiAction.RETRIEVE),
|
|
53
|
+
partial(item_data_typing, api_action=CrudApiAction.CREATE),
|
|
54
|
+
api_action_jsonapi_init,
|
|
55
|
+
],
|
|
56
|
+
)
|
|
57
|
+
def action_create(
|
|
58
|
+
self,
|
|
59
|
+
request: Request,
|
|
60
|
+
file: UploadFileDjango,
|
|
61
|
+
name: str | None = Form(None),
|
|
62
|
+
id: str | None = Form(None),
|
|
63
|
+
**kwargs,
|
|
64
|
+
):
|
|
65
|
+
if not isinstance(file, UploadFileDjango):
|
|
66
|
+
file.__class__ = UploadFileDjango
|
|
67
|
+
|
|
68
|
+
request._json = {
|
|
69
|
+
'data': {
|
|
70
|
+
'id': id,
|
|
71
|
+
'type': self.model.get_resource_label(),
|
|
72
|
+
'attributes': {
|
|
73
|
+
'name': name,
|
|
74
|
+
'file': file,
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
item_data = self.schema_defaults[CrudApiAction.CREATE].model_validate(request._json)
|
|
80
|
+
|
|
81
|
+
return super().action_create.func(self, request, item_data)
|
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bazis-uploadable
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: File upload management module for Bazis framework.
|
|
5
|
+
Author-email: Ilya Kharyn <ilya.tt07@gmail.com>
|
|
6
|
+
Maintainer-email: Ilya Kharyn <ilya.tt07@gmail.com>
|
|
7
|
+
Project-URL: Home, https://github.com/ecofuture-tech/bazis-uploadable
|
|
8
|
+
Keywords: bazis,django,fastapi,pydantic,framework,jsonapi,uploadfiles,storage
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Framework :: Django
|
|
17
|
+
Classifier: Framework :: FastAPI
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: bazis
|
|
21
|
+
Requires-Dist: python-multipart
|
|
22
|
+
Provides-Extra: test
|
|
23
|
+
Requires-Dist: bazis-test-utils; extra == "test"
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: ruff; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# Bazis Uploadable
|
|
28
|
+
|
|
29
|
+
[](https://pypi.org/project/bazis-uploadable/)
|
|
30
|
+
[](https://pypi.org/project/bazis-uploadable/)
|
|
31
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
32
|
+
|
|
33
|
+
An extension package for Bazis, providing a ready-to-use solution for file uploading and management with support for various storage backends.
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Install the package
|
|
39
|
+
uv add bazis-uploadable
|
|
40
|
+
|
|
41
|
+
# Create a model for file uploads
|
|
42
|
+
from bazis.contrib.uploadable.models_abstract import FileUploadAbstract
|
|
43
|
+
from bazis.core.models_abstract import DtMixin, UuidMixin
|
|
44
|
+
|
|
45
|
+
class Document(FileUploadAbstract, DtMixin, UuidMixin):
|
|
46
|
+
"""Uploadable document"""
|
|
47
|
+
|
|
48
|
+
class Meta:
|
|
49
|
+
verbose_name = 'Document'
|
|
50
|
+
verbose_name_plural = 'Documents'
|
|
51
|
+
|
|
52
|
+
# Create a route for uploads
|
|
53
|
+
from bazis.contrib.uploadable.routes_abstract import FileUploadRouteSet
|
|
54
|
+
from django.apps import apps
|
|
55
|
+
|
|
56
|
+
class DocumentRouteSet(FileUploadRouteSet):
|
|
57
|
+
model = apps.get_model('myapp.Document')
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Table of Contents
|
|
61
|
+
|
|
62
|
+
- [Description](#description)
|
|
63
|
+
- [Requirements](#requirements)
|
|
64
|
+
- [Installation](#installation)
|
|
65
|
+
- [Core Components](#core-components)
|
|
66
|
+
- [FileUploadAbstract](#fileuploadabstract)
|
|
67
|
+
- [FileUploadRouteSet](#fileuploadrouteset)
|
|
68
|
+
- [Storage Configuration](#storage-configuration)
|
|
69
|
+
- [Usage](#usage)
|
|
70
|
+
- [Creating a File Model](#creating-a-file-model)
|
|
71
|
+
- [Creating an Upload Route](#creating-an-upload-route)
|
|
72
|
+
- [Uploading Files via API](#uploading-files-via-api)
|
|
73
|
+
- [Storage Configuration](#storage-configuration)
|
|
74
|
+
- [Examples](#examples)
|
|
75
|
+
- [License](#license)
|
|
76
|
+
- [Links](#links)
|
|
77
|
+
|
|
78
|
+
## Description
|
|
79
|
+
|
|
80
|
+
**Bazis Uploadable** is an extension package for the Bazis framework that provides a ready-to-use solution for file uploading and management. The package includes:
|
|
81
|
+
|
|
82
|
+
- **FileUploadAbstract** — abstract model with fields for storing files and metadata
|
|
83
|
+
- **FileUploadRouteSet** — ready-to-use route with multipart/form-data upload support
|
|
84
|
+
- **Flexible storage configuration** — support for local storage, S3, and other backends
|
|
85
|
+
- **Automatic metadata extraction** — filename, extension, size
|
|
86
|
+
- **JSON:API integration** — standardized response format
|
|
87
|
+
|
|
88
|
+
**This package requires the base `bazis` package to be installed.**
|
|
89
|
+
|
|
90
|
+
## Requirements
|
|
91
|
+
|
|
92
|
+
- **Python**: 3.12+
|
|
93
|
+
- **bazis**: latest version
|
|
94
|
+
- **PostgreSQL**: 12+
|
|
95
|
+
- **Redis**: For caching
|
|
96
|
+
|
|
97
|
+
## Installation
|
|
98
|
+
|
|
99
|
+
### Using uv (recommended)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
uv add bazis-uploadable
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Using pip
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install bazis-uploadable
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Core Components
|
|
112
|
+
|
|
113
|
+
### FileUploadAbstract
|
|
114
|
+
|
|
115
|
+
Abstract model for storing uploaded files.
|
|
116
|
+
|
|
117
|
+
**Location**: `bazis.contrib.uploadable.models_abstract.FileUploadAbstract`
|
|
118
|
+
|
|
119
|
+
**Fields**:
|
|
120
|
+
|
|
121
|
+
- `file` — FileField for storing the file
|
|
122
|
+
- `name` — filename (optional, automatically filled from file name)
|
|
123
|
+
- `extension` — file extension (optional, automatically extracted)
|
|
124
|
+
|
|
125
|
+
**Properties**:
|
|
126
|
+
|
|
127
|
+
- `size` — file size in bytes (cached_property)
|
|
128
|
+
|
|
129
|
+
**Features**:
|
|
130
|
+
|
|
131
|
+
- Automatically extracts filename and extension on save
|
|
132
|
+
- Uses `get_file_path` to generate storage path
|
|
133
|
+
- Supports custom storage backends via settings
|
|
134
|
+
- Returns 0 for `size` when file is absent instead of raising an error
|
|
135
|
+
|
|
136
|
+
**Usage example**:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from bazis.contrib.uploadable.models_abstract import FileUploadAbstract
|
|
140
|
+
from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
|
|
141
|
+
|
|
142
|
+
class Document(FileUploadAbstract, DtMixin, UuidMixin):
|
|
143
|
+
"""Document with additional fields"""
|
|
144
|
+
description = models.TextField('Description', blank=True, null=True)
|
|
145
|
+
|
|
146
|
+
class Meta:
|
|
147
|
+
verbose_name = 'Document'
|
|
148
|
+
verbose_name_plural = 'Documents'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### FileUploadRouteSet
|
|
152
|
+
|
|
153
|
+
Ready-to-use route for file uploading via multipart/form-data.
|
|
154
|
+
|
|
155
|
+
**Location**: `bazis.contrib.uploadable.routes_abstract.FileUploadRouteSet`
|
|
156
|
+
|
|
157
|
+
**Features**:
|
|
158
|
+
|
|
159
|
+
- Accepts files via `multipart/form-data`
|
|
160
|
+
- Automatically includes `size` and `extension` fields in schema
|
|
161
|
+
- Supports optional `name` field for renaming files
|
|
162
|
+
- Returns data in JSON:API format
|
|
163
|
+
|
|
164
|
+
**Endpoint**:
|
|
165
|
+
|
|
166
|
+
- **POST /** — upload a new file
|
|
167
|
+
|
|
168
|
+
**Usage example**:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from bazis.contrib.uploadable.routes_abstract import FileUploadRouteSet
|
|
172
|
+
from django.apps import apps
|
|
173
|
+
|
|
174
|
+
class DocumentRouteSet(FileUploadRouteSet):
|
|
175
|
+
model = apps.get_model('myapp.Document')
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Storage Configuration
|
|
179
|
+
|
|
180
|
+
By default, Django's standard `FileSystemStorage` is used. You can configure custom storage via settings.
|
|
181
|
+
|
|
182
|
+
**settings.py**:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
# Local storage (default)
|
|
186
|
+
BAZIS_STORAGE_FILE_UPLOAD = None # or 'django.core.files.storage.FileSystemStorage'
|
|
187
|
+
|
|
188
|
+
# S3-compatible storage
|
|
189
|
+
BAZIS_STORAGE_FILE_UPLOAD = 'storages.backends.s3boto3.S3Boto3Storage'
|
|
190
|
+
|
|
191
|
+
# Other storage backends
|
|
192
|
+
BAZIS_STORAGE_FILE_UPLOAD = 'path.to.your.CustomStorage'
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Usage
|
|
196
|
+
|
|
197
|
+
### Creating a File Model
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from bazis.contrib.uploadable.models_abstract import FileUploadAbstract
|
|
201
|
+
from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
|
|
202
|
+
from django.db import models
|
|
203
|
+
|
|
204
|
+
class Document(FileUploadAbstract, DtMixin, UuidMixin):
|
|
205
|
+
"""Document with metadata"""
|
|
206
|
+
description = models.TextField('Description', blank=True, null=True)
|
|
207
|
+
category = models.CharField('Category', max_length=100, blank=True, null=True)
|
|
208
|
+
|
|
209
|
+
class Meta:
|
|
210
|
+
verbose_name = 'Document'
|
|
211
|
+
verbose_name_plural = 'Documents'
|
|
212
|
+
|
|
213
|
+
def __str__(self):
|
|
214
|
+
return self.name or self.file.name
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Creating an Upload Route
|
|
218
|
+
|
|
219
|
+
**routes.py**:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from bazis.contrib.uploadable.routes_abstract import FileUploadRouteSet
|
|
223
|
+
from bazis.core.schemas.fields import SchemaField, SchemaFields
|
|
224
|
+
from django.apps import apps
|
|
225
|
+
|
|
226
|
+
class DocumentRouteSet(FileUploadRouteSet):
|
|
227
|
+
model = apps.get_model('myapp.Document')
|
|
228
|
+
|
|
229
|
+
# Adding additional fields to schema
|
|
230
|
+
fields = {
|
|
231
|
+
None: SchemaFields(
|
|
232
|
+
include={
|
|
233
|
+
'description': None,
|
|
234
|
+
'category': None,
|
|
235
|
+
},
|
|
236
|
+
),
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**router.py**:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from bazis.core.routing import BazisRouter
|
|
244
|
+
from . import routes
|
|
245
|
+
|
|
246
|
+
router = BazisRouter(tags=['Documents'])
|
|
247
|
+
router.register(routes.DocumentRouteSet.as_router())
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Uploading Files via API
|
|
251
|
+
|
|
252
|
+
#### Uploading a file with automatic name
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
POST /api/v1/documents/document/
|
|
256
|
+
Content-Type: multipart/form-data
|
|
257
|
+
|
|
258
|
+
--boundary
|
|
259
|
+
Content-Disposition: form-data; name="file"; filename="report.pdf"
|
|
260
|
+
Content-Type: application/pdf
|
|
261
|
+
|
|
262
|
+
[binary data]
|
|
263
|
+
--boundary--
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Response**:
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"data": {
|
|
270
|
+
"type": "myapp.document",
|
|
271
|
+
"id": "123e4567-e89b-12d3-a456-426614174000",
|
|
272
|
+
"attributes": {
|
|
273
|
+
"name": "report.pdf",
|
|
274
|
+
"extension": "pdf",
|
|
275
|
+
"size": 102400,
|
|
276
|
+
"dt_created": "2024-01-15T10:30:00Z"
|
|
277
|
+
},
|
|
278
|
+
"relationships": {
|
|
279
|
+
"file": {
|
|
280
|
+
"data": {
|
|
281
|
+
"url": "/media/uploads/2024/01/15/report.pdf"
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### Uploading a file with custom name
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
POST /api/v1/documents/document/
|
|
293
|
+
Content-Type: multipart/form-data
|
|
294
|
+
|
|
295
|
+
--boundary
|
|
296
|
+
Content-Disposition: form-data; name="file"; filename="original.pdf"
|
|
297
|
+
Content-Type: application/pdf
|
|
298
|
+
|
|
299
|
+
[binary data]
|
|
300
|
+
--boundary
|
|
301
|
+
Content-Disposition: form-data; name="name"
|
|
302
|
+
|
|
303
|
+
Monthly Report January 2024.pdf
|
|
304
|
+
--boundary--
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Response**:
|
|
308
|
+
```json
|
|
309
|
+
{
|
|
310
|
+
"data": {
|
|
311
|
+
"type": "myapp.document",
|
|
312
|
+
"id": "123e4567-e89b-12d3-a456-426614174000",
|
|
313
|
+
"attributes": {
|
|
314
|
+
"name": "Monthly Report January 2024.pdf",
|
|
315
|
+
"extension": "pdf",
|
|
316
|
+
"size": 102400
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### Uploading with additional fields
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
POST /api/v1/documents/document/
|
|
326
|
+
Content-Type: multipart/form-data
|
|
327
|
+
|
|
328
|
+
--boundary
|
|
329
|
+
Content-Disposition: form-data; name="file"; filename="invoice.pdf"
|
|
330
|
+
|
|
331
|
+
[binary data]
|
|
332
|
+
--boundary
|
|
333
|
+
Content-Disposition: form-data; name="name"
|
|
334
|
+
|
|
335
|
+
Invoice #12345
|
|
336
|
+
--boundary
|
|
337
|
+
Content-Disposition: form-data; name="description"
|
|
338
|
+
|
|
339
|
+
Invoice for January 2024 services
|
|
340
|
+
--boundary
|
|
341
|
+
Content-Disposition: form-data; name="category"
|
|
342
|
+
|
|
343
|
+
Finance
|
|
344
|
+
--boundary--
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Storage Configuration
|
|
348
|
+
|
|
349
|
+
### Local Storage (default)
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
# settings.py
|
|
353
|
+
MEDIA_ROOT = '/var/www/media/'
|
|
354
|
+
MEDIA_URL = '/media/'
|
|
355
|
+
|
|
356
|
+
# FileSystemStorage is used by default
|
|
357
|
+
BAZIS_STORAGE_FILE_UPLOAD = None
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Amazon S3
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
# Install dependencies
|
|
364
|
+
uv add django-storages boto3
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
# settings.py
|
|
369
|
+
BAZIS_STORAGE_FILE_UPLOAD = 'storages.backends.s3boto3.S3Boto3Storage'
|
|
370
|
+
|
|
371
|
+
# S3 configuration
|
|
372
|
+
AWS_ACCESS_KEY_ID = 'your-access-key'
|
|
373
|
+
AWS_SECRET_ACCESS_KEY = 'your-secret-key'
|
|
374
|
+
AWS_STORAGE_BUCKET_NAME = 'your-bucket-name'
|
|
375
|
+
AWS_S3_REGION_NAME = 'us-east-1'
|
|
376
|
+
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
|
|
377
|
+
AWS_DEFAULT_ACL = 'public-read'
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### MinIO (S3-compatible storage)
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
# settings.py
|
|
384
|
+
BAZIS_STORAGE_FILE_UPLOAD = 'storages.backends.s3boto3.S3Boto3Storage'
|
|
385
|
+
|
|
386
|
+
AWS_ACCESS_KEY_ID = 'minio-access-key'
|
|
387
|
+
AWS_SECRET_ACCESS_KEY = 'minio-secret-key'
|
|
388
|
+
AWS_STORAGE_BUCKET_NAME = 'uploads'
|
|
389
|
+
AWS_S3_ENDPOINT_URL = 'http://minio:9000'
|
|
390
|
+
AWS_S3_USE_SSL = False
|
|
391
|
+
AWS_DEFAULT_ACL = 'public-read'
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Google Cloud Storage
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
# Install dependencies
|
|
398
|
+
uv add django-storages google-cloud-storage
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
# settings.py
|
|
403
|
+
BAZIS_STORAGE_FILE_UPLOAD = 'storages.backends.gcloud.GoogleCloudStorage'
|
|
404
|
+
|
|
405
|
+
GS_BUCKET_NAME = 'your-bucket-name'
|
|
406
|
+
GS_PROJECT_ID = 'your-project-id'
|
|
407
|
+
GS_CREDENTIALS = service_account.Credentials.from_service_account_file(
|
|
408
|
+
'path/to/service-account.json'
|
|
409
|
+
)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Custom Storage
|
|
413
|
+
|
|
414
|
+
You can create your own storage by inheriting from Django Storage:
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
# myapp/storage.py
|
|
418
|
+
from django.core.files.storage import FileSystemStorage
|
|
419
|
+
|
|
420
|
+
class CustomStorage(FileSystemStorage):
|
|
421
|
+
def __init__(self):
|
|
422
|
+
super().__init__(
|
|
423
|
+
location='/custom/path/',
|
|
424
|
+
base_url='/custom/'
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
def _save(self, name, content):
|
|
428
|
+
# Custom save logic
|
|
429
|
+
return super()._save(name, content)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
```python
|
|
433
|
+
# settings.py
|
|
434
|
+
BAZIS_STORAGE_FILE_UPLOAD = 'myapp.storage.CustomStorage'
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Examples
|
|
438
|
+
|
|
439
|
+
### Complete document application example
|
|
440
|
+
|
|
441
|
+
**models.py**:
|
|
442
|
+
|
|
443
|
+
```python
|
|
444
|
+
from bazis.contrib.uploadable.models_abstract import FileUploadAbstract
|
|
445
|
+
from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
|
|
446
|
+
from django.db import models
|
|
447
|
+
|
|
448
|
+
class DocumentCategory(models.TextChoices):
|
|
449
|
+
INVOICE = 'invoice', 'Invoice'
|
|
450
|
+
CONTRACT = 'contract', 'Contract'
|
|
451
|
+
REPORT = 'report', 'Report'
|
|
452
|
+
OTHER = 'other', 'Other'
|
|
453
|
+
|
|
454
|
+
class Document(FileUploadAbstract, DtMixin, UuidMixin):
|
|
455
|
+
"""Document with category and description"""
|
|
456
|
+
description = models.TextField('Description', blank=True, null=True)
|
|
457
|
+
category = models.CharField(
|
|
458
|
+
'Category',
|
|
459
|
+
max_length=20,
|
|
460
|
+
choices=DocumentCategory.choices,
|
|
461
|
+
default=DocumentCategory.OTHER
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
class Meta:
|
|
465
|
+
verbose_name = 'Document'
|
|
466
|
+
verbose_name_plural = 'Documents'
|
|
467
|
+
|
|
468
|
+
def __str__(self):
|
|
469
|
+
return f'{self.get_category_display()}: {self.name}'
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**routes.py**:
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
from bazis.contrib.uploadable.routes_abstract import FileUploadRouteSet
|
|
476
|
+
from bazis.core.schemas.fields import SchemaFields
|
|
477
|
+
from django.apps import apps
|
|
478
|
+
|
|
479
|
+
class DocumentRouteSet(FileUploadRouteSet):
|
|
480
|
+
model = apps.get_model('myapp.Document')
|
|
481
|
+
|
|
482
|
+
fields = {
|
|
483
|
+
None: SchemaFields(
|
|
484
|
+
include={
|
|
485
|
+
'description': None,
|
|
486
|
+
'category': None,
|
|
487
|
+
},
|
|
488
|
+
),
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**router.py**:
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
from bazis.core.routing import BazisRouter
|
|
496
|
+
from . import routes
|
|
497
|
+
|
|
498
|
+
router = BazisRouter(tags=['Documents'])
|
|
499
|
+
router.register(routes.DocumentRouteSet.as_router())
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**admin.py**:
|
|
503
|
+
|
|
504
|
+
```python
|
|
505
|
+
from django.contrib import admin
|
|
506
|
+
from bazis.core.admin_abstract import DtAdminMixin
|
|
507
|
+
from .models import Document
|
|
508
|
+
|
|
509
|
+
@admin.register(Document)
|
|
510
|
+
class DocumentAdmin(DtAdminMixin, admin.ModelAdmin):
|
|
511
|
+
list_display = ('id', 'name', 'category', 'extension', 'size_display', 'dt_created')
|
|
512
|
+
list_filter = ('category', 'extension')
|
|
513
|
+
search_fields = ('name', 'description')
|
|
514
|
+
readonly_fields = ('extension', 'size', 'dt_created', 'dt_updated')
|
|
515
|
+
|
|
516
|
+
def size_display(self, obj):
|
|
517
|
+
"""Human-readable file size"""
|
|
518
|
+
size = obj.size
|
|
519
|
+
if size < 1024:
|
|
520
|
+
return f'{size} B'
|
|
521
|
+
elif size < 1024 * 1024:
|
|
522
|
+
return f'{size / 1024:.2f} KB'
|
|
523
|
+
else:
|
|
524
|
+
return f'{size / (1024 * 1024):.2f} MB'
|
|
525
|
+
size_display.short_description = 'Size'
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Client application usage example
|
|
529
|
+
|
|
530
|
+
**JavaScript (file upload)**:
|
|
531
|
+
|
|
532
|
+
```javascript
|
|
533
|
+
async function uploadDocument(file, category, description) {
|
|
534
|
+
const formData = new FormData();
|
|
535
|
+
formData.append('file', file);
|
|
536
|
+
formData.append('category', category);
|
|
537
|
+
|
|
538
|
+
if (description) {
|
|
539
|
+
formData.append('description', description);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const response = await fetch('/api/v1/documents/document/', {
|
|
543
|
+
method: 'POST',
|
|
544
|
+
headers: {
|
|
545
|
+
'Authorization': `Bearer ${token}`
|
|
546
|
+
},
|
|
547
|
+
body: formData
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
if (!response.ok) {
|
|
551
|
+
throw new Error('Upload failed');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return await response.json();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Usage
|
|
558
|
+
const fileInput = document.getElementById('fileInput');
|
|
559
|
+
const file = fileInput.files[0];
|
|
560
|
+
|
|
561
|
+
try {
|
|
562
|
+
const result = await uploadDocument(file, 'invoice', 'Monthly invoice');
|
|
563
|
+
console.log('Uploaded:', result.data.id);
|
|
564
|
+
console.log('File URL:', result.data.attributes.file);
|
|
565
|
+
} catch (error) {
|
|
566
|
+
console.error('Error:', error);
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
**Python (file upload via requests)**:
|
|
571
|
+
|
|
572
|
+
```python
|
|
573
|
+
import requests
|
|
574
|
+
|
|
575
|
+
def upload_document(file_path, category, description=None):
|
|
576
|
+
url = 'http://api.example.com/api/v1/documents/document/'
|
|
577
|
+
|
|
578
|
+
with open(file_path, 'rb') as file:
|
|
579
|
+
files = {'file': file}
|
|
580
|
+
data = {
|
|
581
|
+
'category': category,
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if description:
|
|
585
|
+
data['description'] = description
|
|
586
|
+
|
|
587
|
+
headers = {
|
|
588
|
+
'Authorization': f'Bearer {token}'
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
response = requests.post(url, files=files, data=data, headers=headers)
|
|
592
|
+
response.raise_for_status()
|
|
593
|
+
|
|
594
|
+
return response.json()
|
|
595
|
+
|
|
596
|
+
# Usage
|
|
597
|
+
result = upload_document(
|
|
598
|
+
'/path/to/invoice.pdf',
|
|
599
|
+
category='invoice',
|
|
600
|
+
description='Monthly invoice for January'
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
print(f"Uploaded: {result['data']['id']}")
|
|
604
|
+
print(f"Size: {result['data']['attributes']['size']} bytes")
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### Getting list of files
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
GET /api/v1/documents/document/
|
|
611
|
+
Authorization: Bearer <token>
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Response**:
|
|
615
|
+
```json
|
|
616
|
+
{
|
|
617
|
+
"data": [
|
|
618
|
+
{
|
|
619
|
+
"type": "myapp.document",
|
|
620
|
+
"id": "123e4567-e89b-12d3-a456-426614174000",
|
|
621
|
+
"attributes": {
|
|
622
|
+
"name": "invoice.pdf",
|
|
623
|
+
"extension": "pdf",
|
|
624
|
+
"size": 102400,
|
|
625
|
+
"category": "invoice",
|
|
626
|
+
"description": "Monthly invoice",
|
|
627
|
+
"dt_created": "2024-01-15T10:30:00Z"
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
"type": "myapp.document",
|
|
632
|
+
"id": "987e6543-e21b-32d1-b654-426614174001",
|
|
633
|
+
"attributes": {
|
|
634
|
+
"name": "report.docx",
|
|
635
|
+
"extension": "docx",
|
|
636
|
+
"size": 256000,
|
|
637
|
+
"category": "report",
|
|
638
|
+
"dt_created": "2024-01-16T14:20:00Z"
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
],
|
|
642
|
+
"meta": {
|
|
643
|
+
"count": 2
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### Filtering by extension
|
|
649
|
+
|
|
650
|
+
```bash
|
|
651
|
+
GET /api/v1/documents/document/?filter[extension]=pdf
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Filtering by category
|
|
655
|
+
|
|
656
|
+
```bash
|
|
657
|
+
GET /api/v1/documents/document/?filter[category]=invoice
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
## License
|
|
661
|
+
|
|
662
|
+
Apache License 2.0
|
|
663
|
+
|
|
664
|
+
See [LICENSE](LICENSE) file for details.
|
|
665
|
+
|
|
666
|
+
## Links
|
|
667
|
+
|
|
668
|
+
- [Bazis Documentation](https://github.com/ecofuture-tech/bazis) — main repository
|
|
669
|
+
- [Bazis Uploadable Repository](https://github.com/ecofuture-tech/bazis-uploadable) — package repository
|
|
670
|
+
- [Issue Tracker](https://github.com/ecofuture-tech/bazis-uploadable/issues) — report bugs or request features
|
|
671
|
+
- [Django Storages](https://django-storages.readthedocs.io/) — storage backend documentation
|
|
672
|
+
|
|
673
|
+
## Support
|
|
674
|
+
|
|
675
|
+
If you have questions or issues:
|
|
676
|
+
- Check the [Bazis documentation](https://github.com/ecofuture-tech/bazis)
|
|
677
|
+
- Search through [existing issues](https://github.com/ecofuture-tech/bazis-uploadable/issues)
|
|
678
|
+
- Create a [new issue](https://github.com/ecofuture-tech/bazis-uploadable/issues/new) with detailed information
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
Made with ❤️ by Bazis team
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
bazis/contrib/uploadable/__init__.py,sha256=yrZO1q_ut-i1aU1GYeg4bypwe_xjNccaqDrQmrUAniw,784
|
|
2
|
+
bazis/contrib/uploadable/_version.py,sha256=6OGz4a0gjMGlckPyPCNiJDWyFDO-tWO8O_ZNx4ajT2Y,704
|
|
3
|
+
bazis/contrib/uploadable/admin.py,sha256=218mMcrIER0bG58waDCoh5G2ctElTcVmioAHAGd5z0I,853
|
|
4
|
+
bazis/contrib/uploadable/apps.py,sha256=M8rxoRma5YzI6HLIaht9IEYpiseLrT_r2J8qckhkstw,827
|
|
5
|
+
bazis/contrib/uploadable/conf.py,sha256=y0AsAfMbJbz-oP0ld1HinXbprwCgEiLUB63I9KYWcNs,898
|
|
6
|
+
bazis/contrib/uploadable/models.py,sha256=IW7oUTUlJqEsU6fz3iaKdTlTeiFMwpGJy-lLs8Bt3Qw,736
|
|
7
|
+
bazis/contrib/uploadable/models_abstract.py,sha256=7HG5LGIKv2KRG9wDwwk-mm2qr8uOZAwksINuev_HA_Q,2197
|
|
8
|
+
bazis/contrib/uploadable/router.py,sha256=SZW_3eLKP_KlvSsyyAQtomLtevYTFOr5q2ENacXoK30,855
|
|
9
|
+
bazis/contrib/uploadable/routes.py,sha256=P9Zb87wEsln071jxDwSB1vEkiPUgievFJD2Z-NXgoxc,2516
|
|
10
|
+
bazis/contrib/uploadable/locale/en/LC_MESSAGES/django.mo,sha256=N1pb17IfLd0ASiKO8d68-B4ygSpDkhKOCs8YTzMXQo0,380
|
|
11
|
+
bazis/contrib/uploadable/locale/en/LC_MESSAGES/django.po,sha256=gYhnI4Ci3W-7Ioo7uSF6fGiql8C6mpZS-__tbovoYOI,948
|
|
12
|
+
bazis/contrib/uploadable/locale/ru/LC_MESSAGES/django.mo,sha256=1oAU-_1G-Rez_Wgt21g_pkxM1dVxDC-pfr3ZeMCEXDc,856
|
|
13
|
+
bazis/contrib/uploadable/locale/ru/LC_MESSAGES/django.po,sha256=5Gu4PyiP5fiHjIlGLHjoO2LS2MxEOgd1RbbZncxS6iw,1244
|
|
14
|
+
bazis/contrib/uploadable/migrations/0001_initial.py,sha256=ZuA4ZRT9wJtWja0cly_if4D8bVfrdbZwaIKkCFuf85U,1773
|
|
15
|
+
bazis/contrib/uploadable/migrations/0002_alter_fileupload_file.py,sha256=Me826ZVKboEOze051_b9N29PfTCjIslX7GSnkZH8Le0,1199
|
|
16
|
+
bazis/contrib/uploadable/migrations/__init__.py,sha256=7MU6gF_TkvnR3NFi-Nfci4RIacoZ6bqMjwlR6ONK8Ms,615
|
|
17
|
+
bazis_uploadable-2.2.0.dist-info/METADATA,sha256=Ah6I62SlUQgSWfiAEHbdRF1W1h5X0ExcuWs3BO5SxR4,17018
|
|
18
|
+
bazis_uploadable-2.2.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
19
|
+
bazis_uploadable-2.2.0.dist-info/top_level.txt,sha256=WgdrPZTZBMG8i_EqxA3vU5qI4ETQ_RsqKqSqsfIApHY,6
|
|
20
|
+
bazis_uploadable-2.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bazis
|