nalodeni.pirati.cz/nalodeni/models.py

932 lines
32 KiB
Python
Raw Permalink Normal View History

2019-04-03 13:55:34 +03:00
# -*- 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
2019-04-03 13:55:34 +03:00
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
2019-04-03 13:55:34 +03:00
class AppUser(AbstractUser, DataAudited):
2019-04-03 13:55:34 +03:00
"""
Uzivatel. Nove nalodeni jsou sem prepisovani z AppRegEmail.
2019-04-03 13:55:34 +03:00
"""
### 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'),
)
2019-04-12 02:01:59 +03:00
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',
}
2019-04-03 13:55:34 +03:00
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ý'),
)
2019-04-12 02:01:59 +03:00
STATUS_CHOICES_STR = {
STATUS_NEW: 'nový',
STATUS_REG: 'registrovaný',
}
2019-04-03 13:55:34 +03:00
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')),
)
2019-04-12 02:01:59 +03:00
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'),
}
2019-04-03 13:55:34 +03:00
# 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)
2019-04-03 13:55:34 +03:00
# Stav KIND_MEMBER se nastavuje dle skutečného členství, a bude
# pravidelně aktualizován synchronizací s DB členů.
2019-04-03 13:55:34 +03:00
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ů.
2019-04-03 13:55:34 +03:00
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.
2019-04-03 13:55:34 +03:00
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.'),
2019-04-03 13:55:34 +03:00
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)
2019-04-03 13:55:34 +03:00
# Kontaktni email, který je ověřen a zapsán v LDAP. Vyplňováno při přihlášení z SSO.
2019-04-03 13:55:34 +03:00
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
2019-04-03 13:55:34 +03:00
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)
2019-04-03 13:55:34 +03:00
# 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)
2019-04-03 13:55:34 +03:00
# pro kompatibilitu kodu a modelu
2019-04-03 13:55:34 +03:00
@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
2019-04-03 13:55:34 +03:00
# 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
2019-04-03 13:55:34 +03:00
# Meta
2019-04-03 13:55:34 +03:00
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',)
2019-04-03 13:55:34 +03:00
class Meta:
verbose_name = _('AppUser')
verbose_name_plural = _('AppUsers')
unique_together = (("email", ),("username",), ("emailToken",),)
ordering = ('username',)
2019-04-03 13:55:34 +03:00
# Permissions
2019-04-03 13:55:34 +03:00
def do_site_perms_calc(self, site_perms):
""" Precalculate needed permissions - district IDs for the given roles """
spc, rslt = {}, []
2019-04-03 13:55:34 +03:00
for p in self.DISTRICT_ROLES:
if p in site_perms:
rslt.append(self.DISTRICT_ROLES[p])
spc['dist'] = rslt
return spc
2019-04-03 13:55:34 +03:00
@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
2019-04-03 13:55:34 +03:00
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',),)
2019-04-03 13:55:34 +03:00
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',),)
2019-04-03 13:55:34 +03:00
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',),)
2019-04-03 13:55:34 +03:00
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):
2019-04-03 13:55:34 +03:00
"""
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'
2019-04-03 13:55:34 +03:00
"""
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)
2019-04-03 13:55:34 +03:00
postcode = IntegerField(_('PSČ (kvůli dělení do krajů)'), blank=False, null=True)
phone = CharField(_('Telefon'), max_length=30, blank=True)
2019-04-03 13:55:34 +03:00
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'
2019-04-03 13:55:34 +03:00
}
"""
# 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 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)
2019-04-03 13:55:34 +03:00
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
2019-04-03 13:55:34 +03:00
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()