From 27c785160028c37c1f15d7f0259c072fd82adb16 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexa=20Valentov=C3=A1?= <git@imaniti.org>
Date: Wed, 11 Dec 2024 09:33:30 +0100
Subject: [PATCH] finish basic career page

---
 .../commands/octopus_people_import.py         |   2 +-
 ...uspersonpage_originating_group_and_more.py |  33 ++--
 .../0305_alter_districtcustompage_options.py  |  10 +-
 .../0063_alter_electionssimplepage_options.py |   7 +-
 main/forms.py                                 |  85 ++++++++-
 .../0132_alter_mainsimplepage_options.py      |   7 +-
 .../0133_maincareerpage_maincareerspage.py    |  80 +++++++--
 ...4_alter_maincareerpage_options_and_more.py |  11 +-
 ...35_maincareerpage_created_date_and_more.py |  62 ++++---
 .../0136_maincareerpage_category.py           |  13 +-
 ...37_maincareerspage_perex_col_1_and_more.py |  31 ++--
 .../migrations/0138_maincareerpage_content.py |   9 +-
 .../0139_maincareerpage_recipient_emails.py   |  19 ++
 main/models.py                                | 162 ++++++++++++------
 main/templates/main/main_career_page.html     | 114 +++---------
 majak/settings/base.py                        |   1 +
 ..._octopuspersonoriginatinggroup_and_more.py |  31 +++-
 shared/people_import.py                       |   4 +-
 shared/static/styleguide2/pirati-ui.svg       |   2 +-
 .../molecules/boxes/main/career_box.html      |   2 +-
 .../organisms/header/main/career_header.html  |   2 +-
 uniweb/migrations/0114_auto_20241122_1238.py  |   7 +-
 22 files changed, 447 insertions(+), 247 deletions(-)
 create mode 100644 main/migrations/0139_maincareerpage_recipient_emails.py

diff --git a/district/management/commands/octopus_people_import.py b/district/management/commands/octopus_people_import.py
index 16e331a4..83b7bc70 100644
--- a/district/management/commands/octopus_people_import.py
+++ b/district/management/commands/octopus_people_import.py
@@ -36,7 +36,7 @@ class Command(BaseCommand):
                 if not hasattr(people_page.root_page, "image_collection_id"):
                     # FIXME: This should not be UniwebHomePage, but sometimes it is.
                     continue
-                
+
                 collection_id = people_page.root_page.image_collection_id
 
                 if collection_id is None:
diff --git a/district/migrations/0304_remove_districtoctopuspersonpage_originating_group_and_more.py b/district/migrations/0304_remove_districtoctopuspersonpage_originating_group_and_more.py
index 4696fa53..96075507 100644
--- a/district/migrations/0304_remove_districtoctopuspersonpage_originating_group_and_more.py
+++ b/district/migrations/0304_remove_districtoctopuspersonpage_originating_group_and_more.py
@@ -4,29 +4,36 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('district', '0303_alter_districtcenterpage_content'),
-        ('shared', '0014_octopuspersonoriginatinggroup_and_more'),
+        ("district", "0303_alter_districtcenterpage_content"),
+        ("shared", "0014_octopuspersonoriginatinggroup_and_more"),
     ]
 
     operations = [
         migrations.RemoveField(
-            model_name='districtoctopuspersonpage',
-            name='originating_group',
+            model_name="districtoctopuspersonpage",
+            name="originating_group",
         ),
         migrations.RemoveField(
-            model_name='districtoctopuspersonpage',
-            name='originating_team',
+            model_name="districtoctopuspersonpage",
+            name="originating_team",
         ),
         migrations.AddField(
-            model_name='districtoctopuspersonpage',
-            name='originating_groups',
-            field=models.ManyToManyField(help_text='Skupiny, ze kterých byla tato osba importována.', to='shared.octopuspersonoriginatinggroup', verbose_name='Skupiny'),
+            model_name="districtoctopuspersonpage",
+            name="originating_groups",
+            field=models.ManyToManyField(
+                help_text="Skupiny, ze kterých byla tato osba importována.",
+                to="shared.octopuspersonoriginatinggroup",
+                verbose_name="Skupiny",
+            ),
         ),
         migrations.AddField(
-            model_name='districtoctopuspersonpage',
-            name='originating_teams',
-            field=models.ManyToManyField(help_text='Týmy, ze kterých byla tato osba importována.', to='shared.octopuspersonoriginatingteam', verbose_name='Tým'),
+            model_name="districtoctopuspersonpage",
+            name="originating_teams",
+            field=models.ManyToManyField(
+                help_text="Týmy, ze kterých byla tato osba importována.",
+                to="shared.octopuspersonoriginatingteam",
+                verbose_name="Tým",
+            ),
         ),
     ]
diff --git a/district/migrations/0305_alter_districtcustompage_options.py b/district/migrations/0305_alter_districtcustompage_options.py
index 6b20ce54..b7ee5563 100644
--- a/district/migrations/0305_alter_districtcustompage_options.py
+++ b/district/migrations/0305_alter_districtcustompage_options.py
@@ -4,14 +4,16 @@ from django.db import migrations
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('district', '0304_remove_districtoctopuspersonpage_originating_group_and_more'),
+        (
+            "district",
+            "0304_remove_districtoctopuspersonpage_originating_group_and_more",
+        ),
     ]
 
     operations = [
         migrations.AlterModelOptions(
-            name='districtcustompage',
-            options={'verbose_name': 'Jednoduchá stránka'},
+            name="districtcustompage",
+            options={"verbose_name": "Jednoduchá stránka"},
         ),
     ]
diff --git a/elections/migrations/0063_alter_electionssimplepage_options.py b/elections/migrations/0063_alter_electionssimplepage_options.py
index 085f69f1..76a8469b 100644
--- a/elections/migrations/0063_alter_electionssimplepage_options.py
+++ b/elections/migrations/0063_alter_electionssimplepage_options.py
@@ -4,14 +4,13 @@ from django.db import migrations
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('elections', '0062_alter_electionsarticlepage_content'),
+        ("elections", "0062_alter_electionsarticlepage_content"),
     ]
 
     operations = [
         migrations.AlterModelOptions(
-            name='electionssimplepage',
-            options={'verbose_name': 'Jednoduchá stránka'},
+            name="electionssimplepage",
+            options={"verbose_name": "Jednoduchá stránka"},
         ),
     ]
diff --git a/main/forms.py b/main/forms.py
index fb710b04..51f89000 100644
--- a/main/forms.py
+++ b/main/forms.py
@@ -1,5 +1,6 @@
 import os
 import tempfile
+
 from django import forms
 
 from shared.forms import ArticlesPageForm as SharedArticlesPageForm
@@ -8,8 +9,90 @@ from shared.forms import JekyllImportForm as SharedJekyllImportForm
 from .tasks import import_jekyll_articles
 
 
+class MultipleFileInput(forms.ClearableFileInput):
+    allow_multiple_selected = True
+
+
+class MultipleFileField(forms.FileField):
+    def __init__(self, *args, **kwargs):
+        kwargs.setdefault("widget", MultipleFileInput())
+        super().__init__(*args, **kwargs)
+
+    def clean(self, data, initial=None):
+        single_file_clean = super().clean
+        if isinstance(data, (list, tuple)):
+            result = [single_file_clean(d, initial) for d in data]
+        else:
+            result = [single_file_clean(data, initial)]
+        return result
+
+
 class CareerSubmissionForm(forms.Form):
-    pass
+    name = forms.CharField(
+        min_length=1,
+        max_length=256,
+        required=True,
+        widget=forms.TextInput(
+            attrs={"placeholder": "Jméno", "class": "lg:w-auto w-full"}
+        ),
+    )
+
+    surname = forms.CharField(
+        min_length=1,
+        max_length=256,
+        required=True,
+        widget=forms.TextInput(
+            attrs={"placeholder": "Příjmení", "class": "lg:w-auto w-full"}
+        ),
+    )
+
+    email = forms.EmailField(
+        min_length=1,
+        max_length=256,
+        required=True,
+        widget=forms.EmailInput(attrs={"placeholder": "Email", "class": "w-full"}),
+    )
+
+    phone = forms.IntegerField(
+        required=True,
+        widget=forms.NumberInput(
+            attrs={"type": "tel", "placeholder": "Telefon", "class": "w-full"}
+        ),
+    )
+
+    own_text = forms.CharField(
+        widget=forms.Textarea(
+            attrs={
+                "placeholder": "VlastnĂ­ text (nepovinnĂ˝)",
+                "class": "w-full",
+                "rows": 3,
+            }
+        ),
+        max_length=65535,
+        required=False,
+    )
+
+    cv_file = forms.FileField(
+        required=True,
+        widget=forms.FileInput(
+            attrs={"class": "max-w-64 mr-auto overflow-hidden break-words"}
+        ),
+    )
+
+    cover_letter_file = forms.FileField(
+        required=True,
+        widget=forms.FileInput(
+            attrs={"class": "max-w-64 mr-auto overflow-hidden break-words"}
+        ),
+    )
+
+    other_files = MultipleFileField(
+        widget=MultipleFileInput(
+            attrs={"class": "max-w-64 mr-auto overflow-hidden break-words"}
+        )
+    )
+
+    personal_data_agreement = forms.BooleanField(required=True)
 
 
 class JekyllImportForm(SharedJekyllImportForm):
diff --git a/main/migrations/0132_alter_mainsimplepage_options.py b/main/migrations/0132_alter_mainsimplepage_options.py
index 9c491cb2..645cd00a 100644
--- a/main/migrations/0132_alter_mainsimplepage_options.py
+++ b/main/migrations/0132_alter_mainsimplepage_options.py
@@ -4,14 +4,13 @@ from django.db import migrations
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('main', '0131_alter_mainarticlepage_content_and_more'),
+        ("main", "0131_alter_mainarticlepage_content_and_more"),
     ]
 
     operations = [
         migrations.AlterModelOptions(
-            name='mainsimplepage',
-            options={'verbose_name': 'Jednoduchá stránka'},
+            name="mainsimplepage",
+            options={"verbose_name": "Jednoduchá stránka"},
         ),
     ]
diff --git a/main/migrations/0133_maincareerpage_maincareerspage.py b/main/migrations/0133_maincareerpage_maincareerspage.py
index 8861b37f..3dd36516 100644
--- a/main/migrations/0133_maincareerpage_maincareerspage.py
+++ b/main/migrations/0133_maincareerpage_maincareerspage.py
@@ -1,40 +1,90 @@
 # Generated by Django 5.0.7 on 2024-12-09 15:00
 
 import django.db.models.deletion
-import shared.models.main
 import wagtailmetadata.models
 from django.db import migrations, models
 
+import shared.models.main
+
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('main', '0132_alter_mainsimplepage_options'),
-        ('wagtailcore', '0094_alter_page_locale'),
-        ('wagtailimages', '0026_delete_uploadedimage'),
+        ("main", "0132_alter_mainsimplepage_options"),
+        ("wagtailcore", "0094_alter_page_locale"),
+        ("wagtailimages", "0026_delete_uploadedimage"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='MainCareerPage',
+            name="MainCareerPage",
             fields=[
-                ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
-                ('search_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image', verbose_name='Search image')),
+                (
+                    "page_ptr",
+                    models.OneToOneField(
+                        auto_created=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        parent_link=True,
+                        primary_key=True,
+                        serialize=False,
+                        to="wagtailcore.page",
+                    ),
+                ),
+                (
+                    "search_image",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.SET_NULL,
+                        related_name="+",
+                        to="wagtailimages.image",
+                        verbose_name="Search image",
+                    ),
+                ),
             ],
             options={
-                'abstract': False,
+                "abstract": False,
             },
-            bases=(shared.models.main.SubpageMixin, wagtailmetadata.models.WagtailImageMetadataMixin, 'wagtailcore.page', models.Model),
+            bases=(
+                shared.models.main.SubpageMixin,
+                wagtailmetadata.models.WagtailImageMetadataMixin,
+                "wagtailcore.page",
+                models.Model,
+            ),
         ),
         migrations.CreateModel(
-            name='MainCareersPage',
+            name="MainCareersPage",
             fields=[
-                ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
-                ('search_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image', verbose_name='Search image')),
+                (
+                    "page_ptr",
+                    models.OneToOneField(
+                        auto_created=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        parent_link=True,
+                        primary_key=True,
+                        serialize=False,
+                        to="wagtailcore.page",
+                    ),
+                ),
+                (
+                    "search_image",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.SET_NULL,
+                        related_name="+",
+                        to="wagtailimages.image",
+                        verbose_name="Search image",
+                    ),
+                ),
             ],
             options={
-                'abstract': False,
+                "abstract": False,
             },
-            bases=(shared.models.main.SubpageMixin, wagtailmetadata.models.WagtailImageMetadataMixin, 'wagtailcore.page', models.Model),
+            bases=(
+                shared.models.main.SubpageMixin,
+                wagtailmetadata.models.WagtailImageMetadataMixin,
+                "wagtailcore.page",
+                models.Model,
+            ),
         ),
     ]
diff --git a/main/migrations/0134_alter_maincareerpage_options_and_more.py b/main/migrations/0134_alter_maincareerpage_options_and_more.py
index d393524d..fc77624c 100644
--- a/main/migrations/0134_alter_maincareerpage_options_and_more.py
+++ b/main/migrations/0134_alter_maincareerpage_options_and_more.py
@@ -4,18 +4,17 @@ from django.db import migrations
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('main', '0133_maincareerpage_maincareerspage'),
+        ("main", "0133_maincareerpage_maincareerspage"),
     ]
 
     operations = [
         migrations.AlterModelOptions(
-            name='maincareerpage',
-            options={'verbose_name': 'PracovnĂ­ nabĂ­dka'},
+            name="maincareerpage",
+            options={"verbose_name": "PracovnĂ­ nabĂ­dka"},
         ),
         migrations.AlterModelOptions(
-            name='maincareerspage',
-            options={'verbose_name': 'Kariéry'},
+            name="maincareerspage",
+            options={"verbose_name": "Kariéry"},
         ),
     ]
diff --git a/main/migrations/0135_maincareerpage_created_date_and_more.py b/main/migrations/0135_maincareerpage_created_date_and_more.py
index 9d496acf..7d7958a5 100644
--- a/main/migrations/0135_maincareerpage_created_date_and_more.py
+++ b/main/migrations/0135_maincareerpage_created_date_and_more.py
@@ -1,49 +1,71 @@
 # Generated by Django 5.0.7 on 2024-12-09 15:36
 
 import datetime
+
 from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('main', '0134_alter_maincareerpage_options_and_more'),
+        ("main", "0134_alter_maincareerpage_options_and_more"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='maincareerpage',
-            name='created_date',
-            field=models.DateField(default=datetime.date.today, verbose_name='Datum vytvoření'),
+            model_name="maincareerpage",
+            name="created_date",
+            field=models.DateField(
+                default=datetime.date.today, verbose_name="Datum vytvoření"
+            ),
         ),
         migrations.AddField(
-            model_name='maincareerpage',
-            name='employment_relationship',
-            field=models.CharField(default=None, help_text="Např. 'Rámcová smlouva na dobu určitou'", max_length=128, verbose_name='Poměr'),
+            model_name="maincareerpage",
+            name="employment_relationship",
+            field=models.CharField(
+                default=None,
+                help_text="Např. 'Rámcová smlouva na dobu určitou'",
+                max_length=128,
+                verbose_name="Poměr",
+            ),
             preserve_default=False,
         ),
         migrations.AddField(
-            model_name='maincareerpage',
-            name='location',
-            field=models.CharField(default=None, help_text="Např. 'Středočeský kraj'", max_length=64, verbose_name='Místo výkonu práce'),
+            model_name="maincareerpage",
+            name="location",
+            field=models.CharField(
+                default=None,
+                help_text="Např. 'Středočeský kraj'",
+                max_length=64,
+                verbose_name="Místo výkonu práce",
+            ),
             preserve_default=False,
         ),
         migrations.AddField(
-            model_name='maincareerpage',
-            name='pay_rate',
-            field=models.CharField(default=None, help_text="Např. '300-350 Kč/h'", max_length=64, verbose_name='Odměna'),
+            model_name="maincareerpage",
+            name="pay_rate",
+            field=models.CharField(
+                default=None,
+                help_text="NapĹ™. '300-350 KÄŤ/h'",
+                max_length=64,
+                verbose_name="Odměna",
+            ),
             preserve_default=False,
         ),
         migrations.AddField(
-            model_name='maincareerpage',
-            name='submission_end_date',
-            field=models.DateField(default=None, verbose_name='Datum konce přihlášek'),
+            model_name="maincareerpage",
+            name="submission_end_date",
+            field=models.DateField(default=None, verbose_name="Datum konce přihlášek"),
             preserve_default=False,
         ),
         migrations.AddField(
-            model_name='maincareerpage',
-            name='time_cost',
-            field=models.CharField(default=None, help_text="Např. '8h denně'", max_length=64, verbose_name='Časová náročnost'),
+            model_name="maincareerpage",
+            name="time_cost",
+            field=models.CharField(
+                default=None,
+                help_text="NapĹ™. '8h dennÄ›'",
+                max_length=64,
+                verbose_name="Časová náročnost",
+            ),
             preserve_default=False,
         ),
     ]
diff --git a/main/migrations/0136_maincareerpage_category.py b/main/migrations/0136_maincareerpage_category.py
index 67b5a7e1..775263fd 100644
--- a/main/migrations/0136_maincareerpage_category.py
+++ b/main/migrations/0136_maincareerpage_category.py
@@ -4,16 +4,19 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('main', '0135_maincareerpage_created_date_and_more'),
+        ("main", "0135_maincareerpage_created_date_and_more"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='maincareerpage',
-            name='category',
-            field=models.CharField(default='Bez kategorie', help_text="Např. 'Koordinátor/ka', 'Programátor/ka', 'Volební manažer/ka', ...", verbose_name='Kategorie pracovní pozice'),
+            model_name="maincareerpage",
+            name="category",
+            field=models.CharField(
+                default="Bez kategorie",
+                help_text="Např. 'Koordinátor/ka', 'Programátor/ka', 'Volební manažer/ka', ...",
+                verbose_name="Kategorie pracovnĂ­ pozice",
+            ),
             preserve_default=False,
         ),
     ]
diff --git a/main/migrations/0137_maincareerspage_perex_col_1_and_more.py b/main/migrations/0137_maincareerspage_perex_col_1_and_more.py
index 89f09a9c..9f923809 100644
--- a/main/migrations/0137_maincareerspage_perex_col_1_and_more.py
+++ b/main/migrations/0137_maincareerspage_perex_col_1_and_more.py
@@ -4,25 +4,34 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('main', '0136_maincareerpage_category'),
+        ("main", "0136_maincareerpage_category"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='maincareerspage',
-            name='perex_col_1',
-            field=models.TextField(blank=True, null=True, verbose_name='Perex - prvnĂ­ sloupec'),
+            model_name="maincareerspage",
+            name="perex_col_1",
+            field=models.TextField(
+                blank=True, null=True, verbose_name="Perex - prvnĂ­ sloupec"
+            ),
         ),
         migrations.AddField(
-            model_name='maincareerspage',
-            name='perex_col_2',
-            field=models.TextField(blank=True, null=True, verbose_name='Perex - druhĂ˝ sloupec'),
+            model_name="maincareerspage",
+            name="perex_col_2",
+            field=models.TextField(
+                blank=True, null=True, verbose_name="Perex - druhĂ˝ sloupec"
+            ),
         ),
         migrations.AddField(
-            model_name='maincareerspage',
-            name='subheading',
-            field=models.CharField(blank=True, help_text='Text pod hlavním nadpisem stránky', max_length=32, null=True, verbose_name='Podtitulek'),
+            model_name="maincareerspage",
+            name="subheading",
+            field=models.CharField(
+                blank=True,
+                help_text="Text pod hlavním nadpisem stránky",
+                max_length=32,
+                null=True,
+                verbose_name="Podtitulek",
+            ),
         ),
     ]
diff --git a/main/migrations/0138_maincareerpage_content.py b/main/migrations/0138_maincareerpage_content.py
index 283c67b8..2be821d3 100644
--- a/main/migrations/0138_maincareerpage_content.py
+++ b/main/migrations/0138_maincareerpage_content.py
@@ -5,15 +5,14 @@ from django.db import migrations
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('main', '0137_maincareerspage_perex_col_1_and_more'),
+        ("main", "0137_maincareerspage_perex_col_1_and_more"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='maincareerpage',
-            name='content',
-            field=wagtail.fields.RichTextField(blank=True, verbose_name='Text nabĂ­dky'),
+            model_name="maincareerpage",
+            name="content",
+            field=wagtail.fields.RichTextField(blank=True, verbose_name="Text nabĂ­dky"),
         ),
     ]
diff --git a/main/migrations/0139_maincareerpage_recipient_emails.py b/main/migrations/0139_maincareerpage_recipient_emails.py
new file mode 100644
index 00000000..59783e9e
--- /dev/null
+++ b/main/migrations/0139_maincareerpage_recipient_emails.py
@@ -0,0 +1,19 @@
+# Generated by Django 5.0.7 on 2024-12-10 16:50
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0138_maincareerpage_content'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='maincareerpage',
+            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'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/main/models.py b/main/models.py
index 0a59148c..e9c2f00c 100644
--- a/main/models.py
+++ b/main/models.py
@@ -1,4 +1,9 @@
+from datetime import date, datetime
+
+from django.contrib import messages
+from django.core.mail import EmailMessage
 from django.db import models
+from django.shortcuts import render
 from modelcluster.contrib.taggit import ClusterTaggableManager
 from modelcluster.fields import ParentalKey, ParentalManyToManyField
 from taggit.models import TaggedItemBase
@@ -8,9 +13,6 @@ from wagtail.contrib.routable_page.models import route
 from wagtail.fields import RichTextField, StreamField
 from wagtail.models import Page
 from wagtailmetadata.models import MetadataPageMixin
-from datetime import date
-from django.contrib import messages
-from django.shortcuts import render
 
 from shared import blocks as shared_blocks
 from shared.const import RICH_TEXT_DEFAULT_FEATURES
@@ -34,7 +36,7 @@ from shared.models import (  # MenuMixin,
 from shared.utils import make_promote_panels
 
 from . import blocks
-from .forms import MainArticlesPageForm, CareerSubmissionForm
+from .forms import CareerSubmissionForm, MainArticlesPageForm
 
 
 class MainHomePage(MainHomePageMixin):
@@ -95,7 +97,6 @@ class MainHomePage(MainHomePageMixin):
     class Meta:
         verbose_name = "HomePage pirati.cz"
 
-
     @property
     def careers_page(self):
         return self._first_subpage_of_type(MainCareersPage)
@@ -383,7 +384,7 @@ class MainCareersPage(
         help_text="Text pod hlavním nadpisem stránky",
         max_length=32,
         blank=True,
-        null=True
+        null=True,
     )
 
     perex_col_1 = models.TextField(
@@ -416,26 +417,16 @@ class MainCareersPage(
 
     def get_career_categories(self) -> list[str]:
         return (
-            MainCareerPage.
-            objects.
-            child_of(self).
-            live().
-            distinct("category").
-            values_list("category", flat=True).
-            order_by("category").
-            all()
+            MainCareerPage.objects.child_of(self)
+            .live()
+            .distinct("category")
+            .values_list("category", flat=True)
+            .order_by("category")
+            .all()
         )
 
-
     def get_career_pages(self):
-        return (
-            MainCareerPage.
-            objects.
-            child_of(self).
-            live().
-            all()
-        )
-
+        return MainCareerPage.objects.child_of(self).live().all()
 
     class Meta:
         verbose_name = "Kariéry"
@@ -444,11 +435,18 @@ class MainCareersPage(
 class MainCareerPage(
     ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, PageInMenuMixin, Page
 ):
+    recipient_emails = models.CharField(
+        verbose_name="Příjemci emailů o nových přihláškách",
+        help_text="Zadej buď jednu adresu, nebo víc, oddělených čárkami.",
+        blank=False,
+        null=False,
+    )
+
     category = models.CharField(
         verbose_name="Kategorie pracovnĂ­ pozice",
         help_text="Např. 'Koordinátor/ka', 'Programátor/ka', 'Volební manažer/ka', ...",
         blank=False,
-        null=False
+        null=False,
     )
 
     location = models.CharField(
@@ -456,7 +454,7 @@ class MainCareerPage(
         help_text="Např. 'Středočeský kraj'",
         max_length=64,
         blank=False,
-        null=False
+        null=False,
     )
 
     time_cost = models.CharField(
@@ -464,7 +462,7 @@ class MainCareerPage(
         help_text="NapĹ™. '8h dennÄ›'",
         max_length=64,
         blank=False,
-        null=False
+        null=False,
     )
 
     employment_relationship = models.CharField(
@@ -472,7 +470,7 @@ class MainCareerPage(
         help_text="Např. 'Rámcová smlouva na dobu určitou'",
         max_length=128,
         blank=False,
-        null=False
+        null=False,
     )
 
     pay_rate = models.CharField(
@@ -480,7 +478,7 @@ class MainCareerPage(
         help_text="NapĹ™. '300-350 KÄŤ/h'",
         max_length=64,
         blank=False,
-        null=False
+        null=False,
     )
 
     submission_end_date = models.DateField(
@@ -488,18 +486,13 @@ class MainCareerPage(
         blank=False,
         null=False,
     )
-    
+
     created_date = models.DateField(
-        verbose_name="Datum vytvoření",
-        blank=False,
-        null=False,
-        default=date.today
+        verbose_name="Datum vytvoření", blank=False, null=False, default=date.today
     )
 
     content = RichTextField(
-        "Text nabĂ­dky",
-        blank=True,
-        features=RICH_TEXT_DEFAULT_FEATURES
+        "Text nabĂ­dky", blank=True, features=RICH_TEXT_DEFAULT_FEATURES
     )
 
     content_panels = Page.content_panels + [
@@ -508,15 +501,14 @@ class MainCareerPage(
                 FieldPanel("submission_end_date"),
                 FieldPanel("created_date"),
             ],
-            "Datumy"
+            "Datumy",
         ),
-
+        FieldPanel("recipient_emails"),
         FieldPanel("category"),
         FieldPanel("location"),
         FieldPanel("time_cost"),
         FieldPanel("employment_relationship"),
         FieldPanel("pay_rate"),
-
         FieldPanel("content"),
     ]
 
@@ -524,27 +516,93 @@ class MainCareerPage(
 
     def serve(self, request):
         form = None
+        current_time = datetime.now()
 
-        if request.method == 'POST':
-            form = CareerSubmissionForm(request.POST)
+        if request.method == "POST":
+            form = CareerSubmissionForm(request.POST, request.FILES)
 
             if form.is_valid():
-                # TODO
-                pass
-            
+                other_files_names = ""
+
+                for file in form.cleaned_data["other_files"]:
+                    other_files_names += f"        - {file.name}\n"
+
+                email = EmailMessage(
+                    # Subject
+                    f"Nová přihláška k pracovní pozici {self.title} - {form.cleaned_data['name']} {form.cleaned_data['surname']}",
+                    # Message
+                    (
+                        f"""
+K pracovní pozici {self.title} se {current_time} přihlásil nový zájemce.
+
+Vyplněné údaje:
+
+    Jméno: {form.cleaned_data['name']}
+    Příjmení: {form.cleaned_data['surname']}
+    E-mail: {form.cleaned_data['email']}
+    Telefon: {form.cleaned_data['phone']}
+    Vlastní text: {form.cleaned_data['own_text'] if form.cleaned_data['own_text'] else '(nevyplněn)'}
+
+CV, motivační dopis a ostatní soubory jsou v přílohách. Názvy souborů:
+
+    CV: {form.cleaned_data["cv_file"].name}
+    Mot. dopis: {form.cleaned_data["cover_letter_file"].name}
+    OstatnĂ­ soubory:
+{other_files_names}
+
+Při otevírání souborů buďte opatrní, virový sken neproběhl!
+                        """
+                    ),
+                    # From email
+                    "vyberka@pirati.cz",
+                    # Recipient list
+                    self.recipient_emails.split(","),
+                )
+
+                email.attach(
+                    form.cleaned_data["cv_file"].name,
+                    form.cleaned_data["cv_file"].read(),
+                    form.cleaned_data["cv_file"].content_type,
+                )
+                email.attach(
+                    form.cleaned_data["cover_letter_file"].name,
+                    form.cleaned_data["cover_letter_file"].read(),
+                    form.cleaned_data["cover_letter_file"].content_type,
+                )
+
+                for file in form.cleaned_data["other_files"]:
+                    email.attach(file.name, file.read(), file.content_type)
+
+                sent_successfully = email.send(fail_silently=True)
+
+                if sent_successfully:
+                    messages.add_message(
+                        request, messages.SUCCESS, "Přihláška odeslána."
+                    )
+                else:
+                    messages.add_message(
+                        request,
+                        messages.ERROR,
+                        "Chyba serveru při odesílání přihlášky.",
+                    )
+            else:
                 messages.add_message(
                     request,
-                    messages.SUCCESS,
-                    "Přihláška odeslána."
+                    messages.ERROR,
+                    "Chyba při odeslání přihlášky - prohlížeč odeslal chybná data.",
                 )
         else:
             form = CareerSubmissionForm()
 
-        return render(request, self.template, {
-            'page': self,
-            'self': self,
-            'form': form,
-        })
+        return render(
+            request,
+            self.template,
+            {
+                "page": self,
+                "self": self,
+                "form": form,
+            },
+        )
 
     class Meta:
         verbose_name = "PracovnĂ­ nabĂ­dka"
@@ -560,4 +618,4 @@ class MainSearchPage(MainSearchPageMixin):
             MainArticlePage,
             MainPersonPage,
             MainSimplePage,
-        ]
\ No newline at end of file
+        ]
diff --git a/main/templates/main/main_career_page.html b/main/templates/main/main_career_page.html
index 98503165..06719098 100644
--- a/main/templates/main/main_career_page.html
+++ b/main/templates/main/main_career_page.html
@@ -34,6 +34,7 @@
                 role="dialog"
                 aria-modal="true"
                 aria-labelledby="modal-1-title"
+                enctype="multipart/form-data"
                 method="POST"
                 action=""
             >
@@ -48,134 +49,71 @@
                         <section
                             class="
                                 flex gap-4 flex-nowrap flex-col w-full
-                                
+
                                 lg:flex-row lg:gap-2
                             "
                         >
+                            {% csrf_token %}
+
                             <div class="lg:w-auto w-full">
-                                <input
-                                    type="text"
-                                    name="name"
-                                    maxlength="256"
-                                    placeholder="Jméno"
-                                    id="id_name"
-                                    class="lg:w-auto w-full"
-                                    required
-                                >
+                                {{ form.name }}
                             </div>
                             <div class="lg:w-auto w-full">
-                                <input
-                                    type="text"
-                                    name="surname"
-                                    maxlength="256"
-                                    placeholder="Příjmení"
-                                    id="id_surname"
-                                    class="lg:w-auto w-full"
-                                    required
-                                >
+                                {{ form.surname }}
                             </div>
                         </section>
                         <section>
                             <div class="w-full">
-                                <input
-                                    class="w-full"
-                                    type="email"
-                                    name="email"
-                                    maxlength="256"
-                                    id="id_email"
-                                    placeholder="E-mail"
-                                    required
-                                >
+                                {{ form.email }}
                             </div>
                         </section>
                         <section>
                             <div class="w-full">
-                                <input
-                                    class="w-full"
-                                    type="tel"
-                                    name="phone"
-                                    maxlength="19"
-                                    id="id_phone"
-                                    placeholder="Telefon"
-                                    required
-                                >
+                                {{ form.phone }}
                             </div>
                         </section>
                         <section>
                             <div class="w-full">
-                                <textarea
-                                    class="w-full"
-                                    name="custom_text"
-                                    maxlength="65535"
-                                    id="id_custom_text"
-                                    placeholder="VlastnĂ­ text (nepovinnĂ˝)"
-                                ></textarea>
+                                {{ form.own_text }}
                             </div>
                         </section>
                         <section class="flex flex-col gap-3 lg:items-center lg:flex-row">
                             <label
                                 class="w-36"
-                                for="cv"
+                                for="id_cv"
                                 id="cv_label"
                             >CV: </label>
-    
-                            <input
-                                type="file"
-                                id="cv"
-                                name="cv"
-                                aria-labelledby="cv_label"
-                                class="max-w-64 mr-auto overflow-hidden break-words"
-                                required
-                            >
-    
+
+                            {{ form.cv_file }}
+
                             <small class="text-grey-300">(Povinné)</small>
                         </section>
                         <section class="flex flex-col gap-3 lg:items-center lg:flex-row">
                             <label
                                 class="w-36"
                                 id="cover_letter_label"
-                                for="cover_letter"
+                                for="id_cover_letter_file"
                             >Mot. dopis: </label>
-    
-                            <input
-                                type="file"
-                                id="cover_letter"
-                                name="cover_letter"
-                                aria-labelledby="cover_letter_label"
-                                class="max-w-64 mr-auto overflow-hidden break-words"
-                                required
-                            >
-    
+
+                            {{ form.cover_letter_file }}
+
                             <small class="text-grey-300">(PovinnĂ˝)</small>
                         </section>
                         <section class="flex flex-col gap-3 lg:items-center lg:flex-row">
                             <label
                                 class="w-36"
                                 id="other_files_label"
-                                for="other_files"
+                                for="id_other_files"
                             >OstatnĂ­ soubory: </label>
-    
-                            <input
-                                type="file"
-                                id="other_files"
-                                name="other_files"
-                                aria-labelledby="other_files_label"
-                                class="max-w-64 mr-auto overflow-hidden break-words"
-                                multiple
-                            >
+
+                            {{ form.other_files }}
                         </section>
-    
+
                         <section class="flex flex-row gap-3 items-start leading-none">
-                            <input
-                                type="checkbox"
-                                id="personal_data_agreement"
-                                name="personal_data_agreement"
-                                aria-labelledby="personal_data_agreement_label"
-                                required
-                            >
-    
+                            {{ form.personal_data_agreement }}
+
                             <label
-                                for="personal_data_agreement"
+                                for="id_personal_data_agreement"
                             >Souhlasím se zpracováním osobním údajů (povinné)</label>
                         </section>
                     </div>
@@ -183,7 +121,7 @@
                 <footer
                     class="
                         modal__footer flex gap-2 justify-end flex-col items-end
-                        
+
                         lg:items-start lg:gap-4 lg:flex-row
                     "
                 >
@@ -191,7 +129,7 @@
                         {% include "styleguide2/includes/atoms/buttons/round_button_form.html" with type="button" text="Zrušit" show_arrow_on_hover=True %}
                     </div>
                     <div>
-                        {% include "styleguide2/includes/atoms/buttons/round_button_form.html" with type="submit" fill="#fff" classes="bg-pirati-yellow text-black" text="Odeslat přihlášku" %}
+                        {% include "styleguide2/includes/atoms/buttons/round_button_form.html" with type="submit" fill="#fff" classes="bg-pirati-yellow text-black" text="Odeslat přihlášku" show_arrow_on_hover=True %}
                     </div>
                 </footer>
             </form>
diff --git a/majak/settings/base.py b/majak/settings/base.py
index 939d0729..3012a6e8 100644
--- a/majak/settings/base.py
+++ b/majak/settings/base.py
@@ -173,6 +173,7 @@ EMAIL_HOST_USER = env.str("EMAIL_HOST_USER", "")
 EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD", "")
 EMAIL_PORT = env.str("EMAIL_PORT", "")
 EMAIL_SUBJECT_PREFIX = env.str("EMAIL_SUBJECT_PREFIX", "[Piráti] ")
+EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", False)
 
 
 # LOGGING
diff --git a/shared/migrations/0014_octopuspersonoriginatinggroup_and_more.py b/shared/migrations/0014_octopuspersonoriginatinggroup_and_more.py
index 02c5c6e3..a2d5843d 100644
--- a/shared/migrations/0014_octopuspersonoriginatinggroup_and_more.py
+++ b/shared/migrations/0014_octopuspersonoriginatinggroup_and_more.py
@@ -4,24 +4,39 @@ from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('shared', '0013_alter_octopusperson_order'),
+        ("shared", "0013_alter_octopusperson_order"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='OctopusPersonOriginatingGroup',
+            name="OctopusPersonOriginatingGroup",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(max_length=128)),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=128)),
             ],
         ),
         migrations.CreateModel(
-            name='OctopusPersonOriginatingTeam',
+            name="OctopusPersonOriginatingTeam",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(max_length=128)),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=128)),
             ],
         ),
     ]
diff --git a/shared/people_import.py b/shared/people_import.py
index 2d210e92..81877be9 100644
--- a/shared/people_import.py
+++ b/shared/people_import.py
@@ -7,8 +7,8 @@ import requests
 from django.conf import settings
 from django.core.files.images import ImageFile
 from gql import Client, gql
-from gql.transport.exceptions import TransportServerError
 from gql.transport.aiohttp import AIOHTTPTransport
+from gql.transport.exceptions import TransportServerError
 from wagtail.images.models import Image
 from wagtail.models.media import Collection
 
@@ -480,7 +480,7 @@ class PeopleTeamImporter(ImporterMixin):
             logger.warning(
                 f"Error getting data for group %s: %s",
                 self.team_shortcut,
-                str(exception)
+                str(exception),
             )
             return []
 
diff --git a/shared/static/styleguide2/pirati-ui.svg b/shared/static/styleguide2/pirati-ui.svg
index ffc20ecc..c6e3a71a 100644
--- a/shared/static/styleguide2/pirati-ui.svg
+++ b/shared/static/styleguide2/pirati-ui.svg
@@ -127,4 +127,4 @@
 <glyph unicode="&#xe976;" glyph-name="price-tags" horiz-adv-x="1280" d="M1232 960h-384c-26.4 0-63.274-15.274-81.942-33.942l-476.116-476.116c-18.668-18.668-18.668-49.214 0-67.882l412.118-412.118c18.668-18.668 49.214-18.668 67.882 0l476.118 476.118c18.666 18.666 33.94 55.54 33.94 81.94v384c0 26.4-21.6 48-48 48zM992 576c-53.020 0-96 42.98-96 96s42.98 96 96 96 96-42.98 96-96-42.98-96-96-96zM128 416l544 544h-80c-26.4 0-63.274-15.274-81.942-33.942l-476.116-476.116c-18.668-18.668-18.668-49.214 0-67.882l412.118-412.118c18.668-18.668 49.214-18.668 67.882 0l30.058 30.058-416 416z" />
 <glyph unicode="&#xe977;" glyph-name="twitter" horiz-adv-x="1001" d="M596.009 526.629l372.819 433.371h-88.346l-323.718-376.29-258.553 376.29h-298.21l390.983-569.018-390.983-454.457h88.351l341.855 397.375 273.051-397.375h298.21l-405.458 590.103zM475 385.969l-354.815 507.521h135.702l624.636-893.48h-135.702l-269.821 385.959z" />
 <glyph unicode="&#xe99b;" glyph-name="stats-dots" d="M128 64h896v-128h-1024v1024h128zM288 128c-53.020 0-96 42.98-96 96s42.98 96 96 96c2.828 0 5.622-0.148 8.388-0.386l103.192 171.986c-9.84 15.070-15.58 33.062-15.58 52.402 0 53.020 42.98 96 96 96s96-42.98 96-96c0-19.342-5.74-37.332-15.58-52.402l103.192-171.986c2.766 0.238 5.56 0.386 8.388 0.386 2.136 0 4.248-0.094 6.35-0.23l170.356 298.122c-10.536 15.408-16.706 34.036-16.706 54.11 0 53.020 42.98 96 96 96s96-42.98 96-96c0-53.020-42.98-96-96-96-2.14 0-4.248 0.094-6.35 0.232l-170.356-298.124c10.536-15.406 16.706-34.036 16.706-54.11 0-53.020-42.98-96-96-96s-96 42.98-96 96c0 19.34 5.74 37.332 15.578 52.402l-103.19 171.984c-2.766-0.238-5.56-0.386-8.388-0.386s-5.622 0.146-8.388 0.386l-103.192-171.986c9.84-15.068 15.58-33.060 15.58-52.4 0-53.020-42.98-96-96-96z" />
-</font></defs></svg>
\ No newline at end of file
+</font></defs></svg>
diff --git a/shared/templates/styleguide2/includes/molecules/boxes/main/career_box.html b/shared/templates/styleguide2/includes/molecules/boxes/main/career_box.html
index 6107ce37..0a139c46 100644
--- a/shared/templates/styleguide2/includes/molecules/boxes/main/career_box.html
+++ b/shared/templates/styleguide2/includes/molecules/boxes/main/career_box.html
@@ -23,4 +23,4 @@
             {% include "styleguide2/includes/atoms/buttons/round_button.html" with fill="#fff" classes="bg-pirati-yellow text-black" text="VĂ­ce" url=career.url show_arrow_on_hover=True %}
         </div>
     </div>
-</div>
\ No newline at end of file
+</div>
diff --git a/shared/templates/styleguide2/includes/organisms/header/main/career_header.html b/shared/templates/styleguide2/includes/organisms/header/main/career_header.html
index 23898c67..9070aae3 100644
--- a/shared/templates/styleguide2/includes/organisms/header/main/career_header.html
+++ b/shared/templates/styleguide2/includes/organisms/header/main/career_header.html
@@ -4,4 +4,4 @@
   {% include 'styleguide2/includes/atoms/header/navigation.html' with classes='mb-6' first_text="PracovnĂ­ nabĂ­dky" first_link=page.root_page.careers_page.url second_text=page.created_date %}
 {% endblock %}
 
-{% block heading_classes %}head-8xl{% endblock %}
\ No newline at end of file
+{% block heading_classes %}head-8xl{% endblock %}
diff --git a/uniweb/migrations/0114_auto_20241122_1238.py b/uniweb/migrations/0114_auto_20241122_1238.py
index ad98c190..a7655234 100644
--- a/uniweb/migrations/0114_auto_20241122_1238.py
+++ b/uniweb/migrations/0114_auto_20241122_1238.py
@@ -21,11 +21,8 @@ def fix_po_people_page(apps, schema_editor):
 
 
 class Migration(migrations.Migration):
-
     dependencies = [
-        ('uniweb', '0113_alter_uniwebhomepage_dark_logo_and_more'),
+        ("uniweb", "0113_alter_uniwebhomepage_dark_logo_and_more"),
     ]
 
-    operations = [
-        migrations.RunPython(fix_po_people_page)
-    ]
+    operations = [migrations.RunPython(fix_po_people_page)]
-- 
GitLab