Skip to content

Commit

Permalink
Merging updates for release 0.0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
jcohen02 committed Jan 19, 2019
2 parents 56621a4 + 9fd46de commit 5935614
Show file tree
Hide file tree
Showing 8 changed files with 326 additions and 56 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The app can be installed from PyPi:
pip install django-drf-filepond
```

or add it to your list of dependencies in a _requirements.txt_ file.
or add it to your list of dependencies in a [_requirements.txt_](https://pip.pypa.io/en/stable/user_guide/#requirements-files) file.

#### Configuration

Expand Down
11 changes: 8 additions & 3 deletions django_drf_filepond/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
A renderers module to host a PlainTextRenderer that will render
plain text responses sending back the raw text to the client.
'''
from rest_framework.renderers import BaseRenderer
import json
from collections import OrderedDict
import json
import logging

from rest_framework.renderers import BaseRenderer


LOG = logging.getLogger(__name__)

# This plaintext renderer is taken from the example in the
# django rest framework docs since this provides exactly what we
Expand All @@ -27,7 +32,7 @@ def render(self, data, media_type=None, renderer_context=None):
'''
Encode the raw data - default charset is UTF-8.
'''
print('Data is <%s>' % data)
LOG.debug('Data is <%s>' % data)

if data:
if type(data) in [dict, OrderedDict]:
Expand Down
103 changes: 66 additions & 37 deletions django_drf_filepond/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import UploadedFile
from django.core.files.uploadedfile import UploadedFile, InMemoryUploadedFile
from django.core.validators import URLValidator
import requests
from requests.exceptions import ConnectionError
Expand All @@ -20,6 +20,8 @@
from django_drf_filepond.models import TemporaryUpload, storage
from django_drf_filepond.parsers import PlainTextParser
from django_drf_filepond.renderers import PlainTextRenderer
import re
import os

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -140,7 +142,8 @@ def get(self, request):
raise NotImplementedError('The restore function is not yet implemented')

class FetchView(APIView):
def get(self, request):

def _process_request(self, request):
LOG.debug('Filepond API: Fetch view GET called...')
'''
Supports retrieving a file on the server side that the user has
Expand Down Expand Up @@ -195,52 +198,78 @@ def get(self, request):
raise ParseError('Provided URL links to HTML content.')

buf = BytesIO()
upload_file_name = None
try:
with requests.get(target_url, allow_redirects=True, stream=True) as r:
if 'Content-Disposition' in r.headers:
cd = r.headers['Content-Disposition']
matches = re.findall('filename=(.+)', cd)
if len(matches):
upload_file_name = matches[0]
for chunk in r.iter_content(chunk_size=1048576):
buf.write(chunk)
except ConnectionError as e:
raise NotFound('Unable to access the requested remote file: %s'
% str(e))

#content_length = buf.tell()
# Generate a default filename and then if we can extract the
# filename from the URL, replace the default name with the one from
# the URL.
file_id = _get_file_id()
upload_file_name = file_id
if not target_url.endswith('/'):
split = target_url.rsplit('/',1)
upload_file_name = split[1] if len(split) > 1 else split[0]

# FIXME: After examining the approach used by fetch in the
# php-boilerplate, it seems that this part of the API is simply used
# to proxy a request to download a file at a specified URL - this
# seems inefficient since the file is sent back to the client and
# then uploaded again to the server.
## Create the file name for the data to be saved. Also get the
## original name from the request.
## There is an issue here in that we need to create a Python file
## object to be wrapped as a Django "File". However, in doing this,
## the file is created. When we save the TemporaryUpload object it
## calls save on the FileField which then fails because it finds that
## the filename is taken, tries to create an extended alternative
## name and this goes outside the 22 character require length.
## As a workaround, creating a Django InMemoryUploadedFile instead
## and using this as the file that will be written to disk.
#memfile = InMemoryUploadedFile(buf, None, file_id, content_type,
# content_length, None)
##filename = os.path.join(storage.base_location, file_id)
##with open(filename, 'w') as f:
## file_obj = File(f)
#tu = TemporaryUpload(file_id=file_id, file=memfile,
# upload_name=upload_file_name,
# upload_type=TemporaryUpload.URL)
#tu.save()
# If filename wasn't extracted from Content-Disposition header, get
# from the URL or otherwise set it to the auto-generated file_id
if not upload_file_name:
if not target_url.endswith('/'):
split = target_url.rsplit('/',1)
upload_file_name = split[1] if len(split) > 1 else split[0]
else:
upload_file_name = file_id

return (buf, file_id, upload_file_name, content_type)

def head(self, request):
LOG.debug('Filepond API: Fetch view HEAD called...')
result = self._process_request(request)
if isinstance(result, tuple):
buf, file_id, upload_file_name, content_type = result
elif isinstance(result, Response):
return result
else:
raise ValueError('process_request result is of an unexpected type')

file_size = buf.seek(0, os.SEEK_END)
buf.seek(0)

# The addressing of filepond issue #154
# (https://github.com/pqina/filepond/issues/154) means that fetch
# can now store a file downloaded from a remote URL and return file
# metadata in the header if a HEAD request is received. If we get a
# GET request then the standard approach of proxying the file back
# to the client is used.
upload_id = _get_file_id()
memfile = InMemoryUploadedFile(buf, None, file_id, content_type,
file_size, None)
tu = TemporaryUpload(upload_id=upload_id, file_id=file_id,
file=memfile, upload_name=upload_file_name,
upload_type=TemporaryUpload.URL)
tu.save()

response = Response(status=status.HTTP_200_OK)
response['Content-Type'] = content_type
response['Content-Length'] = file_size
response['X-Content-Transfer-Id'] = upload_id
response['Content-Disposition'] = ('inline; filename=%s' %
upload_file_name)
return response


def get(self, request):
result = self._process_request(request)
if isinstance(result, tuple):
buf, _, upload_file_name, content_type = result
elif isinstance(result, Response):
return result
else:
raise ValueError('process_request result is of an unexpected type')
response = Response(buf.getvalue(), status=status.HTTP_200_OK,
content_type=content_type)
response['Content-Disposition'] = ('inline; filename="%s"' %
response['Content-Disposition'] = ('inline; filename=%s' %
upload_file_name)
return response

37 changes: 35 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The app can be installed from PyPi::
or add it to your list of dependencies in a *requirements.txt* file.

Configuration
=============
-------------

There are three key configuration updates to make within your Django
application to set up django-drf-filepond:
Expand Down Expand Up @@ -60,4 +60,37 @@ On the client side, you need to set the endpoints of the ``process``,
``revert``, ``fetch``, ``load`` and ``restore`` functions to match the
endpoint used in your path statement above. For example if the first
parameter to ``url`` is ``^fp/`` then the endpoint for the ``process``
function will be ``/fp/process/``.
function will be ``/fp/process/``.

Logging
-------

django-drf-filepond outputs a variety of debug logging messages. You can
configure logging for the app through Django's `logging configuration <https://docs.djangoproject.com/en/2.1/topics/logging/>`_ in your
Django `application settings <https://docs.djangoproject.com/en/2.1/topics/settings/>`_.

For example, taking a basic logging configuration such as the first example
configuration in Django's `logging documentation examples <https://docs.djangoproject.com/en/2.1/topics/logging/#examples>`_, adding
the following to the ``loggers`` section of the ``LOGGING`` configuration dictionary will
enable DEBUG output for all modules in the ``django_drf_filepond`` package::

'django_drf_filepond': {
'handlers': ['file'],
'level': 'DEBUG',
},
You can also enable logging for individual modules or set different logging
levels for different modules by specifying the fully qualified module name in
the configuration, for example::

'django_drf_filepond.views': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': False,
},
'django_drf_filepond.models': {
'handlers': ['file'],
'level': 'INFO',
'propagate': False,
},
26 changes: 24 additions & 2 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/

LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'en-gb'

TIME_ZONE = 'UTC'

Expand All @@ -120,4 +120,26 @@
STATIC_URL = '/static/'

# The URL base used for the URL import
URL_BASE = r'^fp/'
URL_BASE = r'^fp/'

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': '[%(levelname)s]:%(name)s:%(message)s',
},
},
'handlers': {
'console': {
'formatter': 'default',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django_drf_filepond': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}

0 comments on commit 5935614

Please sign in to comment.