From 22891f0c72bb3b36e29450ade4c1ca844b71b4d8 Mon Sep 17 00:00:00 2001 From: Steve Recio Date: Sat, 15 Dec 2018 14:10:00 -0500 Subject: [PATCH 1/6] fixes #47 --- chunked_upload/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chunked_upload/views.py b/chunked_upload/views.py index debdbd9..5df33d7 100644 --- a/chunked_upload/views.py +++ b/chunked_upload/views.py @@ -181,7 +181,7 @@ def _post(self, request, *args, **kwargs): self.is_valid_chunked_upload(chunked_upload) else: attrs = {'filename': chunk.name} - if hasattr(request, 'user') and is_authenticated(request.user): + if hasattr(self.model, 'user') and hasattr(request, 'user') and is_authenticated(request.user): attrs['user'] = request.user attrs.update(self.get_extra_attrs(request)) chunked_upload = self.create_chunked_upload(save=False, **attrs) From 474cb471da56e52ae0b42d4db1af7e6680cf8a88 Mon Sep 17 00:00:00 2001 From: Steve Recio Date: Sat, 15 Dec 2018 14:26:58 -0500 Subject: [PATCH 2/6] follow-up to #47 --- chunked_upload/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chunked_upload/views.py b/chunked_upload/views.py index 5df33d7..5a0337b 100644 --- a/chunked_upload/views.py +++ b/chunked_upload/views.py @@ -32,7 +32,7 @@ def get_queryset(self, request): By default, users can only continue uploading their own uploads. """ queryset = self.model.objects.all() - if hasattr(request, 'user') and is_authenticated(request.user): + if hasattr(self.model, 'user') and hasattr(request, 'user') and is_authenticated(request.user): queryset = queryset.filter(user=request.user) return queryset From 8b1feb98294fd819813e59163a6da46d4a9bd848 Mon Sep 17 00:00:00 2001 From: Steve Recio Date: Thu, 6 May 2021 17:48:43 -0400 Subject: [PATCH 3/6] use filename tmp instead of empty file (https://github.com/juliomalegria/django-chunked-upload/issues/60) --- chunked_upload/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chunked_upload/views.py b/chunked_upload/views.py index 5a0337b..da63710 100644 --- a/chunked_upload/views.py +++ b/chunked_upload/views.py @@ -142,7 +142,7 @@ def create_chunked_upload(self, save=False, **attrs): """ chunked_upload = self.model(**attrs) # file starts empty - chunked_upload.file.save(name='', content=ContentFile(''), save=save) + chunked_upload.file.save(name='tmp', content=ContentFile(''), save=save) return chunked_upload def is_valid_chunked_upload(self, chunked_upload): From 36da531b199c003ff286a22ce909dbd4ff645123 Mon Sep 17 00:00:00 2001 From: Steve Recio Date: Thu, 6 May 2021 19:04:16 -0400 Subject: [PATCH 4/6] fix merge conflict --- .gitignore | 3 +- README.rst | 25 +++++++------- RELEASE.md | 8 +++++ VERSION.txt | 2 +- chunked_upload/admin.py | 10 +++--- chunked_upload/constants.py | 1 + chunked_upload/migrations/0001_initial.py | 41 +++++++++++++++++++++++ chunked_upload/migrations/__init__.py | 0 chunked_upload/models.py | 32 +++++++++--------- chunked_upload/settings.py | 28 +++++++--------- chunked_upload/views.py | 12 ++++--- setup.py | 3 +- 12 files changed, 105 insertions(+), 60 deletions(-) create mode 100644 RELEASE.md create mode 100644 chunked_upload/migrations/0001_initial.py create mode 100644 chunked_upload/migrations/__init__.py diff --git a/.gitignore b/.gitignore index 18957c4..a4cbf44 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ sdist develop-eggs .installed.cfg pip-log.txt -.DS_Store \ No newline at end of file +.DS_Store +*.idea \ No newline at end of file diff --git a/README.rst b/README.rst index cad9898..e8ff300 100644 --- a/README.rst +++ b/README.rst @@ -112,12 +112,6 @@ Add any of these variables into your project settings to override them. * Storage system (should be a class). * Default: ``None`` (use default storage system) -``CHUNKED_UPLOAD_ABSTRACT_MODEL`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Boolean that defines if the ``ChunkedUpload`` model will be abstract or not (`what does abstract model mean? `__). -* Default: ``True`` - ``CHUNKED_UPLOAD_ENCODER`` ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -130,18 +124,25 @@ Add any of these variables into your project settings to override them. * Content-Type for the response data. * Default: ``'application/json'`` -``CHUNKED_UPLOAD_MIMETYPE`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* **Deprecated**, use ``CHUNKED_UPLOAD_CONTENT_TYPE`` instead. - ``CHUNKED_UPLOAD_MAX_BYTES`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Max amount of data (in bytes) that can be uploaded. ``None`` means no limit. * Default: ``None`` +``CHUNKED_UPLOAD_MODEL_USER_FIELD_NULL`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Value of `null `__ option in **user** field of `ChunkedUpload` model +* Default: ``True`` + +``CHUNKED_UPLOAD_MODEL_USER_FIELD_BLANK`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Value of `blank `__ option in **user** field of `ChunkedUpload` model +* Default: ``True`` + Support ------- -If you find any bug or you want to propose a new feature, please use the `issues tracker `__. I'll be happy to help you! :-) +If you find any bug or you want to propose a new feature, please use the `issues tracker `__. I'll be happy to help you! :-) \ No newline at end of file diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..8c83961 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,8 @@ + +# Version 2.0 +* `CHUNKED_UPLOAD_ABSTRACT_MODEL` settings variable removed +* `CHUNKED_UPLOAD_MIMETYPE` settings variable removed in favour of `CHUNKED_UPLOAD_CONTENT_TYPE` +* Support for new Django versions +* Added new settings variables, + 1. `CHUNKED_UPLOAD_MODEL_USER_FIELD_NULL` + 2. `CHUNKED_UPLOAD_MODEL_USER_FIELD_BLANK` \ No newline at end of file diff --git a/VERSION.txt b/VERSION.txt index 9c1218c..227cea2 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.1.3 \ No newline at end of file +2.0.0 diff --git a/chunked_upload/admin.py b/chunked_upload/admin.py index c9a139c..516df30 100644 --- a/chunked_upload/admin.py +++ b/chunked_upload/admin.py @@ -1,13 +1,11 @@ from django.contrib import admin - from .models import ChunkedUpload -from .settings import ABSTRACT_MODEL class ChunkedUploadAdmin(admin.ModelAdmin): - list_display = ('upload_id', 'filename', 'user', 'status', 'created_on') - search_fields = ('filename',) + list_display = ('upload_id', 'filename', 'status', 'created_on') + search_fields = ('filename', 'filename') list_filter = ('status',) -if not ABSTRACT_MODEL: # If the model exists - admin.site.register(ChunkedUpload, ChunkedUploadAdmin) + +admin.site.register(ChunkedUpload, ChunkedUploadAdmin) diff --git a/chunked_upload/constants.py b/chunked_upload/constants.py index 4b798a1..dbc4a49 100644 --- a/chunked_upload/constants.py +++ b/chunked_upload/constants.py @@ -7,6 +7,7 @@ class http_status: HTTP_403_FORBIDDEN = 403 HTTP_410_GONE = 410 + UPLOADING = 1 COMPLETE = 2 diff --git a/chunked_upload/migrations/0001_initial.py b/chunked_upload/migrations/0001_initial.py new file mode 100644 index 0000000..33418cc --- /dev/null +++ b/chunked_upload/migrations/0001_initial.py @@ -0,0 +1,41 @@ +# Generated by Django 2.2.7 on 2019-12-08 15:40 + +import chunked_upload.models +import chunked_upload.settings +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='ChunkedUpload', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('upload_id', + models.CharField(default=chunked_upload.models.generate_upload_id, editable=False, max_length=32, unique=True)), + ('file', models.FileField(max_length=255, upload_to=chunked_upload.settings.UPLOAD_TO)), + ('filename', models.CharField(max_length=255)), + ('offset', models.BigIntegerField(default=0)), + ('created_on', models.DateTimeField(auto_now_add=True)), + ('status', models.PositiveSmallIntegerField(choices=[(1, 'Uploading'), (2, 'Complete')], default=1)), + ('completed_on', models.DateTimeField(blank=True, null=True)), + ('user', + models.ForeignKey(blank=chunked_upload.settings.DEFAULT_MODEL_USER_FIELD_BLANK, + null=chunked_upload.settings.DEFAULT_MODEL_USER_FIELD_NULL, + on_delete=django.db.models.deletion.CASCADE, + related_name='chunked_uploads', + to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/chunked_upload/migrations/__init__.py b/chunked_upload/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chunked_upload/models.py b/chunked_upload/models.py index 098e4f0..f1f7309 100644 --- a/chunked_upload/models.py +++ b/chunked_upload/models.py @@ -6,17 +6,15 @@ from django.core.files.uploadedfile import UploadedFile from django.utils import timezone -from .settings import EXPIRATION_DELTA, UPLOAD_TO, STORAGE, ABSTRACT_MODEL +from .settings import EXPIRATION_DELTA, UPLOAD_TO, STORAGE, DEFAULT_MODEL_USER_FIELD_NULL, DEFAULT_MODEL_USER_FIELD_BLANK from .constants import CHUNKED_UPLOAD_CHOICES, UPLOADING -AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') - def generate_upload_id(): return uuid.uuid4().hex -class BaseChunkedUpload(models.Model): +class AbstractChunkedUpload(models.Model): """ Base chunked upload model. This model is abstract (doesn't create a table in the database). @@ -54,19 +52,19 @@ def md5(self): def delete(self, delete_file=True, *args, **kwargs): if self.file: storage, path = self.file.storage, self.file.path - super(BaseChunkedUpload, self).delete(*args, **kwargs) + super(AbstractChunkedUpload, self).delete(*args, **kwargs) if self.file and delete_file: storage.delete(path) - def __unicode__(self): + def __str__(self): return u'<%s - upload_id: %s - bytes: %s - status: %s>' % ( self.filename, self.upload_id, self.offset, self.status) def append_chunk(self, chunk, chunk_size=None, save=True): self.file.close() - self.file.open(mode='ab') # mode = append+binary - # We can use .read() safely because chunk is already in memory - self.file.write(chunk.read()) + with open(self.file.path, mode='ab') as file_obj: # mode = append+binary + file_obj.write(chunk.read()) # We can use .read() safely because chunk is already in memory + if chunk_size is not None: self.offset += chunk_size elif hasattr(chunk, 'size'): @@ -88,14 +86,14 @@ class Meta: abstract = True -class ChunkedUpload(BaseChunkedUpload): +class ChunkedUpload(AbstractChunkedUpload): """ Default chunked upload model. - To use it, set CHUNKED_UPLOAD_ABSTRACT_MODEL as True in your settings. """ - - user = models.ForeignKey(AUTH_USER_MODEL, related_name='chunked_uploads', - on_delete=models.CASCADE) - - class Meta: - abstract = ABSTRACT_MODEL + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name='chunked_uploads', + null=DEFAULT_MODEL_USER_FIELD_NULL, + blank=DEFAULT_MODEL_USER_FIELD_BLANK + ) diff --git a/chunked_upload/settings.py b/chunked_upload/settings.py index 783e50e..3bf4df6 100644 --- a/chunked_upload/settings.py +++ b/chunked_upload/settings.py @@ -1,7 +1,7 @@ -from datetime import timedelta import time import os.path - +from datetime import timedelta +from django.utils.module_loading import import_string from django.conf import settings try: @@ -15,13 +15,11 @@ except ImportError: raise ImportError('Dude! what version of Django are you using?') - # How long after creation the upload will expire DEFAULT_EXPIRATION_DELTA = timedelta(days=1) EXPIRATION_DELTA = getattr(settings, 'CHUNKED_UPLOAD_EXPIRATION_DELTA', DEFAULT_EXPIRATION_DELTA) - # Path where uploading files will be stored until completion DEFAULT_UPLOAD_PATH = 'chunked_uploads/%Y/%m/%d' UPLOAD_PATH = getattr(settings, 'CHUNKED_UPLOAD_PATH', DEFAULT_UPLOAD_PATH) @@ -31,33 +29,31 @@ def default_upload_to(instance, filename): filename = os.path.join(UPLOAD_PATH, instance.upload_id + '.part') return time.strftime(filename) -UPLOAD_TO = getattr(settings, 'CHUNKED_UPLOAD_TO', default_upload_to) -# Storage system -STORAGE = getattr(settings, 'CHUNKED_UPLOAD_STORAGE_CLASS', lambda: None)() +UPLOAD_TO = getattr(settings, 'CHUNKED_UPLOAD_TO', default_upload_to) +# Storage system -# Boolean that defines if the ChunkedUpload model is abstract or not -ABSTRACT_MODEL = getattr(settings, 'CHUNKED_UPLOAD_ABSTRACT_MODEL', True) +try: + STORAGE = getattr(settings, 'CHUNKED_UPLOAD_STORAGE_CLASS', lambda: None)() +except TypeError: + STORAGE = import_string(getattr(settings, 'CHUNKED_UPLOAD_STORAGE_CLASS', lambda: None))() # Function used to encode response data. Receives a dict and return a string DEFAULT_ENCODER = DjangoJSONEncoder().encode ENCODER = getattr(settings, 'CHUNKED_UPLOAD_ENCODER', DEFAULT_ENCODER) - # Content-Type for the response data DEFAULT_CONTENT_TYPE = 'application/json' CONTENT_TYPE = getattr(settings, 'CHUNKED_UPLOAD_CONTENT_TYPE', DEFAULT_CONTENT_TYPE) - -# CHUNKED_UPLOAD_MIMETYPE is deprecated, but kept for backward compatibility -CONTENT_TYPE = getattr(settings, 'CHUNKED_UPLOAD_MIMETYPE', - DEFAULT_CONTENT_TYPE) - - # Max amount of data (in bytes) that can be uploaded. `None` means no limit DEFAULT_MAX_BYTES = None MAX_BYTES = getattr(settings, 'CHUNKED_UPLOAD_MAX_BYTES', DEFAULT_MAX_BYTES) + +# determine the "null" and "blank" properties of "user" field in the "ChunkedUpload" model +DEFAULT_MODEL_USER_FIELD_NULL = getattr(settings, 'CHUNKED_UPLOAD_MODEL_USER_FIELD_NULL', True) +DEFAULT_MODEL_USER_FIELD_BLANK = getattr(settings, 'CHUNKED_UPLOAD_MODEL_USER_FIELD_BLANK', True) diff --git a/chunked_upload/views.py b/chunked_upload/views.py index da63710..21bb29f 100644 --- a/chunked_upload/views.py +++ b/chunked_upload/views.py @@ -25,6 +25,7 @@ class ChunkedUploadBaseView(View): # Has to be a ChunkedUpload subclass model = ChunkedUpload + user_field_name = 'user' # the field name that point towards the AUTH_USER in ChunkedUpload class or its subclasses def get_queryset(self, request): """ @@ -32,8 +33,8 @@ def get_queryset(self, request): By default, users can only continue uploading their own uploads. """ queryset = self.model.objects.all() - if hasattr(self.model, 'user') and hasattr(request, 'user') and is_authenticated(request.user): - queryset = queryset.filter(user=request.user) + if hasattr(self.model, self.user_field_name) and hasattr(request, 'user') and is_authenticated(request.user): + queryset = queryset.filter(**{self.user_field_name: request.user}) return queryset def validate(self, request): @@ -123,7 +124,10 @@ def get_extra_attrs(self, request): Extra attribute values to be passed to the new ChunkedUpload instance. Should return a dictionary-like object. """ - return {} + attrs = {} + if hasattr(self.model, self.user_field_name) and hasattr(request, 'user') and is_authenticated(request.user): + attrs[self.user_field_name] = request.user + return attrs def get_max_bytes(self, request): """ @@ -181,8 +185,6 @@ def _post(self, request, *args, **kwargs): self.is_valid_chunked_upload(chunked_upload) else: attrs = {'filename': chunk.name} - if hasattr(self.model, 'user') and hasattr(request, 'user') and is_authenticated(request.user): - attrs['user'] = request.user attrs.update(self.get_extra_attrs(request)) chunked_upload = self.create_chunked_upload(save=False, **attrs) diff --git a/setup.py b/setup.py index d0b3866..8991022 100644 --- a/setup.py +++ b/setup.py @@ -15,10 +15,9 @@ 'https://github.com/juliomalegria/django-chunked-upload/tarball/%s' ) - setup( name='django-chunked-upload', - packages=['chunked_upload'], + packages=['chunked_upload', 'chunked_upload.migrations', 'chunked_upload.management'], version=version, description=('Upload large files to Django in multiple chunks, with the ' 'ability to resume if the upload is interrupted.'), From d6211d68bd60342be3c55fa23f0b2ef39a8e845f Mon Sep 17 00:00:00 2001 From: Steve Recio Date: Thu, 14 Dec 2023 16:07:24 -0600 Subject: [PATCH 5/6] ugettext deprecated --- chunked_upload/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chunked_upload/constants.py b/chunked_upload/constants.py index dbc4a49..2875839 100644 --- a/chunked_upload/constants.py +++ b/chunked_upload/constants.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ class http_status: From c95a8830405fab677869a4aa0a15c108678712dc Mon Sep 17 00:00:00 2001 From: Steve Recio Date: Thu, 14 Dec 2023 16:08:08 -0600 Subject: [PATCH 6/6] ugettext deprecated --- chunked_upload/management/commands/delete_expired_uploads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chunked_upload/management/commands/delete_expired_uploads.py b/chunked_upload/management/commands/delete_expired_uploads.py index 2f5fc1d..e434bf9 100644 --- a/chunked_upload/management/commands/delete_expired_uploads.py +++ b/chunked_upload/management/commands/delete_expired_uploads.py @@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand from django.utils import timezone -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext as _ from chunked_upload.settings import EXPIRATION_DELTA from chunked_upload.models import ChunkedUpload