#!/usr/bin/env python
# cardinal_pythonlib/django/fields/restrictedcontentfile.py
"""
===============================================================================
Original code copyright (C) 2009-2022 Rudolf Cardinal (rudolf@pobox.com).
This file is part of cardinal_pythonlib.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
===============================================================================
**Django field class storing a file (by reference to a disk file, as for
"django.db.models.FileField") but also implementing limits on the maximum
upload size.**
"""
from typing import Any
# noinspection PyUnresolvedReferences
from django import forms
# noinspection PyUnresolvedReferences
from django.core.files.uploadedfile import UploadedFile
# noinspection PyUnresolvedReferences
from django.db import models
# noinspection PyUnresolvedReferences
from django.template.defaultfilters import filesizeformat
# noinspection PyUnresolvedReferences
from django.utils.translation import gettext_lazy
# =============================================================================
# ContentTypeRestrictedFileField
# =============================================================================
[docs]class ContentTypeRestrictedFileField(models.FileField):
"""
Same as ``FileField``, but you can specify:
- ``content_types`` - list containing allowed content_types.
Example: ``['application/pdf', 'image/jpeg']``
- ``max_upload_size`` - a number indicating the maximum file size allowed
for upload.
.. code-block:: none
2.5MB - 2621440
5MB - 5242880
10MB - 10485760
20MB - 20971520
50MB - 5242880
100MB - 104857600
250MB - 214958080
500MB - 429916160
See:
- https://djangosnippets.org/snippets/2206/
- https://docs.djangoproject.com/en/1.8/ref/files/uploads/
- https://stackoverflow.com/questions/2472422/django-file-upload-size-limit
"""
def __init__(self, *args, **kwargs) -> None:
self.content_types = kwargs.pop("content_types", None)
if self.content_types is None:
self.content_types = []
self.max_upload_size = kwargs.pop("max_upload_size", None)
super().__init__(*args, **kwargs)
[docs] def clean(self, *args, **kwargs) -> Any:
data = super().clean(*args, **kwargs)
# log.debug("data: {!r}", data)
f = data.file
if not isinstance(f, UploadedFile): # RNC
# no new file uploaded; there won't be a content-type to check
return data
# log.debug("f: {!r}", f)
content_type = f.content_type
if content_type not in self.content_types:
raise forms.ValidationError(
gettext_lazy("Filetype not supported.")
)
if hasattr(f, "size"): # e.g. Django 2.1.2
uploaded_file_size = f.size
elif hasattr(f, "_size"): # e.g. Django 1.8 ?
# noinspection PyProtectedMember,PyUnresolvedReferences
uploaded_file_size = f._size
else:
raise AssertionError(f"Don't know how to get file size from {f!r}")
if (
self.max_upload_size is not None
and uploaded_file_size > self.max_upload_size
):
raise forms.ValidationError(
gettext_lazy(
"Please keep filesize under %s. Current filesize %s"
)
% (
filesizeformat(self.max_upload_size),
filesizeformat(uploaded_file_size),
)
)
return data