forked from beba/foerderbarometer
Add ProjectRequest and ProjectsDeclined models for project funding under 1000 EUR
This commit is contained in:
parent
a239922e14
commit
011e262df6
122
input/models.py
122
input/models.py
|
|
@ -3,6 +3,8 @@ from datetime import date
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
|
|
||||||
EMAIL_STATES = {'NONE': 'noch keine Mail versendet',
|
EMAIL_STATES = {'NONE': 'noch keine Mail versendet',
|
||||||
'INF': 'die Benachrichtigung zur Projektabschlussmail wurde versendet',
|
'INF': 'die Benachrichtigung zur Projektabschlussmail wurde versendet',
|
||||||
|
|
@ -261,6 +263,7 @@ TYPE_LIST = 'LIST'
|
||||||
TYPE_TRAV = 'TRAV'
|
TYPE_TRAV = 'TRAV'
|
||||||
TYPE_SOFT = 'SOFT'
|
TYPE_SOFT = 'SOFT'
|
||||||
TYPE_VIS = 'VIS'
|
TYPE_VIS = 'VIS'
|
||||||
|
TYPE_PROJ_LT_1000 = 'PROJ_LT_1000'
|
||||||
|
|
||||||
TYPE_CHOICES = {
|
TYPE_CHOICES = {
|
||||||
TYPE_BIB: type_link('Zugang_zu_Fachliteratur#Bibliotheksstipendium', 'Bibliotheksstipendium'),
|
TYPE_BIB: type_link('Zugang_zu_Fachliteratur#Bibliotheksstipendium', 'Bibliotheksstipendium'),
|
||||||
|
|
@ -272,6 +275,7 @@ TYPE_CHOICES = {
|
||||||
TYPE_TRAV: type_link('Reisekostenerstattungen', 'Reisekosten'),
|
TYPE_TRAV: type_link('Reisekostenerstattungen', 'Reisekosten'),
|
||||||
TYPE_SOFT: type_link('Software-Stipendien', 'Softwarestipendium'),
|
TYPE_SOFT: type_link('Software-Stipendien', 'Softwarestipendium'),
|
||||||
TYPE_VIS: type_link('E-Mail-Adressen_und_Visitenkarten#Visitenkarten', 'Visitenkarten'),
|
TYPE_VIS: type_link('E-Mail-Adressen_und_Visitenkarten#Visitenkarten', 'Visitenkarten'),
|
||||||
|
TYPE_PROJ_LT_1000: type_link('Projektplanung', 'Projektförderung unter 1000 EUR'),
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBRARY_TYPES = TYPE_BIB, TYPE_ELIT, TYPE_SOFT
|
LIBRARY_TYPES = TYPE_BIB, TYPE_ELIT, TYPE_SOFT
|
||||||
|
|
@ -422,6 +426,124 @@ class BusinessCard(Extern):
|
||||||
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name="interne Anmerkungen")
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_CATEGORIES = [
|
||||||
|
'Erstellung und Weiterentwicklung von Inhalten für die Wikimedia-Projekte',
|
||||||
|
'Aufklärung über die Wikimedia-Projekte',
|
||||||
|
'Formate zur Ansprache, Gewinnung und Bindung von Ehrenamtlichen für die Wikimedia-Projekte',
|
||||||
|
'Beteiligung von Menschen, die einen erschwerten Zugang zum Engagement in den Wikimedia-Projekten haben',
|
||||||
|
'Vernetzung und Austausch innerhalb der Communitys oder zwischen den Communitys und externen Partner*innen',
|
||||||
|
'Vermittlung von Kompetenzen, die die ehrenamtliche Arbeit stärken',
|
||||||
|
'Stärkung einer respektvollen, konstruktiven Kommunikationskultur und der Wertschätzung in den Wikimedia-Projekten',
|
||||||
|
'Verbesserung der Selbstorganisation in Bezug auf interne Regeln, Strukturen und Prozesse der Wikimedia-Projektcommunitys',
|
||||||
|
'Ehrenamtliche Aktivitäten, die der Erstellung, Pflege und Weiterentwicklung von Tools oder sonstigen technischen Verbesserungen dienen',
|
||||||
|
'Sonstiges'
|
||||||
|
]
|
||||||
|
|
||||||
|
WIKIMEDIA_CHOICES = ['Wikipedia', 'Wikimedia Commons', 'Wikidata', 'Anderes']
|
||||||
|
|
||||||
|
|
||||||
|
class Decision(models.TextChoices):
|
||||||
|
OPEN = 'OPEN', 'offen'
|
||||||
|
APPROVED = 'APPROVED', 'bewilligt'
|
||||||
|
DECLINED = 'DECLINED', 'abgelehnt'
|
||||||
|
|
||||||
|
|
||||||
|
# Application for project funding < 1000 EUR
|
||||||
|
class ProjectRequest(Volunteer):
|
||||||
|
name = models.CharField(max_length=200, verbose_name='Name des Projekts')
|
||||||
|
description = models.TextField(max_length=500, verbose_name='Kurzbeschreibung des Projekts')
|
||||||
|
|
||||||
|
# Multi-select: stored pragmatically as JSON
|
||||||
|
categories = models.JSONField(default=list, verbose_name='Projektkategorie')
|
||||||
|
categories_other = models.CharField(max_length=200, null=True, blank=True,
|
||||||
|
verbose_name='Projektkategorie: Sonstiges (kurz)')
|
||||||
|
wikimedia_projects = models.JSONField(default=list, verbose_name='Wikimedia Projekt(e)')
|
||||||
|
wikimedia_other = models.CharField(max_length=200, null=True, blank=True,
|
||||||
|
verbose_name='Wikimedia-Projekt: Anderes (kurz)'
|
||||||
|
)
|
||||||
|
|
||||||
|
start = models.DateField('Startdatum')
|
||||||
|
end = models.DateField('Erwartetes Projektende')
|
||||||
|
participants_estimated = models.IntegerField(verbose_name='Zahl der Teilnehmenden',
|
||||||
|
validators=[MinValueValidator(0)])
|
||||||
|
|
||||||
|
page = models.URLField(max_length=2000, null=True, blank=True, verbose_name='Link zur Projektseite')
|
||||||
|
group = models.CharField(max_length=2000, null=True, blank=True, verbose_name='Mitorganisierende')
|
||||||
|
location = models.CharField(max_length=2000, null=True, blank=True, verbose_name='Ort')
|
||||||
|
|
||||||
|
cost = models.IntegerField(verbose_name='Höhe der Projektkosten',
|
||||||
|
validators=[MinValueValidator(0), MaxValueValidator(1000)])
|
||||||
|
insurance = models.BooleanField(default=False, verbose_name='Versicherung gewünscht?')
|
||||||
|
notes = models.TextField(max_length=2000, null=True, blank=True, verbose_name='Anmerkungen')
|
||||||
|
|
||||||
|
# Workflow fields (used only for the request process)
|
||||||
|
decision = models.CharField(
|
||||||
|
max_length=10, choices=Decision.choices, default=Decision.OPEN, db_index=True
|
||||||
|
)
|
||||||
|
decision_date = models.DateField(null=True, blank=True, db_index=True)
|
||||||
|
decided_by = models.CharField(max_length=100, null=True, blank=True, verbose_name='Entschieden von')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Projektförderungs-Antrag (< 1000 EUR)'
|
||||||
|
verbose_name_plural = 'Projects_requested'
|
||||||
|
ordering = ('-id',)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"[Antrag] {self.name} ({self.realname})"
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
|
||||||
|
# 1) Additional guard if MaxValueValidator is removed
|
||||||
|
if self.cost is not None and self.cost > 1000:
|
||||||
|
raise ValidationError({
|
||||||
|
'cost': ('Bitte beachte, dass für Projektkosten über 1.000 EUR '
|
||||||
|
'ein öffentlicher Projektplan erforderlich ist '
|
||||||
|
'(siehe Wikipedia:Förderung/Projektplanung).')
|
||||||
|
})
|
||||||
|
|
||||||
|
# 2) Required and allowed values
|
||||||
|
if not self.categories:
|
||||||
|
raise ValidationError({'categories': 'Bitte wähle mindestens eine Projektkategorie.'})
|
||||||
|
unknown = set(self.categories) - set(PROJECT_CATEGORIES)
|
||||||
|
if unknown:
|
||||||
|
raise ValidationError({'categories': f'Unzulässige Kategorie(n): {", ".join(unknown)}'})
|
||||||
|
|
||||||
|
if not self.wikimedia_projects:
|
||||||
|
raise ValidationError({'wikimedia_projects': 'Bitte wähle mindestens ein Wikimedia-Projekt.'})
|
||||||
|
unknown_w = set(self.wikimedia_projects) - set(WIKIMEDIA_CHOICES)
|
||||||
|
if unknown_w:
|
||||||
|
raise ValidationError({'wikimedia_projects': f'Ungültige Auswahl: {", ".join(unknown_w)}'})
|
||||||
|
|
||||||
|
# 3) Require short text for “Sonstiges/Anderes”
|
||||||
|
if 'Sonstiges' in self.categories and not (self.categories_other and self.categories_other.strip()):
|
||||||
|
raise ValidationError({'categories_other': 'Bitte kurz beschreiben (Sonstiges).'})
|
||||||
|
|
||||||
|
if 'Anderes' in self.wikimedia_projects and not (self.wikimedia_other and self.wikimedia_other.strip()):
|
||||||
|
raise ValidationError({'wikimedia_other': 'Bitte kurz angeben (Anderes).'})
|
||||||
|
|
||||||
|
# 4) Date consistency
|
||||||
|
if self.start and self.end and self.end < self.start:
|
||||||
|
raise ValidationError({'end': 'Erwartetes Projektende darf nicht vor dem Startdatum liegen.'})
|
||||||
|
|
||||||
|
|
||||||
|
# Archive table for declined applications (no PID/Finance-ID)
|
||||||
|
class ProjectsDeclined(models.Model):
|
||||||
|
original_request_id = models.PositiveIntegerField()
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
realname = models.CharField(max_length=200)
|
||||||
|
email = models.EmailField()
|
||||||
|
decision_date = models.DateField()
|
||||||
|
reason = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = 'Projects_declined'
|
||||||
|
ordering = ('-decision_date', '-id')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"[Abgelehnt] {self.name} – {self.decision_date}"
|
||||||
|
|
||||||
|
|
||||||
MODELS = {
|
MODELS = {
|
||||||
TYPE_BIB: Library,
|
TYPE_BIB: Library,
|
||||||
TYPE_ELIT: ELiterature,
|
TYPE_ELIT: ELiterature,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue