parsers.py 2.95 KB
Newer Older
1
"""
2 3 4 5 6 7
Custom DRF request parsers.  These can be used by views to handle different
content types, as specified by `<Parser>.media_type`.

To use these in an APIView, set `<View>.parser_classes` to a list including the
desired parsers.  See http://www.django-rest-framework.org/api-guide/parsers/
for details.
8
"""
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

from rest_framework.exceptions import ParseError, UnsupportedMediaType
from rest_framework.parsers import FileUploadParser, JSONParser


class TypedFileUploadParser(FileUploadParser):
    """
    Handles upload of files, ensuring that the media type is supported, and
    that the uploaded filename matches the Content-type.

    Requirements:
        * The view must have an `upload_media_types` attribute which is a
          set (or other container) enumerating the mimetypes of the supported
          media formats

          Example:

              View.upload_media_types = {'audio/mp3', 'audio/ogg', 'audio/wav'}

        * Content-type must be set to a supported type (as
          defined in View.upload_media_types above).

          Example:

              Content-type: audio/ogg

        * Content-disposition must include a filename with a valid extension
          for the specified Content-type.

          Example:

              Content-disposition: attachment; filename="lecture-1.ogg"
    """

    media_type = '*/*'

    # Add more entries to this as needed.  All extensions should be lowercase.
    file_extensions = {
        'image/gif': {'.gif'},
        'image/jpeg': {'.jpeg', '.jpg'},
        'image/pjpeg': {'.jpeg', '.jpg'},
        'image/png': {'.png'},
        'image/svg': {'.svg'},
    }

    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parse the request, returning a DataAndFiles object with the data dict
        left empty, and the body of the request placed in files['file'].
        """

        upload_media_types = getattr(parser_context['view'], 'upload_media_types', set())
        if media_type not in upload_media_types:
            raise UnsupportedMediaType(media_type)

        filename = self.get_filename(stream, media_type, parser_context)
        if media_type in self.file_extensions:
            fileparts = filename.rsplit('.', 1)
            if len(fileparts) < 2:
                ext = ''
            else:
                ext = '.{}'.format(fileparts[1])
            if ext.lower() not in self.file_extensions[media_type]:
                errmsg = (
                    u'File extension does not match requested Content-type. '
                    u'Filename: "{filename}", Content-type: "{contenttype}"'
                )
                raise ParseError(errmsg.format(filename=filename, contenttype=media_type))
        return super(TypedFileUploadParser, self).parse(stream, media_type, parser_context)
78 79


80
class MergePatchParser(JSONParser):
81 82 83 84
    """
    Custom parser to be used with the "merge patch" implementation (https://tools.ietf.org/html/rfc7396).
    """
    media_type = 'application/merge-patch+json'