foerderbarometer/input/models.py

661 lines
26 KiB
Python
Executable File

from contextlib import suppress
from datetime import date
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.forms import ModelMultipleChoiceField, CheckboxSelectMultiple
from django.utils.functional import cached_property, classproperty
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from foerderbarometer.constants import *
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):
"""Abstract mixin to add a terms_accepted field for documenting user consent."""
terms_accepted = models.BooleanField(default=False, verbose_name='Nutzungsbedingungen zugestimmt')
class Meta:
abstract = True
class RequestUrlMixin(models.Model):
"""
Abstract mixin for adding an OTRS request URL field to admin models.
This field stores a direct link to the related OTRS ticket.
Note: OTRS links may contain semicolons, which must not be URL-encoded.
"""
request_url = models.URLField(max_length=2000, null=True, verbose_name='Antrag (URL)')
class Meta:
abstract = True
class Volunteer(models.Model):
realname = models.CharField(max_length=200, null=True, verbose_name='Realname',
help_text='Bitte gib deinen Vornamen und deinen Nachnamen ein.', default='')
email = models.EmailField(max_length=200, null=True, verbose_name='E-Mail-Adresse',
help_text=mark_safe('Bitte gib deine E-Mail-Adresse ein, damit dich<br>Wikimedia Deutschland bei Rückfragen oder für<br>die Zusage kontaktieren kann.'))
# the following Fields are not supposed to be edited by users
granted = models.BooleanField(null=True, verbose_name='bewilligt')
granted_date = models.DateField(null=True, verbose_name='bewilligt am')
survey_mail_date = models.DateField(verbose_name='Umfragemail wurde verschickt am', null=True, blank=True)
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)
obj.granted = b
obj.granted_date = date.today()
obj.save()
class Meta:
abstract = True
class Extern(Volunteer):
''' abstract basis class for all data entered by extern volunteers '''
username = models.CharField(max_length=200, null=True, verbose_name='Benutzer_innenname',
help_text=mark_safe('Wikimedia Benutzer_innenname'))
# 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):
# we don't call save with args/kwargs to avoid UNIQUE CONSTRAINT errors
# but maybe there is a better solution?
super().save()
self.service_id = type(self).__name__ + str(self.pk)
super().save()
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)
description = models.CharField('Beschreibung', max_length=60, default='NO DESCRIPTION')
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
class Meta:
verbose_name = 'Kostenstelle'
verbose_name_plural = 'Kostenstellen'
def __str__(self):
return f'{self.code} {self.description}'
@property
def has_subaccounts(self):
return self.code == '21111'
class BaseProjectCategory(models.Model):
OTHER: str
name = models.CharField('Name', max_length=200)
order = models.PositiveIntegerField('Reihenfolge')
class Meta:
abstract = True
ordering = ['order']
def __str__(self):
return self.name
@cached_property
def project_count(self):
return self.projects.count()
@classproperty
def other(cls):
return cls(id=0, name=cls.OTHER)
class ProjectCategory(BaseProjectCategory):
OTHER = 'Sonstiges'
class Meta(BaseProjectCategory.Meta):
verbose_name = 'Projektkategorie'
verbose_name_plural = 'Projektkategorien'
class WikimediaProject(BaseProjectCategory):
OTHER = 'Anderes'
class Meta(BaseProjectCategory.Meta):
verbose_name = 'Wikimedia-Projekt'
verbose_name_plural = 'Wikimedia-Projekte'
class ProductCategoryChoiceIterator(ModelMultipleChoiceField.iterator):
def __iter__(self):
yield from ModelMultipleChoiceField.iterator.__iter__(self)
yield f'{self.field.other.id}', self.field.other.name
class ProductCategoryFormField(ModelMultipleChoiceField):
widget = CheckboxSelectMultiple
iterator = ProductCategoryChoiceIterator
def __init__(self, *, other, **kwargs):
super().__init__(**kwargs)
self.other = other
def _check_values(self, value, *, other=False):
with suppress(TypeError):
value = set(value)
if other := f'{self.other.id}' in value:
value.remove(f'{self.other.id}')
queryset = super()._check_values(value)
if other:
return [*queryset, self.other]
return list(queryset)
class ProjectCategoryField(models.ManyToManyField):
def __init__(self, to, **kwargs):
kwargs['to'] = to
kwargs['related_name'] = 'projects'
super().__init__(**kwargs)
self.other_field = models.CharField(max_length=200, blank=True)
def contribute_to_class(self, cls, name, **kwargs):
super().contribute_to_class(cls, name, **kwargs)
model, other_field = self.remote_field.model, self.other_field
if not isinstance(model, str):
self.verbose_name = self._verbose_name = verbose_name = model._meta.verbose_name_plural
other_field.verbose_name = other_field._verbose_name = f'{verbose_name} ({model.OTHER})'
other_field.contribute_to_class(cls, f'{name}_other')
def formfield(self, **kwargs):
kwargs['form_class'] = ProductCategoryFormField
kwargs['other'] = self.remote_field.model.other
return super().formfield(**kwargs)
def save_form_data(self, instance, data):
data = list(data)
with suppress(ValueError):
data.remove(self.remote_field.model.other)
return super().save_form_data(instance, data)
class Project(Volunteer):
username = models.CharField(max_length=200, blank=True, verbose_name='Benutzer_innenname',
help_text=mark_safe('Wikimedia Benutzer_innenname'))
end_mail_send = models.BooleanField(default=False, verbose_name='Keine Projektabschlussmail schicken')
name = models.CharField(max_length=200, verbose_name='Name des Projekts')
description = models.CharField(max_length=500, verbose_name='Kurzbeschreibung', null=True)
start = models.DateField('Startdatum', null=True)
end = models.DateField('Erwartetes Projektende', null=True)
otrs = models.URLField(max_length=300, null=True, verbose_name='OTRS-Link')
plan = models.URLField(max_length=2000, null=True, blank=True, verbose_name='Link zum Förderplan')
page = models.URLField(max_length=2000, null=True, blank=True, verbose_name='Link zur Projektseite')
urls = models.CharField(max_length=2000, null=True, blank=True, verbose_name='Weitere Links')
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/Adresse/Location')
participants_estimated = models.IntegerField(blank=True, null=True, verbose_name='Teilnehmende angefragt')
participants_real = models.IntegerField(blank=True, null=True, verbose_name='Teilnehmende ausgezählt')
insurance = models.BooleanField(default=False, verbose_name='Haftpflichtversicherung')
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, blank=True, null=True, to_field='code', db_constraint=False)
granted_date = models.DateField(blank=True, null=True, verbose_name='Bewilligt am')
granted_from = models.CharField(max_length=100, blank=True, 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')
categories = ProjectCategoryField(ProjectCategory)
wikimedia_projects = ProjectCategoryField(WikimediaProject)
# 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')
finance_id = models.CharField(max_length=15, null= True, blank=True)
project_of_year = models.IntegerField(default=0)
end_quartal = models.CharField(max_length=15, null=True, blank=True, verbose_name='Quartal Projekt Ende')
class Meta:
verbose_name = 'Projekt'
verbose_name_plural = 'Projekte'
def __str__(self):
return f'{self.pid or self.id} {self.name}'
def save(self, *, using=None, **kwargs):
kwargs['using'] = using
if self.end:
self.end_quartal = f'Q{self.end.month // 4 + 1}'
else:
self.end_quartal = ''
if not self.account:
self.finance_id = ''
self.project_of_year = 0
return super().save(**kwargs)
if self.should_generate_finance_id():
self.generate_finance_id()
super().save(**kwargs)
if not self.pid:
self.pid = f'{self.account.code}{self.id:08d}'
super().save(update_fields=['pid'], using=using)
def should_generate_finance_id(self):
if self.id is None:
return True
if not self.finance_id:
return True
start, account_id = type(self).objects.values_list('start', 'account').get(id=self.id)
return not (self.start.year == start.year and self.account_id == account_id)
def generate_finance_id(self):
"""
This is an improved version of the old code for generating a finance id.
There is still no protection by constraints against duplicate finance ids!
"""
queryset = Project.objects.exclude(id=self.id).filter(start__year=self.start.year)
max_project_of_year = queryset.aggregate(max=models.Max('project_of_year')).get('max') or 0
self.project_of_year = project_of_year = max_project_of_year + 1
if self.account.has_subaccounts:
self.finance_id = f'{self.account.code}-{project_of_year:03d}'
else:
self.finance_id = self.account.code
def clean(self):
if (self.start and self.end) and (self.end < self.start):
raise forms.ValidationError({
'end': [
forms.ValidationError('Das erwartete Projektende muss nach dem Startdatum liegen.'),
],
})
class ProjectRequest(Project):
class Meta:
proxy = True
verbose_name = 'Projekt (beantragt)'
verbose_name_plural = 'Projekte (beantragt)'
class ProjectDeclined(Project):
class Meta:
proxy = True
verbose_name = 'Projekt (abgelehnt)'
verbose_name_plural = 'Projekte (abgelehnt)'
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)
class Meta:
verbose_name = 'Bescheinigung'
verbose_name_plural = 'Bescheinigungen'
def __str__(self):
return f'Bescheinigung für {self.realname}'
TRANSPORT_CHOICES = {
'BAHN': 'Bahn',
'NONE': 'Keine Fahrtkosten',
'OTHER': 'Sonstiges (mit Begründung)',
}
PAYEDBY_CHOICES = {
'WMDE': 'WMDE',
'REQU': 'Antragstellender Mensch',
}
HOTEL_CHOICES = {
'TRUE': mark_safe('Hotelzimmer benötigt'),
'FALSE': mark_safe('Kein Hotelzimmer benötigt'),
}
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
project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True, blank=True)
project_name = models.CharField(max_length=50, null=True, blank=True, verbose_name='Projektname')
transport = models.CharField(max_length=5, choices=TRANSPORT_CHOICES.items(), default='BAHN', verbose_name='Transportmittel')
other_transport = models.CharField(max_length=200, null=True, blank=True, verbose_name='Sonstige Transportmittel (mit Begründung)')
travelcost = models.CharField(max_length=10, default='0', verbose_name='Fahrtkosten')
checkin = models.DateField(blank=True, null=True, verbose_name='Anreise')
checkout = models.DateField(blank=True, null=True, verbose_name='Abreise')
payed_for_hotel_by = models.CharField(max_length=4, choices=PAYEDBY_CHOICES.items(), blank=True, null=True, verbose_name='Kostenauslage Hotel durch')
payed_for_travel_by = models.CharField(max_length=4, choices=PAYEDBY_CHOICES.items(), blank=True, null=True, verbose_name='Kostenauslage Fahrt durch')
hotel = models.CharField(max_length=10, choices=HOTEL_CHOICES.items(), verbose_name='Hotelzimmer benötigt')
notes = models.TextField(max_length=1000, blank=True, verbose_name='Anmerkungen')
request_url = models.URLField(max_length=2000, verbose_name='Antrag (URL)')
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
project_end = models.DateField(blank=True, null=True, verbose_name='Projektende')
# 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')
class Meta:
verbose_name = 'Reisekosten'
verbose_name_plural = 'Reisekosten'
def __str__(self):
return f'Reisekosten für {self.realname}'
def clean(self):
if (self.checkin and self.checkout) and (self.checkout < self.checkin):
raise forms.ValidationError({
'checkout': [
forms.ValidationError('Das Datum der Abreise muss nach dem Datum der Anreise liegen.'),
],
})
@receiver(pre_save, sender=Travel, dispatch_uid='get_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
# abstract base class for Library and IFG
class Grant(RequestUrlMixin, Extern):
cost = models.CharField(max_length=10, verbose_name='Kosten',
help_text='Bitte gib die ungefähr zu erwartenden Kosten in Euro an.')
notes = models.TextField(max_length=1000, blank=True, verbose_name='Anmerkungen',
help_text='Bitte gib an, wofür Du das Stipendium verwenden willst.')
class Meta:
abstract = True
def type_link(path, label):
return format_html(
format_string='<a href="{href}" target="_blank" rel="noopener">{label}</a>',
href=f'https://de.wikipedia.org/wiki/Wikipedia:Förderung/{path}',
label=label,
)
TYPE_CHOICES = {
TYPE_BIB: type_link('Zugang_zu_Fachliteratur#Bibliotheksstipendium', 'Bibliotheksstipendium'),
TYPE_ELIT: type_link('Zugang_zu_Fachliteratur#eLiteraturstipendium', 'eLiteraturstipendium'),
TYPE_MAIL: type_link('E-Mail-Adressen_und_Visitenkarten#E-Mail-Adressen', 'E-Mail-Adresse'),
TYPE_IFG: type_link('Gebührenerstattungen_für_Behördenanfragen', 'Kostenübernahme IFG-Anfrage'),
TYPE_LIT: type_link('Zugang_zu_Fachliteratur#Literaturstipendium', 'Literaturstipendium'),
TYPE_LIST: type_link('E-Mail-Adressen_und_Visitenkarten#Mailinglisten', 'Mailingliste'),
TYPE_TRAV: type_link('Reisekostenerstattungen', 'Reisekosten'),
TYPE_SOFT: type_link('Software-Stipendien', 'Softwarestipendium'),
TYPE_VIS: type_link('E-Mail-Adressen_und_Visitenkarten#Visitenkarten', 'Visitenkarten'),
TYPE_PROJ: type_link('Projektplanung', 'Projektförderung unter 1000 EUR'),
}
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
LIBRARY_LABEL = 'Bibliothek'
LIBRARY_HELP_TEXT = 'Für welche Bibliothek gilt das Stipendium?'
DURATION_HELP_TEXT = mark_safe('In welchem Zeitraum möchtest du recherchieren oder<br>wie lange ist der Bibliotheksausweis gültig?')
type = models.CharField(max_length=4, choices=LIBRARY_TYPE_CHOICES, default=TYPE_BIB)
library = models.CharField(max_length=200)
duration = models.CharField(max_length=100, verbose_name='Dauer')
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
class Meta:
verbose_name = 'Bibliotheksstipendium'
verbose_name_plural = 'Bibliotheksstipendien'
def __str__(self):
return self.library
def save(self, **kwargs):
self.type = self.TYPE
return super().save(**kwargs)
class ELiterature(Library):
TYPE = TYPE_ELIT
LIBRARY_LABEL = 'Datenbank/Online-Ressource'
LIBRARY_HELP_TEXT = 'Für welche Datenbank/Online-Ressource gilt das Stipendium?'
DURATION_HELP_TEXT = 'Wie lange gilt der Zugang?'
class Meta:
proxy = True
verbose_name = 'eLiteraturstipendium'
verbose_name_plural = 'eLiteraturstipendien'
class Software(Library):
TYPE = TYPE_SOFT
LIBRARY_LABEL = 'Software'
LIBRARY_HELP_TEXT = 'Für welche Software gilt das Stipendium?'
DURATION_HELP_TEXT = 'Wie lange gilt die Lizenz?'
class Meta:
proxy = True
verbose_name = 'Softwarestipendium'
verbose_name_plural = 'Softwarestipendien'
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):
info = models.CharField(max_length=500, verbose_name='Informationen zum Werk',
help_text=mark_safe('Bitte gib alle Informationen zum benötigten Werk an,<br>\
die eine eindeutige Identifizierung ermöglichen (Autor, Titel, Verlag, ISBN, ...)'))
source = models.CharField(max_length=200, verbose_name='Bezugsquelle',
help_text='Bitte gib an, wo du das Werk kaufen möchtest.')
selfbuy = models.CharField( max_length=10, verbose_name='Selbstkauf?', choices=SELFBUY_CHOICES.items(), default='TRUE')
selfbuy_give_data = models.BooleanField(verbose_name=mark_safe('Datenweitergabe erlauben'), help_text=mark_safe('Ich stimme der Weitergabe meiner Daten (Name, Postadresse) an den von mir angegebenen Anbieter/Dienstleister zu.'))
selfbuy_data = models.TextField(max_length=1000, verbose_name='Persönliche Daten sowie Adresse', default='',\
help_text=mark_safe('Bitte gib hier alle persönlichen Daten an, die wir benötigen, um das Werk<br>\
für dich zu kaufen und es dir anschließend zu schicken (z.B. Vorname Nachname, Anschrift, <br>\
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 Meta:
verbose_name = 'Literaturstipendium'
verbose_name_plural = 'Literaturstipendien'
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.')
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
class Meta:
verbose_name = 'IFG-Anfrage'
verbose_name_plural = 'IFG-Anfragen'
def __str__(self):
return f'IFG-Anfrage von {self.realname}'
DOMAIN_CHOICES = {
'PEDIA': '@wikipedia.de',
'BOOKS': '@wikibooks.de',
'QUOTE': '@wikiquote.de',
'SOURCE': '@wikisource.de',
'VERSITY': '@wikiversity.de',
}
class Domain(RequestUrlMixin, Extern):
domain = models.CharField(max_length=10,
choices=DOMAIN_CHOICES.items(),
default='PEDIA')
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.'),
}
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,<br>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')
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
class Meta:
verbose_name = 'E-Mail-Adresse'
verbose_name_plural = 'E-Mail-Adressen'
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,<br>der sich vor der Domain befinden soll.'))
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
class Meta:
verbose_name = 'Mailingliste'
verbose_name_plural = 'Mailinglisten'
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(RequestUrlMixin, TermsConsentMixin, Extern):
project = models.CharField(max_length=20, choices=PROJECT_CHOICE.items(),
default='PEDIA', verbose_name='Wikimedia-Projekt',
help_text='Für welches Wikimedia-Projekt möchtest Du Visitenkarten?')
data = models.TextField(max_length=1000, verbose_name='Persönliche Daten für die Visitenkarten', default='',
help_text=mark_safe('Bitte gib hier alle persönlichen Daten an, und zwar genau so,<br>\
wie sie (auch in der entsprechenden Reihenfolge) auf den Visitenkarten stehen sollen<br>\
(z.B. Vorname Nachname, Benutzer:/Benutzerin:, Benutzer-/-innenname, Anschrift,<br>\
Telefonnummer, E-Mail-Adresse usw.). Trenne die einzelnen Angaben durch Zeilenumbrüche.<br>\
Hinweis: Telefonnummern bilden wir üblicherweise im internationalen Format gemäß<br>\
DIN 5008 ab. Als anzugebende E-Mail-Adresse empfehlen wir dir eine Wikimedia-Projekt-<br>\
Adresse, die du ebenfalls beantragen kannst, sofern du nicht bereits eine besitzt.'))
variant = models.CharField(max_length=5, choices=BC_VARIANT.items(),
default='NOPIC', verbose_name='Variante',
help_text=mark_safe('so sehen die Varianten aus: <a href="https://upload.wikimedia.org/wikipedia/commons/c/cd/Muster_Visitenkarten_WMDE_2018.jpg">\
mit Bild</a> <a href="https://upload.wikimedia.org/wikipedia/commons/d/d3/Muster_Visitenkarte_WMDE.png">ohne Bild</a>'))
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.')
sent_to = models.TextField(max_length=1000, verbose_name='Versandadresse',
default='', help_text='Bitte gib den Namen und die vollständige Adresse ein, an welche die Visitenkarten geschickt werden sollen.')
send_data_to_print = models.BooleanField(default=False, verbose_name=mark_safe('Datenweitergabe erlauben'), help_text=mark_safe('Hiermit erlaube ich die Weitergabe meiner Daten (Name, Postadresse) an den von Wikimedia<br> Deutschland ausgewählten Dienstleister (z. B. <a href="wir-machen-druck.de">wir-machen-druck.de</a>) zum Zwecke des direkten <br> Versands der Druckerzeugnisse an mich.'))
intern_notes = models.TextField(max_length=1000, blank=True, verbose_name='interne Anmerkungen')
class Meta:
verbose_name = 'Visitenkarte'
verbose_name_plural = 'Visitenkarten'
MODELS = {
TYPE_BIB: Library,
TYPE_ELIT: ELiterature,
TYPE_MAIL: Email,
TYPE_IFG: IFG,
TYPE_LIT: Literature,
TYPE_LIST: List,
TYPE_TRAV: Travel,
TYPE_SOFT: Software,
TYPE_VIS: BusinessCard,
}