Skip to content
Snippets Groups Projects
Commit c274d7a5 authored by Alexa Valentová's avatar Alexa Valentová
Browse files

scan attachments with clamav

parent 27c78516
Branches
No related tags found
2 merge requests!1208Release,!1201Release career template test
Pipeline #20306 passed
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
line_length = 88 line_length = 88
multi_line_output = 3 multi_line_output = 3
include_trailing_comma = true include_trailing_comma = true
known_third_party = PyPDF2,arrow,bleach,bs4,captcha,celery,dateutil,django,environ,faker,fastjsonschema,gql,httplib2,icalendar,instaloader,markdown,modelcluster,nh3,pirates,pytest,pytz,requests,sentry_sdk,taggit,wagtail,wagtailmetadata,weasyprint,willow,yaml known_third_party = PyPDF2,arrow,bleach,bs4,captcha,celery,dateutil,django,django_ratelimit,environ,faker,fastjsonschema,gql,httplib2,icalendar,instaloader,markdown,modelcluster,nh3,pirates,pytest,pytz,requests,sentry_sdk,taggit,wagtail,wagtailmetadata,weasyprint,willow,yaml
...@@ -152,6 +152,8 @@ V produkci musí být navíc nastaveno: ...@@ -152,6 +152,8 @@ V produkci musí být navíc nastaveno:
| `EMAIL_HOST_USER` | --||-- Username | | `EMAIL_HOST_USER` | --||-- Username |
| `EMAIL_HOST_PASSWORD` | --||-- Heslo | | `EMAIL_HOST_PASSWORD` | --||-- Heslo |
| `EMAIL_PORT` | --||-- Port | | `EMAIL_PORT` | --||-- Port |
| `CLAMD_TCP_ADDR` | ClamAV host (pro skenování virů v nahraných souborech) |
| `CLAMD_TCP_SOCKET` | ClamAV socket |
Různé: Různé:
......
...@@ -2,6 +2,7 @@ import os ...@@ -2,6 +2,7 @@ import os
import tempfile import tempfile
from django import forms from django import forms
from django.core.exceptions import ValidationError
from shared.forms import ArticlesPageForm as SharedArticlesPageForm from shared.forms import ArticlesPageForm as SharedArticlesPageForm
from shared.forms import JekyllImportForm as SharedJekyllImportForm from shared.forms import JekyllImportForm as SharedJekyllImportForm
...@@ -18,15 +19,53 @@ class MultipleFileField(forms.FileField): ...@@ -18,15 +19,53 @@ class MultipleFileField(forms.FileField):
kwargs.setdefault("widget", MultipleFileInput()) kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
TOTAL_MAX_FILE_SIZE = 25 * 1024 * 1024 # 25 MB
def clean(self, data, initial=None): def clean(self, data, initial=None):
single_file_clean = super().clean single_file_clean = super().clean
if isinstance(data, (list, tuple)): if isinstance(data, (list, tuple)):
total_size = 0
for file in data:
total_size += file.size
if total_size > self.TOTAL_MAX_FILE_SIZE:
raise ValidationError(
"Celková velikost nahraných souborů je příliš velká."
)
result = [single_file_clean(d, initial) for d in data] result = [single_file_clean(d, initial) for d in data]
else: else:
result = [single_file_clean(data, initial)] result = [single_file_clean(data, initial)]
return result return result
# Allowed MIME types
ALLOWED_FILE_TYPES = [
"application/pdf", # PDF
"application/msword", # DOC
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", # DOCX
"image/png", # PNG
"application/vnd.oasis.opendocument.text", # ODT
]
def validate_file_type(file):
if file.content_type not in ALLOWED_FILE_TYPES:
raise ValidationError(
f"Chybný formát souboru: {file.content_type}. Povolené jsou pouze PDF, DOC, DOCX, PNG, and ODT."
)
def validate_file_size(file, max_size=10 * 1024 * 1024): # Default: 10 MB
if file.size > max_size:
raise ValidationError(
f"Soubory mohou být max. {max_size / (1024 * 1024)} MB velké."
)
class CareerSubmissionForm(forms.Form): class CareerSubmissionForm(forms.Form):
name = forms.CharField( name = forms.CharField(
min_length=1, min_length=1,
...@@ -74,22 +113,35 @@ class CareerSubmissionForm(forms.Form): ...@@ -74,22 +113,35 @@ class CareerSubmissionForm(forms.Form):
cv_file = forms.FileField( cv_file = forms.FileField(
required=True, required=True,
validators=[validate_file_type, validate_file_size],
widget=forms.FileInput( widget=forms.FileInput(
attrs={"class": "max-w-64 mr-auto overflow-hidden break-words"} attrs={
"class": "max-w-64 mr-auto overflow-hidden break-words",
"accept": ".pdf,.doc,.docx,.png,.odt",
}
), ),
) )
cover_letter_file = forms.FileField( cover_letter_file = forms.FileField(
required=True, required=True,
validators=[validate_file_type, validate_file_size],
widget=forms.FileInput( widget=forms.FileInput(
attrs={"class": "max-w-64 mr-auto overflow-hidden break-words"} attrs={
"class": "max-w-64 mr-auto overflow-hidden break-words",
"accept": ".pdf,.doc,.docx,.png,.odt",
}
), ),
) )
other_files = MultipleFileField( other_files = MultipleFileField(
required=False,
validators=[validate_file_type, validate_file_size],
widget=MultipleFileInput( widget=MultipleFileInput(
attrs={"class": "max-w-64 mr-auto overflow-hidden break-words"} attrs={
) "class": "max-w-64 mr-auto overflow-hidden break-words",
"accept": ".pdf,.doc,.docx,.png,.odt",
}
),
) )
personal_data_agreement = forms.BooleanField(required=True) personal_data_agreement = forms.BooleanField(required=True)
......
from io import BytesIO
import clamd
from django.conf import settings
from django.http import HttpResponseForbidden
class ClamAVMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
# If there is no Clamd connection set, don't check files as we are presumably
# in a development environment.
if not settings.CLAMD_TCP_SOCKET or not settings.CLAMD_TCP_ADDR:
return self.get_response(request)
cd = clamd.ClamdNetworkSocket(
host=settings.CLAMD_TCP_ADDR, port=settings.CLAMD_TCP_SOCKET, timeout=120
)
if request.method == "POST" and len(request.FILES) > 0:
for file_ in request.FILES.values():
scan_result = cd.instream(BytesIO(file_.read()))
if scan_result["stream"][0] == "FOUND":
return HttpResponseForbidden(
"Nahraný soubor obsahuje potenciálně škodlivý obsah."
)
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
...@@ -4,16 +4,19 @@ from django.db import migrations, models ...@@ -4,16 +4,19 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('main', '0138_maincareerpage_content'), ("main", "0138_maincareerpage_content"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='maincareerpage', model_name="maincareerpage",
name='recipient_emails', name="recipient_emails",
field=models.CharField(default='', help_text='Zadej buď jednu adresu, nebo víc, oddělených čárkami.', verbose_name='Příjemci emailů o nových přihláškách'), field=models.CharField(
default="",
help_text="Zadej buď jednu adresu, nebo víc, oddělených čárkami.",
verbose_name="Příjemci emailů o nových přihláškách",
),
preserve_default=False, preserve_default=False,
), ),
] ]
from datetime import date, datetime from datetime import date, datetime
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.db import models from django.db import models
from django.shortcuts import render from django.shortcuts import render
from django_ratelimit.core import is_ratelimited
from modelcluster.contrib.taggit import ClusterTaggableManager from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey, ParentalManyToManyField from modelcluster.fields import ParentalKey, ParentalManyToManyField
from taggit.models import TaggedItemBase from taggit.models import TaggedItemBase
...@@ -515,6 +517,11 @@ class MainCareerPage( ...@@ -515,6 +517,11 @@ class MainCareerPage(
parent_page_types = ["main.MainCareersPage"] parent_page_types = ["main.MainCareersPage"]
def serve(self, request): def serve(self, request):
if is_ratelimited(
request, group="career_submissions", key="ip", rate="2/m", method="POST"
):
raise PermissionDenied("Rate limit exceeded")
form = None form = None
current_time = datetime.now() current_time = datetime.now()
...@@ -573,7 +580,7 @@ Při otevírání souborů buďte opatrní, virový sken neproběhl! ...@@ -573,7 +580,7 @@ Při otevírání souborů buďte opatrní, virový sken neproběhl!
for file in form.cleaned_data["other_files"]: for file in form.cleaned_data["other_files"]:
email.attach(file.name, file.read(), file.content_type) email.attach(file.name, file.read(), file.content_type)
sent_successfully = email.send(fail_silently=True) sent_successfully = email.send()
if sent_successfully: if sent_successfully:
messages.add_message( messages.add_message(
...@@ -583,13 +590,21 @@ Při otevírání souborů buďte opatrní, virový sken neproběhl! ...@@ -583,13 +590,21 @@ Při otevírání souborů buďte opatrní, virový sken neproběhl!
messages.add_message( messages.add_message(
request, request,
messages.ERROR, messages.ERROR,
"Chyba serveru při odesílání přihlášky.", "Odeslání přihlášky selhalo. Zkuste to znovu.",
) )
else: else:
errors = ""
for error_val in form.errors.values():
errors += f"{error_val.as_text()}\n"
messages.add_message( messages.add_message(
request, request,
messages.ERROR, messages.ERROR,
"Chyba při odeslání přihlášky - prohlížeč odeslal chybná data.", f"""
Odeslání přihlášky selhalo:
{errors}
""",
) )
else: else:
form = CareerSubmissionForm() form = CareerSubmissionForm()
......
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
{{ form.cv_file }} {{ form.cv_file }}
<small class="text-grey-300">(Povinné)</small> <small class="text-grey-300">(Povinné, max. 10 MB)</small>
</section> </section>
<section class="flex flex-col gap-3 lg:items-center lg:flex-row"> <section class="flex flex-col gap-3 lg:items-center lg:flex-row">
<label <label
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
{{ form.cover_letter_file }} {{ form.cover_letter_file }}
<small class="text-grey-300">(Povinný)</small> <small class="text-grey-300">(Povinný, max. 10 MB)</small>
</section> </section>
<section class="flex flex-col gap-3 lg:items-center lg:flex-row"> <section class="flex flex-col gap-3 lg:items-center lg:flex-row">
<label <label
...@@ -106,7 +106,13 @@ ...@@ -106,7 +106,13 @@
for="id_other_files" for="id_other_files"
>Ostatní soubory: </label> >Ostatní soubory: </label>
<div class="flex flex-col gap-2">
{{ form.other_files }} {{ form.other_files }}
<small class="text-grey-300">
(Max. 10 MB na jeden soubor, 25 MB celkem)
</small>
</div>
</section> </section>
<section class="flex flex-row gap-3 items-start leading-none"> <section class="flex flex-row gap-3 items-start leading-none">
......
...@@ -115,6 +115,7 @@ MIDDLEWARE = [ ...@@ -115,6 +115,7 @@ MIDDLEWARE = [
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"wagtail.contrib.redirects.middleware.RedirectMiddleware", "wagtail.contrib.redirects.middleware.RedirectMiddleware",
"main.middlewares.ClamAVMiddleware",
] ]
# STATIC # STATIC
...@@ -163,6 +164,12 @@ CSRF_COOKIE_HTTPONLY = True ...@@ -163,6 +164,12 @@ CSRF_COOKIE_HTTPONLY = True
SECURE_BROWSER_XSS_FILTER = True SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = "DENY" X_FRAME_OPTIONS = "DENY"
# ClamAV
CLAMD_USE_TCP = True
CLAMD_TCP_SOCKET = env.int("CLAMD_TCP_SOCKET", default=0)
CLAMD_TCP_ADDR = env.str("CLAMD_TCP_ADDR", default="")
# needed for editing large map collections # needed for editing large map collections
DATA_UPLOAD_MAX_NUMBER_FIELDS = None DATA_UPLOAD_MAX_NUMBER_FIELDS = None
......
clamd
wagtail wagtail
wagtail-metadata wagtail-metadata
wagtail-trash wagtail-trash
...@@ -7,6 +8,7 @@ django-extensions ...@@ -7,6 +8,7 @@ django-extensions
django-redis django-redis
django-settings-export django-settings-export
django-widget-tweaks django-widget-tweaks
django-ratelimit
django-simple-captcha django-simple-captcha
gql[all] gql[all]
numpy numpy
......
...@@ -2,29 +2,29 @@ ...@@ -2,29 +2,29 @@
# This file is autogenerated by pip-compile with Python 3.11 # This file is autogenerated by pip-compile with Python 3.11
# by the following command: # by the following command:
# #
# pip-compile base.in # pip-compile requirements/base.in
# #
aiohappyeyeballs==2.4.3 aiohappyeyeballs==2.4.4
# via aiohttp # via aiohttp
aiohttp==3.10.10 aiohttp==3.11.10
# via gql # via gql
aiosignal==1.3.1 aiosignal==1.3.1
# via aiohttp # via aiohttp
amqp==5.2.0 amqp==5.3.1
# via kombu # via kombu
anyascii==0.3.2 anyascii==0.3.2
# via wagtail # via wagtail
anyio==4.6.2.post1 anyio==4.7.0
# via # via
# gql # gql
# httpx # httpx
arrow==1.3.0 arrow==1.3.0
# via # via
# -r base.in # -r requirements/base.in
# ics # ics
asgiref==3.8.1 asgiref==3.8.1
# via django # via django
asttokens==2.4.1 asttokens==3.0.0
# via stack-data # via stack-data
attrs==24.2.0 attrs==24.2.0
# via # via
...@@ -36,20 +36,20 @@ backoff==2.2.1 ...@@ -36,20 +36,20 @@ backoff==2.2.1
# via gql # via gql
beautifulsoup4==4.12.3 beautifulsoup4==4.12.3
# via # via
# -r base.in # -r requirements/base.in
# wagtail # wagtail
billiard==4.2.1 billiard==4.2.1
# via celery # via celery
bleach==6.1.0 bleach==6.2.0
# via -r base.in # via -r requirements/base.in
botocore==1.35.50 botocore==1.35.78
# via gql # via gql
brotli==1.1.0 brotli==1.1.0
# via fonttools # via fonttools
cattrs==24.1.2 cattrs==24.1.2
# via requests-cache # via requests-cache
celery==5.4.0 celery==5.4.0
# via -r base.in # via -r requirements/base.in
certifi==2024.8.30 certifi==2024.8.30
# via # via
# httpcore # httpcore
...@@ -62,6 +62,8 @@ cffi==1.17.1 ...@@ -62,6 +62,8 @@ cffi==1.17.1
# weasyprint # weasyprint
charset-normalizer==3.4.0 charset-normalizer==3.4.0
# via requests # via requests
clamd==1.0.2
# via -r requirements/base.in
click==8.1.7 click==8.1.7
# via # via
# celery # celery
...@@ -74,7 +76,7 @@ click-plugins==1.1.1 ...@@ -74,7 +76,7 @@ click-plugins==1.1.1
# via celery # via celery
click-repl==0.3.0 click-repl==0.3.0
# via celery # via celery
cryptography==43.0.3 cryptography==44.0.0
# via # via
# josepy # josepy
# mozilla-django-oidc # mozilla-django-oidc
...@@ -87,7 +89,7 @@ defusedxml==0.7.1 ...@@ -87,7 +89,7 @@ defusedxml==0.7.1
# via willow # via willow
django==5.0.7 django==5.0.7
# via # via
# -r base.in # -r requirements/base.in
# django-extensions # django-extensions
# django-filter # django-filter
# django-modelcluster # django-modelcluster
...@@ -103,9 +105,9 @@ django==5.0.7 ...@@ -103,9 +105,9 @@ django==5.0.7
# mozilla-django-oidc # mozilla-django-oidc
# wagtail # wagtail
django-environ==0.11.2 django-environ==0.11.2
# via -r base.in # via -r requirements/base.in
django-extensions==3.2.3 django-extensions==3.2.3
# via -r base.in # via -r requirements/base.in
django-filter==24.3 django-filter==24.3
# via wagtail # via wagtail
django-modelcluster==6.3 django-modelcluster==6.3
...@@ -114,18 +116,20 @@ django-permissionedforms==0.1 ...@@ -114,18 +116,20 @@ django-permissionedforms==0.1
# via wagtail # via wagtail
django-ranged-response==0.2.0 django-ranged-response==0.2.0
# via django-simple-captcha # via django-simple-captcha
django-ratelimit==4.1.0
# via -r requirements/base.in
django-redis==5.4.0 django-redis==5.4.0
# via -r base.in # via -r requirements/base.in
django-settings-export==1.2.1 django-settings-export==1.2.1
# via -r base.in # via -r requirements/base.in
django-simple-captcha==0.6.0 django-simple-captcha==0.6.0
# via -r base.in # via -r requirements/base.in
django-taggit==5.0.1 django-taggit==6.1.0
# via wagtail # via wagtail
django-treebeard==4.7.1 django-treebeard==4.7.1
# via wagtail # via wagtail
django-widget-tweaks==1.5.0 django-widget-tweaks==1.5.0
# via -r base.in # via -r requirements/base.in
djangorestframework==3.15.2 djangorestframework==3.15.2
# via wagtail # via wagtail
draftjs-exporter==5.0.0 draftjs-exporter==5.0.0
...@@ -134,43 +138,41 @@ et-xmlfile==2.0.0 ...@@ -134,43 +138,41 @@ et-xmlfile==2.0.0
# via openpyxl # via openpyxl
executing==2.1.0 executing==2.1.0
# via stack-data # via stack-data
fastjsonschema==2.20.0 fastjsonschema==2.21.1
# via -r base.in # via -r requirements/base.in
filetype==1.2.0 filetype==1.2.0
# via willow # via willow
fonttools[woff]==4.54.1 fonttools[woff]==4.55.3
# via weasyprint # via weasyprint
frozenlist==1.5.0 frozenlist==1.5.0
# via # via
# aiohttp # aiohttp
# aiosignal # aiosignal
gql[all]==3.5.0 gql[all]==3.5.0
# via -r base.in # via -r requirements/base.in
graphql-core==3.2.5 graphql-core==3.2.5
# via gql # via gql
h11==0.14.0 h11==0.14.0
# via httpcore # via httpcore
html5lib==1.1 httpcore==1.0.7
# via weasyprint
httpcore==1.0.6
# via httpx # via httpx
httplib2==0.22.0 httplib2==0.22.0
# via -r base.in # via -r requirements/base.in
httpx==0.27.2 httpx==0.28.1
# via gql # via gql
icalendar==6.0.1 icalendar==6.1.0
# via -r base.in # via -r requirements/base.in
ics==0.7.2 ics==0.7.2
# via -r base.in # via -r requirements/base.in
idna==3.10 idna==3.10
# via # via
# anyio # anyio
# httpx # httpx
# requests # requests
# yarl # yarl
ipython==8.29.0 ipython==8.30.0
# via -r base.in # via -r requirements/base.in
jedi==0.19.1 jedi==0.19.2
# via ipython # via ipython
jmespath==1.0.1 jmespath==1.0.1
# via botocore # via botocore
...@@ -183,7 +185,7 @@ l18n==2021.3 ...@@ -183,7 +185,7 @@ l18n==2021.3
laces==0.1.1 laces==0.1.1
# via wagtail # via wagtail
markdown==3.7 markdown==3.7
# via -r base.in # via -r requirements/base.in
matplotlib-inline==0.1.7 matplotlib-inline==0.1.7
# via ipython # via ipython
mozilla-django-oidc==3.0.0 mozilla-django-oidc==3.0.0
...@@ -192,44 +194,46 @@ multidict==6.1.0 ...@@ -192,44 +194,46 @@ multidict==6.1.0
# via # via
# aiohttp # aiohttp
# yarl # yarl
nh3==0.2.18 nh3==0.2.19
# via -r base.in # via -r requirements/base.in
numpy==2.1.2 numpy==2.2.0
# via # via
# -r base.in # -r requirements/base.in
# opencv-python # opencv-python
oauthlib==3.2.2 oauthlib==3.2.2
# via # via
# requests-oauthlib # requests-oauthlib
# tweepy # tweepy
opencv-python==4.10.0.84 opencv-python==4.10.0.84
# via -r base.in # via -r requirements/base.in
openpyxl==3.1.5 openpyxl==3.1.5
# via wagtail # via wagtail
parso==0.8.4 parso==0.8.4
# via jedi # via jedi
pexpect==4.9.0 pexpect==4.9.0
# via ipython # via ipython
pillow==10.4.0 pillow==11.0.0
# via # via
# django-simple-captcha # django-simple-captcha
# pillow-heif # pillow-heif
# wagtail # wagtail
# weasyprint # weasyprint
pillow-heif==0.20.0 pillow-heif==0.21.0
# via willow # via willow
pirates==0.7.0 pirates==0.7.0
# via -r base.in # via -r requirements/base.in
platformdirs==4.3.6 platformdirs==4.3.6
# via requests-cache # via requests-cache
prompt-toolkit==3.0.48 prompt-toolkit==3.0.48
# via # via
# click-repl # click-repl
# ipython # ipython
propcache==0.2.0 propcache==0.2.1
# via yarl # via
# aiohttp
# yarl
psycopg2-binary==2.9.10 psycopg2-binary==2.9.10
# via -r base.in # via -r requirements/base.in
ptyprocess==0.7.0 ptyprocess==0.7.0
# via pexpect # via pexpect
pure-eval==0.2.3 pure-eval==0.2.3
...@@ -240,12 +244,12 @@ pydyf==0.11.0 ...@@ -240,12 +244,12 @@ pydyf==0.11.0
# via weasyprint # via weasyprint
pygments==2.18.0 pygments==2.18.0
# via ipython # via ipython
pyopenssl==24.2.1 pyopenssl==24.3.0
# via josepy # via josepy
pyparsing==3.2.0 pyparsing==3.2.0
# via httplib2 # via httplib2
pypdf2==3.0.1 pypdf2==3.0.1
# via -r base.in # via -r requirements/base.in
pyphen==0.17.0 pyphen==0.17.0
# via weasyprint # via weasyprint
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
...@@ -257,16 +261,16 @@ python-dateutil==2.9.0.post0 ...@@ -257,16 +261,16 @@ python-dateutil==2.9.0.post0
# ics # ics
pytz==2024.2 pytz==2024.2
# via # via
# -r base.in # -r requirements/base.in
# django-modelcluster # django-modelcluster
# l18n # l18n
pyyaml==6.0.2 pyyaml==6.0.2
# via -r base.in # via -r requirements/base.in
redis==5.2.0 redis==5.2.1
# via django-redis # via django-redis
requests==2.32.3 requests==2.32.3
# via # via
# -r base.in # -r requirements/base.in
# gql # gql
# mozilla-django-oidc # mozilla-django-oidc
# requests-cache # requests-cache
...@@ -275,33 +279,28 @@ requests==2.32.3 ...@@ -275,33 +279,28 @@ requests==2.32.3
# tweepy # tweepy
# wagtail # wagtail
requests-cache==1.2.1 requests-cache==1.2.1
# via -r base.in # via -r requirements/base.in
requests-oauthlib==1.3.1 requests-oauthlib==1.3.1
# via tweepy # via tweepy
requests-toolbelt==1.0.0 requests-toolbelt==1.0.0
# via gql # via gql
sentry-sdk==2.17.0 sentry-sdk==2.19.2
# via -r base.in # via -r requirements/base.in
six==1.16.0 six==1.17.0
# via # via
# asttokens
# bleach
# html5lib
# ics # ics
# l18n # l18n
# python-dateutil # python-dateutil
# url-normalize # url-normalize
sniffio==1.3.1 sniffio==1.3.1
# via # via anyio
# anyio
# httpx
soupsieve==2.6 soupsieve==2.6
# via beautifulsoup4 # via beautifulsoup4
sqlparse==0.5.1 sqlparse==0.5.3
# via django # via django
stack-data==0.6.3 stack-data==0.6.3
# via ipython # via ipython
tatsu==5.12.1 tatsu==5.12.2
# via ics # via ics
telepath==0.3.1 telepath==0.3.1
# via wagtail # via wagtail
...@@ -309,16 +308,20 @@ tinycss2==1.4.0 ...@@ -309,16 +308,20 @@ tinycss2==1.4.0
# via # via
# cssselect2 # cssselect2
# weasyprint # weasyprint
tinyhtml5==2.0.0
# via weasyprint
traitlets==5.14.3 traitlets==5.14.3
# via # via
# ipython # ipython
# matplotlib-inline # matplotlib-inline
tweepy==4.14.0 tweepy==4.14.0
# via -r base.in # via -r requirements/base.in
types-python-dateutil==2.9.0.20241003 types-python-dateutil==2.9.0.20241206
# via arrow # via arrow
typing-extensions==4.12.2 typing-extensions==4.12.2
# via ipython # via
# anyio
# ipython
tzdata==2024.2 tzdata==2024.2
# via # via
# celery # celery
...@@ -337,39 +340,39 @@ vine==5.1.0 ...@@ -337,39 +340,39 @@ vine==5.1.0
# amqp # amqp
# celery # celery
# kombu # kombu
wagtail==6.2.2 wagtail==6.3.1
# via # via
# -r base.in # -r requirements/base.in
# wagtail-metadata # wagtail-metadata
# wagtail-modeladmin # wagtail-modeladmin
# wagtail-trash # wagtail-trash
wagtail-metadata==5.0.0 wagtail-metadata==5.0.0
# via -r base.in # via -r requirements/base.in
wagtail-modeladmin==2.1.0 wagtail-modeladmin==2.1.0
# via wagtail-trash # via wagtail-trash
wagtail-trash==3.0.0 wagtail-trash==3.0.0
# via -r base.in # via -r requirements/base.in
wand==0.6.13 wand==0.6.13
# via -r base.in # via -r requirements/base.in
wcwidth==0.2.13 wcwidth==0.2.13
# via prompt-toolkit # via prompt-toolkit
weasyprint==62.3 weasyprint==63.1
# via -r base.in # via -r requirements/base.in
webencodings==0.5.1 webencodings==0.5.1
# via # via
# bleach # bleach
# cssselect2 # cssselect2
# html5lib
# tinycss2 # tinycss2
# tinyhtml5
websockets==11.0.3 websockets==11.0.3
# via gql # via gql
whitenoise==5.3.0 whitenoise==5.3.0
# via -r base.in # via -r requirements/base.in
willow[heif]==1.9.0 willow[heif]==1.9.0
# via # via
# wagtail # wagtail
# willow # willow
yarl==1.17.0 yarl==1.18.3
# via # via
# aiohttp # aiohttp
# gql # gql
......
...@@ -2,21 +2,21 @@ ...@@ -2,21 +2,21 @@
# This file is autogenerated by pip-compile with Python 3.11 # This file is autogenerated by pip-compile with Python 3.11
# by the following command: # by the following command:
# #
# pip-compile dev.in # pip-compile requirements/dev.in
# #
asgiref==3.8.1 asgiref==3.8.1
# via django # via django
coverage[toml]==7.6.4 coverage[toml]==7.6.9
# via pytest-cov # via pytest-cov
django==5.0.7 django==5.0.7
# via # via
# -r dev.in # -r requirements/dev.in
# django-debug-toolbar # django-debug-toolbar
django-debug-toolbar==4.4.6 django-debug-toolbar==4.4.6
# via -r dev.in # via -r requirements/dev.in
factory-boy==3.3.1 factory-boy==3.3.1
# via pytest-factoryboy # via pytest-factoryboy
faker==30.8.1 faker==33.1.0
# via factory-boy # via factory-boy
fastdiff==0.3.0 fastdiff==0.3.0
# via snapshottest # via snapshottest
...@@ -26,45 +26,45 @@ inflection==0.5.1 ...@@ -26,45 +26,45 @@ inflection==0.5.1
# via pytest-factoryboy # via pytest-factoryboy
iniconfig==2.0.0 iniconfig==2.0.0
# via pytest # via pytest
packaging==24.1 packaging==24.2
# via # via
# pytest # pytest
# pytest-factoryboy # pytest-factoryboy
# pytest-sugar # pytest-sugar
pluggy==1.5.0 pluggy==1.5.0
# via pytest # via pytest
pytest==8.3.3 pytest==8.3.4
# via # via
# -r dev.in # -r requirements/dev.in
# pytest-cov # pytest-cov
# pytest-django # pytest-django
# pytest-factoryboy # pytest-factoryboy
# pytest-freezegun # pytest-freezegun
# pytest-mock # pytest-mock
# pytest-sugar # pytest-sugar
pytest-cov==5.0.0 pytest-cov==6.0.0
# via -r dev.in # via -r requirements/dev.in
pytest-django==4.9.0 pytest-django==4.9.0
# via -r dev.in # via -r requirements/dev.in
pytest-factoryboy==2.7.0 pytest-factoryboy==2.7.0
# via -r dev.in # via -r requirements/dev.in
pytest-freezegun==0.4.2 pytest-freezegun==0.4.2
# via -r dev.in # via -r requirements/dev.in
pytest-mock==3.14.0 pytest-mock==3.14.0
# via -r dev.in # via -r requirements/dev.in
pytest-sugar==1.0.0 pytest-sugar==1.0.0
# via -r dev.in # via -r requirements/dev.in
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
# via # via
# faker # faker
# freezegun # freezegun
six==1.16.0 six==1.17.0
# via # via
# python-dateutil # python-dateutil
# snapshottest # snapshottest
snapshottest==0.6.0 snapshottest==0.6.0
# via -r dev.in # via -r requirements/dev.in
sqlparse==0.5.1 sqlparse==0.5.3
# via # via
# django # django
# django-debug-toolbar # django-debug-toolbar
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
# This file is autogenerated by pip-compile with Python 3.11 # This file is autogenerated by pip-compile with Python 3.11
# by the following command: # by the following command:
# #
# pip-compile production.in # pip-compile requirements/production.in
# #
gunicorn==23.0.0 gunicorn==23.0.0
# via -r production.in # via -r requirements/production.in
packaging==24.1 packaging==24.2
# via gunicorn # via gunicorn
<ul class="flex flex-col w-full"> <ul class="flex flex-col w-full">
{% for message in messages %} {% for message in messages %}
<script>alert("{{ message }}");</script> <script>alert(`{{ message }}`);</script>
{% comment %} {% comment %}
<li> <li>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment