921 lines
31 KiB
Python
921 lines
31 KiB
Python
# -*- 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)
|
|
|
|
# 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)
|
|
|
|
|
|
# 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)
|
|
|
|
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()
|
|
|