Skip to content

Commit

Permalink
Actualizando chunked_upload
Browse files Browse the repository at this point in the history
  • Loading branch information
luisza committed Aug 16, 2022
1 parent 1c38573 commit 15d4251
Show file tree
Hide file tree
Showing 25 changed files with 661 additions and 34 deletions.
9 changes: 9 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/bash

rm -rf djgentelella/static/vendors
mkdir djgentelella/static/vendors
cd demo
python manage.py createbasejs
python manage.py loaddevstatic
cd ..
python setup.py sdist
2 changes: 1 addition & 1 deletion djgentelella/blog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.utils.text import slugify
from django.utils import timezone
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from markitup.fields import MarkupField


Expand Down
2 changes: 1 addition & 1 deletion djgentelella/blog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.template.loader import render_to_string
from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _

from . import models
from .forms import EntryForm, CategoryForm
Expand Down
1 change: 1 addition & 0 deletions djgentelella/chunked_upload/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '1.0'
11 changes: 11 additions & 0 deletions djgentelella/chunked_upload/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.contrib import admin
from .models import ChunkedUpload


class ChunkedUploadAdmin(admin.ModelAdmin):
list_display = ('upload_id', 'filename', 'status', 'created_on')
search_fields = ('filename', 'filename')
list_filter = ('status',)


admin.site.register(ChunkedUpload, ChunkedUploadAdmin)
17 changes: 17 additions & 0 deletions djgentelella/chunked_upload/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.utils.translation import gettext as _


class http_status:
HTTP_200_OK = 200
HTTP_400_BAD_REQUEST = 400
HTTP_403_FORBIDDEN = 403
HTTP_410_GONE = 410


UPLOADING = 1
COMPLETE = 2

CHUNKED_UPLOAD_CHOICES = (
(UPLOADING, _('Uploading')),
(COMPLETE, _('Complete')),
)
13 changes: 13 additions & 0 deletions djgentelella/chunked_upload/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
Exceptions raised by django-chunked-upload.
"""


class ChunkedUploadError(Exception):
"""
Exception raised if errors in the request/process.
"""

def __init__(self, status, **data):
self.status_code = status
self.data = data
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from optparse import make_option

from django.core.management.base import BaseCommand
from django.utils import timezone
from django.utils.translation import ugettext as _

from chunked_upload.settings import EXPIRATION_DELTA
from chunked_upload.models import ChunkedUpload
from chunked_upload.constants import UPLOADING, COMPLETE

prompt_msg = _(u'Do you want to delete {obj}?')


class Command(BaseCommand):

# Has to be a ChunkedUpload subclass
model = ChunkedUpload

help = 'Deletes chunked uploads that have already expired.'

option_list = BaseCommand.option_list + (
make_option('--interactive',
action='store_true',
dest='interactive',
default=False,
help='Prompt confirmation before each deletion.'),
)

def handle(self, *args, **options):
interactive = options.get('interactive')

count = {UPLOADING: 0, COMPLETE: 0}
qs = self.model.objects.all()
qs = qs.filter(created_on__lt=(timezone.now() - EXPIRATION_DELTA))

for chunked_upload in qs:
if interactive:
prompt = prompt_msg.format(obj=chunked_upload) + u' (y/n): '
answer = raw_input(prompt).lower()
while answer not in ('y', 'n'):
answer = raw_input(prompt).lower()
if answer == 'n':
continue

count[chunked_upload.status] += 1
# Deleting objects individually to call delete method explicitly
chunked_upload.delete()

print('%i complete uploads were deleted.' % count[COMPLETE])
print('%i incomplete uploads were deleted.' % count[UPLOADING])
41 changes: 41 additions & 0 deletions djgentelella/chunked_upload/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by Django 2.2.7 on 2019-12-08 15:40

import djgentelella.chunked_upload.models
import djgentelella.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=djgentelella.chunked_upload.models.generate_upload_id, editable=False, max_length=32, unique=True)),
('file', models.FileField(max_length=255, upload_to=djgentelella.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=djgentelella.chunked_upload.settings.DEFAULT_MODEL_USER_FIELD_BLANK,
null=djgentelella.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,
},
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.1 on 2022-08-09 18:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chunked_upload', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='chunkedupload',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]
Empty file.
99 changes: 99 additions & 0 deletions djgentelella/chunked_upload/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import hashlib
import uuid

from django.db import models
from django.conf import settings
from django.core.files.uploadedfile import UploadedFile
from django.utils import timezone

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


def generate_upload_id():
return uuid.uuid4().hex


class AbstractChunkedUpload(models.Model):
"""
Base chunked upload model. This model is abstract (doesn't create a table
in the database).
Inherit from this model to implement your own.
"""

upload_id = models.CharField(max_length=32, unique=True, editable=False,
default=generate_upload_id)
file = models.FileField(max_length=255, upload_to=UPLOAD_TO,
storage=STORAGE)
filename = models.CharField(max_length=255)
offset = models.BigIntegerField(default=0)
created_on = models.DateTimeField(auto_now_add=True)
status = models.PositiveSmallIntegerField(choices=CHUNKED_UPLOAD_CHOICES,
default=UPLOADING)
completed_on = models.DateTimeField(null=True, blank=True)

@property
def expires_on(self):
return self.created_on + EXPIRATION_DELTA

@property
def expired(self):
return self.expires_on <= timezone.now()

@property
def md5(self):
if getattr(self, '_md5', None) is None:
md5 = hashlib.md5()
for chunk in self.file.chunks():
md5.update(chunk)
self._md5 = md5.hexdigest()
return self._md5

def delete(self, delete_file=True, *args, **kwargs):
if self.file:
storage, path = self.file.storage, self.file.path
super(AbstractChunkedUpload, self).delete(*args, **kwargs)
if self.file and delete_file:
storage.delete(path)

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()
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'):
self.offset += chunk.size
else:
self.offset = self.file.size
self._md5 = None # Clear cached md5
if save:
self.save()
self.file.close() # Flush

def get_uploaded_file(self):
self.file.close()
self.file.open(mode='rb') # mode = read+binary
return UploadedFile(file=self.file, name=self.filename,
size=self.offset)

class Meta:
abstract = True


class ChunkedUpload(AbstractChunkedUpload):
"""
Default chunked upload 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
)
16 changes: 16 additions & 0 deletions djgentelella/chunked_upload/response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.http import HttpResponse

from .settings import ENCODER, CONTENT_TYPE


class Response(HttpResponse):
"""
"""

def __init__(self, content, status=None, *args, **kwargs):
super(Response, self).__init__(
content=ENCODER(content),
content_type=CONTENT_TYPE,
status=status,
*args, **kwargs
)
59 changes: 59 additions & 0 deletions djgentelella/chunked_upload/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import time
import os.path
from datetime import timedelta
from django.utils.module_loading import import_string
from django.conf import settings

try:
from django.core.serializers.json import DjangoJSONEncoder
except ImportError:
try:
# Deprecated class name (for backwards compatibility purposes)
from django.core.serializers.json import (
DateTimeAwareJSONEncoder as DjangoJSONEncoder
)
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)


# upload_to function to be used in the FileField
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


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)

# 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)
2 changes: 2 additions & 0 deletions djgentelella/chunked_upload/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Tests for chunked_upload should be created on the app where it is being used,
# with its own views and models.
Loading

0 comments on commit 15d4251

Please sign in to comment.