diff --git a/input/forms.py b/input/forms.py index a832a76..c6074fb 100755 --- a/input/forms.py +++ b/input/forms.py @@ -1,10 +1,11 @@ +from django import forms from django.conf import settings -from django.forms import ModelForm, ChoiceField, RadioSelect, BooleanField, CharField, EmailField from django.contrib.admin.widgets import AdminDateWidget +from django.forms import ModelForm, ChoiceField, RadioSelect, BooleanField, CharField, EmailField 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 ( @@ -90,7 +91,6 @@ class CommonOrderMixin(forms.Form): class ExternForm(FdbForm): - choice = ChoiceField(choices=TYPE_CHOICES.items(), widget=RadioSelect, label='Was möchtest Du beantragen?') @@ -103,24 +103,22 @@ class ExternForm(FdbForm): exclude = ('username', 'granted', 'granted_date', 'survey_mail_send', 'service_id', 'survey_mail_date', 'mail_state') -INTERN_CHOICES = {'PRO': 'Projektsteckbrief', - 'HON': 'Ehrenamtsbescheinigung, Akkreditierung oder Redaktionsbestätigung', - 'TRAV': 'Reisekostenerstattung'} +INTERN_CHOICES = { + 'PRO': 'Projektsteckbrief', + 'HON': 'Ehrenamtsbescheinigung, Akkreditierung oder Redaktionsbestätigung', + 'TRAV': 'Reisekostenerstattung', +} + class InternForm(FdbForm): - choice = ChoiceField(choices = INTERN_CHOICES.items(), widget=RadioSelect, - label = 'Was möchtest Du eingeben?') + choice = ChoiceField(choices=INTERN_CHOICES.items(), widget=RadioSelect, + label='Was möchtest Du eingeben?') class Meta: model = ConcreteVolunteer exclude = ('granted', 'granted_date', 'survey_mail_send', 'survey_mail_date', 'mail_state') -HOTEL_CHOICES = {'TRUE': mark_safe('Hotelzimmer benötigt'), - 'FALSE': mark_safe('Kein Hotelzimmer benötigt') - } - - class BaseApplicationForm(FdbForm): """ Base form for all external applications. @@ -147,9 +145,17 @@ class BaseApplicationForm(FdbForm): settings.DATAPROTECTION, settings.FOERDERRICHTLINIEN)) +HOTEL_CHOICES = { + 'TRUE': mark_safe('Hotelzimmer benötigt'), + 'FALSE': mark_safe('Kein Hotelzimmer benötigt'), +} + + class TravelForm(BaseApplicationForm, CommonOrderMixin): # TODO: add some javascript to show/hide other-field + hotel = ChoiceField(label='Hotelzimmer benötigt:', choices=HOTEL_CHOICES.items(), widget=RadioSelect()) + # this is the code, to change required to false if needed def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -162,16 +168,18 @@ class TravelForm(BaseApplicationForm, CommonOrderMixin): class Meta: model = Travel - exclude = ('granted', 'granted_date', 'survey_mail_send', 'realname', 'email', 'survey_mail_date', 'project', 'request_url', 'payed_for_hotel_by', 'payed_for_travel_by', 'intern_notes', 'mail_state' ) - widgets = {'checkin': AdminDateWidget(), - 'checkout': AdminDateWidget(),} fields = ['project_name', 'transport', 'travelcost', 'checkin', 'checkout', 'hotel', 'notes'] - hotel = ChoiceField(label='Hotelzimmer benötigt:', choices=HOTEL_CHOICES.items(), widget=RadioSelect()) + exclude = ('granted', 'granted_date', 'survey_mail_send', 'realname', 'email', 'survey_mail_date', 'project', + 'request_url', 'payed_for_hotel_by', 'payed_for_travel_by', 'intern_notes', 'mail_state') + widgets = { + 'checkin': AdminDateWidget, + 'checkout': AdminDateWidget, + } class Media: js = ('dropdown/js/otrs_link.js',) css = { - 'all': ('css/dateFieldNoNowShortcutInTravels.css',) + 'all': ('css/dateFieldNoNowShortcutInTravels.css',) } @@ -208,6 +216,7 @@ class HonoraryCertificateForm(FdbForm): model = HonoraryCertificate fields = ['request_url', 'project'] exclude = ['intern_notes'] + class Media: js = ('dropdown/js/otrs_link.js',) @@ -242,20 +251,23 @@ class LiteratureForm(BaseApplicationForm, CommonOrderMixin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['selfbuy_give_data'].required = True + class Meta: model = Literature fields = ['cost', 'info', 'source', 'notes', 'selfbuy', 'selfbuy_data', 'selfbuy_give_data', 'terms_accepted'] exclude = ['intern_notes', 'survey_mail_send', 'mail_state'] + class Media: js = ('dropdown/js/literature.js',) -ADULT_CHOICES = {'TRUE': mark_safe('Ich bin volljährig.'), - 'FALSE': mark_safe('Ich bin noch nicht volljährig.') - } + +ADULT_CHOICES = { + 'TRUE': mark_safe('Ich bin volljährig.'), + 'FALSE': mark_safe('Ich bin noch nicht volljährig.'), +} class EmailForm(BaseApplicationForm, CommonOrderMixin): - termstoaccept = settings.NUTZUNGSBEDINGUNGEN_EMAIL_SERVICE # this is the code, to change required to false if needed @@ -266,20 +278,19 @@ class EmailForm(BaseApplicationForm, CommonOrderMixin): adult = ChoiceField(label='Volljährigkeit', choices=ADULT_CHOICES.items(), widget=RadioSelect()) - - # TODO: add some javascript to show/hide other-field class Meta: model = Email fields = ['domain', 'address', 'other', 'adult', 'terms_accepted'] exclude = ['intern_notes', 'survey_mail_send', 'mail_state'] + class Media: js = ('dropdown/js/mail.js',) - class BusinessCardForm(BaseApplicationForm, CommonOrderMixin): termstoaccept = settings.NUTZUNGSBEDINGUNGEN_VISITENKARTEN + # this is the code, to change required to false if needed def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -290,16 +301,18 @@ class BusinessCardForm(BaseApplicationForm, CommonOrderMixin): model = BusinessCard exclude = ['intern_notes', 'survey_mail_send', 'mail_state'] fields = ['project', 'data', 'variant', 'url_of_pic', 'send_data_to_print', 'sent_to', 'terms_accepted'] + class Media: js = ('dropdown/js/businessCard.js',) class ListForm(BaseApplicationForm, CommonOrderMixin): termstoaccept = settings.NUTZUNGSBEDINGUNGEN_MAILINGLISTEN + class Meta: model = List fields = ['domain', 'address', 'terms_accepted'] - exclude = ['intern_notes', 'survey_mail_send','mail_state'] + exclude = ['intern_notes', 'survey_mail_send', 'mail_state'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/input/models.py b/input/models.py index 9f5c89a..2b415c6 100755 --- a/input/models.py +++ b/input/models.py @@ -1,15 +1,20 @@ from datetime import date +from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ValidationError +from django.core.validators import MaxValueValidator from django.db import models +from django.db.models.signals import pre_save +from django.dispatch import receiver from django.utils.html import format_html 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', - 'INF': 'die Benachrichtigung zur Projektabschlussmail wurde versendet', - 'CLOSE': 'die Projektabschlussmail wurde versendet', - 'END': 'alle automatischen Mails, auch surveyMail, wurden versendet'} +EMAIL_STATES = { + 'NONE': 'noch keine Mail versendet', + 'INF': 'die Benachrichtigung zur Projektabschlussmail wurde versendet', + 'CLOSE': 'die Projektabschlussmail wurde versendet', + 'END': 'alle automatischen Mails, auch surveyMail, wurden versendet', +} class TermsConsentMixin(models.Model): @@ -35,7 +40,6 @@ class Volunteer(models.Model): mail_state = models.CharField(max_length=6, choices=EMAIL_STATES.items(), default='NONE') survey_mail_send = models.BooleanField(default=False, verbose_name='Keine Umfragemail schicken') - @classmethod def set_granted(cl, key, b): obj = cl.objects.get(pk=key) @@ -47,7 +51,6 @@ class Volunteer(models.Model): abstract = True - class Extern(Volunteer): ''' abstract basis class for all data entered by extern volunteers ''' @@ -57,7 +60,7 @@ class Extern(Volunteer): # the following Fields are not supposed to be edited by users service_id = models.CharField(max_length=15, null=True, blank=True) - def save(self,*args,**kwargs): + def save(self, *args, **kwargs): # we don't call save with args/kwargs to avoid UNIQUE CONSTRAINT errors # but maybe there is a better solution? super().save() @@ -67,18 +70,21 @@ class Extern(Volunteer): class Meta: abstract = True + class ConcreteExtern(Extern): ''' needed because we can't initiate abstract base classes in the view''' pass + class Account(models.Model): - code = models.CharField('Kostenstelle', max_length=5, default='DEF', - null=False, primary_key = True) + code = models.CharField('Kostenstelle', max_length=5, default='DEF', null=False, primary_key=True) description = models.CharField('Beschreibung', max_length=60, default='NO DESCRIPTION') intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen') + def __str__(self): return f'{self.code} {self.description}' + class Project(Volunteer): end_mail_send = models.BooleanField(default=False, verbose_name='Keine Projektabschlussmail schicken') name = models.CharField(max_length=200, verbose_name='Name des Projekts') @@ -97,12 +103,11 @@ class Project(Volunteer): insurance_technic = models.BooleanField(default=False, verbose_name='Technikversicherung Ausland') support = models.CharField(max_length=300, blank=True, null=True, verbose_name='Betreuungsperson und Vertretung') cost = models.IntegerField(blank=True, null=True, verbose_name='Kosten') - account = models.ForeignKey('Account', on_delete=models.CASCADE, null=True, to_field='code', db_constraint = False) - granted_from = models.CharField(max_length=100,null=True,verbose_name='Bewilligt von') - notes = models.TextField(max_length=1000,null=True,blank=True,verbose_name='Anmerkungen') + account = models.ForeignKey('Account', on_delete=models.CASCADE, null=True, to_field='code', db_constraint=False) + granted_from = models.CharField(max_length=100, null=True, verbose_name='Bewilligt von') + notes = models.TextField(max_length=1000, null=True, blank=True, verbose_name='Anmerkungen') intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen') - # the following Fields are not supposed to be edited by users pid = models.CharField(max_length=15, null=True, blank=True) status = models.CharField(max_length=3,choices=(('RUN', 'läuft'),('END','beendet'),('NOT','nicht stattgefunden')),default='RUN') @@ -110,66 +115,59 @@ class Project(Volunteer): project_of_year = models.IntegerField(default=0) end_quartal = models.CharField(max_length=15, null=True, blank=True, verbose_name='Quartal Projekt Ende') + def save(self, *args, **kwargs): - def save(self,*args,**kwargs): - - generate_finance_id=False + generate_finance_id = False '''we generate the autogenerated fields here''' # we don't call save with args/kwargs to avoid UNIQUE CONSTRAINT errors # but maybe there is a better solution? if not self.pk: - print ('NO PK THERE'); - generate_finance_id=True + print('NO PK THERE') + generate_finance_id = True super().save() else: orig = type(self).objects.get(pk=self.pk) # Originaldaten aus der DB abrufen if orig.start.year != self.start.year: - generate_finance_id=True + generate_finance_id = True if orig.account.code != self.account.code: if str(self.account.code) == '21111': - generate_finance_id=True + generate_finance_id = True else: self.finance_id = str(self.account.code) - - - - if generate_finance_id: - print ('MUST GENERATE FINANCE ID') + print('MUST GENERATE FINANCE ID') year = self.start.year projects = Project.objects.filter(start__year=year) if not projects: self.project_of_year = 1 - #self.pid = str(self.start.year) + '-' + str(self.account.code) + str(self.project_of_year).zfill(3) + # self.pid = str(self.start.year) + '-' + str(self.account.code) + str(self.project_of_year).zfill(3) else: # get the project of year number of latest entry projects = projects.order_by('-project_of_year')[0] # add one to value of latest entry self.project_of_year = int(projects.project_of_year) + 1 -# self.pid = str(self.start.year) + '-' + str(self.account.code) + str(self.project_of_year).zfill(3) - + # self.pid = str(self.start.year) + '-' + str(self.account.code) + str(self.project_of_year).zfill(3) if str(self.account.code) == '21111': self.finance_id = str(self.account.code) + '-' + str(self.project_of_year).zfill(3) else: self.finance_id = str(self.account.code) -# print (('Current PID',self.pid)) + # print (('Current PID',self.pid)) if not self.pid: self.pid = str(self.account.code) + str(self.pk).zfill(8) # self.pid = str(self.account.code) + str(self.pk).zfill(3) - print (('Hallo Leute! Ich save jetzt mal MIT PID DANN!!!',self.pid)) + print(('Hallo Leute! Ich save jetzt mal MIT PID DANN!!!', self.pid)) if self.end: self.end_quartal = f'Q{self.end.month // 4 + 1}' super().save() - def __str__(self): return f'{self.pid} {self.name}' @@ -178,33 +176,41 @@ class Intern(Volunteer): '''abstract base class for data entry from /intern (except Project)''' request_url = models.URLField(max_length=2000, verbose_name='Antrag (URL)') intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen') + class Meta: abstract = True + class ConcreteVolunteer(Volunteer): ''' needed because we can't initiate abstract base classes in the view''' pass + class HonoraryCertificate(Intern): ''' this class is also used for accreditations ''' project = models.ForeignKey(Project, null=True, blank=True, on_delete=models.SET_NULL) + def __str__(self): return 'Certificate for ' + self.realname -TRANSPORT_CHOICES = {'BAHN': 'Bahn', - 'NONE': 'Keine Fahrtkosten', - 'OTHER': 'Sonstiges (mit Begründung)'} +TRANSPORT_CHOICES = { + 'BAHN': 'Bahn', + 'NONE': 'Keine Fahrtkosten', + 'OTHER': 'Sonstiges (mit Begründung)', +} -PAYEDBY_CHOICES = {'WMDE': 'WMDE', - 'REQU': 'Antragstellender Mensch'} +PAYEDBY_CHOICES = { + 'WMDE': 'WMDE', + 'REQU': 'Antragstellender Mensch', +} -HOTEL_CHOICES = {'TRUE': mark_safe('Hotelzimmer benötigt'), - 'FALSE': mark_safe('Kein Hotelzimmer benötigt') - } +HOTEL_CHOICES = { + 'TRUE': mark_safe('Hotelzimmer benötigt'), + 'FALSE': mark_safe('Kein Hotelzimmer benötigt'), +} -from django.contrib.contenttypes.models import ContentType class Travel(Extern): # project variable is now null true and blank true, which means it can be saved without project id to be later on filled out by admins @@ -225,27 +231,15 @@ class Travel(Extern): # use content type model to get the end date for the project foreign key project_end_quartal = models.CharField(max_length=15, null=True, blank=True, verbose_name='Quartal Projekt Ende') -from django.db.models.signals import pre_save -from django.dispatch import receiver @receiver(pre_save, sender=Travel, dispatch_uid='get_project_end') -def getProjectEnd(sender, instance, **kwargs): - #instance.project_end = instance.project.end - +def get_project_end(sender, instance, **kwargs): if instance.project: instance.project_end = instance.project.end instance.project_end_quartal = instance.project.end_quartal -# using pre save instead -# def save(self,*args,**kwargs): -# '''we generate the autogenerated fields here''' -# # we don't call save with args/kwargs to avoid UNIQUE CONSTRAINT errors -# # but maybe there is a better solution? -# intern_notes -# project_end = self.checkout -# super(Travel, self).save(*args,**kwargs) -#abstract base class for Library and IFG +# abstract base class for Library and IFG class Grant(Extern): cost = models.CharField(max_length=10, verbose_name='Kosten', help_text='Bitte gib die ungefähr zu erwartenden Kosten in Euro an.') @@ -291,6 +285,7 @@ TYPE_CHOICES = { LIBRARY_TYPES = TYPE_BIB, TYPE_ELIT, TYPE_SOFT LIBRARY_TYPE_CHOICES = [(choice, TYPE_CHOICES[choice]) for choice in LIBRARY_TYPES] + # same model is used for Library, ELitStip and Software! class Library(Grant): TYPE = TYPE_BIB @@ -332,9 +327,10 @@ class Software(Library): proxy = True -SELFBUY_CHOICES = {'TRUE': mark_safe('Ich möchte das Werk selbst kaufen und per Kostenerstattung bei Wikimedia Deutschland abrechnen.'), - 'FALSE': mark_safe('Ich möchte, dass Wikimedia Deutschland das Werk für mich kauft'), - } +SELFBUY_CHOICES = { + 'TRUE': mark_safe('Ich möchte das Werk selbst kaufen und per Kostenerstattung bei Wikimedia Deutschland abrechnen.'), + 'FALSE': mark_safe('Ich möchte, dass Wikimedia Deutschland das Werk für mich kauft'), +} class Literature(TermsConsentMixin, Grant): @@ -351,19 +347,24 @@ class Literature(TermsConsentMixin, Grant): Telefonnummer, E-Mail-Adresse usw.). Trenne die einzelnen Angaben durch Zeilenumbrüche.')) intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen') + class IFG(Grant): url = models.URLField(max_length=2000, verbose_name='URL', - help_text='Bitte gib den Link zu deiner Anfrage bei Frag den Staat an.') + help_text='Bitte gib den Link zu deiner Anfrage bei Frag den Staat an.') intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen') def __str__(self): return 'IFG-Anfrage von ' + self.realname -DOMAIN_CHOICES = {'PEDIA': '@wikipedia.de', - 'BOOKS': '@wikibooks.de', - 'QUOTE': '@wikiquote.de', - 'SOURCE': '@wikisource.de', - 'VERSITY': '@wikiversity.de',} + +DOMAIN_CHOICES = { + 'PEDIA': '@wikipedia.de', + 'BOOKS': '@wikibooks.de', + 'QUOTE': '@wikiquote.de', + 'SOURCE': '@wikisource.de', + 'VERSITY': '@wikiversity.de', +} + class Domain(Extern): domain = models.CharField(max_length=10, @@ -373,42 +374,54 @@ class Domain(Extern): class Meta: abstract = True -MAIL_CHOICES = {'REALNAME': 'Vorname.Nachname', - 'USERNAME': 'Username', - 'OTHER': 'Sonstiges:'} -ADULT_CHOICES = {'TRUE': mark_safe('Ich bin volljährig.'), - 'FALSE': mark_safe('Ich bin noch nicht volljährig.') - } +MAIL_CHOICES = { + 'REALNAME': 'Vorname.Nachname', + 'USERNAME': 'Username', + 'OTHER': 'Sonstiges:', +} + +ADULT_CHOICES = { + 'TRUE': mark_safe('Ich bin volljährig.'), + 'FALSE': mark_safe('Ich bin noch nicht volljährig.'), +} + class Email(TermsConsentMixin, Domain): address = models.CharField(max_length=50, - choices=MAIL_CHOICES.items(), - default='USERNAME', verbose_name='Adressbestandteil', - help_text=mark_safe('Bitte gib hier den gewünschten Adressbestandteil an,
der sich vor der Domain befinden soll.')) + choices=MAIL_CHOICES.items(), + default='USERNAME', verbose_name='Adressbestandteil', + help_text=mark_safe('Bitte gib hier den gewünschten Adressbestandteil an,
der sich vor der Domain befinden soll.')) - other = models.CharField(max_length=50,blank=True,null=True, verbose_name='Sonstiges') - adult = models.CharField( max_length=10, verbose_name='Volljährigkeit', choices=ADULT_CHOICES.items(), default='FALSE') + other = models.CharField(max_length=50, blank=True, null=True, verbose_name='Sonstiges') + adult = models.CharField(max_length=10, verbose_name='Volljährigkeit', choices=ADULT_CHOICES.items(), default='FALSE') intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen') + class List(TermsConsentMixin, Domain): address = models.CharField(max_length=50, default='NO_ADDRESS', verbose_name='Adressbestandteil für Projektmailingliste', help_text=mark_safe('Bitte gib hier den gewünschten Adressbestandteil an,
der sich vor der Domain befinden soll.')) intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen') -PROJECT_CHOICE = {'PEDIA': 'Wikipedia', - 'SOURCE': 'Wikisource', - 'BOOKS': 'Wikibooks', - 'QUOTE': 'Wikiquote', - 'VERSITY': 'Wikiversity', - 'VOYAGE': 'Wikivoyage', - 'DATA': 'Wikidata', - 'NEWS': 'Wikinews', - 'COMMONS': 'Wikimedia Commons'} -BC_VARIANT = {'PIC': 'mit Bild', - 'NOPIC': 'ohne Bild'} +PROJECT_CHOICE = { + 'PEDIA': 'Wikipedia', + 'SOURCE': 'Wikisource', + 'BOOKS': 'Wikibooks', + 'QUOTE': 'Wikiquote', + 'VERSITY': 'Wikiversity', + 'VOYAGE': 'Wikivoyage', + 'DATA': 'Wikidata', + 'NEWS': 'Wikinews', + 'COMMONS': 'Wikimedia Commons', +} + +BC_VARIANT = { + 'PIC': 'mit Bild', + 'NOPIC': 'ohne Bild', +} + class BusinessCard(TermsConsentMixin, Extern): project = models.CharField(max_length=20, choices=PROJECT_CHOICE.items(), @@ -426,7 +439,7 @@ class BusinessCard(TermsConsentMixin, Extern): variant = models.CharField(max_length=5, choices=BC_VARIANT.items(), default='NOPIC', verbose_name='Variante', help_text=mark_safe('so sehen die Varianten aus: \ - mit Bild ohne Bild' )) + mit Bild ohne Bild')) url_of_pic = models.CharField(max_length=200, verbose_name='Url des Bildes', default='', help_text='Bitte gib die Wikimedia-Commons-URL des Bildes an.') @@ -467,6 +480,7 @@ validate_cost = MaxValueValidator( ), ) + # Application for project funding < 1000 EUR class ProjectRequest(Volunteer): name = models.CharField('Name des Projekts', max_length=200)