django-async-upload-kg 4.0.1__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.
- django-async-upload-kg-4.0.1/LICENSE +20 -0
- django-async-upload-kg-4.0.1/MANIFEST.in +3 -0
- django-async-upload-kg-4.0.1/PKG-INFO +82 -0
- django-async-upload-kg-4.0.1/README.rst +62 -0
- django-async-upload-kg-4.0.1/admin_async_upload/__init__.py +0 -0
- django-async-upload-kg-4.0.1/admin_async_upload/fields.py +14 -0
- django-async-upload-kg-4.0.1/admin_async_upload/files.py +150 -0
- django-async-upload-kg-4.0.1/admin_async_upload/models.py +15 -0
- django-async-upload-kg-4.0.1/admin_async_upload/static/admin_resumable/js/resumable.js +1071 -0
- django-async-upload-kg-4.0.1/admin_async_upload/storage.py +49 -0
- django-async-upload-kg-4.0.1/admin_async_upload/templates/admin_resumable/admin_file_input.html +142 -0
- django-async-upload-kg-4.0.1/admin_async_upload/templates/admin_resumable/user_file_input.html +61 -0
- django-async-upload-kg-4.0.1/admin_async_upload/urls.py +7 -0
- django-async-upload-kg-4.0.1/admin_async_upload/validators.py +68 -0
- django-async-upload-kg-4.0.1/admin_async_upload/views.py +39 -0
- django-async-upload-kg-4.0.1/admin_async_upload/widgets.py +87 -0
- django-async-upload-kg-4.0.1/django_async_upload_kg.egg-info/PKG-INFO +82 -0
- django-async-upload-kg-4.0.1/django_async_upload_kg.egg-info/SOURCES.txt +23 -0
- django-async-upload-kg-4.0.1/django_async_upload_kg.egg-info/dependency_links.txt +1 -0
- django-async-upload-kg-4.0.1/django_async_upload_kg.egg-info/requires.txt +1 -0
- django-async-upload-kg-4.0.1/django_async_upload_kg.egg-info/top_level.txt +1 -0
- django-async-upload-kg-4.0.1/setup.cfg +7 -0
- django-async-upload-kg-4.0.1/setup.py +44 -0
- django-async-upload-kg-4.0.1/tests/test_uploads.py +214 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014 jonatron
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
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, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: django-async-upload-kg
|
|
3
|
+
Version: 4.0.1
|
|
4
|
+
Summary: A Django app for the uploading of large files from the django admin site.
|
|
5
|
+
Home-page: https://github.com/bit/django-async-upload
|
|
6
|
+
Author: Kajala Group Ltd.
|
|
7
|
+
Author-email: support@kajala.com
|
|
8
|
+
License: MIT License
|
|
9
|
+
Classifier: Environment :: Web Environment
|
|
10
|
+
Classifier: Framework :: Django
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: Django>=3.0.14
|
|
20
|
+
|
|
21
|
+
django-async-upload
|
|
22
|
+
===============================
|
|
23
|
+
|
|
24
|
+
Based on fork from https://github.com/bit/django-async-upload. Allows install via pip:
|
|
25
|
+
|
|
26
|
+
pip install django-async-upload-kg
|
|
27
|
+
|
|
28
|
+
django-async-upload is a django app to allow you to upload large files from within the django admin site asynchrously, that means that you can add any number of files on the admin page (e.g. through inline models) and continue editing other fields while files are uploading.
|
|
29
|
+
|
|
30
|
+
django-async-upload is compatible with django-storages (tested with S3Storage)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Installation
|
|
34
|
+
------------
|
|
35
|
+
|
|
36
|
+
* pip install django-async-upload
|
|
37
|
+
* Add ``admin_async_upload`` to your ``INSTALLED_APPS``
|
|
38
|
+
* Add ``url(r'^admin_async_upload/', include('admin_async_upload.urls')),`` to your urls.py
|
|
39
|
+
* Add a model field eg: ``from admin_resumable.models import ResumableFileField``
|
|
40
|
+
|
|
41
|
+
::
|
|
42
|
+
|
|
43
|
+
class Foo(models.Model):
|
|
44
|
+
bar = models.CharField(max_length=200)
|
|
45
|
+
foo = AsyncFileField()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
Optionally:
|
|
50
|
+
|
|
51
|
+
* Set ``ADMIN_RESUMABLE_CHUNKSIZE``, default is ``"1*1024*1024"``
|
|
52
|
+
* Set ``ADMIN_RESUMABLE_STORAGE``, default is setting of DEFAULT_FILE_STORAGE and ultimately ``'django.core.files.storage.FileSystemStorage'``. If you don't want the default FileSystemStorage behaviour of creating new files on the server with filenames appended with _1, _2, etc for consecutive uploads of the same file, then you could use this to set your storage class to something like https://djangosnippets.org/snippets/976/
|
|
53
|
+
* Set ``ADMIN_RESUMABLE_CHUNK_STORAGE``, default is ``'django.core.files.storage.FileSystemStorage'`` . If you don't want the default FileSystemStorage behaviour of creating new files on the server with filenames appended with _1, _2, etc for consecutive uploads of the same file, then you could use this to set your storage class to something like https://djangosnippets.org/snippets/976/
|
|
54
|
+
* Set ``ADMIN_RESUMABLE_SHOW_THUMB``, default is False. Shows a thumbnail next to the "Currently:" link.
|
|
55
|
+
* Set ``ADMIN_SIMULTANEOUS_UPLOADS`` to limit number of simulteneous uploads, dedaults to `3`. If you have broken pipe issues in local development environment, set this value to `1`.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
Versions
|
|
59
|
+
--------
|
|
60
|
+
|
|
61
|
+
4.0.0 - inital fork of django-admin-async-upload 3.0.4 with support for Django 4 and later
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
Compatibility
|
|
65
|
+
-------------
|
|
66
|
+
|
|
67
|
+
Tested on Django 4.2 running on python 3.11
|
|
68
|
+
|
|
69
|
+
Thanks to
|
|
70
|
+
---------
|
|
71
|
+
|
|
72
|
+
original django-admin-resumable-js by jonatron https://github.com/jonatron/django-admin-resumable-js
|
|
73
|
+
|
|
74
|
+
django-admin-resumable-js fork by roxel https://github.com/roxel/django-admin-resumable-js
|
|
75
|
+
|
|
76
|
+
django-admin-async-upload fork by DataGreed https://github.com/DataGreed/django-admin-async-upload
|
|
77
|
+
|
|
78
|
+
Resumable.js https://github.com/23/resumable.js
|
|
79
|
+
|
|
80
|
+
django-resumable https://github.com/jeanphix/django-resumable
|
|
81
|
+
|
|
82
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
django-async-upload
|
|
2
|
+
===============================
|
|
3
|
+
|
|
4
|
+
Based on fork from https://github.com/bit/django-async-upload. Allows install via pip:
|
|
5
|
+
|
|
6
|
+
pip install django-async-upload-kg
|
|
7
|
+
|
|
8
|
+
django-async-upload is a django app to allow you to upload large files from within the django admin site asynchrously, that means that you can add any number of files on the admin page (e.g. through inline models) and continue editing other fields while files are uploading.
|
|
9
|
+
|
|
10
|
+
django-async-upload is compatible with django-storages (tested with S3Storage)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Installation
|
|
14
|
+
------------
|
|
15
|
+
|
|
16
|
+
* pip install django-async-upload
|
|
17
|
+
* Add ``admin_async_upload`` to your ``INSTALLED_APPS``
|
|
18
|
+
* Add ``url(r'^admin_async_upload/', include('admin_async_upload.urls')),`` to your urls.py
|
|
19
|
+
* Add a model field eg: ``from admin_resumable.models import ResumableFileField``
|
|
20
|
+
|
|
21
|
+
::
|
|
22
|
+
|
|
23
|
+
class Foo(models.Model):
|
|
24
|
+
bar = models.CharField(max_length=200)
|
|
25
|
+
foo = AsyncFileField()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Optionally:
|
|
30
|
+
|
|
31
|
+
* Set ``ADMIN_RESUMABLE_CHUNKSIZE``, default is ``"1*1024*1024"``
|
|
32
|
+
* Set ``ADMIN_RESUMABLE_STORAGE``, default is setting of DEFAULT_FILE_STORAGE and ultimately ``'django.core.files.storage.FileSystemStorage'``. If you don't want the default FileSystemStorage behaviour of creating new files on the server with filenames appended with _1, _2, etc for consecutive uploads of the same file, then you could use this to set your storage class to something like https://djangosnippets.org/snippets/976/
|
|
33
|
+
* Set ``ADMIN_RESUMABLE_CHUNK_STORAGE``, default is ``'django.core.files.storage.FileSystemStorage'`` . If you don't want the default FileSystemStorage behaviour of creating new files on the server with filenames appended with _1, _2, etc for consecutive uploads of the same file, then you could use this to set your storage class to something like https://djangosnippets.org/snippets/976/
|
|
34
|
+
* Set ``ADMIN_RESUMABLE_SHOW_THUMB``, default is False. Shows a thumbnail next to the "Currently:" link.
|
|
35
|
+
* Set ``ADMIN_SIMULTANEOUS_UPLOADS`` to limit number of simulteneous uploads, dedaults to `3`. If you have broken pipe issues in local development environment, set this value to `1`.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Versions
|
|
39
|
+
--------
|
|
40
|
+
|
|
41
|
+
4.0.0 - inital fork of django-admin-async-upload 3.0.4 with support for Django 4 and later
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Compatibility
|
|
45
|
+
-------------
|
|
46
|
+
|
|
47
|
+
Tested on Django 4.2 running on python 3.11
|
|
48
|
+
|
|
49
|
+
Thanks to
|
|
50
|
+
---------
|
|
51
|
+
|
|
52
|
+
original django-admin-resumable-js by jonatron https://github.com/jonatron/django-admin-resumable-js
|
|
53
|
+
|
|
54
|
+
django-admin-resumable-js fork by roxel https://github.com/roxel/django-admin-resumable-js
|
|
55
|
+
|
|
56
|
+
django-admin-async-upload fork by DataGreed https://github.com/DataGreed/django-admin-async-upload
|
|
57
|
+
|
|
58
|
+
Resumable.js https://github.com/23/resumable.js
|
|
59
|
+
|
|
60
|
+
django-resumable https://github.com/jeanphix/django-resumable
|
|
61
|
+
|
|
62
|
+
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from django.core.exceptions import ValidationError
|
|
2
|
+
from django.forms import fields
|
|
3
|
+
|
|
4
|
+
from admin_async_upload.widgets import ResumableAdminWidget
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FormResumableFileField(fields.FileField):
|
|
8
|
+
widget = ResumableAdminWidget
|
|
9
|
+
|
|
10
|
+
def to_python(self, data):
|
|
11
|
+
if self.required:
|
|
12
|
+
if not data or data == "None":
|
|
13
|
+
raise ValidationError(self.error_messages['empty'])
|
|
14
|
+
return data
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import fnmatch
|
|
3
|
+
import tempfile
|
|
4
|
+
|
|
5
|
+
from django.contrib.contenttypes.models import ContentType
|
|
6
|
+
from django.core.files import File
|
|
7
|
+
from django.utils.functional import cached_property
|
|
8
|
+
|
|
9
|
+
from admin_async_upload.storage import ResumableStorage
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ResumableFile(object):
|
|
13
|
+
"""
|
|
14
|
+
Handles file saving and processing.
|
|
15
|
+
It must only have access to chunk storage where it saves file chunks.
|
|
16
|
+
When all chunks are uploaded it collects and merges them returning temporary file pointer
|
|
17
|
+
that can be used to save the complete file to persistent storage.
|
|
18
|
+
|
|
19
|
+
Chunk storage should preferably be some local storage to avoid traffic
|
|
20
|
+
as files usually must be downloaded to server as chunks and re-uploaded as complete files.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, field, user, params):
|
|
24
|
+
self.field = field
|
|
25
|
+
self.user = user
|
|
26
|
+
self.params = params
|
|
27
|
+
self.chunk_suffix = "_part_"
|
|
28
|
+
|
|
29
|
+
@cached_property
|
|
30
|
+
def resumable_storage(self):
|
|
31
|
+
return ResumableStorage()
|
|
32
|
+
|
|
33
|
+
@cached_property
|
|
34
|
+
def persistent_storage(self):
|
|
35
|
+
return self.resumable_storage.get_persistent_storage()
|
|
36
|
+
|
|
37
|
+
@cached_property
|
|
38
|
+
def chunk_storage(self):
|
|
39
|
+
return ResumableStorage().get_chunk_storage()
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def storage_filename(self):
|
|
43
|
+
instance_id = self.params.get("instance_id")
|
|
44
|
+
if instance_id:
|
|
45
|
+
instance = self.field.model.objects.filter(pk=instance_id).first()
|
|
46
|
+
else:
|
|
47
|
+
instance = None
|
|
48
|
+
return self.resumable_storage.full_filename(self.filename, self.upload_to, instance=instance)
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def upload_to(self):
|
|
52
|
+
return self.field.upload_to
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def chunk_exists(self):
|
|
56
|
+
"""
|
|
57
|
+
Checks if the requested chunk exists.
|
|
58
|
+
"""
|
|
59
|
+
return self.chunk_storage.exists(self.current_chunk_name) and \
|
|
60
|
+
self.chunk_storage.size(self.current_chunk_name) == int(self.params.get('resumableCurrentChunkSize'))
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def chunk_names(self):
|
|
64
|
+
"""
|
|
65
|
+
Iterates over all stored chunks.
|
|
66
|
+
"""
|
|
67
|
+
chunks = []
|
|
68
|
+
files = sorted(self.chunk_storage.listdir('')[1])
|
|
69
|
+
for file in files:
|
|
70
|
+
if fnmatch.fnmatch(file, '%s%s*' % (self.filename,
|
|
71
|
+
self.chunk_suffix)):
|
|
72
|
+
chunks.append(file)
|
|
73
|
+
return chunks
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def current_chunk_name(self):
|
|
77
|
+
# TODO: add user identifier to chunk name
|
|
78
|
+
return "%s%s%s" % (
|
|
79
|
+
self.filename,
|
|
80
|
+
self.chunk_suffix,
|
|
81
|
+
self.params.get('resumableChunkNumber').zfill(4)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def chunks(self):
|
|
85
|
+
"""
|
|
86
|
+
Iterates over all stored chunks.
|
|
87
|
+
"""
|
|
88
|
+
# TODO: add user identifier to chunk name
|
|
89
|
+
files = sorted(self.chunk_storage.listdir('')[1])
|
|
90
|
+
for file in files:
|
|
91
|
+
if fnmatch.fnmatch(file, '%s%s*' % (self.filename,
|
|
92
|
+
self.chunk_suffix)):
|
|
93
|
+
yield self.chunk_storage.open(file, 'rb').read()
|
|
94
|
+
|
|
95
|
+
def delete_chunks(self):
|
|
96
|
+
[self.chunk_storage.delete(chunk) for chunk in self.chunk_names]
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def file(self):
|
|
100
|
+
"""
|
|
101
|
+
Merges file and returns its file pointer.
|
|
102
|
+
"""
|
|
103
|
+
if not self.is_complete:
|
|
104
|
+
raise Exception('Chunk(s) still missing')
|
|
105
|
+
outfile = tempfile.NamedTemporaryFile("w+b")
|
|
106
|
+
for chunk in self.chunk_names:
|
|
107
|
+
outfile.write(self.chunk_storage.open(chunk).read())
|
|
108
|
+
return outfile
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def filename(self):
|
|
112
|
+
"""
|
|
113
|
+
Gets the filename.
|
|
114
|
+
"""
|
|
115
|
+
# TODO: add user identifier to chunk name
|
|
116
|
+
filename = self.params.get('resumableFilename')
|
|
117
|
+
if '/' in filename:
|
|
118
|
+
raise Exception('Invalid filename')
|
|
119
|
+
value = "%s_%s" % (self.params.get('resumableTotalSize'), filename)
|
|
120
|
+
return value
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def is_complete(self):
|
|
124
|
+
"""
|
|
125
|
+
Checks if all chunks are already stored.
|
|
126
|
+
"""
|
|
127
|
+
return int(self.params.get('resumableTotalSize')) == self.size
|
|
128
|
+
|
|
129
|
+
def process_chunk(self, file):
|
|
130
|
+
"""
|
|
131
|
+
Saves chunk to chunk storage.
|
|
132
|
+
"""
|
|
133
|
+
if self.chunk_storage.exists(self.current_chunk_name):
|
|
134
|
+
self.chunk_storage.delete(self.current_chunk_name)
|
|
135
|
+
self.chunk_storage.save(self.current_chunk_name, file)
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def size(self):
|
|
139
|
+
"""
|
|
140
|
+
Gets size of all chunks combined.
|
|
141
|
+
"""
|
|
142
|
+
size = 0
|
|
143
|
+
for chunk in self.chunk_names:
|
|
144
|
+
size += self.chunk_storage.size(chunk)
|
|
145
|
+
return size
|
|
146
|
+
|
|
147
|
+
def collect(self):
|
|
148
|
+
actual_filename = self.persistent_storage.save(self.storage_filename, File(self.file))
|
|
149
|
+
self.delete_chunks()
|
|
150
|
+
return actual_filename
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from admin_async_upload.widgets import ResumableAdminWidget
|
|
3
|
+
from admin_async_upload.fields import FormResumableFileField
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AsyncFileField(models.FileField):
|
|
7
|
+
|
|
8
|
+
def formfield(self, **kwargs):
|
|
9
|
+
defaults = {'form_class': FormResumableFileField}
|
|
10
|
+
if self.model and self.name:
|
|
11
|
+
defaults['widget'] = ResumableAdminWidget(attrs={
|
|
12
|
+
'model': self.model,
|
|
13
|
+
'field_name': self.name})
|
|
14
|
+
kwargs.update(defaults)
|
|
15
|
+
return super(AsyncFileField, self).formfield(**kwargs)
|