diff --git a/input/migrations/0100_projectrequest_projectsdeclined.py b/input/migrations/0100_projectrequest_projectsdeclined.py index 9592aaa..688855a 100644 --- a/input/migrations/0100_projectrequest_projectsdeclined.py +++ b/input/migrations/0100_projectrequest_projectsdeclined.py @@ -1,8 +1,9 @@ -# Generated by Django 5.2.5 on 2025-09-24 16:58 +# Generated by Django 5.2.5 on 2025-10-15 10:14 -import django.core.validators from django.db import migrations, models +from input.models import validate_cost + class Migration(migrations.Migration): @@ -25,32 +26,33 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=200, verbose_name='Name des Projekts')), ('description', models.TextField(max_length=500, verbose_name='Kurzbeschreibung des Projekts')), ('categories', models.JSONField(default=list, verbose_name='Projektkategorie')), - ('categories_other', models.CharField(blank=True, max_length=200, null=True, verbose_name='Projektkategorie: Sonstiges (kurz)')), + ('categories_other', models.CharField(blank=True, max_length=200, verbose_name='Projektkategorie: Sonstiges (kurz)')), ('wikimedia_projects', models.JSONField(default=list, verbose_name='Wikimedia Projekt(e)')), - ('wikimedia_other', models.CharField(blank=True, max_length=200, null=True, verbose_name='Wikimedia-Projekt: Anderes (kurz)')), + ('wikimedia_other', models.CharField(blank=True, max_length=200, verbose_name='Wikimedia-Projekt: Anderes (kurz)')), ('start', models.DateField(verbose_name='Startdatum')), ('end', models.DateField(verbose_name='Erwartetes Projektende')), - ('participants_estimated', models.IntegerField(validators=[django.core.validators.MinValueValidator(0)], verbose_name='Teilnehmende angefragt')), - ('page', models.URLField(blank=True, max_length=2000, null=True, verbose_name='Link zur Projektseite')), - ('group', models.CharField(blank=True, max_length=2000, null=True, verbose_name='Mitorganisierende')), - ('location', models.CharField(blank=True, max_length=2000, null=True, verbose_name='Ort/Adresse/Location')), - ('cost', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='Kosten (EUR, ganzzahlig)')), - ('insurance', models.BooleanField(default=False, verbose_name='Haftpflichtversicherung gewünscht?')), - ('notes', models.TextField(blank=True, max_length=2000, null=True, verbose_name='Anmerkungen')), - ('decision', models.CharField(choices=[('OPEN', 'offen'), ('APPROVED', 'bewilligt'), ('DECLINED', 'abgelehnt')], default='OPEN', max_length=10)), - ('decision_date', models.DateField(blank=True, null=True)), - ('decided_by', models.CharField(blank=True, max_length=100, null=True, verbose_name='Entschieden von')), + ('participants_estimated', models.PositiveIntegerField(verbose_name='Zahl der Teilnehmenden')), + ('page', models.URLField(blank=True, max_length=2000, verbose_name='Link zur Projektseite')), + ('group', models.CharField(blank=True, max_length=2000, verbose_name='Mitorganisierende')), + ('location', models.CharField(blank=True, max_length=2000, verbose_name='Ort')), + ('cost', models.PositiveIntegerField(validators=[validate_cost], verbose_name='Höhe der Projektkosten')), + ('insurance', models.BooleanField(default=False, verbose_name='Versicherung gewünscht?')), + ('notes', models.TextField(blank=True, max_length=2000, verbose_name='Anmerkungen')), + ('decision', models.CharField(choices=[('OPEN', 'offen'), ('APPROVED', 'bewilligt'), ('DECLINED', 'abgelehnt')], db_index=True, default='OPEN', max_length=10)), + ('decision_date', models.DateField(blank=True, db_index=True, null=True)), + ('decided_by', models.CharField(blank=True, max_length=100, verbose_name='Entschieden von')), ], options={ 'verbose_name': 'Projektförderungs-Antrag (< 1000 EUR)', 'verbose_name_plural': 'Projects_requested', + 'ordering': ('-id',), }, ), migrations.CreateModel( name='ProjectsDeclined', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('original_request_id', models.IntegerField()), + ('original_request_id', models.PositiveIntegerField()), ('name', models.CharField(max_length=200)), ('realname', models.CharField(max_length=200)), ('email', models.EmailField(max_length=254)), @@ -59,6 +61,7 @@ class Migration(migrations.Migration): ], options={ 'verbose_name_plural': 'Projects_declined', + 'ordering': ('-decision_date', '-id'), }, ), ] diff --git a/input/migrations/0101_alter_projectrequest_options_and_more.py b/input/migrations/0101_alter_projectrequest_options_and_more.py deleted file mode 100644 index b6597fa..0000000 --- a/input/migrations/0101_alter_projectrequest_options_and_more.py +++ /dev/null @@ -1,57 +0,0 @@ -# Generated by Django 5.2.5 on 2025-09-28 22:27 - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('input', '0100_projectrequest_projectsdeclined'), - ] - - operations = [ - migrations.AlterModelOptions( - name='projectrequest', - options={'ordering': ('-id',), 'verbose_name': 'Projektförderungs-Antrag (< 1000 EUR)', 'verbose_name_plural': 'Projects_requested'}, - ), - migrations.AlterModelOptions( - name='projectsdeclined', - options={'ordering': ('-decision_date', '-id'), 'verbose_name_plural': 'Projects_declined'}, - ), - migrations.AlterField( - model_name='projectrequest', - name='cost', - field=models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)], verbose_name='Höhe der Projektkosten'), - ), - migrations.AlterField( - model_name='projectrequest', - name='decision', - field=models.CharField(choices=[('OPEN', 'offen'), ('APPROVED', 'bewilligt'), ('DECLINED', 'abgelehnt')], db_index=True, default='OPEN', max_length=10), - ), - migrations.AlterField( - model_name='projectrequest', - name='decision_date', - field=models.DateField(blank=True, db_index=True, null=True), - ), - migrations.AlterField( - model_name='projectrequest', - name='insurance', - field=models.BooleanField(default=False, verbose_name='Versicherung gewünscht?'), - ), - migrations.AlterField( - model_name='projectrequest', - name='location', - field=models.CharField(blank=True, max_length=2000, null=True, verbose_name='Ort'), - ), - migrations.AlterField( - model_name='projectrequest', - name='participants_estimated', - field=models.IntegerField(validators=[django.core.validators.MinValueValidator(0)], verbose_name='Zahl der Teilnehmenden'), - ), - migrations.AlterField( - model_name='projectsdeclined', - name='original_request_id', - field=models.PositiveIntegerField(), - ), - ] diff --git a/input/models.py b/input/models.py index 4cbc556..9f5c89a 100755 --- a/input/models.py +++ b/input/models.py @@ -458,40 +458,42 @@ class Decision(models.TextChoices): DECLINED = 'DECLINED', 'abgelehnt' +validate_cost = MaxValueValidator( + limit_value=100, + message=( + 'Bitte beachte, dass für Projektkosten über 1.000 EUR ' + 'ein öffentlicher Projektplan erforderlich ist ' + '(siehe Wikipedia:Förderung/Projektplanung).' + ), +) + # 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') + name = models.CharField('Name des Projekts', max_length=200) + description = models.TextField('Kurzbeschreibung des Projekts', max_length=500) # 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)' - ) + categories = models.JSONField('Projektkategorie', default=list) + categories_other = models.CharField('Projektkategorie: Sonstiges (kurz)', max_length=200, blank=True) + wikimedia_projects = models.JSONField('Wikimedia Projekt(e)', default=list) + wikimedia_other = models.CharField('Wikimedia-Projekt: Anderes (kurz)', max_length=200, blank=True) start = models.DateField('Startdatum') end = models.DateField('Erwartetes Projektende') - participants_estimated = models.IntegerField(verbose_name='Zahl der Teilnehmenden', - validators=[MinValueValidator(0)]) + participants_estimated = models.PositiveIntegerField('Zahl der Teilnehmenden') - 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') + page = models.URLField('Link zur Projektseite', max_length=2000, blank=True) + group = models.CharField('Mitorganisierende', max_length=2000, blank=True) + location = models.CharField('Ort', max_length=2000, blank=True) - 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') + cost = models.PositiveIntegerField('Höhe der Projektkosten', validators=[validate_cost]) + insurance = models.BooleanField('Versicherung gewünscht?', default=False) + notes = models.TextField('Anmerkungen', max_length=2000, blank=True) # Workflow fields (used only for the request process) - decision = models.CharField( - max_length=10, choices=Decision.choices, default=Decision.OPEN, db_index=True - ) + 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') + decided_by = models.CharField('Entschieden von', max_length=100, blank=True) class Meta: verbose_name = 'Projektförderungs-Antrag (< 1000 EUR)' @@ -502,16 +504,6 @@ class ProjectRequest(Volunteer): 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.'})