From 284d849d97e0a852decb904bb929b735464035c8 Mon Sep 17 00:00:00 2001 From: Julian Dehm Date: Thu, 18 Jul 2024 12:00:11 +0200 Subject: [PATCH] add option to limit the file size for image and file uploads - add a validator to validate the max file size (FileMaxSizeValidator) - return the first form error in a JsonResponse so it is actually shown to the user instead of a generic error message. - add separate test_settings for pytest (to allow testing with small file sizes) - add tests for file extension and file size --- README.rst | 11 + django_ckeditor_5/forms.py | 3 + django_ckeditor_5/validators.py | 40 + django_ckeditor_5/views.py | 2 + example/blog/articles/views.py | 5 + example/blog/blog/test_settings.py | 329 ++++++++ example/blog/conftest.py | 17 + example/blog/fixtures/files/test.dat | 1 + example/blog/fixtures/files/test_big.png | Bin 0 -> 67191 bytes example/blog/tests/coverage.xml | 992 +++++++++++++++-------- example/blog/tests/test_upload_file.py | 33 + example/pyproject.toml | 2 +- pyproject.toml | 1 + 13 files changed, 1095 insertions(+), 341 deletions(-) create mode 100644 django_ckeditor_5/validators.py create mode 100644 example/blog/blog/test_settings.py create mode 100644 example/blog/fixtures/files/test.dat create mode 100644 example/blog/fixtures/files/test_big.png diff --git a/README.rst b/README.rst index 83428ef..00a8126 100644 --- a/README.rst +++ b/README.rst @@ -386,6 +386,17 @@ distinguish between image and file upload. Exposing the file upload to all/untrusted users poses a risk! +Restrict upload file size: +^^^^^^^^^^^^^^^^^^^^^^^^^^ +You can restrict the maximum size for uploaded images and files by adding + + .. code-block:: python + + CKEDITOR_5_MAX_FILE_SIZE = 5 # Max size in MB + +to your config. Default is 0 (allow any file size). + + Installing from GitHub: ^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash diff --git a/django_ckeditor_5/forms.py b/django_ckeditor_5/forms.py index 1f71355..77011a2 100644 --- a/django_ckeditor_5/forms.py +++ b/django_ckeditor_5/forms.py @@ -2,6 +2,8 @@ from django.conf import settings from django.core.validators import FileExtensionValidator +from django_ckeditor_5.validators import FileMaxSizeValidator + class UploadFileForm(forms.Form): upload = forms.FileField( @@ -13,5 +15,6 @@ class UploadFileForm(forms.Form): ["jpg", "jpeg", "png", "gif", "bmp", "webp", "tiff"], ), ), + FileMaxSizeValidator(getattr(settings, "CKEDITOR_5_MAX_FILE_SIZE", 0)), ], ) diff --git a/django_ckeditor_5/validators.py b/django_ckeditor_5/validators.py new file mode 100644 index 0000000..4f8a32c --- /dev/null +++ b/django_ckeditor_5/validators.py @@ -0,0 +1,40 @@ +from django import get_version +from django.core.exceptions import ValidationError +from django.utils.deconstruct import deconstructible + +if get_version() >= "4.0": + from django.utils.translation import gettext_lazy as _ +else: + from django.utils.translation import ugettext_lazy as _ + + +@deconstructible() +class FileMaxSizeValidator: + """Validate that a file is not bigger than max_size mb, otherwise raise ValidationError. + If zero is passed for max_size any file size is allowed. + """ + + message = _("File should be at most %(max_size)s MB.") + code = "invalid_size" + + def __init__(self, max_size): + self.max_size = max_size * 1024 * 1024 + self.orig_max_size = max_size + + def __call__(self, value): + if value.size > self.max_size > 0: + raise ValidationError( + self.message, + code=self.code, + params={ + "max_size": self.orig_max_size, + }, + ) + + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and self.max_size == other.max_size + and self.message == other.message + and self.code == other.code + ) diff --git a/django_ckeditor_5/views.py b/django_ckeditor_5/views.py index 61c440c..5176088 100644 --- a/django_ckeditor_5/views.py +++ b/django_ckeditor_5/views.py @@ -87,4 +87,6 @@ def upload_file(request): if form.is_valid(): url = handle_uploaded_file(request.user, request.FILES["upload"]) return JsonResponse({"url": url}) + if form.errors["upload"]: + return JsonResponse({"error": {"message": form.errors["upload"][0]}}, status=400) raise Http404(_("Page not found.")) diff --git a/example/blog/articles/views.py b/example/blog/articles/views.py index b095d3d..3d23b83 100644 --- a/example/blog/articles/views.py +++ b/example/blog/articles/views.py @@ -76,4 +76,9 @@ def custom_upload_file(request): if form.is_valid(): url = handle_uploaded_file(request.FILES["upload"]) return JsonResponse({"url": url}) + if form.errors["upload"]: + return JsonResponse( + {"error": {"message": form.errors["upload"][0]}}, + status=400, + ) raise Http404(_("Page not found.")) diff --git a/example/blog/blog/test_settings.py b/example/blog/blog/test_settings.py new file mode 100644 index 0000000..8c6de4b --- /dev/null +++ b/example/blog/blog/test_settings.py @@ -0,0 +1,329 @@ +""" +Django settings for blog project. + +Generated by 'django-admin startproject' using Django 3.0. + +For more information on this file, see +https://docs.djangoproject.com/en/3.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.0/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "mr3e&=yzha$_#-#1=ro@*rfsgm_k-ka%w$!=xgx1t@$9g!pz(_" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [ + "127.0.0.1", +] + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_ckeditor_5", + "articles", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "blog.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "blog.wsgi.application" + +# Database +# https://docs.djangoproject.com/en/3.0/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), + }, +} + +# Password validation +# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/3.0/topics/i18n/ + +LANGUAGE_CODE = "en" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.0/howto/static-files/ + +STATIC_URL = "/static/" +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(BASE_DIR, "media") +STATIC_ROOT = os.path.join(BASE_DIR, "static") + +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "static_files"), +] + +customColorPalette = [ + {"color": "hsl(4, 90%, 58%)", "label": "Red"}, + {"color": "hsl(340, 82%, 52%)", "label": "Pink"}, + {"color": "hsl(291, 64%, 42%)", "label": "Purple"}, + {"color": "hsl(262, 52%, 47%)", "label": "Deep Purple"}, + {"color": "hsl(231, 48%, 48%)", "label": "Indigo"}, + {"color": "hsl(207, 90%, 54%)", "label": "Blue"}, +] +CKEDITOR_5_ALLOW_ALL_FILE_TYPES = True +CKEDITOR_5_MAX_FILE_SIZE = 0.06 +CKEDITOR_5_CONFIGS = { + "default": { + "removePlugins": ["WordCount"], + "toolbar": [ + "heading", + "|", + "bold", + "italic", + "link", + "bulletedList", + "numberedList", + "blockQuote", + ], + }, + "comment": { + "language": {"ui": "en", "content": "ar"}, + "toolbar": [ + "heading", + "|", + "bold", + "italic", + "link", + "bulletedList", + "numberedList", + "blockQuote", + ], + }, + "extends": { + "language": "ru", + "blockToolbar": [ + "paragraph", + "heading1", + "heading2", + "heading3", + "|", + "bulletedList", + "numberedList", + "|", + "blockQuote", + ], + "toolbar": { + "items": [ + "heading", + "horizontalLine", + "codeBlock", + "htmlEmbed", + "|", + "outdent", + "indent", + "|", + "bold", + "italic", + "link", + "underline", + "strikethrough", + "code", + "subscript", + "superscript", + "highlight", + "|", + "bulletedList", + "numberedList", + "todoList", + "|", + "blockQuote", + "linkImage", + "insertImage", + "|", + "fontSize", + "fontFamily", + "fontColor", + "fontBackgroundColor", + "mediaEmbed", + "removeFormat", + "insertTable", + "sourceEditing", + "style", + "specialCharacters", + "fileUpload", + "showBlocks", + "selectAll", + "findAndReplace", + ], + "shouldNotGroupWhenFull": True, + }, + "image": { + "toolbar": [ + "imageTextAlternative", + "|", + "imageStyle:alignLeft", + "imageStyle:alignRight", + "imageStyle:alignCenter", + "imageStyle:side", + "|", + ], + "styles": [ + "full", + "side", + "alignLeft", + "alignRight", + "alignCenter", + ], + }, + "table": { + "contentToolbar": [ + "tableColumn", + "tableRow", + "mergeTableCells", + "tableProperties", + "tableCellProperties", + "toggleTableCaption", + ], + "tableProperties": { + "borderColors": customColorPalette, + "backgroundColors": customColorPalette, + }, + "tableCellProperties": { + "borderColors": customColorPalette, + "backgroundColors": customColorPalette, + }, + }, + "heading": { + "options": [ + { + "model": "paragraph", + "title": "Paragraph", + "class": "ck-heading_paragraph", + }, + { + "model": "heading1", + "view": "h1", + "title": "Heading 1", + "class": "ck-heading_heading1", + }, + { + "model": "heading2", + "view": "h2", + "title": "Heading 2", + "class": "ck-heading_heading2", + }, + { + "model": "heading3", + "view": "h3", + "title": "Heading 3", + "class": "ck-heading_heading3", + }, + ], + }, + "list": { + "properties": { + "styles": True, + "startIndex": True, + "reversed": True, + }, + }, + "link": {"defaultProtocol": "https://"}, + "htmlSupport": { + "allow": [ + {"name": "/.*/", "attributes": True, "classes": True, "styles": True}, + ], + }, + "mention": { + "feeds": [ + { + "marker": "@", + "feed": [ + "@Barney", + "@Lily", + "@Marry Ann", + "@Marshall", + "@Robin", + "@Ted", + ], + "minimumCharacters": 1, + }, + ], + }, + "style": { + "definitions": [ + {"name": "Article category", "element": "h3", "classes": ["category"]}, + {"name": "Info box", "element": "p", "classes": ["info-box"]}, + ], + }, + }, +} +STORAGES = { + "default": {"BACKEND": "articles.storage.CustomStorage"}, + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, +} +CKEDITOR_5_CUSTOM_CSS = "custom.css" +CK_EDITOR_5_UPLOAD_FILE_VIEW_NAME = "custom_upload_file" +CSRF_COOKIE_NAME = "new_csrf_cookie_name" diff --git a/example/blog/conftest.py b/example/blog/conftest.py index 250322e..4e78115 100644 --- a/example/blog/conftest.py +++ b/example/blog/conftest.py @@ -12,6 +12,23 @@ def file(): return open(file_path, "rb") +@pytest.fixture() +def file_big(): + file_path = os.path.join( + os.path.dirname(__file__), + "fixtures", + "files", + "test_big.png", + ) + return open(file_path, "rb") + + +@pytest.fixture() +def file_dat(): + file_path = os.path.join(os.path.dirname(__file__), "fixtures", "files", "test.dat") + return open(file_path, "rb") + + @pytest.fixture() def ckeditor5_field(): return CKEditor5Field() diff --git a/example/blog/fixtures/files/test.dat b/example/blog/fixtures/files/test.dat new file mode 100644 index 0000000..16b14f5 --- /dev/null +++ b/example/blog/fixtures/files/test.dat @@ -0,0 +1 @@ +test file diff --git a/example/blog/fixtures/files/test_big.png b/example/blog/fixtures/files/test_big.png new file mode 100644 index 0000000000000000000000000000000000000000..53b2e689856a214e3b3e1e8e81819f876c3d868a GIT binary patch literal 67191 zcmeFYbyQnlw>FvtcX#(v+?_y6ixp|n;!e@v4oQ*H7I&8d1&UMLDYSSCEydm4Em(5X z{@!!Wd&d37xZ{j*zyA(0va|MF&sy`@bIq(d*UUR@O=SXHT3i4CK%lCk^a20?p)NrH zY)sV4x!>;_)XTR3T|=)ImcGnx9uPYRXIo}3e>YoZTR#Uo0Kjk7McbGVjRpH|i(D>L zR#!H5u0a(twnjze>?4u$6^JDQ6O=!qlI6t4U^n2aOty*VjA`Y_q`$X#=5Y*)2>7{T zz??MA8mJhhY;q&~{_Vo&AmLy3N#`VhMhXy@!NW33vN zYlq{0(0?`YW&M}Ht6%X&&62oP7+7G+azg$et%x)>qLSfVMPsa!VQV4n zZCA~KSlAcB;}r_{V_tL5I99DV+a6+?lY9jP2)%}l@LFpoCT>f19v&S41+ltE(-dp8E6Ub6&}Cn}>#cZD{&nl?8mDPIJYnyBKO`HEA5T`qxmf4ytEE-# zSf3hQUBM?3Eiz?Yjglaiwb~*7s5nmE*fvQ<)XRLm#R==zW^3 zpF@tvJsanHOw<W2COlCdrV6O$%UTcYN9;G10hnPk&JEuDd_*H zNn1l)_$_T9R<`_pE^a8x0{~>fer}f5PPSgmR<`yIu5zqLZC$L)4mNVE24Wh58g7qm zUpc4*c-ZO$XzE%AI9W^Cu!7}rW&Na404}y(mdt)G&aR%)esZjTaivk$f0_kYng5D- zImxjaYG^Y*hIrUAi}H)|3-UejbMO&nmB(e4^{}y%exdaAUl6Ewa;&esyxgP(1blsc z`F%zBAs+SuLQ+yv0)oNSXoi`ng3&cE^Znc{|WEv`7ahwdg2wPp>CFD3E_m=>OQm zQx~;P1zy;CLcBe!ZJ+qqx_YtwJA{q(fA)9t_Hh2Ij*Yc|t+TBQO4JiIs?dKMQdw0) z`#*d9p}^k3#qDn|6xshx(#yf_zsUM;bNh4WuR8xe5mfj8#Qks5|DpR|VU(1HhO`pI z+WXJ+RF&jd|BNqf1F?3nk^bjW$X3Kk!b*rw%u3XTPgGc3m`_SlQiM-ZP|8L^)K1V+ z+{)_Tpj2Hwy)0d=ZT~=_!1*0eI8tK55_VRSc6_3?wnBWOLZae)mg06&d{PqDcH+Wz z))Ep{qW=b=<>7#`N=xT|pVc2I8x)k7t)-2qrJW$3kfewppQx0m6$(YlnomkfQp(!a z&PH5NMB*=$jkWYsh=+?M>Tx=_SlZhPxVhT@z43=|X+>>SIaXnQ!T)N}cDD4gLv@g2 zedgfm?e||dbRAr5b-XP9uqh-VEF>Z*A|@p)E+sA~_RpOE;_2IZc%m%v52lbHzlh}D z?tdPOG)fs1v6g@A6b10N9i@x(V-H(PFNlXO1mY~m`UisfPtX6nt$|ujHkMwNN|s)> zC{RIRQE5R5X(35nArWaY5otjw9zkJg!GDv7*f`kv|9?sUSv<_L|2%RP2T#=a{(qbP zSy4K+?*F{|=dH8D-=)ON{C81ETU!64f~TdAtIPtw}PmQTV`R8q)JLRipR-1@(xdqV8Id@Vg}741y5zNcdlb3H(c8fj>UuKQxvV_#n~UX^MCQ@U(e$I;s_|{{~hFi#P9#o^}lrej~MtL z3IDHk{V!erBL@CQ!vAYs|G&|N`(NRdtt)C5_(TLq5>^UD|KZh0P@doen&|X zss+bQ<)tS8K-l}|AJ`8mwLvvvd8ukV!P>?m#S_DyVIh$K0GI))N{YIEX8RpD&9(-5 z{H>Zf#hMqvWe+ttQr^Ue$A8tLRR!I2UlBh?uPTM*%W~9P_4`uz#|`@?>``p!;G}nV z6i_KCRS$KTbVspj;4h4at0+AJIq-Zcy3sW+@kfF;>78Q4u1pr>1M{T4f5;A&bzB*F zeh-O7GFit;*{6Q&(nb4(`GTN`Rz)n{(PD6-dmxd={h?dgYhARn=V+|PiDhI#XeMhb{VI`0o4X-n|&(edowk2!Z%4B@J9 z-h&Xnk6Jxj^>OlomYqlcv+rM*T9VC zbkc&`&8!4Cc;W=8#E4SG7#zFR?{@S+Ko$%l zb;DLsTBtu=V? z+a*O1{1!XU`i(*b=$MMdA^lPwG)OB5>-+TH>L)RbJ?PD}I%UXjl+nP%Z>Ia@#ou2q zY!I)Pb8&J`D(-O|LxQK;7B6VyVJ>jZkbwn$kD`;QXd$2;4!-=qM zN#z;l($(*)S~fGEYXb9K4GLl%fYZ^}3sGIi5dtQ^g<8pjNPYv>-7go8LL}POVr6pg zKMDjpfNy#h8DxCs+k$T|Zzn5W*5TdTNW5>sTDeT5UQYT^5J|!xCiZSkP@UbvkHPXE zW~Nks{7z3g&R9xan+*xL9{)mQ6GYk*Owb9D_1T{e_>MCmrSX-fXOIhd9teMV6>WLl zFDcq-C`-#p+0U(?{G(iz`l1bFggm# zJUJt^m7Qejw-PaI2JfNrpnWwSMkEgpkAvQ=VI6fL5!Jm9k2Q$ha}hn>vassYGHTmtSig-ee57oW7L z2OiDTajp4o`cDj{%)GSEN$FUns5ZPB)CxImHSRj!P?vBvgx=a}1+6la>sJR5Kl??AvR_g!nw%LRcm$06Q4P9n+-rQdildOa}Zj;CxiAnqI%FpRrXEkT`jMy8A z3%HCEL=ZtGQn-VD`08352R?v{%3_7=aK)uM}W8lXVj^;rFVn z*HBve={|K;QWU+hcrZb^^^e3iHS~AiYX|VY3ikRXo-_wu9qymyA=~GX(51`9iLKng zLtFE^RjTTK$#(jq{q6<^m(LtAy;Xeke0K#b09Ck`lOLTlP9A&E&5_)O#D|R!GJ~0J zLzIbt#1qEV-%s}%j5hO64JhL5e*Ul~lF`6xt(Cg4EFL{IV$EkavIUM4Y9NKaDXzUs z0Ng%XiwtwYOiZsd?=n-lo$9=7!0!ai2;Bb?sDy#LIhZk~7$n`6g%bfbED116A5h{6 zOFy{!C)dh&N2}hzkCJ8eo{~VB994++8r3ZyVr-IwhAfMxiYF^nQ&K`M`le=i>P;jkZfqdeyQLuoWE0gpUs(P?@mJ zy!)Z*yP@@OROUe|Tk=;ePA4*`Dfq&^Jp~w=qewUsQ-!mm<}oGO^+BdLf`tw9b>F~w zPWqYA+Ass!biGyX2}9u?`8+9OvE2T73VJvtHr}S%o81A`5z_o{R(7IYG6m8ap97-| z+`CPQE^f_;8uVvY-?}w<>4Gr{lUnPCNV^Y;$Lmc>swfEBOL+l}sE=;1Bl>@hdfo|) zfxPM@^)a$YpZ$zcc`ZH~*)6p+DW#EzS_rHG=En1+QUoPNQ2e{ctP1Kdvp6uq@3ImS ztX|9!azX`lKmDaHuz-^Xb1GK>q+)Awk_uk^Y~99IAeHw#BG>DI912bKTQtO?J^@VU z>|O7?mu2y5@cC?5Sg4<7HhMeXOtK0NcpZ4bDwmFB&OX{f_aq&F;HMxjS_QCo1ndUg zEdme>Wb|Jr$zb&BrCSvAALK~G#*y?pX66HKvBbDc3K4guagho(xwmg)ch8p|+{Mq!=`TX!r<#cjef6^%~ z&z)tR+|y*CboQxvpE;NET9+N6T+7&Izve;w)h%&*56kOc`1EPbMLS;m2AVoD0;K+C z6}-1OU0Vk|mzA;@ZXC1DVtsQ@e^1z%%*mf+jGA2U8gvEdO1xPyTo0!lcSx87mA(Y9_~9Vbk4+%U@Q72nuWbkscvOJ$IHo$DSX z`9_Mc>ngCzcfYFb&?u}j+`zi7iLJRmEYyy2T5|v4CFL`Ci!*xBSgLk_ANOok0qGB z+}_LUO$9s)`b(Wkyxp9TKTC!$OHg!>z*t?Yr{4uoKM)Z&aiiC^wG+VGZ03kjBj>$c zL?0Rm%Hpm3LVrm6%A>q?G7#e}InfsFmswX9fLf1rzU>!BYIGlY?8sCe`rF}Za(^;2<(lT#k_h5J^!gJvd^Aj8gjky`QX7VUIQsE zQzzbvC;eW^(d*+DjQicWW?yhsB+JO<@)Ww*s!l9Ds(YLB$ zSZF&jaKG+DrJ0JQ$4eAToDYuK1#sQ#v#2(4X8c@eOk_uN#6hlPT;#H$n)BdWL zcL$@Sl_7gATlP*W_PEG`^o^GFr2 z^u?>&{n-=_jR7gZqXuthKSx=3QCH6X>sb}LLgUr59xfav3Q1M?$RaqdCvq+2LCswk zVkHJDE-WALJqvss+fb)7?!@y^qZzLn@k5cIMCGAhID7shX-SwD_oeIUp63hP-fJ@= z6)9lpfIDOL%Y3E$ueXHTVO|`vY#SnPK2bRklG88&QY`oYpK57A`4Lf}P}w6k^d%bK zN4ZM0EPCkoGc=R_R6mX5I1;Pe6)BFV0$=DNE_WOxITT)WhSZcXE0pV3kA$qvRFC#4 z_tg>Dy9`ghMklUuC~jg`FdC|6et`!N3;J<;w9sat0s!wsqB&K?W8cJ%LBiQ#CtrT? zq{8SR?I&>G&a<30OB}dzcpKhxuwdL;O&+ds{E^p7jBeMSpCTvk(>l+e?w@z;7$Apb z^cfeu;de7SBC?ltR{{@%rrGd{&8#-W-_WiUhLz+TuaLAHuJbaarSK)-F=?1!37wU9 z>V0F8)SNCo{xl_TXh$L+-H%(BpZ!FB_(Jzb-C*8(x`1Jw(ppzr=h?T{3`R-VFYG?- zy^X7&oH(22e#I_DWzQcpufY`*pPRgZ%k-ux)3jtoD2oB?!`g;iiTo^Z&??t<*i&ND zPsB86m(~)a?o}cbs1PsB`YBQvgCv3&gE%L~&;2lg77v41BO@TFGJspBM(*O17DKOY zw^0>gH!h3~8yq3|d_QBy-;jYBoqp;xt$n*c1ew#+37JZW6hcflS%oR?O8yd1r>(+U zgFTngIi{)C-~)~YC3nYNTQ8-#QooZG7pGwFusgcdL5Q#N^ocEwMk)m_`84+*e;lEH zq(lG7^tW;aBU_HaED-X6X^5KWbRSa%yjE@L2EQDwr2CcoFe}@;+n%!&3T7^J`6bGX zN8`1-u6w!tS#Bn2(mXLI-D5+)L>?q@8zsG@M&U=A&+WFDw-%R&9)vgy?mGj2k zG@Q@_xwSL2p*E?Liy^lM<)f(BsPIgdFzew~wEtoRfPJN~_W@2`K^WJHNL~*YQyB6v zLI4g$oF*ZiG{j|upKFCXslcAoMNq#y^Fvei3V+DD<)@=Dx>?IXKo}7{RA$J#Y`8CD z4yyc>0E#6~epkT)q*!eN^uWkdhMwOIlz&w z{>|j&u^Rg8ob@a!4Fr17kL$B;W>b%WtKYtnHm}!V?8}F3M`pKL$a5`rU}OU2cL8j1 z0h{)`Vk5UBuNiEWWgLt|4`eUT-l+MbaF$pJY z&=AYxl{F0uRx(3IU#aV&uy6Wg-;wF+bw>3gAH`lV4)y4XXairFjh79|y)74Qwm?i0 zJQN6e6-vIM<7!kAA)#_1-&#T;f8AamwTkb zo2_1isc0{cOW}BYQ$h)tP}1dj{-x+Xoffl!aG_MGBbz(uK{jK+Hp{z2b@Y`AuM_ZH zFW4mNT#)kf8F9Z4Ht!+0mT*c?ocpJcN;ML{7^zp+GAQ-J$%^3f#YXRX@Wez1$eJ)d z!N6H=_sZ~hVs7a3-0bm5+vqL#x!BR3<|^iuJH6xw`%MVn^w*g|RQ`>xc^w|y7!2YX z$Z&?De;fLCcbgX4_+ck6tv(02Z#{6)-XqWa^_}&+@wy3XF{Z7W!k!Ryq@x(V`L62V z(2r9|S^va28=ILAk0WieiHp4P-|*n{kILBi2gBd2$i=$5Qh#-;4nA=5 zhmCzREDL1lXPMQEC1pI7q*tordnIq^%A>q)+%X9$e>WeYJwZ{G&Ecr! z7?fMuMYG%3?W?|$Fv!gnmv`iGSl_1&Hupo8(MwaUb*FD-ct?Q2unZB-m znFuJ#Qc;Ku8IJ&{(5SI{P&=^H4BwQOy z*Xli3*=V_VsQVmn2`D;A`>gtTS+8eMxnp`!RI4{#_PXQZ#i#AvW8d|O%g6Mh8cD0!xa%6`pAGbUqJFsKGtb8N@5wC?A=KXB+IwFOC>e!PG zY5SBWc5gi9yZXCMj-T+yJIpFZ zs`-TdB3NV(3i`&eGG2?dK`PA@R>@W~>nt>yjCa>K4#~{*EIMZq5D1XxJFXX7|^Yd0Fq~g8W;(A_C*zA6+9y#6Qn=^;zTqFUDbG!J^j - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - - - + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -56,37 +521,42 @@ - - + - - - - - - - - - + + + + + + - - - - + + + + - - - + + - - - + + + - + + + + + + + + + + + - + @@ -175,9 +645,9 @@ - - - + + + @@ -246,53 +716,57 @@ - - - - + + + - - - - + + + + + + - - - - - - - - - - + + + + + - + + + - - - - - - - - - + + + + + - + - + + + + + + + + + + + - + @@ -356,9 +830,9 @@ - + - + @@ -369,7 +843,7 @@ - + @@ -418,8 +892,8 @@ - - + + @@ -470,25 +944,76 @@ - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -563,7 +1088,7 @@ - + @@ -600,14 +1125,14 @@ - - - - + + + + - + @@ -647,7 +1172,7 @@ - + @@ -771,20 +1296,20 @@ - - - - + + + + - - + + - + - - + + @@ -805,25 +1330,25 @@ - - - + + + - + - - - - - - - + + + + + + + - - + + @@ -837,7 +1362,7 @@ - + @@ -847,11 +1372,11 @@ - - - - - + + + + + @@ -948,218 +1473,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/example/blog/tests/test_upload_file.py b/example/blog/tests/test_upload_file.py index 635de7f..8392330 100644 --- a/example/blog/tests/test_upload_file.py +++ b/example/blog/tests/test_upload_file.py @@ -11,3 +11,36 @@ def test_upload_file(admin_client, file): ) assert response.status_code == 200 assert "url" in response.json() + + +def test_upload_file_too_big(admin_client, file_big): + with file_big as upload: + upload_view_name = getattr(settings, "CK_EDITOR_5_UPLOAD_FILE_VIEW_NAME", "") + response = admin_client.post( + reverse(upload_view_name), + {"upload": upload}, + ) + assert response.status_code == 400 + response_data = response.json() + assert "error" in response_data + error = response_data["error"] + assert "message" in error + assert error["message"] == "File should be at most 0.06 MB." + + +def test_upload_file_forbbiden_file_typ(admin_client, file_dat): + with file_dat as upload: + upload_view_name = getattr(settings, "CK_EDITOR_5_UPLOAD_FILE_VIEW_NAME", "") + response = admin_client.post( + reverse(upload_view_name), + {"upload": upload}, + ) + assert response.status_code == 400 + response_data = response.json() + assert "error" in response_data + error = response_data["error"] + assert "message" in error + assert error["message"] == ( + "File extension “dat” is not allowed. Allowed extensions are: jpg, jpeg, png, gif, " + "bmp, webp, tiff." + ) diff --git a/example/pyproject.toml b/example/pyproject.toml index 566e9c8..b06fefd 100644 --- a/example/pyproject.toml +++ b/example/pyproject.toml @@ -49,7 +49,7 @@ exclude = ''' [tool.pytest.ini_options] # py.test configuration: http://doc.pytest.org/en/latest/customize.html norecursedirs = "tests/fixtures *.egg .eggs dist build docs .tox .git __pycache__ venv env migrations" -DJANGO_SETTINGS_MODULE = "blog.settings" +DJANGO_SETTINGS_MODULE = "blog.test_settings" filterwarnings = """ ignore::DeprecationWarning """ diff --git a/pyproject.toml b/pyproject.toml index 3dabde3..da9e35a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -182,5 +182,6 @@ max-statements = 50 # Recommended: 50 "example/blog/conftest.py" = ["SIM115"] "example/blog/manage.py" = ["TRY003"] "example/blog/blog/settings.py" = ["N816", "S105"] +"example/blog/blog/test_settings.py" = ["N816", "S105"] "example/blog/tests/*" = ["S101"] "example/blog/tests/test_upload_file.py" = ["ARG001"]