|
| 1 | +import os |
| 2 | +import time |
| 3 | +import hashlib |
| 4 | +import uuid |
| 5 | +from django.db import models |
| 6 | +from django.core.files.uploadedfile import UploadedFile |
| 7 | +from django.core.files.storage import FileSystemStorage |
| 8 | +from django.utils import timezone |
| 9 | +from django.conf import settings |
| 10 | +from chunked_upload.models import CHUNKED_UPLOAD_CHOICES, UPLOADING, \ |
| 11 | + AUTH_USER_MODEL, EXPIRATION_DELTA |
| 12 | + |
| 13 | +def generate_upload_id(): |
| 14 | + return uuid.uuid4().hex |
| 15 | + |
| 16 | +class BaseChunkedUpload(models.Model): |
| 17 | + """ |
| 18 | + Base chunked upload model. This model is abstract (doesn't create a table |
| 19 | + in the database). |
| 20 | + Inherit from this model to implement your own. |
| 21 | + """ |
| 22 | + |
| 23 | + upload_id = models.CharField(max_length=32, unique=True, editable=False, |
| 24 | + default=generate_upload_id) |
| 25 | + # The field "file" need to be defined in your own class to set attributes. |
| 26 | + #file = models.FileField(max_length=255, upload_to=generate_filename, |
| 27 | + # storage=STORAGE) |
| 28 | + filename = models.CharField(max_length=255) |
| 29 | + offset = models.BigIntegerField(default=0) |
| 30 | + created_on = models.DateTimeField(auto_now_add=True) |
| 31 | + status = models.PositiveSmallIntegerField(choices=CHUNKED_UPLOAD_CHOICES, |
| 32 | + default=UPLOADING) |
| 33 | + completed_on = models.DateTimeField(null=True, blank=True) |
| 34 | + |
| 35 | + @property |
| 36 | + def expires_on(self): |
| 37 | + return self.created_on + EXPIRATION_DELTA |
| 38 | + |
| 39 | + @property |
| 40 | + def expired(self): |
| 41 | + return self.expires_on <= timezone.now() |
| 42 | + |
| 43 | + @property |
| 44 | + def md5(self): |
| 45 | + if getattr(self, '_md5', None) is None: |
| 46 | + md5 = hashlib.md5() |
| 47 | + for chunk in self.file.chunks(): |
| 48 | + md5.update(chunk) |
| 49 | + self._md5 = md5.hexdigest() |
| 50 | + return self._md5 |
| 51 | + |
| 52 | + def delete(self, delete_file=True, *args, **kwargs): |
| 53 | + storage, path = self.file.storage, self.file.path |
| 54 | + super(BaseChunkedUpload, self).delete(*args, **kwargs) |
| 55 | + if delete_file: |
| 56 | + storage.delete(path) |
| 57 | + |
| 58 | + def __unicode__(self): |
| 59 | + return u'<%s - upload_id: %s - bytes: %s - status: %s>' % ( |
| 60 | + self.filename, self.upload_id, self.offset, self.status) |
| 61 | + |
| 62 | + def close_file(self): |
| 63 | + """ |
| 64 | + Bug in django 1.4: FieldFile `close` method is not reaching all the |
| 65 | + way to the actual python file. |
| 66 | + Fix: we had to loop all inner files and close them manually. |
| 67 | + """ |
| 68 | + file_ = self.file |
| 69 | + while file_ is not None: |
| 70 | + file_.close() |
| 71 | + file_ = getattr(file_, 'file', None) |
| 72 | + |
| 73 | + def append_chunk(self, chunk, chunk_size=None, save=True): |
| 74 | + self.close_file() |
| 75 | + self.file.open(mode='ab') # mode = append+binary |
| 76 | + # We can use .read() safely because chunk is already in memory |
| 77 | + self.file.write(chunk.read()) |
| 78 | + if chunk_size is not None: |
| 79 | + self.offset += chunk_size |
| 80 | + elif hasattr(chunk, 'size'): |
| 81 | + self.offset += chunk.size |
| 82 | + else: |
| 83 | + self.offset = self.file.size |
| 84 | + self._md5 = None # Clear cached md5 |
| 85 | + if save: |
| 86 | + self.save() |
| 87 | + self.close_file() # Flush |
| 88 | + |
| 89 | + def get_uploaded_file(self): |
| 90 | + self.close_file() |
| 91 | + self.file.open(mode='rb') # mode = read+binary |
| 92 | + return UploadedFile(file=self.file, name=self.filename, |
| 93 | + size=self.offset) |
| 94 | + |
| 95 | + class Meta: |
| 96 | + abstract = True |
| 97 | + |
| 98 | +from django.db import ProgrammingError |
| 99 | +from configuration.models import Path |
| 100 | + |
| 101 | +# GateareUpload |
| 102 | +try: |
| 103 | + Reception_upload_root = Path.objects.get(entity='path_ingest_reception').value |
| 104 | +except (Path.DoesNotExist, ProgrammingError) as e: |
| 105 | + Reception_upload_root = settings.MEDIA_ROOT |
| 106 | + |
| 107 | +def Reception_filename(instance, filename): |
| 108 | + upload_path = os.path.join(Reception_upload_root,'chunked_uploads/%Y/%m/%d') |
| 109 | + filename = os.path.join(upload_path, instance.upload_id + '.part') |
| 110 | + return time.strftime(filename) |
| 111 | + |
| 112 | +class Reception_storage(FileSystemStorage): |
| 113 | + def __init__(self): |
| 114 | + super(Reception_storage, self).__init__(location=Reception_upload_root) |
| 115 | + |
| 116 | +class ReceptionUpload(BaseChunkedUpload): |
| 117 | + file = models.FileField(max_length=255, upload_to=Reception_filename, |
| 118 | + storage=Reception_storage()) |
| 119 | + user = models.ForeignKey(AUTH_USER_MODEL, blank=True) |
| 120 | + |
| 121 | +# Override the default ChunkedUpload to make the `user` field nullable |
| 122 | +#TmpWorkareaUpload._meta.get_field('user').null = True |
| 123 | +#TmpWorkareaUpload._meta.get_field('file').upload_to = generate_filename2 |
| 124 | + |
| 125 | + |
0 commit comments