diff --git a/input/forms.py b/input/forms.py
index cd9e281..4b32d60 100755
--- a/input/forms.py
+++ b/input/forms.py
@@ -5,6 +5,7 @@ from django.forms.renderers import DjangoTemplates
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django import forms
+from .models import ProjectRequest, PROJECT_CATEGORIES, WIKIMEDIA_CHOICES
from .models import (
TYPE_CHOICES,
@@ -306,3 +307,117 @@ class ListForm(BaseApplicationForm, CommonOrderMixin):
model = List
fields = ['domain', 'address']
exclude = ['intern_notes', 'survey_mail_send','mail_state']
+
+
+class ProjectRequestForm(CommonOrderMixin, forms.ModelForm):
+ """
+ Public-facing form for < 1000 EUR project requests.
+
+ Key points:
+ - JSONField-backed multi-selects are exposed as MultipleChoiceField with checkbox widgets.
+ - We return `list(...)` in clean_* so the JSONField gets a native list.
+ - Extra UX tweaks: textareas for long text, number inputs with min/max/step, help_texts with links via format_html.
+ """
+
+ # Expose JSON-backed categories as a checkbox multi-select
+ categories = forms.MultipleChoiceField(
+ choices=[(c, c) for c in PROJECT_CATEGORIES],
+ widget=forms.CheckboxSelectMultiple,
+ label='Projektkategorie',
+ help_text='In welche dieser Kategorien lässt sich dein Projekt einordnen?'
+ )
+
+ # Expose JSON-backed wikimedia_projects as a checkbox multi-select
+ wikimedia_projects = forms.MultipleChoiceField(
+ choices=[(w, w) for w in WIKIMEDIA_CHOICES],
+ widget=forms.CheckboxSelectMultiple,
+ label='Wikimedia Projekt(e)',
+ help_text='Auf welches Wikimedia-Projekt bezieht sich dein Vorhaben?',
+ )
+
+ class Meta:
+ model = ProjectRequest
+ fields = [
+ 'realname', 'email',
+ 'name', 'description',
+ 'categories', 'categories_other',
+ 'wikimedia_projects', 'wikimedia_other',
+ 'start', 'end', 'participants_estimated',
+ 'page', 'group', 'location',
+ 'cost', 'insurance', 'notes',
+ ]
+
+ # Widgets are chosen for better UX and to gently guide valid inputs in the browser
+ widgets = {
+ 'start': AdminDateWidget(),
+ 'end': AdminDateWidget(),
+
+ # Long-text fields as textareas with sensible row counts
+ 'description': forms.Textarea(attrs={'rows': 5}),
+ 'notes': forms.Textarea(attrs={'rows': 6}),
+
+ # Integer-like fields: browser-side constraints (server still validates in the model)
+ 'participants_estimated': forms.NumberInput(attrs={'min': 0, 'step': 1}),
+ 'cost': forms.NumberInput(attrs={'min': 0, 'max': 1000, 'step': 1}),
+ }
+
+ # Human-readable help_texts; use format_html for safe HTML (links)
+ help_texts = {
+ 'name': 'Bitte gib einen Namen für das Projekt an.',
+ 'description': 'Bitte beschreibe kurz, was die Ziele deines Projekts sind.',
+ 'participants_estimated': 'Wie viele Personen werden ungefähr an diesem Projekt teilnehmen?',
+ 'page': 'Bitte gib einen Link zur Projektseite in den Wikimedia-Projekten an, wenn vorhanden.',
+ 'group': 'Sofern zutreffend: Bitte gib an, welche Personen das Projekt gemeinsam mit dir organisieren.',
+ 'location': 'Sofern zutreffend: Bitte gib hier den Ort an, an welchem das Projekt stattfinden wird.',
+ 'cost': 'Wie hoch werden die Projektkosten voraussichtlich sein? Bitte gib diese auf volle Euro gerundet an.',
+ 'insurance': format_html(
+ 'Möchtest du die Unfall- und Haftpflichtversicherung von Wikimedia Deutschland in Anspruch nehmen?'),
+ 'notes': format_html(
+ 'Falls du noch weitere Informationen hast, teile sie gern an dieser Stelle mit uns. Für umfangreichere Informationen, schreibe uns eine E-Mail an community@wikimedia.de.'),
+ }
+
+ # Persist multi-selects as Python lists so JSONField stores a JSON array
+ def clean_categories(self):
+ return list(self.cleaned_data.get('categories', []))
+
+ def clean_wikimedia_projects(self):
+ return list(self.cleaned_data.get('wikimedia_projects', []))
+
+
+class ProjectRequestAdminForm(forms.ModelForm):
+ """
+ Admin form for ProjectRequest.
+
+ Key points:
+ - Same checkbox multi-selects for JSON-backed fields to improve admin UX.
+ - Keep fields="__all__" so admin users can inspect/set workflow fields if needed.
+ - Do NOT add extra business logic here; validation lives in the model's clean().
+ """
+
+ categories = forms.MultipleChoiceField(
+ choices=[(c, c) for c in PROJECT_CATEGORIES],
+ widget=forms.CheckboxSelectMultiple,
+ label='Projektkategorie'
+ )
+ wikimedia_projects = forms.MultipleChoiceField(
+ choices=[(w, w) for w in WIKIMEDIA_CHOICES],
+ widget=forms.CheckboxSelectMultiple,
+ label='Wikimedia Projekt(e)'
+ )
+
+ class Meta:
+ model = ProjectRequest
+ fields = "__all__"
+
+ # Make longer texts easier to edit in the admin UI
+ widgets = {
+ 'description': forms.Textarea(attrs={'rows': 5}),
+ 'notes': forms.Textarea(attrs={'rows': 6}),
+ }
+
+ # Ensure JSONField receives a list
+ def clean_categories(self):
+ return list(self.cleaned_data.get('categories', []))
+
+ def clean_wikimedia_projects(self):
+ return list(self.cleaned_data.get('wikimedia_projects', []))