Code Monkey home page Code Monkey logo

drf-chunked-upload's Introduction

drf-chunked-upload

test-status-image coverage-status-image pypi-version

This simple django app enables users to upload large files to Django Rest Framework in multiple chunks, with the ability to resume if the upload is interrupted.

This app is based to a large degree on the work of Julio Malegria, specifically his django-chunked-upload app.

License: MIT-Zero.

Installation

Install via pip:

pip install drf-chunked-upload

And then add it to your Django INSTALLED_APPS:

INSTALLED_APPS = (
    # ...
    'drf_chunked_upload',
)

Typical usage

  1. An initial PUT request is sent to the url linked to ChunkedUploadView (or any subclass) with the first chunk of the file. The name of the chunk file can be overriden in the view (class attribute field_name). Example:

    r = requests.put(
        upload_url,
        headers={
            "Content-Range": "bytes {}-{}/{}".format(index, index + size - 1, total),
        },
        data={"filename": build_file},
        files={'file': chunk_data},
    )
  2. In return, the server will respond with the url of the upload, and the current offset. Example:

    {
        'id': 'f64ebd67-83a3-45b6-8acd-c749ea1ed4cd'
        'url': 'https://your-host/<path_to_view>/f64ebd67-83a3-45b6-8acd-c749ea1ed4cd',
        'file': 'https://your-host/<path_to_file>/f64ebd67-83a3-45b6-8acd-c749ea1ed4cd.part',
        'filename': 'example.bin',
        'offset': 10000,
        `created_at`: '2021-05-18T17:12:50.318718Z',
        'status': 1,
        'completed_at': None,
        'user': 1
    }
  3. Repeatedly PUT subsequent chunks to the url returned from the server. Example:

    # PUT to https://your-host/<path_to_view>/f64ebd67-83a3-45b6-8acd-c749ea1ed4cd
    upload_url = "https://your-host/<path_to_view>/f64ebd67-83a3-45b6-8acd-c749ea1ed4cd"
    r = requests.put(
        upload_url,
        headers={
            "Content-Range": "bytes {}-{}/{}".format(index, index + size - 1, total),
        },
        data={"filename": build_file},
        files={'file': chunk_data},
    )
  4. Server will continue responding with the url and current offset.

  5. Finally, when upload is completed, POST a request to the returned url. This request must include the checksum (hex) of the entire file. Example:

    # POST to https://your-host/<path_to_view>/f64ebd67-83a3-45b6-8acd-c749ea1ed4cd
    upload_url = "https://your-host/<path_to_view>/f64ebd67-83a3-45b6-8acd-c749ea1ed4cd"
    r = requests.post(upload_url, data={"md5": "fc3ff98e8c6a0d3087d515c0473f8677"})
  6. If everything is OK, server will response with status code 200 and the data returned in the method get_response_data (if any).

If you want to upload a file as a single chunk, this is also possible! Simply make the first request a POST and include the checksum digest for the file. You don't need to include the Content-Range header if uploading a whole file.

If you want to see the list of pending chunked uploads, make a GET request to the URL linked to ChunkedUploadView (or any subclass). You will get a list of pending chunked uploads (for the currently authenticated user only).

Possible error responses:

  • Upload has expired. Server responds 410 (Gone).
  • id does not match any upload. Server responds 404 (Not found).
  • No chunk file is found in the indicated key. Server responds 400 (Bad request).
  • Request does not contain Content-Range header. Server responds 400 (Bad request).
  • Size of file exceeds limit (if specified). Server responds 400 (Bad request).
  • Offsets do not match. Server responds 400 (Bad request).
  • Checksums do not match. Server responds 400 (Bad request).

Settings

Add any of these variables into your project settings to override them.

DRF_CHUNKED_UPLOAD_EXPIRATION_DELTA

  • How long after creation the upload will expire.
  • Default: datetime.timedelta(days=1)

DRF_CHUNKED_UPLOAD_PATH

  • Path where uploaded files will be stored.
  • Default: 'chunked_uploads/%Y/%m/%d'

DRF_CHUNKED_UPLOAD_CHECKSUM

  • The type of checksum to use when verifying checksums. Options include anything supported by Python's hashlib (md5, sha1, sha256, etc)
  • Default: 'md5'

DRF_CHUNKED_UPLOAD_COMPLETE_EXT

  • Extension to use for completed uploads. Uploads will be renamed using this extension on completion, unless this extension matched DRF_CHUNKED_UPLOAD_INCOMPLETE_EXT.
  • Default: '.done'

DRF_CHUNKED_UPLOAD_INCOMPLETE_EXT

  • Extension for in progress upload files.
  • Default: '.part'

DRF_CHUNKED_UPLOAD_STORAGE_CLASS

  • Storage system (should be a class)
  • Default: None (use default storage system)

DRF_CHUNKED_UPLOAD_USER_RESTRICED

  • Boolean that determines whether only the user who created an upload can view/continue an upload.
  • Default: True

DRF_CHUNKED_UPLOAD_ABSTRACT_MODEL

DRF_CHUNKED_UPLOAD_MAX_BYTES

  • Max amount of data (in bytes) that can be uploaded. None means no limit.
  • Default: None

Support

If you find any bug or you want to propose a new feature, please use the issues tracker. Pull requests are accepted.

drf-chunked-upload's People

Contributors

carsolcas avatar ericswpark avatar jdavid avatar jkeifer avatar juliomalegria avatar milesand avatar scream4ik avatar vargg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

drf-chunked-upload's Issues

Stable release of 0.5.0?

Hi, when would the code in the ci-test branch be merged into master? No rush, but it's been nearly a month and I was wondering if the code would make it to a stable release. Thanks!

Add support for django 1.8.x UUID field

As it says, add support for django 1.8.x UUID field, but keep current compatibility but checking for UUID field and gracefully falling back to current implementation.

No Reverse Match for `chunkedupload-detail`

Using the basic setup:

from drf_chunked_upload.views import ChunkedUploadView

class MyChunkView(ChunkedUploadView):
    pass

I'm hitting a NoReverseMatch error. This seems to be cause on the serializer.py where it tries to to find a chunkedupload-detail URL and doesn't seem to find the one in tests.py (assuming this is the reverse target). I've had to get around it just creating my own url and view e.g.

urls.py

path(
    "uploadchuncks/<uuid:pk>/",
    ChunkedUploadDetailView.as_view(),
    name="chunkedupload-detail",
),

views.py

class ChunkedUploadDetailView(RetrieveAPIView):
    queryset = ChunkedUpload.objects.all()
    serializer_class = ChunkedUploadSerializer

Curious if anyone else is experiencing this?

Packages:
Django==3.2.16

RecursionError on deletion

When deleting a ChunkedUpload object, the deletion fails because the deletion function continuously calls itself.

It only occurs when calling the .delete() function on a ChunkedUpload object (i.e. chunked_upload.delete()) or if a single ChunkedUpload object is deleted in the admin panel. For some reason it doesn't occur when bulk-deleting (i.e. checking each item in the ChunkedUpload table and delete using bulk actions).

super(ChunkedUpload, self).delete(*args, **kwargs)

"Offset does not match" error when offset matches

I'm getting the following response from drf-chunked-upload:

{'detail': 'Offsets do not match', 'offset': 55000000}

The weird thing is, I am sending a chunk with the content range header start set to the offset provided:

{'Authorization': 'Token [redacted]', 'Content-Range': 'bytes 55000000-55999999/1733612877'}

I'm guessing an off-by-one mistake here on my part, but the code clearly compares the "start" regex group of the Content-Range header and the offset stored in the incomplete chunked upload:

self.is_valid_chunked_upload(chunked_upload)
if chunked_upload.offset != start:
raise ChunkedUploadError(status=status.HTTP_400_BAD_REQUEST,
detail='Offsets do not match',
offset=chunked_upload.offset)

Any idea why this would be occurring?

AttributeError when uploading?

Hi, I've set up this module with a project I'm working on and am trying to upload a file using a Python script (relevant portion attached below):

  with open(file, 'rb') as file_raw:
      while chunk := file_raw.read(chunk_size):
          r = requests.put(upload_url, headers={
              "Authorization": "Token {}".format(token),
              "Content-Range": "bytes {}-{}/{}".format(current_chunk * chunk_size + 1,
                                                       current_chunk * chunk_size + len(chunk),
                                                       total_file_size)
          },
                           data={'file': chunk})

However, I'm getting an "AttributeError: 'str' object has no attribute 'size'" response. The trace points to the following code snippet:

if chunk.size != chunk_size:

I'm guessing the error is occurring because the chunk object is being read in as a string and not in binary, so there is no size attribute. I don't know why the file is being read as a string; is there anything to fix in my code? Any help would be appreciated!

dummy example?

hey @jkeifer ! I'm implementing a restful chunked upload, and hoping to give your module a try! Do you have a dummy example to go from?

Support for S3Boto3Storage

I tried to set the DRF_CHUNKED_UPLOAD_STORAGE_CLASS to S3Boto3Storage (from django-storage).

# drf-chunked-upload Config
# https://github.com/jkeifer/drf-chunked-upload
DRF_CHUNKED_UPLOAD_ABSTRACT_MODEL = False
from storages.backends.s3boto3 import S3Boto3Storage

DRF_CHUNKED_UPLOAD_STORAGE_CLASS = S3Boto3Storage

But, I got an error while "uploading the second chunk"

AttributeError: File was not opened in write mode.
Internal Server Error: /api/v1/polls/files/4fcb004a-4155-4a19-9822-8fe6a8f3f1f7/
Traceback (most recent call last):
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/drf_chunked_upload/views.py", line 59, in put
    return self._put(request, pk=pk, *args, **kwargs)
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/drf_chunked_upload/views.py", line 221, in _put
    chunked_upload = self._put_chunk(request, pk=pk, *args, **kwargs)
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/drf_chunked_upload/views.py", line 194, in _put_chunk
    chunked_upload.append_chunk(chunk, chunk_size=chunk_size)
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/drf_chunked_upload/models.py", line 106, in append_chunk
    self.file.write(subchunk)
  File "/home/jpg/.local/share/virtualenvs/generic-django-example-Kc6WXfau/lib/python3.9/site-packages/storages/backends/s3boto3.py", line 168, in write
    raise AttributeError("File was not opened in write mode.")
AttributeError: File was not opened in write mode.

Packages used

django~=3.2.0
drf-chunked-upload~=0.5.1
django-storages[boto3]~=1.13.2

Uploads are not renamed to match filename

When the upload is in progress, the filename is the upload UUID with the extension .part. Once the upload finishes, the filename should be renamed to match the filename sent by the user.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.