# -*- coding: utf-8 -*- import datetime import django from django.db import utils, IntegrityError from django.db import models from django.db.models import * from django.contrib.auth import get_user_model from django.contrib.auth.models import AbstractUser, PermissionsMixin from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager from django.dispatch import receiver from django.db.models.signals import pre_save from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ from django.conf import settings as appSettings from keycloak_oidc import exceptions as sso_exc from records_audit.utils import DataAudited class AppUser(AbstractUser, DataAudited): """ Uzivatel. Nove nalodeni jsou sem prepisovani z AppRegEmail. """ ### Jiz definovano v predkovi #first_name = CharField(_('firstName'),max_length=100, default='') #last_name = CharField(_('lastName'),max_length=100, default='') #email = CharField(_('email'),max_length=100, default='', blank=True, null=True) # Keycloak user ID ssoUid = CharField(_(u'Keycloak SSO user ID'), max_length=40, default=None, blank=True, null=True) createdStamp = DateTimeField(_('Uživatel vytvořen'), default=datetime.datetime.now, blank=False, null=False, editable=False) # Datum a čas poslední změny údajů, které se mají propisovat do LDAP. # Podle tohoto údaje se v synchronizačním skriptu pozná, jaké # záznamy je potřeba aktualizovat v LDAP. # Email login token emailToken = CharField(_(u'E-mail login token'), max_length=120, default=None, blank=True, null=True) etStamp = DateTimeField(_('E-mail token timestamp'), editable=False, default=None, blank=True, null=True) # Constants DISTRICT_PHA = 0 DISTRICT_JHC = 1 DISTRICT_JHM = 2 DISTRICT_KVK = 3 DISTRICT_VYS = 4 DISTRICT_KHK = 5 DISTRICT_LBK = 6 DISTRICT_MSK = 7 DISTRICT_OLK = 8 DISTRICT_PAK = 9 DISTRICT_PLK = 10 DISTRICT_STC = 11 DISTRICT_ULK = 12 DISTRICT_ZLK = 13 DISTRICT_CHOICES = ( (DISTRICT_PHA, 'Hlavní město Praha'), (DISTRICT_JHC, 'Jihočeský kraj'), (DISTRICT_JHM, 'Jihomoravský kraj'), (DISTRICT_KVK, 'Karlovarský kraj'), (DISTRICT_VYS, 'Kraj Vysočina'), (DISTRICT_KHK, 'Královéhradecký kraj'), (DISTRICT_LBK, 'Liberecký kraj'), (DISTRICT_MSK, 'Moravskoslezský kraj'), (DISTRICT_OLK, 'Olomoucký kraj'), (DISTRICT_PAK, 'Pardubický kraj'), (DISTRICT_PLK, 'Plzeňský kraj'), (DISTRICT_STC, 'Středočeský kraj'), (DISTRICT_ULK, 'Ústecký kraj'), (DISTRICT_ZLK, 'Zlínský kraj'), ) DISTRICT_CHOICES_STR = { DISTRICT_PHA: 'Hlavní město Praha', DISTRICT_JHC: 'Jihočeský kraj', DISTRICT_JHM: 'Jihomoravský kraj', DISTRICT_KVK: 'Karlovarský kraj', DISTRICT_VYS: 'Kraj Vysočina', DISTRICT_KHK: 'Královéhradecký kraj', DISTRICT_LBK: 'Liberecký kraj', DISTRICT_MSK: 'Moravskoslezský kraj', DISTRICT_OLK: 'Olomoucký kraj', DISTRICT_PAK: 'Pardubický kraj', DISTRICT_PLK: 'Plzeňský kraj', DISTRICT_STC: 'Středočeský kraj', DISTRICT_ULK: 'Ústecký kraj', DISTRICT_ZLK: 'Zlínský kraj', } DISTRICT_ROLES = { 'sso_kraj_pha' : DISTRICT_PHA, 'sso_kraj_jhc' : DISTRICT_JHC, 'sso_kraj_jhm' : DISTRICT_JHM, 'sso_kraj_kvk' : DISTRICT_KVK, 'sso_kraj_vys' : DISTRICT_VYS, 'sso_kraj_khk' : DISTRICT_KHK, 'sso_kraj_lbk' : DISTRICT_LBK, 'sso_kraj_msk' : DISTRICT_MSK, 'sso_kraj_olk' : DISTRICT_OLK, 'sso_kraj_pak' : DISTRICT_PAK, 'sso_kraj_plk' : DISTRICT_PLK, 'sso_kraj_stc' : DISTRICT_STC, 'sso_kraj_ulk' : DISTRICT_ULK, 'sso_kraj_zlk' : DISTRICT_ZLK, } STATUS_NEW = 0 STATUS_REG = 1 STATUS_CHOICES = ( (STATUS_NEW, 'nový'), (STATUS_REG, 'registrovaný'), ) STATUS_CHOICES_STR = { STATUS_NEW: 'nový', STATUS_REG: 'registrovaný', } KIND_NEWSLETTER = 0 KIND_WANT_SUPPORTER = 1 KIND_WANT_MEMBER = 2 KIND_HELP_VOLUNTEER = 3 KIND_HELP_EXPERT = 4 KIND_ALREADY_MEMBER = 5 KIND_ALREADY_OTHER = 6 KIND_CHOICES = ( (KIND_NEWSLETTER, _('dostávat novinky')), (KIND_HELP_VOLUNTEER, _('pomáhat jako dobrovolník')), (KIND_HELP_EXPERT, _('pomáhat jako expert')), (KIND_WANT_SUPPORTER, _('se stát příznivcem')), (KIND_WANT_MEMBER, _('se stát členem')), (KIND_ALREADY_MEMBER, _('již jsem člen')), (KIND_ALREADY_OTHER, _('již pirátím jinak')), ) KIND_CHOICES_STR = { KIND_NEWSLETTER: _('dostávat novinky'), KIND_HELP_VOLUNTEER: _('pomáhat jako dobrovolník'), KIND_HELP_EXPERT: _('pomáhat jako expert'), KIND_WANT_SUPPORTER: _('se stát příznivcem'), KIND_WANT_MEMBER: _('se stát členem'), KIND_ALREADY_MEMBER: _('již jsem člen'), KIND_ALREADY_OTHER: _('již pirátím jinak'), } # User self-editable fields postcode = IntegerField(_('PSČ'), blank=False, null=True) city = CharField(_(u'Město'), max_length=120, default=None, blank=True, null=True) district = IntegerField(_('Kraj'), blank=True, null=True, choices=DISTRICT_CHOICES, default=None) phone = CharField(_('Telefon'), max_length=30, blank=True) # Stav KIND_MEMBER se nastavuje dle skutečného členství, a bude # pravidelně aktualizován synchronizací s DB členů. kind = IntegerField(_('Chci'), blank=False, null=False, default = KIND_NEWSLETTER, choices=KIND_CHOICES) # Stav KIND_MEMBER se nastavuje dle skutečného členství, a bude # pravidelně aktualizován synchronizací s DB členů. interestedIn = CharField(_('Moje dovednosti (chci pomoci s)'), blank=True, null=True, max_length=150) # Kontaktni email, ktery slouzi ke skryti emailu registrovaneho na SSO # a pouziteho napr. v prihlasce o clenstvi. # SSO bude dalsim aplikacim, krome nalodeni, predavat pouze kontaktni email, bude-li nastaven. email_contact = EmailField(_('Kontaktní e-mail'),max_length=100, default='', blank=True, null=True) # Token pro ověření kontaktního emailu. Má tvat timestamp - token, # kde timestamp se neposílá emailem, ale slouží pro vypršení platnosti. email_contact_token = CharField(_('Ověřovací token pro kontaktní e-mail.'), max_length=150, default=None, blank=True, null=True) # Kontaktni email je potřeba ověřit zaslání ověřovacího emailu. email_contact_verified = BooleanField(_('Kontaktní e-mail byl ověřen.'), default=False, blank=True) # Kontaktni email, který je ověřen a zapsán v LDAP. Vyplňováno při přihlášení z SSO. email_contact_active = EmailField(_('Kontaktní e-mail aktivní'),max_length=100, default='', blank=True, null=True) # Datum a čas poslední změny údajů, které se mají propisovat do LDAP. # Podle tohoto údaje se v synchronizačním skriptu pozná, jaké záznamy je třeba aktualizovat v LDAP ts_for_ldap_sync = DateTimeField(_('Timestamp pro LDAP synchronizaci'), default=datetime.datetime.now, blank=True, null=True) # datum udeleni a odvolani souhlasu se zpracovanim osobnich udaju # logika: je-li dc_stamp=Null, souhlas neni udelen. # pole dc_undo_stamp ma pouze informacni vyznam, kdy k odvolani souhlasu doslo dc_stamp = DateTimeField(_('Data consent timestamp'), default=None, blank=True, null=True) dc_undo_stamp = DateTimeField(_('Datum odvolání souhlasu se zprac.os.údajů'), default=None, blank=True, null=True) # dotaznik pro uzivatele userform = ForeignKey('UserForm', on_delete=CASCADE, verbose_name=_('dotazník'), blank=True, null=True, default=None) # stav uzivatele - jaky? proc? ceho? boha jeho... status = IntegerField(_('Stav'), blank=False, null=False, default = STATUS_NEW, choices=STATUS_CHOICES) # Okres, prirazuje se automaticky pri zmene postcode (PSC) ve fci UserModel_presave county = ForeignKey('County', on_delete=SET_NULL, verbose_name=_('Okres'), blank=True, null=True, default=None) admin_note = TextField(_('Administrátorská poznámka'), blank=True) # pro kompatibilitu kodu a modelu @property def firstName(self): return self.first_name @firstName.setter def firstName(self,value): self.first_name = value @property def lastName(self): return self.last_name @lastName.setter def lastName(self, value): self.last_name = value # Ukladani a nacitani nastaveni # consent_gdpr @property def consent_gdpr(self): return readUserSetting(self, 'consent_gdpr', None, datetime.date) @consent_gdpr.setter def consent_gdpr(self, value): writeUserSetting(self, 'consent_gdpr', value, datetime.date) # consent_cookies @property def consent_cookies(self): return readUserSetting(self, 'consent_cookies', None, datetime.date) @consent_cookies.setter def consent_cookies(self, value): writeUserSetting(self, 'consent_cookies', value, datetime.date) # consent_terms @property def consent_terms(self): return readUserSetting(self, 'consent_terms', None, datetime.date) @consent_terms.setter def consent_terms(self, value): writeUserSetting(self, 'consent_terms', value, datetime.date) # properties mimo model def get_district_name(self): """ Vrat nazev kraje - textova reprezentace, muze se menit, nepouzivat jako klic """ try: ret = self.DISTRICT_CHOICES_STR[self.district] except KeyError: ret = "- Nezadán -" return ret # Meta def __str__(self): return self.username if self.ssoUid: return "%s %s" % (self.first_name, self.last_name) else: return self.email _audit_fields = ('postcode', 'district', 'kind', 'password') _audit_fields_exclude = ('emailToken',) _audit_fields_private = ('password',) class Meta: verbose_name = _('AppUser') verbose_name_plural = _('AppUsers') unique_together = (("email", ),("username",), ("emailToken",),) ordering = ('username',) # Permissions def do_site_perms_calc(self, site_perms): """ Precalculate needed permissions - district IDs for the given roles """ spc, rslt = {}, [] for p in self.DISTRICT_ROLES: if p in site_perms: rslt.append(self.DISTRICT_ROLES[p]) spc['dist'] = rslt return spc @receiver(pre_save, sender=AppUser) def UserModel_presave(sender, instance, **kwargs): """ Pred ulozenim modelu UserModel vyhleda a pripoji podle prefixu PSC prislusny okres """ try: obj = County.objects.get(zip_prefix=str(instance.postcode)[:3]) except: obj = None instance.county = obj USER_MODEL = AppUser #USER_MODEL = get_user_model() class County(Model): """ Ciselnik okresu. S vyuzitim zip_prefix je k uzivateli automaticky prirazen okres, podle psc, ktere zadal pri registraci """ name = CharField(_('Název'), max_length=100) zip_prefix = CharField(_('Předčíslí PSČ'), max_length=3) def __str__(self): return self.name class Meta: ordering = ['name',] unique_together = ( ('zip_prefix',),) class InterestRegion(Model): """ Dostupné emailové zdroje informací. """ name = CharField(_('Název'), blank=True, null=True, max_length=100) tag = CharField(_('Značka (tag) newsletteru'), blank=True, null=True, max_length=20) def __str__(self): return self.name class Meta: ordering = ['name',] unique_together = ( ('tag',),) class UserSkill(Model): """ Dovednosti, které může mít daný uživatel. """ name = CharField(_('Název'), blank=True, null=True, max_length=100) tag = CharField(_('Značka (tag) dovednosti'), blank=True, null=True, max_length=20) def __str__(self): return self.name class Meta: ordering = ['tag',] unique_together = ( ('tag',),) class UserTopic(Model): """ Zájmová témata daného uživatele. """ name = CharField(_('Název'), blank=True, null=True, max_length=100) tag = CharField(_('Značka (tag) tématu'), blank=True, null=True, max_length=20) def __str__(self): return self.name class Meta: ordering = ['name',] unique_together = ( ('tag',),) class UserForm(Model, DataAudited): """ Dotaznik ohledne dovednosti a schopnosti uzivatele. """ skills = ManyToManyField(UserSkill, blank=True, verbose_name=_('Dovednosti')) skills_note = CharField(_('Poznámka k dovednostem'),max_length=250, default=None, blank=True, null=True) topics = ManyToManyField(UserTopic, blank=True, verbose_name=_('Zájmová témata')) regions = ManyToManyField(InterestRegion, blank=True, verbose_name=_('Zájmové regiony')) class AppRegEmail(Model): """ Pozadavky na registraci emailu - sem jdou novi lide, kteri vyplni formular 'jdu do toho' """ createdStamp = DateTimeField(_('Uživatel vytvořen'), default=datetime.datetime.now, blank=False, null=False, editable=False) email = CharField(_('e-mail'), max_length=100, default='', blank=True, null=True) postcode = IntegerField(_('PSČ (kvůli dělení do krajů)'), blank=False, null=True) phone = CharField(_('Telefon'), max_length=30, blank=True) kind = IntegerField(_('Chci'), blank=True, null=True, default = AppUser.KIND_NEWSLETTER, choices=AppUser.KIND_CHOICES) # Stav KIND_MEMBER se nastavuje dle skutečného členství, a bude # pravidelně aktualizován synchronizací s DB členů. interestedIn = CharField(_('Poznámka (info pro koordinátora)'), blank=True, null=True, max_length=100) data_consent = BooleanField(_('Souhlasím se zpracováním osobních údajů.'), default=False, blank=False, null=False) # Souhlas se zpracováním os. údajů dc_stamp = DateTimeField(_('Data consent timestamp'), editable=False, default=None, blank=True, null=True) # registration token emailToken = CharField(_(u'Registration token'), max_length=120, default=None, blank=True, null=True) etStamp = DateTimeField(_('Registration token timestamp'), editable=False, default=None, blank=True, null=True) userform = ForeignKey(UserForm, on_delete=CASCADE, verbose_name=_('dotazník'), blank=True, null=True, default=None) class Meta: verbose_name = _('AppRegEmail') verbose_name_plural = _('AppRegEmails') unique_together = (("emailToken",),("email",)) class Euro2019Interest(Model): """ Zajemci o pomoc ve volbach 2019. """ createdStamp = DateTimeField(_('Uživatel vytvořen'), default=datetime.datetime.now, blank=False, null=False, editable=False) email = CharField(_('e-mail'),max_length=100, blank=False, null=True) district = IntegerField(_('Kraj'), blank=True, null=True, choices=AppUser.DISTRICT_CHOICES, default=None) postcode = IntegerField(_('PSČ (kvůli dělení do krajů)'), blank=False, null=True) want_info = BooleanField(_('Chci dostávat informace e-mailem'), default=True, blank=False, null=False) want_campaign = BooleanField(_('Chci rozdávat Pirátské listy'), default=False, blank=False, null=False) want_be_active = BooleanField(_('Chci se podílet chodu kampaně'), default=False, blank=False, null=False) note = CharField(_('Poznámka (info pro koordinátora)'), blank=True, null=True, max_length=100) data_consent = BooleanField(_('Souhlasím se zpracováním osobních údajů'), default=False, blank=False, null=False) # Souhlas se zpracováním os. údajů dc_stamp = DateTimeField(_('Data consent timestamp'), editable=False, default=None, blank=True, null=True) class Meta: unique_together = (("email",)) def get_user_by_keycloak_email(id): """ Funkce pro dohledani uzivatele podle dat ze SSO serveru. "aud" a "azp" jsou client-id dle SSO serveru. Struktura id : { 'jti': '0d5be61a-fccc-49ce-b82d-78fe53eec7ac' 'exp': 1505657690 'nbf': 0 'iat': 1505657390 'iss': 'http://localhost:8080/auth/realms/nga' 'aud': 'nalodeni' 'sub': 'db831083-7e1e-42ee-9682-2ff85021fcbc' 'typ': 'ID' 'azp': 'nalodeni' 'auth_time': 1505657362 'session_state': '73b44047-b86b-429b-b21f-800b2bc0d33f' 'acr': '0' 'name': 'John Doe' 'preferred_username': 'jd' 'given_name': 'John' 'family_name': 'Doe' 'email': 'john.doe@example.com' } """ # check presence of required attributes if not 'sub' in id: raise sso_exc.MissingSsoInfo(_('SSO server nepředal identifikaci uživatele.')) if not 'preferred_username' in id: raise sso_exc.MissingSsoInfo(_('SSO server nepředal uživatelské jméno.')) if not 'email' in id: raise sso_exc.MissingSsoInfo(_('SSO server nepředal email uživatele.')) if not 'given_name' in id: raise sso_exc.MissingSsoInfo(_('SSO server nepředal křestní jméno uživatele.')) if not 'family_name' in id: raise sso_exc.MissingSsoInfo(_('SSO server nepředal příjmení uživatele.')) id_email = id['email'].lower().strip() if 'email_contact' in id: id_email_contact_active = id['email_contact'].lower().strip() else: id_email_contact_active = None # get user by SSO id sso_users = USER_MODEL.objects.filter(ssoUid=id['sub']) if len(sso_users) == 0: # user unknown in Django email_users = USER_MODEL.objects.filter(email=id_email) if len(email_users) == 0: if len(id['preferred_username']) > 30: # Django User username is 30 character limited raise sso_exc.MissingSsoInfo(_('Uživatelské jméno je příliš dlouhé.')) usernames_found = USER_MODEL.objects.filter(username=id['preferred_username']) if len(usernames_found) > 0: raise sso_exc.UsernameAlreadyTaken( _('Uživatelské jméno %s je ' + 'již obsazeno.') % id['preferred_username']) user = USER_MODEL() user.ssoUid = id['sub'] user.email = id_email user.email_contact_active = id_email_contact_active user.username = id['preferred_username'] user.first_name = id['given_name'] user.last_name = id['family_name'] user.save() elif len(email_users) == 1: user = email_users[0] if user.ssoUid == None: user.ssoUid = id['sub'] else: # email souhlasí, ale ssoUid nikoliv raise sso_exc.EmailVersusIdentityMismatch( _('Účet nalodění s tímto emailem (%s, %s) ' + 'je již napárován na jinou identitu (%s) na Pirátské identitě.') % ( user.email, user.ssoUid, id['sub'] ) ) #check duplicity of the new username if len(id['preferred_username']) > 30: # Django User username is 30 character limited raise sso_exc.MissingSsoInfo(_('Uživatelské jméno je příliš dlouhé.')) rslt = USER_MODEL.objects.filter(username=id['preferred_username']) if len(rslt) > 0: raise sso_exc.UsernameAlreadyTaken( _('Uživatelské jméno %s je ' + 'již obsazeno.') % id['preferred_username']) user.username = id['preferred_username'] user.first_name = id['given_name'] user.last_name = id['family_name'] user.email_contact_active = id_email_contact_active # reset email login attempts user.emailToken = None user.etStamp = None user.save() elif len(email_users) > 1: raise Exception("More users with one email value.") elif len(sso_users) == 1: # user known user = sso_users[0] changed = False if user.username != id['preferred_username']: #check duplicity of the new values (username, email) if len(id['preferred_username']) > 30: # Django User username is 30 character limited raise sso_exc.MissingSsoInfo(_('Uživatelské jméno je příliš dlouhé.')) rslt = USER_MODEL.objects.filter(username=id['preferred_username']) if len(rslt) > 0: raise sso_exc.UsernameAlreadyTaken( _('Uživatelské jméno %s je ' + 'již obsazeno.') % id['preferred_username']) user.username = id['preferred_username'] changed = True if user.first_name != id['given_name']: user.first_name = id['given_name'] changed = True if user.last_name != id['family_name']: user.last_name = id['family_name'] changed = True if user.email != id_email: # check duplicity rslt = USER_MODEL.objects.filter(email=id_email) if len(rslt) > 0: raise sso_exc.EmailAlreadyTaken( _('Email %s je ' + 'již obsazen jiným účtem.') % id_email) user.email = id_email changed = True if user.email_contact_active != id_email_contact_active: user.email_contact_active = id_email_contact_active changed = True if changed: user.save() else: raise Exception("More users with one sso-uid value.") return user class Newsletter(Model): """ Záznam o newsletteru, který se chce zasílat. """ is_del = BooleanField(_('Smazaný'), default=False, blank=False, null=False, editable=False) name = CharField(_('Název'), blank=True, null=True, max_length=150) desc = CharField(_('Popis'), blank=True, null=True, max_length=500) PER_WEEK = 0 PER_MONTH = 1 PER_IRR = 2 PER_CHOICES = ( (PER_WEEK, _('Týdně')), (PER_MONTH, _('Měsíčně')), (PER_IRR, _('Nepravidelně')), ) period = IntegerField(_('Perioda zasílání'), blank=False, null=False, default=PER_MONTH, choices=PER_CHOICES) # Kdo může newsletter nastavovat managed_by = ForeignKey(USER_MODEL, models.CASCADE, verbose_name=_('Správce newsletteru'), related_name="newsletter_managed") # Kdo může pomocí tohoto newsletteru odesílat zprávy sent_by = ManyToManyField(USER_MODEL, blank=True, verbose_name=_('Odesílatelé'), related_name="newsletter_sent") enabled = BooleanField(_('Aktivní'), default=False, blank=False, null=False) recipients = CharField(_('Příjemci (pomocí tagů)'), blank=True, null=True, max_length=500) replyToEmail = CharField(_('E-mailová adresa pro odpovědi'), blank=True, null=True, max_length=150) class Meta: ordering = ("-enabled", "name",) def __str__(self): return self.name def get_recip_users(self): """ Parse self.recipients and return a list of matching users. """ all_users = USER_MODEL.objects.none() if self.recipients is None: return all_users for group in self.recipients.strip().replace('\n',' ').split('*'): group = group.strip() if group == "": continue topic_ids = [] region_ids = [] skill_ids = [] for item in group.split(" "): item = item.strip() if item == "": continue if item[0] == "s": rslt = UserSkill.objects.filter(tag=item[2:]) if len(rslt) == 1: skill_ids.append(rslt[0].id) elif item[0] == "r": rslt = InterestRegion.objects.filter(tag=item[2:]) if len(rslt) == 1: region_ids.append(rslt[0].id) elif item[0] == "t": rslt = UserTopic.objects.filter(tag=item[2:]) if len(rslt) == 1: topic_ids.append(rslt[0].id) rslt_u = AppUser.objects.all() if len(topic_ids) > 0: rslt_u = rslt_u.filter(userform__topics__in = topic_ids) if len(skill_ids) > 0: rslt_u = rslt_u.filter(userform__skills__in = skill_ids) if len(region_ids) > 0: rslt_u = rslt_u.filter(userform__regions__in = region_ids) all_users = all_users | rslt_u # use distinct because ORM _will_ multiply the results return all_users.distinct() def get_recip_users_count(self): return len(self.get_recip_users()) class NewsCond(Model): """ Podmínky pro filtrování uživatelů, kterým se má newsletter odesílat. Podmínky v rámci skupiny se spojují operátorem AND, skupiny jako celek jsou pak spojeny operátorem OR. """ news = ForeignKey(Newsletter, models.CASCADE, verbose_name=_('Newsletter')) group = IntegerField(_('Skupina podmínek'), default=False, blank=False, null=False) neg = BooleanField(_('Negovat podmínku'), default=False, blank=False, null=False) KIND_SKILL = 0 KIND_TOPIC = 1 KIND_REGION = 2 KIND_CHOICES = ( (KIND_SKILL, _('Dovednost')), (KIND_TOPIC, _('Téma')), (KIND_REGION, _('Území')), ) kind = IntegerField(_('Druh'), blank=False, null=False, default=KIND_SKILL, choices=KIND_CHOICES) opt_id = IntegerField(_('ID vybrané možnosti'), blank=False, null=False) class NewsMsg(Model): """ Jedna konkrétní zpráva k rozeslání, skládá se z bloků, které tvoří její obsah. Uživatelé mohou reagovat na NewsMsg pomocí rozhraní nad NewsMsgReply. """ is_del = BooleanField(_('Smazaný'), default=False, blank=False, null=False, editable=False) news = ForeignKey(Newsletter, models.CASCADE, verbose_name=_('Newsletter')) created_by = ForeignKey(USER_MODEL, models.CASCADE, verbose_name=_('Vytvořil')) created_ts = DateTimeField(_('Datum vytvoření'), default=datetime.datetime.now, blank=False, null=False, editable=False) delivery_ts = DateTimeField(_('Datum plánovaného rozeslání'), blank=True, null=True, editable=True) sent_ts = DateTimeField(_('Datum skutečného rozeslání'), blank=True, null=True, editable=True) title = CharField(_('Název, předmět emailu'), blank=False, null=True, max_length=150) headerText = TextField(_('Hlavička'), blank=True, null=True) footerText = TextField(_('Patička'), blank=True, null=True) testMailRecipients = CharField(_('Přidaní adresáti testovacího emailu'), blank=True, null=True, max_length=150) def __str__(self): return self.title class NewsMsgBlock(Model): """ Jeden blok newsletteru, ze kterých se skládá celá zpráva. """ newsmsg = ForeignKey(NewsMsg, models.CASCADE, verbose_name=_('Zpráva')) order = IntegerField(_('Pořadí'), default=False, blank=False, null=False) heading = CharField(_('Nadpis'), max_length=150, blank=True, null=True) content = TextField(_('Text (i validované HTML)'), blank=True, null=True) link_text = CharField(_('Text odkazu'), max_length=100, blank=True, null=True) link = CharField(_('Odkaz'), max_length=500, blank=True, null=True) # časem je asi možné obrázky nahrávat přímo do nalodění img_thumb_url = CharField(_('Obrázek náhledu (URL)'), max_length=500, blank=True, null=True) img_url = CharField(_('Obrázek (URL)'), max_length=500, blank=True, null=True) img_label = CharField(_('Popis obrázku'), max_length=100, blank=True, null=True) class Meta: ordering = ['order', 'heading'] class NewsMsgReply(Model): """ Reakce ctenaru na dany informacni blok. """ block = ForeignKey(NewsMsgBlock, models.CASCADE, verbose_name=_('Část zprávy')) user = ForeignKey(USER_MODEL, models.CASCADE, verbose_name=_('Vytvořil')) comment = CharField(_('Poznámka'), max_length=250, blank=True, null=True) rating_usefullness = IntegerField(_('Využiji to'), default=0, blank=True, null=True) rating_interest = IntegerField(_('Zajímá mě to'), default=0, blank=True, null=True) rating_action = IntegerField(_('Zapojím se / Pomůžu'), default=0, blank=True, null=True) class UserSetting(Model): """ Different settings and values stored for global purpose. """ user = ForeignKey(USER_MODEL, models.CASCADE, verbose_name=_('user')) key = CharField(_("Key"), max_length=64) value = CharField(_("Value"), max_length=128, null=True) def __str__(self): return str(self.key) class Meta: unique_together = (( 'user','key', ),) def readUserSetting(user, key, default, dataType): """ Reads user settings for the current SITE_ID. Returns 'default' value if the setting is not present or the value is None. """ obj = UserSetting.objects.filter(key=key,user=user) if len(obj) == 0: return default else: val = obj[0].value if val is None: return default if dataType == datetime.date: return datetime.datetime.strptime(val,'%Y-%m-%d').date() elif dataType == bool: return val == 'T' else: return dataType(val) def writeUserSetting(user, key, value, dataType): """ Saves a user setting for the current SITE_ID. """ obj, created = UserSetting.objects.get_or_create(key=key,user=user) # modify if value is not None: if dataType == bool: value = 'T' if value else 'F' elif dataType == datetime.date: value = value.strftime('%Y-%m-%d') # save modified value, or the original one, # or None if value is None obj.value = value obj.save() class AppSetting(Model): """ Different settings and values stored for global purpose. """ key = CharField(_("Key"), max_length=64) # not null ... because we agree "" is null value = CharField(_("Value"), max_length=128) def __str__(self): return self.key class Meta: unique_together = (( 'key', ),) def readAppSetting(key, default=None): try: obj = AppSetting.objects.get(key=key) if obj.value == "": return default else: return obj.value except: return default def writeAppSetting(key, value): obj, created = AppSetting.objects.get_or_create(key=key) if created: pass # means you have created a new person else: pass # person just refers to the existing one if value is not None: obj.value = str(value) else: obj.value = '' obj.save()