Presun z predchoziho repo

This commit is contained in:
Martin Rejman 2019-04-03 12:55:34 +02:00
commit c1093a54fe
191 changed files with 26762 additions and 0 deletions

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
local_python
lopy
static_files
log_files
venv
*.sw[op]
*.pyc
settings_local.py
docker-compose.yml

45
install.sh Normal file
View File

@ -0,0 +1,45 @@
virtualenv -p python3.5 venv
source ./venv/bin/activate
pip install Django==2.0.3 # 2.0.3
# s binarne distribuovanou libssl
pip install psycopg2
pip install django-statici18n # preklad Javascript souboru
# nahrada za SUDS, zalozeno na lxml
#pip install zeep
pip install bleach
##
# Keycloak SSO
##
#pip install git+https://github.com/jhuapl-boss/django-oidc.git
#pip install git+https://github.com/jhuapl-boss/drf-oidc-auth.git
#pip install git+https://github.com/jhuapl-boss/boss-oidc.git
pip install -U django-auth-oidc pyjwkest
#pip install -U service_identity
#pip install -U openid_connect
##
# Database setup
##
psql <<SQL
CREATE USER piratinalodeni;
CREATE DATABASE piratinalodeni OWNER=piratinalodeni ENCODING='UTF8' LC_COLLATE='cs_CZ.UTF-8' template template0;
SQL
##
# App Setup
##
./src/manage.py makemigrations nalodeni
./src/manage.py migrate

2
src/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
nalodeni.env

32
src/Dockerfile Normal file
View File

@ -0,0 +1,32 @@
FROM debian:stretch
#FROM python:3
EXPOSE 8000
WORKDIR /
RUN apt-get update
RUN apt-get install -y python-virtualenv python-pip
RUN virtualenv -p python3 venv
# ponechano kvuli vyuziti cache
#RUN bash -c 'source /venv/bin/activate; pip install -r pip-requirements.txt'
RUN bash -c 'source /venv/bin/activate; pip install Django==2.0.3 psycopg2==2.7.4 django-statici18n==1.7.0 django-auth-oidc==0.4.5 pyjwkest==1.4.0 bleach==2.1.4 django-anymail==3.0 html2text==2018.1.9'
ADD . /nalodeni/src/
#RUN bash -c 'touch /nalodeni/src/main/settings_local.py' # this file needs to exist
#RUN adduser --disabled-login --quiet --gecos nalodeni nalodeni
#RUN chown -R nalodeni:nalodeni /nalodeni/
#RUN chmod u+x /nalodeni/src/docker-entrypoint.sh
RUN bash -c 'adduser --disabled-login --quiet --gecos nalodeni nalodeni && \
chmod -R o+r /nalodeni/ && \
chown -R nalodeni:nalodeni /nalodeni/src/static_files && \
chmod o+x /nalodeni/src/docker-entrypoint.sh && \
touch /nalodeni/src/main/settings_local.py '
USER nalodeni
ENTRYPOINT /nalodeni/src/docker-entrypoint.sh

View File

@ -0,0 +1,22 @@
upstream nalodeni.pirati.io {
ip_hash;
server nalodeni:8000;
}
server {
server_name nalodeni.pirati.io;
listen 8001;
location / {
proxy_pass http://nalodeni.pirati.io/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static/ {
alias /static_nalodeni/;
}
}

1
src/deploy/nginx/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
nalodeni.conf

0
src/deploy/nginx/.keep Normal file
View File

View File

@ -0,0 +1,27 @@
version: '2'
services:
nginx:
image: nginx:latest
container_name: nalodeni-nginx
ports:
- "80:8001"
volumes:
- ./deploy/nginx:/etc/nginx/conf.d
- vol_static_nalodeni:/static_nalodeni
depends_on:
- nalodeni
nalodeni:
build: .
container_name: nalodeni
command: bash -c "/nalodeni/src/docker-entrypoint.sh"
volumes:
- vol_static_nalodeni:/nalodeni/src/static_files
ports:
- "8000"
environment:
- NALODENI_DEBUG=off
- NALODENI_DEBUG_LOCAL=off
volumes:
vol_static_nalodeni:

16
src/docker-entrypoint.sh Normal file
View File

@ -0,0 +1,16 @@
#!/bin/bash
trap "echo TRAPed signal" HUP INT QUIT KILL TERM
source /venv/bin/activate
cd /nalodeni/src
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py loaddata nalodeni_newsletter nalodeni_topics nalodeni_skills
PYTHONIOENCODING=utf-8 python manage.py runserver 0.0.0.0:8000

View File

57
src/keycloak_oidc/auth.py Normal file
View File

@ -0,0 +1,57 @@
import os
import requests
from django.conf import settings
from openid_connect import connect, connect_url
from openid_connect._oidc import OpenIDClient, TokenResponse
class OpenIDClientCustom(OpenIDClient):
def refresh_session(self, refresh_token):
r = requests.post(self.token_endpoint, auth=self.auth, data=dict(
grant_type="refresh_token",
refresh_token=refresh_token
), headers={'Accept': 'application/json'})
if r.status_code != 200:
#r.raise_for_status()
return None
resp = TokenResponse(r.json(), self)
if "scope" in resp._data:
resp.scope = set(self.translate_scope_out(set(resp._data["scope"].split(" "))))
if not hasattr(resp, "scope") or "openid" in resp.scope:
resp.id = self.get_id(resp)
return resp
server = None
def get_server_orig():
global server
if server is not None:
return server
AUTH_URL = os.environ.get("AUTH_URL")
if AUTH_URL:
server = connect_url(AUTH_URL)
else:
server = connect(settings.AUTH_SERVER, settings.AUTH_CLIENT_ID,
settings.AUTH_CLIENT_SECRET,
getattr(settings, 'AUTH_PROTOCOL', None))
return server
def get_server():
global server
if server is not None:
return server
server = OpenIDClientCustom( settings.AUTH_SERVER, settings.AUTH_CLIENT_ID,
settings.AUTH_CLIENT_SECRET)
return server

View File

@ -0,0 +1,13 @@
# encoding: utf8
class MissingSsoInfo(Exception):
pass
class EmailVersusIdentityMismatch(Exception):
pass
class UsernameAlreadyTaken(Exception):
pass
class EmailAlreadyTaken(Exception):
pass

View File

@ -0,0 +1,29 @@
# -*- encoding: utf-8 -*-
import time
import keycloak_oidc.views
class KeycloakSessionRefreshMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
refresh_expires_at = request.session.get('refresh_expires_at',None)
#print("refresh_expires_at", refresh_expires_at, time.time())
if refresh_expires_at is not None:
if refresh_expires_at - time.time() < 5*60: # 5 minutes before expiration
rslt = keycloak_oidc.views.renew_session(request)
if rslt != 0:
# renew not successful
return keycloak_oidc.views.logout(request, multiSessionFound=False)
return self.get_response(request)

14
src/keycloak_oidc/urls.py Normal file
View File

@ -0,0 +1,14 @@
from django.conf.urls import url
from . import views
app_name = 'keycloak_oidc'
urlpatterns = [
url(r'^$', views.login, name='login-base'),
url(r'^login/$', views.login, name='login'),
url(r'^login/(?P<idp_hint>[a-z]+)/$', views.login, name='login_idp'),
url(r'^done/$', views.callback, name='login-done'),
url(r'^logout/$', views.logout, name='logout', kwargs={'ssoLogout':True}),
url(r'^logoutLocal/$', views.logout, name='logout'),
url(r'^account/$', views.account, name='account'),
url(r'^refresh_session/$', views.refresh_session, name='refresh-session'),
]

337
src/keycloak_oidc/views.py Normal file
View File

@ -0,0 +1,337 @@
# encoding: utf8
from importlib import import_module
import logging
from jwkest.jwt import JWT
from requests.exceptions import HTTPError
from django.contrib import auth
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import redirect, resolve_url
from django.urls import reverse
from django.utils.http import is_safe_url
from django.conf import settings as appSettings
from . import auth as _auth
from . import exceptions as sso_exc
logger = logging.getLogger(__name__)
try:
LOGIN_REDIRECT_URL = appSettings.LOGIN_REDIRECT_URL
except AttributeError:
LOGIN_REDIRECT_URL = '/'
try:
LOGOUT_REDIRECT_URL = appSettings.LOGOUT_REDIRECT_URL
except AttributeError:
LOGOUT_REDIRECT_URL = '/sso/logout/'
if LOGOUT_REDIRECT_URL is None:
LOGOUT_REDIRECT_URL = '/sso/logout/'
try:
AUTH_SCOPE = appSettings.AUTH_SCOPE
except AttributeError:
AUTH_SCOPE = ('openid',)
try:
AUTH_AVAIL_IDP = appSettings.AUTH_AVAIL_IDP
except AttributeError:
AUTH_AVAIL_IDP = []
try:
GET_USER_FUNCTION = appSettings.AUTH_GET_USER_FUNCTION
except AttributeError:
GET_USER_FUNCTION = 'keycloak_oidc:get_user_by_username'
try:
LOGOUT_REDIRECT_VIEW = appSettings.AUTH_LOGOUT_REDIRECT_VIEW
except AttributeError:
LOGOUT_REDIRECT_VIEW = 'nalodeni:about'
try:
LOGIN_ROLE_REQUIRED = appSettings.AUTH_LOGIN_ROLE_REQUIRED
except AttributeError:
LOGIN_ROLE_REQUIRED = None
try:
LOGIN_ROLE_REQUIRED_ANY = appSettings.AUTH_LOGIN_ROLE_REQUIRED_ANY
except AttributeError:
LOGIN_ROLE_REQUIRED_ANY = False
jwt_singleton = JWT()
def _import_object(path, def_name):
try:
mod, cls = path.split(':', 1)
except ValueError:
mod = path
cls = def_name
return getattr(import_module(mod), cls)
get_user = _import_object(GET_USER_FUNCTION, 'get_user')
def login(request, idp_hint=None):
return_path = request.GET.get(auth.REDIRECT_FIELD_NAME, "/")
if idp_hint and idp_hint not in AUTH_AVAIL_IDP:
idp_hint = None
try:
srv = _auth.get_server()
except Exception as e:
messages.error(request, "Bohužel nastaly potíže s centrálním přihlášením uživatele.")
logger.error('Chyba SSO login: %s' % e)
return redirect(request.build_absolute_uri("/"))
return redirect(_auth.get_server().authorize(
redirect_uri = request.build_absolute_uri(reverse("keycloak_oidc:login-done")),
state = return_path,
scope = AUTH_SCOPE,
) + "&kc_locale=" + appSettings.AUTH_SSO_LOCALE + (
('&kc_idp_hint=' + idp_hint) if idp_hint is not None else ""
)
)
def renew_session(request, returnHttp=False):
"""
Refresh the session using a refresh_token.
"""
reftoken = request.session.get('refresh_token', None)
if reftoken is None:
return -3
#print("Refresh token IAT:", jwt_singleton.unpack(reftoken).payload()['iat'])
#print(jwt_singleton.unpack(reftoken).payload())
try:
srv = _auth.get_server()
except Exception as e:
messages.error(request,"Bohužel nastaly potíže s centrálním SSO serverem.")
logger.error('Chyba SSO renew: %s' % e)
if returnHttp:
return redirect(request.build_absolute_uri("/"))
else:
return -1
new_token = srv.refresh_session(reftoken)
if new_token is None:
if returnHttp:
return redirect(request.build_absolute_uri("/"))
else:
return -2
rt = new_token._data['refresh_token']
#print("Refresh token NEW IAT:", jwt_singleton.unpack(rt).payload()['iat'])
rt_payload = jwt_singleton.unpack(rt).payload()
# save the new refresh token
request.session['refresh_token'] = rt
request.session['refresh_expires_at'] = rt_payload['exp']
if returnHttp:
return redirect(request.build_absolute_uri("/"))
else:
return 0
def callback(request):
return_path = request.GET.get("state",'/')
sso_code = request.GET.get('code','')
if sso_code == "" and return_path == "":
messages.error(request, "Chyba při přihlášení na centrálním serveru SSO.")
return redirect(request.build_absolute_uri("/"))
else:
try:
res = _auth.get_server().request_token(
redirect_uri = request.build_absolute_uri(reverse("keycloak_oidc:login-done")),
code = sso_code,
)
except HTTPError as e:
messages.error(request, "Přihlášení uživatele se nezdařilo. Chyba komunikace s centrálním serverem přihlášení.")
logger.error('Chyba SSO callback: %s' % e)
return redirect(request.build_absolute_uri("/"))
#print(res)
#print(dir(res))
#print(res.id_token)
#print(res.access_token)
id_token_data = jwt_singleton.unpack(res.id_token).payload()
reftoken = jwt_singleton.unpack(res._data['refresh_token']).payload()
# Get the user from local DB
#
try:
user = get_user(id_token_data)
user.backend = 'django.contrib.auth.backends.ModelBackend'
except sso_exc.MissingSsoInfo as e:
messages.error(request, "Účet na Pirátské identitě není řádně vyplněn. %s" % e )
messages.info(request,"Zkontrolujte údaje na <a href='%s' target='_blank'>auth.pirati.cz</a>." % (appSettings.AUTH_SERVER + "account/"), extra_tags="safe")
logger.error('SSO account incomplete 1: %s' % e)
return redirect(request.build_absolute_uri("/"))
except sso_exc.EmailVersusIdentityMismatch as e:
messages.error(request, "Účet v nalodění a na Pirátské identitě nesouhlasí.")
messages.info(request,"Obraťte se na technický odbor Pirátů.")
logger.error('SSO account email vs. identity mismatch: %s' % e)
return redirect(request.build_absolute_uri("/"))
except sso_exc.UsernameAlreadyTaken as e:
messages.error(request, "%s" % e)
messages.info(request,'Pokud se jedná o vaše uživatelské jméno,'
+ 'obraťte se na technický odbor Pirátů.')
logger.error('Username already taken in Nalodeni: %s' % e)
return redirect(request.build_absolute_uri("/"))
except sso_exc.EmailAlreadyTaken as e:
messages.error(request, "%s" % e)
messages.info(request,'Pokud se jedná o váš email,'
+ 'obraťte se na technický odbor Pirátů.')
logger.error('Email already taken in Nalodeni: %s' % e)
return redirect(request.build_absolute_uri("/"))
# Process the user roles, if any ( taken from bossoidc.backend )
#
jwt = jwt_singleton.unpack(res.access_token).payload()
#print("access_token:", jwt_singleton.unpack(res.access_token).payload())
#print("id_token:", jwt_singleton.unpack(res.id_token).payload())
#### if 'realm_access' in jwt: # Session logins and Bearer tokens from password Grant Types
#### roles = jwt['realm_access']['roles']
#### else:
#### roles = []
resource_roles = []
if 'resource_access' in jwt:
if jwt['azp'] in jwt['resource_access']:
if 'roles' in jwt['resource_access'][jwt['azp']]:
resource_roles = jwt['resource_access'][jwt['azp']]['roles']
if (LOGIN_ROLE_REQUIRED_ANY and len(resource_roles) == 0) or (LOGIN_ROLE_REQUIRED is not None and LOGIN_ROLE_REQUIRED not in resource_roles):
messages.error(request, "Váš pirátský účet nemá přidělené potřebné přístupové role. Přístup odmítnut.")
logger.error('SSO account %s: no roles assigned' % (user.ssoUid,))
return redirect(request.build_absolute_uri("/"))
# Update user information
#
if 'given_name' in jwt and 'family_name' in jwt:
user.first_name = jwt['given_name']
user.last_name = jwt['family_name']
else:
messages.error(request, "Účet na Pirátské identitě není řádně vyplněn.")
messages.info(request,"Zkontrolujte údaje na <a href='%s' target='_blank'>auth.pirati.cz</a>." % (appSettings.AUTH_SERVER + "account/"), extra_tags="safe")
logger.error('SSO account incomplete 2: %s' % e)
return redirect(request.build_absolute_uri("/"))
user.loginSession = jwt['session_state']
user.save()
# Authenticate the user
#
auth.login(request, user)
request.session['openid_token'] = res.id_token
request.session['openid'] = id_token_data
request.session['refresh_token'] = res._data['refresh_token']
request.session['refresh_expires_at'] = reftoken['exp']
request.session['loginSession'] = user.loginSession
##
# Set client-system roles based on SSO roles
#
# After setting permissions, the User object should be reloaded,
# but we do a redirect, so it is fine.
##
#print(roles)
#print(resource_roles)
user.user_permissions.clear()
request.session['site_perms'] = []
# prepend "sso_" not to mix with other perms
for rr in resource_roles:
perm = 'sso_'+rr
request.session['site_perms'].append(perm)
# tell the User to calculate needed permissions
request.session['spc'] = user.do_site_perms_calc(request.session['site_perms'])
# tell us, what roles do we have
if appSettings.DEBUG:
print("Roles assigned:", request.session['site_perms'])
print("Roles calculated:", request.session['spc'])
url_is_safe = is_safe_url(
url = return_path,
host = request.get_host(),
allowed_hosts = set(request.get_host()),
require_https = request.is_secure(),
)
if not url_is_safe:
return redirect(resolve_url(LOGIN_REDIRECT_URL))
return redirect(return_path)
def logout(request, multiSessionFound=False, ssoLogout=False):
id_token = request.session.get('openid_token', None)
if request.user.is_authenticated:
if not multiSessionFound:
# reset the DB session, because this is
# a user requested logout
request.user.loginSession = None
request.user.save()
request.session['loginSession'] = None
auth.logout(request)
if ssoLogout:
try:
srv = _auth.get_server()
except Exception as e:
messages.error(request,"Bohužel nastaly potíže s centrálním odhlášením uživatele.")
logger.error('Chyba SSO logout: %s' % e)
return redirect(request.build_absolute_uri("/"))
if _auth.get_server().end_session_endpoint and id_token is not None:
messages.info(request,"Byli jste odhlášení z aplikace i Centrální identity.")
return redirect(_auth.get_server().end_session(
post_logout_redirect_uri = request.build_absolute_uri(LOGOUT_REDIRECT_URL),
state = '',
id_token_hint = id_token,
))
else:
if id_token:
messages.info(request,"Byli jste odhlášení z aplikace. Vaše centrální identita na <a href='%s' target='_blank'>auth.pirati.cz</a> zůstává přihlášena." % (appSettings.AUTH_SERVER + "account/"), extra_tags="safe")
else:
messages.info(request,"Byli jste odhlášení z aplikace.")
return redirect(LOGOUT_REDIRECT_VIEW)
def account(request):
return redirect(
request.build_absolute_uri(appSettings.AUTH_SERVER+"account/")
+ "?kc_locale=" + appSettings.AUTH_SSO_LOCALE
)
def refresh_session(request):
"""
Does a keycloak session refresh via middleware.
"""
resp = { 'exp' : request.session.get('refresh_expires_at', None) }
return HttpResponse(json.dumps(resp), content_type="application/json")

0
src/main/__init__.py Normal file
View File

160
src/main/settings.py Normal file
View File

@ -0,0 +1,160 @@
# -*- encoding:utf-8 -*-
"""
Main settings file for one domain.
Domain: .CZ
"""
import os
from .settings_global import *
from .settings_local import *
##
# Load ENV variables with prefix NALODENI_ to UPDATE_CONFIGS
#
# Docker deployment needs setting to be supplied externally.
#
# ENV_APPROVED_UPDATES contains known variables we can set,
# the tuple contains "path" to the setting.
ENV_APPROVED_UPDATES = {
'AUTH_SERVER' : ('AUTH_SERVER',),
'AUTH_CLIENT_ID' : ('AUTH_CLIENT_ID',),
'AUTH_CLIENT_SECRET' : ('AUTH_CLIENT_SECRET',),
'AUTH_AVAIL_IDP' : ('AUTH_AVAIL_IDP',),
'HTTP_PROTOCOL' : ('HTTP_PROTOCOL',),
'BASE_DOMAIN' : ('BASE_DOMAIN',),
'BASE_SUBDOMAIN' : ('BASE_SUBDOMAIN',),
'BASE_PORT' : ('BASE_PORT',),
'PSQL_USER' : ('DATABASES','default','USER'),
'PSQL_PASSWORD' : ('DATABASES','default','PASSWORD'),
'PSQL_DBNAME' : ('DATABASES','default','NAME'),
'PSQL_HOST' : ('DATABASES','default','HOST'),
'PSQL_PORT' : ('DATABASES','default','PORT'),
'APP_REG_LIMIT_HARD' : ('APP_REG_LIMIT_HARD', ),
'APP_REG_LIMIT_SOFT' : ('APP_REG_LIMIT_SOFT', ),
'TOKEN_VALID_SEC' : ('TOKEN_VALID_SEC', ),
'EMAIL_BACKEND' : ('EMAIL_BACKEND',),
'EMAIL_HOST' : ('EMAIL_HOST',),
'EMAIL_PORT' : ('EMAIL_PORT',),
'EMAIL_HOST_USER' : ('EMAIL_HOST_USER',),
'EMAIL_HOST_PASSWORD' : ('EMAIL_HOST_PASSWORD',),
'EMAIL_USE_TLS' : ('EMAIL_USE_TLS',),
'EMAIL_USE_SSL' : ('EMAIL_USE_SSL',),
'EMAIL_SSL_CERTFILE' : ('EMAIL_SSL_CERTFILE',),
'EMAIL_SSL_KEYFILE' : ('EMAIL_SSL_KEYFILE',),
'DEBUG' : ('DEBUG',),
'DEBUG_LOCAL' : ('DEBUG_LOCAL',),
}
for evk in os.environ:
if evk[0:9] == "NALODENI_":
var_key = evk[9:]
if var_key in ENV_APPROVED_UPDATES:
val_type = os.environ[evk][0:2]
val = str(os.environ[evk][2:])
if val_type == "s-":
val = (val, ) # tuple
elif val_type == "b-":
val = (val == 'on', ) # tuple
elif val_type == "a-":
# nested tuple, one gets eaten by + operator
val = ( tuple(val.split(',')) ,)
else:
print("Wrong ENV value for '%s', skipping" % evk)
continue
if val == "":
val = (None, )
UPDATE_CONFIGS.append( ENV_APPROVED_UPDATES[var_key] + val )
##
# Update configs from settings_local.UPDATE_CONFIGS
#
# Each item is a list-like path to the setting to be
# updated, the last value of the list is the value of
# the setting updated. E.g.:
# ('CACHES', 'default', 'KEY_PREFIX', 'cz:'),
# is the same as
# CACHES['default']['KEY_PREFIX'] = 'cz:'
# written directly in the settings file.
#
# Use array references to update the existing array.
# [:-2] used to preserve the last array-like link, so
# we can update the value there, and not the local variable.
for item in UPDATE_CONFIGS:
p = vars()
for i in item[:-2]:
if not i in p:
p[i] = {}
p = p[i]
p[item[-2]]=item[-1]
#
##
## ##
# DO NOT EDIT BELOW !!! #
## ##
##
# SECURITY - do not change.
##
#CSRF_USE_SESSIONS = False
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = (HTTP_PROTOCOL == 'https') # fix to HTTPS when available
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = (HTTP_PROTOCOL == 'https') # fix to HTTPS when available
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', HTTP_PROTOCOL)
##
# Domain setup
##
BASE_URL = HTTP_PROTOCOL + '://' + BASE_SUBDOMAIN + BASE_DOMAIN + ((':' + BASE_PORT) if BASE_PORT != "" else "")
SESSION_COOKIE_DOMAIN= BASE_SUBDOMAIN + BASE_DOMAIN
CSRF_COOKIE_DOMAIN=BASE_SUBDOMAIN + BASE_DOMAIN
CSRF_TRUSTED_ORIGINS = [ SESSION_COOKIE_DOMAIN , ]
ALLOWED_HOSTS = [ BASE_SUBDOMAIN + BASE_DOMAIN, ]
##
# Internationalization, https://docs.djangoproject.com/en/1.11/topics/i18n/
##
LANGUAGE_CODE = 'cs'
TIME_ZONE = 'Europe/Prague'
##
# User model
##
AUTH_USER_MODEL = "nalodeni.AppUser"
##
# Logger config
##
LOG_FILES = os.path.join(BASE_DIR, 'log_files')
LOG_INCOMING_REQUESTS_FILE = os.path.join(
LOG_FILES, 'incoming_requests_%s.log' % BASE_DOMAIN
)
##
# Media files path setup
##
MEDIA_ROOT = os.path.join(BASE_DIR,'media_files')
##
# Debugging settings
##
if DEBUG_LOCAL:
ALLOWED_HOSTS += [
'localhost',
]
CSRF_COOKIE_DOMAIN = "localhost"
SESSION_COOKIE_DOMAIN= CSRF_COOKIE_DOMAIN

208
src/main/settings_global.py Normal file
View File

@ -0,0 +1,208 @@
"""
Django settings for main project.
Generated by 'django-admin startproject' using Django 2.0.2.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""
import os
# Used to update config values _after_ importing settings_local
UPDATE_CONFIGS = []
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '-replace-this-in-install-settings-3243v432'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
DEBUG_LOCAL=False
DEBUG_PROPAGATE_EXCEPTIONS = True
LOG_INCOMING_REQUESTS = False
ALLOWED_HOSTS = []
HTTP_PROTOCOL = 'https'
BASE_DOMAIN = "localhost"
BASE_SUBDOMAIN = ""
BASE_PORT = ""
# Application definition
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#'statici18n', # e.g. for javascript translation
'keycloak_oidc',
'anymail',
'nalodeni',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'keycloak_oidc.middleware.KeycloakSessionRefreshMiddleware', # added
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.gzip.GZipMiddleware',
]
ROOT_URLCONF = 'main.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
#'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.i18n', # for statici18n
],
},
},
]
WSGI_APPLICATION = 'main.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': '',
'USER': '',
'PASSWORD': '',
'HOST': '', # Set to empty string for localhost.
'PORT': '', # Set to empty string for default.
}
}
# Specify in settings
AUTH_USER_MODEL = None
# Other auth backends
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'nalodeni.auth.EmailTokenAuthBackend',
]
# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
# {
# 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
# },
# {
# 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
# },
# {
# 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
# },
]
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
LANGUAGE_CODE = 'cs'
TIME_ZONE = 'Europe/Prague'
USE_I18N = True
USE_L10N = True
USE_TZ = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR,'static_files')
##
# Locale, translation files
##
LOCALE_PATHS = [
BASE_DIR + "/locale",
]
##
# Email setup
##
EMAIL_HOST="localhost"
EMAIL_PORT=25
#DEFAULT_FROM_EMAIL = 'nalodeni@pirati.cz'
ANYMAIL = {
"MAILGUN_API_KEY": "",
"MAILGUN_SENDER_DOMAIN": '',
"MAILGUN_API_URL": 'https://api.eu.mailgun.net/v3',
}
#EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
##
# SingleSignOn
#
# AUTH_SCOPE - list of scopes to request from the auth server
# AUTH_GET_USER_FUNCTION - name of a function that takes the user info dict,
# and returns an user object representing that user
##
AUTH_SERVER = "https://pttest1.kouzelnakrabicka.cz/auth/realms/pirati/"
AUTH_CLIENT_ID = "" # view setting_local.py
AUTH_CLIENT_SECRET = "" # view setting_local.py
AUTH_SCOPE = ['openid',]
AUTH_GET_USER_FUNCTION = 'nalodeni.models:get_user_by_keycloak_email'
AUTH_SSO_LOCALE = 'cs'
AUTH_AVAIL_IDP = []
#LOGIN_REDIRECT_URL = '/sso/login'
#LOGOUT_REDIRECT_URL = '/sso/logout'
##
# Email registration
##
APP_REG_LIMIT_HARD = 50
APP_REG_LIMIT_SOFT = 0
TOKEN_VALID_SEC = 30*60
##
# Defaults for settings_local variables
##

View File

@ -0,0 +1,57 @@
# -*- encoding:utf-8 -*-
"""
Application local settings:
- database
- server connections
"""
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '5k@dbpq_3+t7g+ylt5h1*ox79lnwp-qijry3y60^1r_9q*m5b('
##
# Database setup
##
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql', # Add 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'mainApp', # Or path to database file if using sqlite3.
'USER': 'user', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}
##
# Email setup
##
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
ANYMAIL = {
"MAILGUN_API_KEY": "",
"MAILGUN_SENDER_DOMAIN": '',
"MAILGUN_API_URL": 'https://api.eu.mailgun.net/v3',
"FROM_NAME" : "",
"FROM_EMAIL" : "",
"PUBLIC_TO_EMAIL" : "",
}
##
# Update config array with these values
##
UPDATE_CONFIGS = [
]
UPDATE_CONFIGS = [
#('CACHES', 'default', 'LOCATION', '/var/run/memcached/memcached.sock'),
('CACHES', 'default', 'KEY_PREFIX', 'mainApp:'),
('AUTH_CLIENT_ID','mainApp'),
('AUTH_CLIENT_SECRET',''),
('AUTH_SSO_LOCALE', 'cs'),
('AUTH_AVAIL_IDP',('facebook',)), # tuple
]
LOG_INCOMING_REQUESTS = False
DEBUG_LOCAL=False

View File

@ -0,0 +1,96 @@
<!doctype html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang=""> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang=""> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9" lang=""> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang=""> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<link rel="stylesheet" href="static/css/bootstrap.min.css">
<style>
body {
padding-top: 50px;
padding-bottom: 20px;
}
</style>
<link rel="stylesheet" href="static/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="static/css/main.css">
<script src="static/js/vendor/modernizr-2.8.3-respond-1.4.2.min.js"></script>
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Project name</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<form class="navbar-form navbar-right" role="form">
<div class="form-group">
<input type="text" placeholder="Email" class="form-control">
</div>
<div class="form-group">
<input type="password" placeholder="Password" class="form-control">
</div>
<button type="submit" class="btn btn-success">Sign in</button>
</form>
</div><!--/.navbar-collapse -->
</div>
</nav>
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
<div class="container">
<h1>Hello, world!</h1>
<p>This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique.</p>
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more &raquo;</a></p>
</div>
</div>
<div class="container">
<!-- Example row of columns -->
<div class="row">
<div class="col-md-4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
<p><a class="btn btn-default" href="#" role="button">View details &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
<p><a class="btn btn-default" href="#" role="button">View details &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Heading</h2>
<p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
<p><a class="btn btn-default" href="#" role="button">View details &raquo;</a></p>
</div>
</div>
<hr>
<footer>
<p>&copy; Company 2015</p>
</footer>
</div> <!-- /container -->
<script>window.jQuery || document.write('<script src="static/js/vendor/jquery-1.11.2.min.js"><\/script>')</script>
<script src="static/js/vendor/bootstrap.min.js"></script>
<script src="static/js/main.js"></script>
</body>
</html>

38
src/main/urls.py Normal file
View File

@ -0,0 +1,38 @@
"""main URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path, re_path
import nalodeni.urls
urlpatterns = [
#path('admin/', admin.site.urls),
path('', include(nalodeni.urls)),
# SSO
path('sso/', include('keycloak_oidc.urls')),
]
# Javascript translations
from django.views.i18n import JavaScriptCatalog
urlpatterns += [
path('jsi18n/nalodeni/',
JavaScriptCatalog.as_view(packages=['nalodeni']),
name='js-cat-website'),
]

16
src/main/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
WSGI config for main project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "main.settings")
application = get_wsgi_application()

15
src/manage.py Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "main.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)

0
src/nalodeni/__init__.py Normal file
View File

262
src/nalodeni/auth.py Normal file
View File

@ -0,0 +1,262 @@
# -*- encoding: utf-8 -*-
import hashlib
from datetime import datetime
import logging
from django.contrib.auth import get_user_model
from django.utils.crypto import get_random_string
from django.core.mail import send_mail
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.conf import settings as appSettings
from . import models
# Get an instance of a logger
logger = logging.getLogger(__name__)
# Our user model
USER_MODEL = get_user_model()
class EmailTokenAuthBackend:
"""
Provides authorization via email workflow.
The user is verified by a token previously sent by email.
"""
def get_user(self, user_id):
try:
return USER_MODEL.objects.get(pk=user_id)
except USER_MODEL.DoesNotExist:
return None
def authenticate(self, request, emailToken=None):
if emailToken is None:
return None
# do we have a user with this valid token ?
try:
u = USER_MODEL.objects.get(emailToken=emailToken)
if u.etStamp and (datetime.now() - u.etStamp).total_seconds() < int(appSettings.TOKEN_VALID_SEC):
u.emailToken = None
u.etStamp = None
u.save()
return u
else:
u.emailToken = None
u.etStamp = None
u.save()
raise ValidationError("Přihlašovací odkaz vypršel, nechte si poslat nový.")
except USER_MODEL.DoesNotExist:
pass
# is this a first-time login, so the user has to be created ?
rslt = models.AppRegEmail.objects.filter(emailToken=emailToken)
if len(rslt) == 0:
return None
elif len(rslt) == 1:
reg = rslt[0]
if reg.etStamp and (datetime.now() - reg.etStamp).total_seconds() < int(appSettings.TOKEN_VALID_SEC):
rsltUsers = models.AppUser.objects.filter(username=reg.email)
if len(rsltUsers) != 0 :
raise ValidationError("Uživatelské jméno je již obsazeno.")
rsltUsers = models.AppUser.objects.filter(email=reg.email)
if len(rsltUsers) != 0 :
raise ValidationError("E-mailová adresa je již obsazena.")
# token valid, create the user
u = USER_MODEL()
u.username = reg.email
u.email = reg.email
u.postcode = reg.postcode
u.kind = reg.kind
u.interestedIn = reg.interestedIn
u.userform = reg.userform
u.dc_stamp = datetime.now()
u.save()
# remove the approved email from registration
reg.userform = None
reg.save()
reg.delete()
return u
else:
raise ValidationError("Registrační odkaz vypršel, nechte si poslat nový.")
else:
# multiple tokens, which should not happen
raise Exception("Multiple records with the same token.")
return None
def sendLoginToken(user):
"""
Generate and send a token to the user.
"""
emailToken = get_random_string(120)
user.emailToken = emailToken
user.etStamp = datetime.now()
user.save()
emailSubj = 'Piráti - nalodění - přihlašovací odkaz'
emailBody = """\
Dobrý den,
níže zasíláme přihlašovací odkaz do aplikace Pirátů "Nalodění":
{baseUrl}/prihlaseni/?t={emailToken}
Přihlásíte se kliknutím na odkaz, nebo jeho překopírováním do prohlížeče internetových stránek.
Odkaz je možné použít pouze jednou, v případě potřeby si nechte zaslat nový odkaz.
S pozdravem
Piráti
"""
send_mail(
emailSubj,
emailBody.format(
emailToken=emailToken,
baseUrl=appSettings.BASE_URL),
"nalodeni@pirati.cz",
[user.email], # email to ...
fail_silently=False,
)
def sendRegisterToken(f_email):
"""
Generate and send a token to the registered email.
"""
# Check for user non-existence with this email
f_email = f_email.strip().lower()
rslt = models.AppUser.objects.filter(email__iexact=f_email)
if len(rslt) == 0:
pass
elif len(rslt) == 1:
# send login token instead
return sendLoginToken(rslt[0])
else:
logger.error("More AppUser objects with the same email.")
return
# check registrations
rslt = models.AppRegEmail.objects.filter(email__iexact=f_email)
if len(rslt) == 1:
rt = rslt[0]
elif len(rslt) == 0:
rt = models.AppRegEmail()
rt.email = f_email
else:
logger.error("More AppRegEmail objects with the same email.")
return
sendRegisterTokenReg(rt)
def sendRegisterTokenReg(rt):
""" Send registration email to an already existing AppRegEmail instance 'rt'. """
# create token
emailToken = get_random_string(120)
rt.emailToken = emailToken
rt.etStamp = datetime.now()
rt.save()
emailSubj = 'Piráti - nalodění - registrační odkaz'
emailBody = """\
Dobrý den,
níže Vám zasíláme registrační odkaz do aplikace Pirátů "Nalodění":
{baseUrl}/prihlaseni/?t={emailToken}
V registraci pokračujte kliknutím na odkaz, nebo jeho překopírováním do prohlížeče internetových stránek.
Odkaz je možné použít pouze jednou, v případě potřeby si nechte zaslat nový odkaz.
S pozdravem
Piráti
"""
send_mail(
emailSubj,
emailBody.format(
emailToken=emailToken,
baseUrl=appSettings.BASE_URL),
"nalodeni@pirati.cz",
[rt.email], # email to ...
fail_silently=False,
)
def sendEmailContactVerificationToken(user):
""" Send validation email to user.email_contact. """
# The wanted new value, save it here so we do not accidentally change it,
# or someone else through the user-object.
ecw = user.email_contact
if ecw is None or ecw == "":
raise ValidationError("Chybí kontaktní email.")
# ValidationError se odchytava vyse
validate_email(ecw)
# create token
rand_str = get_random_string(120)
user.email_contact_token = "%s-%s" % ( int(datetime.now().timestamp()), rand_str )
user.email_contact_verified = False
user.save()
sha256hash = hashlib.sha256()
sha256hash.update(rand_str.encode('utf-8'))
sha256hash.update(ecw.encode('utf-8'))
emailToken = sha256hash.hexdigest()
emailSubj = 'Piráti - nalodění - ověření kontaktního emailu'
emailBody = """\
Dobrý den,
níže Vám zasíláme odkaz pro ověření platnosti kontaktní e-mailové adresy.
Změna adresy na {email_contact} byla vyžádána v aplikace Pirátů "Nalodění".
Adresu ověříte přihlášením se do aplikace Nalodění
(https://nalodeni.pirati.cz/prihlaseni/) a následným kliknutím na odkaz:
{baseUrl}/ja-pirat/profil/?t={emailToken}
Pokud jste tuto změnu nežádali, můžete tuto zprávu smazat.
S pozdravem
Piráti
"""
send_mail(
emailSubj,
emailBody.format(
email_contact = ecw,
emailToken=emailToken,
baseUrl=appSettings.BASE_URL),
"nalodeni@pirati.cz",
[ecw], # email to ...
fail_silently=False,
)
return True
#TODO:: osetreni transakci (pro pripad utoku typu DDoS)

View File

@ -0,0 +1,122 @@
[
{
"model": "nalodeni.interestregion",
"pk": 1,
"fields": {
"name": "Celostátní",
"tag": "news-republic"
}
},
{
"model": "nalodeni.interestregion",
"pk": 3,
"fields": {
"name": "Hlavní město Praha",
"tag": "news-region-pha"
}
},
{
"model": "nalodeni.interestregion",
"pk": 4,
"fields": {
"name": "Jihočeský kraj",
"tag": "news-region-jhc"
}
},
{
"model": "nalodeni.interestregion",
"pk": 5,
"fields": {
"name": "Jihomoravský kraj",
"tag": "news-region-jhm"
}
},
{
"model": "nalodeni.interestregion",
"pk": 6,
"fields": {
"name": "Karlovarský kraj",
"tag": "news-region-kvk"
}
},
{
"model": "nalodeni.interestregion",
"pk": 7,
"fields": {
"name": "Kraj Vysočina",
"tag": "news-region-vys"
}
},
{
"model": "nalodeni.interestregion",
"pk": 8,
"fields": {
"name": "Královéhradecký kraj",
"tag": "news-region-khk"
}
},
{
"model": "nalodeni.interestregion",
"pk": 9,
"fields": {
"name": "Liberecký kraj",
"tag": "news-region-lbk"
}
},
{
"model": "nalodeni.interestregion",
"pk": 10,
"fields": {
"name": "Moravskoslezský kraj",
"tag": "news-region-msk"
}
},
{
"model": "nalodeni.interestregion",
"pk": 11,
"fields": {
"name": "Olomoucký kraj",
"tag": "news-region-olk"
}
},
{
"model": "nalodeni.interestregion",
"pk": 12,
"fields": {
"name": "Pardubický kraj",
"tag": "news-region-pak"
}
},
{
"model": "nalodeni.interestregion",
"pk": 13,
"fields": {
"name": "Plzeňský kraj",
"tag": "news-region-plk"
}
},
{
"model": "nalodeni.interestregion",
"pk": 14,
"fields": {
"name": "Středočeský kraj",
"tag": "news-region-stc"
}
},
{
"model": "nalodeni.interestregion",
"pk": 15,
"fields": {
"name": "Ústecký kraj",
"tag": "news-region-ulk"
}
},
{
"model": "nalodeni.interestregion",
"pk": 16,
"fields": {
"name": "Zlínský kraj",
"tag": "news-region-zlk"
}
}
]

View File

@ -0,0 +1,75 @@
- model: nalodeni.interestregion
pk: 1
fields:
name: Celostátní
tag: news-republic
- model: nalodeni.interestregion
pk: 3
fields:
name: Hlavní město Praha
tag: news-region-pha
- model: nalodeni.interestregion
pk: 4
fields:
name: Jihočeský kraj
tag: news-region-jhc
- model: nalodeni.interestregion
pk: 5
fields:
name: Jihomoravský kraj
tag: news-region-jhm
- model: nalodeni.interestregion
pk: 6
fields:
name: Karlovarský kraj
tag: news-region-kvk
- model: nalodeni.interestregion
pk: 7
fields:
name: Kraj Vysočina
tag: news-region-vys
- model: nalodeni.interestregion
pk: 8
fields:
name: Královéhradecký kraj
tag: news-region-khk
- model: nalodeni.interestregion
pk: 9
fields:
name: Liberecký kraj
tag: news-region-lbk
- model: nalodeni.interestregion
pk: 10
fields:
name: Moravskoslezský kraj
tag: news-region-msk
- model: nalodeni.interestregion
pk: 11
fields:
name: Olomoucký kraj
tag: news-region-olk
- model: nalodeni.interestregion
pk: 12
fields:
name: Pardubický kraj
tag: news-region-pak
- model: nalodeni.interestregion
pk: 13
fields:
name: Plzeňský kraj
tag: news-region-plk
- model: nalodeni.interestregion
pk: 14
fields:
name: Středočeský kraj
tag: news-region-stc
- model: nalodeni.interestregion
pk: 15
fields:
name: Ústecký kraj
tag: news-region-ulk
- model: nalodeni.interestregion
pk: 16
fields:
name: Zlínský kraj
tag: news-region-zlk

View File

@ -0,0 +1,114 @@
[
{
"model": "nalodeni.userskill",
"pk": 1,
"fields": {
"name": "Roznášení Pirátských listů",
"tag": "akt_roznos-listu"
}
},
{
"model": "nalodeni.userskill",
"pk": 2,
"fields": {
"name": "Hlídání dětí",
"tag": "akt_hlidani-deti"
}
},
{
"model": "nalodeni.userskill",
"pk": 3,
"fields": {
"name": "Zajištění občerstvení",
"tag": "akt_catering"
}
},
{
"model": "nalodeni.userskill",
"pk": 4,
"fields": {
"name": "Řidič (možnost převozu něčeho někam)",
"tag": "akt_ridic"
}
},
{
"model": "nalodeni.userskill",
"pk": 5,
"fields": {
"name": "Psaní článků a tiskových zpráv",
"tag": "odb_psavec"
}
},
{
"model": "nalodeni.userskill",
"pk": 6,
"fields": {
"name": "Fotografické práce",
"tag": "odb_foto"
}
},
{
"model": "nalodeni.userskill",
"pk": 7,
"fields": {
"name": "Grafické práce",
"tag": "odb_grafik"
}
},
{
"model": "nalodeni.userskill",
"pk": 8,
"fields": {
"name": "Připomínkování legislativních návrhů",
"tag": "odb_pripominkovani"
}
},
{
"model": "nalodeni.userskill",
"pk": 9,
"fields": {
"name": "Hudební produkce (mám kapelu, jsem DJ, ... )",
"tag": "akt_hudebni"
}
},
{
"model": "nalodeni.userskill",
"pk": 10,
"fields": {
"name": "Programování, IT skills",
"tag": "odb_it"
}
},
{
"model": "nalodeni.userskill",
"pk": 11,
"fields": {
"name": "Mám prostor pro banner",
"tag": "akt_banner"
}
},
{
"model": "nalodeni.userskill",
"pk": 12,
"fields": {
"name": "Mám prostor pro vlajku",
"tag": "akt_vlajka"
}
},
{
"model": "nalodeni.userskill",
"pk": 13,
"fields": {
"name": "Mám možnost umístit reklamu na auto",
"tag": "akt_autopolep"
}
},
{
"model": "nalodeni.userskill",
"pk": 14,
"fields": {
"name": "Pomoc s administrativou",
"tag": "odb_administrativa"
}
}
]

View File

@ -0,0 +1,70 @@
- model: nalodeni.userskill
pk: 1
fields:
name: Roznášení Pirátských listů
tag: akt_roznos-listu
- model: nalodeni.userskill
pk: 2
fields:
name: Hlídání dětí
tag: akt_hlidani-deti
- model: nalodeni.userskill
pk: 3
fields:
name: Zajištění občerstvení
tag: akt_catering
- model: nalodeni.userskill
pk: 4
fields:
name: Řidič (možnost převozu něčeho někam)
tag: akt_ridic
- model: nalodeni.userskill
pk: 5
fields:
name: Psaní článků a tiskových zpráv
tag: odb_psavec
- model: nalodeni.userskill
pk: 6
fields:
name: Fotografické práce
tag: odb_foto
- model: nalodeni.userskill
pk: 7
fields:
name: Grafické práce
tag: odb_grafik
- model: nalodeni.userskill
pk: 8
fields:
name: Připomínkování legislativních návrhů
tag: odb_pripominkovani
- model: nalodeni.userskill
pk: 9
fields:
name: Hudební produkce (mám kapelu, jsem DJ, ... )
tag: akt_hudebni
- model: nalodeni.userskill
pk: 10
fields:
name: Programování, IT skills
tag: odb_it
- model: nalodeni.userskill
pk: 11
fields:
name: Mám prostor pro banner
tag: akt_banner
- model: nalodeni.userskill
pk: 12
fields:
name: Mám prostor pro vlajku
tag: akt_vlajka
- model: nalodeni.userskill
pk: 13
fields:
name: Mám možnost umístit reklamu na auto
tag: akt_autopolep
- model: nalodeni.userskill
pk: 14
fields:
name: Pomoc s administrativou
tag: odb_administrativa

View File

@ -0,0 +1,170 @@
[
{
"model": "nalodeni.usertopic",
"pk": 1,
"fields": {
"name": "Evropská unie",
"tag": "eu"
}
},
{
"model": "nalodeni.usertopic",
"pk": 2,
"fields": {
"name": "Obrana",
"tag": "obrana"
}
},
{
"model": "nalodeni.usertopic",
"pk": 3,
"fields": {
"name": "Informatika",
"tag": "informatika"
}
},
{
"model": "nalodeni.usertopic",
"pk": 4,
"fields": {
"name": "Místní rozvoj",
"tag": "rozvoj"
}
},
{
"model": "nalodeni.usertopic",
"pk": 5,
"fields": {
"name": "Práce a sociální věci",
"tag": "pracesoc"
}
},
{
"model": "nalodeni.usertopic",
"pk": 6,
"fields": {
"name": "Doprava a logistika",
"tag": "doprava"
}
},
{
"model": "nalodeni.usertopic",
"pk": 7,
"fields": {
"name": "Finance",
"tag": "finance"
}
},
{
"model": "nalodeni.usertopic",
"pk": 8,
"fields": {
"name": "Kultura",
"tag": "kultura"
}
},
{
"model": "nalodeni.usertopic",
"pk": 9,
"fields": {
"name": "Průmysl",
"tag": "prumysl"
}
},
{
"model": "nalodeni.usertopic",
"pk": 10,
"fields": {
"name": "Obchod",
"tag": "obchod"
}
},
{
"model": "nalodeni.usertopic",
"pk": 11,
"fields": {
"name": "Vnitro",
"tag": "vnitro"
}
},
{
"model": "nalodeni.usertopic",
"pk": 12,
"fields": {
"name": "Otevřený stát",
"tag": "otevrstat"
}
},
{
"model": "nalodeni.usertopic",
"pk": 13,
"fields": {
"name": "Zdravotnictví",
"tag": "zdravotnictvi"
}
},
{
"model": "nalodeni.usertopic",
"pk": 14,
"fields": {
"name": "Zemědělství",
"tag": "zemedelstvi"
}
},
{
"model": "nalodeni.usertopic",
"pk": 15,
"fields": {
"name": "Životní prostředí",
"tag": "zivprostredi"
}
},
{
"model": "nalodeni.usertopic",
"pk": 16,
"fields": {
"name": "Školství",
"tag": "skolstvi"
}
},
{
"model": "nalodeni.usertopic",
"pk": 17,
"fields": {
"name": "Spravedlnost a justice",
"tag": "spravedlnost"
}
},
{
"model": "nalodeni.usertopic",
"pk": 18,
"fields": {
"name": "Legalizace",
"tag": "legalizace"
}
},
{
"model": "nalodeni.usertopic",
"pk": 19,
"fields": {
"name": "Kontrola moci a mocných",
"tag": "kontrola"
}
},
{
"model": "nalodeni.usertopic",
"pk": 20,
"fields": {
"name": "Exekuce",
"tag": "exekuce"
}
},
{
"model": "nalodeni.usertopic",
"pk": 21,
"fields": {
"name": "Demokracie",
"tag": "demokracie"
}
}
]

View File

@ -0,0 +1,108 @@
#
# Vim skript pro generování položky z formátu "tag[mezera]name":
#
- model: nalodeni.usertopic
pk: 1
fields:
name: Evropská unie
tag: eu
- model: nalodeni.usertopic
pk: 2
fields:
name: Obrana
tag: obrana
- model: nalodeni.usertopic
pk: 3
fields:
name: Informatika
tag: informatika
- model: nalodeni.usertopic
pk: 4
fields:
name: Místní rozvoj
tag: rozvoj
- model: nalodeni.usertopic
pk: 5
fields:
name: Práce a sociální věci
tag: pracesoc
- model: nalodeni.usertopic
pk: 6
fields:
name: Doprava a logistika
tag: doprava
- model: nalodeni.usertopic
pk: 7
fields:
name: Finance
tag: finance
- model: nalodeni.usertopic
pk: 8
fields:
name: Kultura
tag: kultura
- model: nalodeni.usertopic
pk: 9
fields:
name: Průmysl
tag: prumysl
- model: nalodeni.usertopic
pk: 10
fields:
name: Obchod
tag: obchod
- model: nalodeni.usertopic
pk: 11
fields:
name: Vnitro
tag: vnitro
- model: nalodeni.usertopic
pk: 12
fields:
name: Otevřený stát
tag: otevrstat
- model: nalodeni.usertopic
pk: 13
fields:
name: Zdravotnictví
tag: zdravotnictvi
- model: nalodeni.usertopic
pk: 14
fields:
name: Zemědělství
tag: zemedelstvi
- model: nalodeni.usertopic
pk: 15
fields:
name: Životní prostředí
tag: zivprostredi
- model: nalodeni.usertopic
pk: 16
fields:
name: Školství
tag: skolstvi
- model: nalodeni.usertopic
pk: 17
fields:
name: Spravedlnost a justice
tag: spravedlnost
- model: nalodeni.usertopic
pk: 18
fields:
name: Legalizace
tag: legalizace
- model: nalodeni.usertopic
pk: 19
fields:
name: Kontrola moci a mocných
tag: kontrola
- model: nalodeni.usertopic
pk: 20
fields:
name: Exekuce
tag: exekuce
- model: nalodeni.usertopic
pk: 21
fields:
name: Demokracie
tag: demokracie

203
src/nalodeni/forms.py Normal file
View File

@ -0,0 +1,203 @@
# -*- encoding: utf-8 -*-
from django import forms
from django.forms import ModelForm
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from . import models
class AppUserForm(ModelForm):
def __init__(self, *args, **kwargs):
super(AppUserForm, self).__init__(*args, **kwargs)
self.fields['email'].disabled = True
self.fields['email_contact_active'].disabled = True
self.fields['dc_stamp'].disabled = True
class Meta:
model = models.AppUser
fields = ['first_name','last_name', 'city', 'postcode', 'district', 'kind',
'email', 'email_contact', 'email_contact_active', 'dc_stamp']
def clean_postcode(self):
data = self.cleaned_data['postcode']
if data is not None:
if data < 10000 or data > 99999:
raise ValidationError(_('PSČ musí být číslo mezi 10000 a 99999.'), code='invalid')
return data
def clean_first_name(self):
data = self.cleaned_data['first_name']
if data is not None:
if len(data) > 30:
raise ValidationError(_('Jméno může mít maximálně 30 znaků.'), code='invalid')
return data
def clean_last_name(self):
data = self.cleaned_data['last_name']
if data is not None:
if len(data) > 30:
raise ValidationError(_('Příjmení může mít maximálně 30 znaků.'), code='invalid')
return data
def clean_city(self):
data = self.cleaned_data['city']
if data is not None:
if len(data) > 50:
raise ValidationError(_('Město může mít maximálně 50 znaků.'), code='invalid')
return data
class AppUserSsoForm(ModelForm):
def __init__(self, *args, **kwargs):
super(AppUserSsoForm, self).__init__(*args, **kwargs)
self.fields['email'].disabled = True
self.fields['email_contact_active'].disabled = True
self.fields['dc_stamp'].disabled = True
class Meta:
model = models.AppUser
fields = ['city', 'postcode', 'district', 'kind',
'email', 'email_contact', 'email_contact_active', 'dc_stamp']
class AppRegEmailForm(ModelForm):
class Meta:
model = models.AppRegEmail
fields = ['email', 'postcode', 'kind', 'interestedIn', 'data_consent']
def clean_postcode(self):
data = self.cleaned_data['postcode']
if data is not None:
if data < 10000 or data > 99999:
raise ValidationError(_('PSČ musí být číslo mezi 10000 a 99999.'), code='invalid')
return data
def clean_data_consent(self):
data = self.cleaned_data['data_consent']
if data is None or data == False:
raise ValidationError(_('Prosím, potvrďte souhlas se zpracováním osobních údajů.'), code='invalid')
return data
def clean_kind(self):
data = self.cleaned_data['kind']
if data is None:
raise ValidationError(_('Prosím vyberte, o co máte zájem.'), code='invalid')
return data
class AppRegFollowEmailForm(ModelForm):
class Meta:
model = models.AppRegEmail
fields = ['email', 'data_consent']
def clean_data_consent(self):
data = self.cleaned_data['data_consent']
if data is None or data == False:
raise ValidationError(_('Prosím, potvrďte souhlas se zpracováním osobních údajů.'), code='invalid')
return data
class UserFormForm(ModelForm):
def __init__(self, *args, **kwargs):
super(UserFormForm, self).__init__(*args, **kwargs)
self.fields['skills'] = forms.ModelMultipleChoiceField(
label=_("Moje dovednosti (dokážu pomoci s)"),
widget=forms.CheckboxSelectMultiple,
queryset=models.UserSkill.objects.all(),
required=False)
self.fields['topics'] = forms.ModelMultipleChoiceField(
label=_("Zájmová témata"),
widget=forms.CheckboxSelectMultiple,
queryset=models.UserTopic.objects.all(),
required=False)
self.fields['regions'] = forms.ModelMultipleChoiceField(
label=_("Zájmové regiony"),
widget=forms.CheckboxSelectMultiple,
queryset=models.InterestRegion.objects.all(),
required=False)
class Meta:
model = models.UserForm
fields = ['skills','skills_note', 'topics','regions']
class UserFollowFormForm(ModelForm):
def __init__(self, *args, **kwargs):
super(UserFollowFormForm, self).__init__(*args, **kwargs)
self.fields['topics'] = forms.ModelMultipleChoiceField(
label=_("Zájmová témata"),
widget=forms.CheckboxSelectMultiple,
queryset=models.UserTopic.objects.all(),
required=False)
self.fields['regions'] = forms.ModelMultipleChoiceField(
label=_("Novinky zasílané e-mailem"),
widget=forms.CheckboxSelectMultiple,
queryset=models.InterestRegion.objects.all(),
required=False)
class Meta:
model = models.UserForm
fields = ['topics','regions']
class EmailVizitkaForm(forms.Form):
name = forms.CharField(label=_('Jméno'), max_length=50)
email = forms.CharField(label=_('E-mail'), max_length=50, required=False)
nazev_funkce = forms.CharField(label=_('Název funkce'), max_length=50, required=False)
soc_fb = forms.CharField(label=_('Facebook odkaz'), max_length=50, required=False)
soc_in = forms.CharField(label=_('LinkedIn odkaz'), max_length=50, required=False)
phone = forms.CharField(label=_('Telefon'), max_length=50, required=False)
##
# Newsletters
##
class NewsletterForm(ModelForm):
#def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
class Meta:
model = models.Newsletter
exclude = []
class NewsMsgForm(ModelForm):
#def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
class Meta:
model = models.NewsMsg
fields = ['title', 'headerText', 'footerText',
'delivery_ts', 'sent_ts',
'testMailRecipients']
class NewsMsgBlockForm(ModelForm):
#def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
class Meta:
model = models.NewsMsgBlock
exclude = ['newsmsg']
class Euro2019InterestForm(ModelForm):
class Meta:
model = models.Euro2019Interest
exclude = ['createdStamp', 'dc_stamp']
def clean_postcode(self):
data = self.cleaned_data['postcode']
if data is not None:
if data < 10000 or data > 99999:
raise ValidationError(_('PSČ musí být číslo mezi 10000 a 99999.'), code='invalid')
return data
def clean_data_consent(self):
data = self.cleaned_data['data_consent']
if data is None or data == False:
raise ValidationError(_('Prosím, potvrďte souhlas se zpracováním osobních údajů.'), code='invalid')
return data

View File

@ -0,0 +1,76 @@
# Generated by Django 2.0.2 on 2018-02-28 12:12
from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]
operations = [
migrations.CreateModel(
name='AppUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('ssoUid', models.CharField(blank=True, default=None, max_length=40, null=True, verbose_name='Keycloak SSO user ID')),
('postcode', models.IntegerField(blank=True, default='', null=True, verbose_name='PSČ')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'verbose_name_plural': 'AppUsers',
'verbose_name': 'AppUser',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='AppSetting',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.CharField(max_length=64, verbose_name='Key')),
('value', models.CharField(max_length=128, verbose_name='Value')),
],
),
migrations.CreateModel(
name='UserSetting',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('key', models.CharField(max_length=64, verbose_name='Key')),
('value', models.CharField(max_length=128, null=True, verbose_name='Value')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
),
migrations.AlterUniqueTogether(
name='appsetting',
unique_together={('key',)},
),
migrations.AlterUniqueTogether(
name='usersetting',
unique_together={('user', 'key')},
),
migrations.AlterUniqueTogether(
name='appuser',
unique_together={('email',)},
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.0.2 on 2018-02-28 16:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='appuser',
name='postcode',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-02-28 16:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0002_remove_appuser_postcode'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='district',
field=models.IntegerField(blank=True, choices=[(0, 'Hlavní město Praha'), (1, 'Jihočeský kraj'), (2, 'Jihomoravský kraj'), (3, 'Karlovarský kraj'), (4, 'Kraj Vysočina'), (5, 'Královéhradecký kraj'), (6, 'Liberecký kraj'), (7, 'Moravskoslezský kraj'), (8, 'Olomoucký kraj'), (9, 'Pardubický kraj'), (10, 'Plzeňský kraj'), (11, 'Středočeský kraj'), (12, 'Ústecký kraj'), (13, 'Zlínský kraj')], null=True, verbose_name='Kraj'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-02-28 16:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0003_appuser_district'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='postcode',
field=models.IntegerField(blank=True, null=True, verbose_name='PSČ'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-03-12 01:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0004_appuser_postcode'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='emailToken',
field=models.CharField(blank=True, default=None, max_length=120, null=True, verbose_name='Email login token'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.0.3 on 2018-03-12 01:12
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0005_appuser_emailtoken'),
]
operations = [
migrations.AlterUniqueTogether(
name='appuser',
unique_together={('emailToken',), ('email',)},
),
]

View File

@ -0,0 +1,35 @@
# Generated by Django 2.0.3 on 2018-03-12 02:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0006_auto_20180312_0112'),
]
operations = [
migrations.CreateModel(
name='AppRegEmail',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='email')),
('token', models.CharField(blank=True, default=None, max_length=120, null=True, verbose_name='Registration token')),
('rtts', models.DateTimeField(blank=True, default=None, editable=False, null=True, verbose_name='Registration token timestamp')),
],
options={
'verbose_name_plural': 'AppRegEmails',
'verbose_name': 'AppRegEmail',
},
),
migrations.AddField(
model_name='appuser',
name='etDate',
field=models.DateTimeField(blank=True, default=None, editable=False, null=True, verbose_name='Email token timestamp'),
),
migrations.AlterUniqueTogether(
name='appregemail',
unique_together={('token',)},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-03-12 02:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0007_auto_20180312_0213'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='city',
field=models.CharField(blank=True, default=None, max_length=120, null=True, verbose_name='Město'),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 2.0.3 on 2018-03-23 11:45
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0008_appuser_city'),
]
operations = [
migrations.RenameField(
model_name='appregemail',
old_name='rtts',
new_name='timestamp',
),
migrations.AlterUniqueTogether(
name='appregemail',
unique_together={('token',), ('email',)},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-03-23 12:14
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0009_auto_20180323_1145'),
]
operations = [
migrations.RenameField(
model_name='appuser',
old_name='etDate',
new_name='etStamp',
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 2.0.3 on 2018-03-23 13:12
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0010_auto_20180323_1214'),
]
operations = [
migrations.RenameField(
model_name='appregemail',
old_name='token',
new_name='emailToken',
),
migrations.RenameField(
model_name='appregemail',
old_name='timestamp',
new_name='etStamp',
),
migrations.AlterUniqueTogether(
name='appregemail',
unique_together={('email',), ('emailToken',)},
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.0.3 on 2018-03-25 18:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0011_auto_20180323_1312'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='kind',
field=models.IntegerField(choices=[(0, 'zájemce o novinky'), (1, 'příznivec'), (2, 'zájemce o členství'), (3, 'člen')], default=0, verbose_name='Stav'),
),
migrations.AddField(
model_name='appuser',
name='status',
field=models.IntegerField(choices=[(0, 'nový'), (1, 'ok')], default=0, verbose_name='Stav'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 2.0.3 on 2018-03-25 21:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0012_auto_20180325_1851'),
]
operations = [
migrations.AlterField(
model_name='appuser',
name='district',
field=models.IntegerField(choices=[(0, 'Hlavní město Praha'), (1, 'Jihočeský kraj'), (2, 'Jihomoravský kraj'), (3, 'Karlovarský kraj'), (4, 'Kraj Vysočina'), (5, 'Královéhradecký kraj'), (6, 'Liberecký kraj'), (7, 'Moravskoslezský kraj'), (8, 'Olomoucký kraj'), (9, 'Pardubický kraj'), (10, 'Plzeňský kraj'), (11, 'Středočeský kraj'), (12, 'Ústecký kraj'), (13, 'Zlínský kraj')], default=0, verbose_name='Kraj'),
),
migrations.AlterField(
model_name='appuser',
name='kind',
field=models.IntegerField(choices=[(0, 'dostávat novinky'), (1, 'stát se příznivcem'), (2, 'stát se členem'), (3, 'již jsem člen')], default=0, verbose_name='Chci'),
),
migrations.AlterField(
model_name='appuser',
name='status',
field=models.IntegerField(choices=[(0, 'nový'), (1, 'registrovaný')], default=1, verbose_name='Stav'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-03-25 21:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0013_auto_20180325_2109'),
]
operations = [
migrations.AlterField(
model_name='appuser',
name='postcode',
field=models.IntegerField(default=-1, verbose_name='PSČ'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.0.3 on 2018-03-25 23:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0014_auto_20180325_2110'),
]
operations = [
migrations.AlterField(
model_name='appuser',
name='postcode',
field=models.IntegerField(null=True, verbose_name='PSČ'),
),
migrations.AlterField(
model_name='appuser',
name='status',
field=models.IntegerField(choices=[(0, 'nový'), (1, 'registrovaný')], default=0, verbose_name='Stav'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-03-26 09:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0015_auto_20180325_2326'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='interestedIn',
field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Dovednosti (chci pomoci s)'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 2.0.3 on 2018-04-10 11:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0016_appuser_interestedin'),
]
operations = [
migrations.AddField(
model_name='appregemail',
name='interestedIn',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Poznámka (info pro koordinátora)'),
),
migrations.AddField(
model_name='appregemail',
name='kind',
field=models.IntegerField(choices=[(0, 'dostávat novinky'), (1, 'stát se příznivcem'), (2, 'stát se členem'), (3, 'již jsem člen'), (3, 'již pirátím jinak')], default=0, verbose_name='Chci'),
),
migrations.AlterField(
model_name='appuser',
name='kind',
field=models.IntegerField(choices=[(0, 'dostávat novinky'), (1, 'stát se příznivcem'), (2, 'stát se členem'), (3, 'již jsem člen'), (3, 'již pirátím jinak')], default=0, verbose_name='Chci'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.0.3 on 2018-04-10 15:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0017_auto_20180410_1144'),
]
operations = [
migrations.AddField(
model_name='appregemail',
name='postcode',
field=models.IntegerField(null=True, verbose_name='PSČ'),
),
migrations.AlterField(
model_name='appregemail',
name='kind',
field=models.IntegerField(blank=True, choices=[(0, 'dostávat novinky'), (1, 'stát se příznivcem'), (2, 'stát se členem'), (3, 'již jsem člen'), (3, 'již pirátím jinak')], default=0, null=True, verbose_name='Chci'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 2.0.3 on 2018-06-17 11:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0018_auto_20180410_1547'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='email_contact',
field=models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='Kontaktní email'),
),
migrations.AlterField(
model_name='appregemail',
name='postcode',
field=models.IntegerField(null=True, verbose_name='PSČ (kvůli dělení do krajů)'),
),
migrations.AlterField(
model_name='appuser',
name='interestedIn',
field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Moje dovednosti (chci pomoci s)'),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 2.0.3 on 2018-06-17 11:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0019_auto_20180617_1104'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='email_contact_active',
field=models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='Kontaktní e-mail aktivní'),
),
migrations.AlterField(
model_name='appregemail',
name='kind',
field=models.IntegerField(blank=True, choices=[(0, 'dostávat novinky'), (1, 'se stát příznivcem'), (2, 'se stát členem'), (3, 'již jsem člen'), (3, 'již pirátím jinak')], default=0, null=True, verbose_name='Chci'),
),
migrations.AlterField(
model_name='appuser',
name='email_contact',
field=models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='Kontaktní e-mail'),
),
migrations.AlterField(
model_name='appuser',
name='kind',
field=models.IntegerField(choices=[(0, 'dostávat novinky'), (1, 'se stát příznivcem'), (2, 'se stát členem'), (3, 'již jsem člen'), (3, 'již pirátím jinak')], default=0, verbose_name='Chci'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 2.0.3 on 2018-06-17 13:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0020_auto_20180617_1146'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='email_contact_verified',
field=models.BooleanField(default=False, verbose_name='Kontaktní e-mail byl ověřen.'),
),
migrations.AlterField(
model_name='appuser',
name='email_contact',
field=models.EmailField(blank=True, default='', max_length=100, null=True, verbose_name='Kontaktní e-mail'),
),
migrations.AlterField(
model_name='appuser',
name='email_contact_active',
field=models.EmailField(blank=True, default='', max_length=100, null=True, verbose_name='Kontaktní e-mail aktivní'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-06-17 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0021_auto_20180617_1310'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='email_contact_token',
field=models.CharField(blank=True, default=None, max_length=150, null=True, verbose_name='Ověřovací token pro kontaktní email.'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.0.3 on 2018-06-18 07:49
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0022_appuser_email_contact_token'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='ts_for_ldap_sync',
field=models.DateTimeField(blank=True, default=datetime.datetime.now, null=True, verbose_name='Timestamp pro LDAP synchronizaci'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-06-25 01:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0023_appuser_ts_for_ldap_sync'),
]
operations = [
migrations.AddField(
model_name='appregemail',
name='data_consent',
field=models.BooleanField(default=False, verbose_name='Souhlasím se zpracováním os. údajů.'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-06-25 01:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0024_appregemail_data_consent'),
]
operations = [
migrations.AddField(
model_name='appregemail',
name='dc_stamp',
field=models.DateTimeField(blank=True, default=None, editable=False, null=True, verbose_name='Data consent timestamp'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-06-25 01:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0025_appregemail_dc_stamp'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='dc_stamp',
field=models.DateTimeField(blank=True, default=None, editable=False, null=True, verbose_name='Data consent timestamp'),
),
]

View File

@ -0,0 +1,75 @@
# Generated by Django 2.0.3 on 2018-06-25 03:14
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0026_appuser_dc_stamp'),
]
operations = [
migrations.CreateModel(
name='Newsletter',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Název')),
('tag', models.CharField(blank=True, max_length=20, null=True, verbose_name='Značka (tag) newsletteru')),
],
),
migrations.CreateModel(
name='UserForm',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('newsletters', models.ManyToManyField(blank=True, to='nalodeni.Newsletter')),
],
),
migrations.CreateModel(
name='UserSkill',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Název')),
('tag', models.CharField(blank=True, max_length=20, null=True, verbose_name='Značka (tag) dovednosti')),
],
),
migrations.CreateModel(
name='UserTopic',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Název')),
('tag', models.CharField(blank=True, max_length=20, null=True, verbose_name='Značka (tag) tématu')),
],
),
migrations.AlterField(
model_name='appregemail',
name='data_consent',
field=models.BooleanField(default=False, verbose_name='Souhlasím se zpracováním osobních údajů.'),
),
migrations.AlterField(
model_name='appuser',
name='dc_stamp',
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Data consent timestamp'),
),
migrations.AddField(
model_name='userform',
name='skills',
field=models.ManyToManyField(blank=True, to='nalodeni.UserSkill'),
),
migrations.AddField(
model_name='userform',
name='topics',
field=models.ManyToManyField(blank=True, to='nalodeni.UserTopic'),
),
migrations.AddField(
model_name='appregemail',
name='userform',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='nalodeni.UserForm', verbose_name='dotazník'),
),
migrations.AddField(
model_name='appuser',
name='userform',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='nalodeni.UserForm', verbose_name='dotazník'),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 2.0.3 on 2018-07-07 15:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0027_auto_20180625_0314'),
]
operations = [
migrations.AlterField(
model_name='userform',
name='newsletters',
field=models.ManyToManyField(blank=True, to='nalodeni.Newsletter', verbose_name='Zasílané novinky emailem'),
),
migrations.AlterField(
model_name='userform',
name='skills',
field=models.ManyToManyField(blank=True, to='nalodeni.UserSkill', verbose_name='Dovednosti'),
),
migrations.AlterField(
model_name='userform',
name='topics',
field=models.ManyToManyField(blank=True, to='nalodeni.UserTopic', verbose_name='Zájmová témata'),
),
migrations.AlterUniqueTogether(
name='appuser',
unique_together={('email',), ('emailToken',), ('username',)},
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.0.3 on 2018-07-07 15:36
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0028_auto_20180707_1508'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='createdStamp',
field=models.DateTimeField(default=datetime.datetime.now, editable=False, verbose_name='Uživatel vytvořen'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.0.3 on 2018-07-07 15:41
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0029_appuser_createdstamp'),
]
operations = [
migrations.AddField(
model_name='appregemail',
name='createdStamp',
field=models.DateTimeField(default=datetime.datetime.now, editable=False, verbose_name='Uživatel vytvořen'),
),
]

View File

@ -0,0 +1,60 @@
# Generated by Django 2.0.3 on 2018-08-15 06:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0030_appregemail_createdstamp'),
]
operations = [
migrations.AlterModelOptions(
name='newsletter',
options={'ordering': ['name']},
),
migrations.AlterModelOptions(
name='userskill',
options={'ordering': ['tag']},
),
migrations.AlterModelOptions(
name='usertopic',
options={'ordering': ['name']},
),
migrations.AddField(
model_name='userform',
name='skills_note',
field=models.CharField(blank=True, default=None, max_length=250, null=True, verbose_name='Poznámka k dovednostem'),
),
migrations.AlterField(
model_name='appregemail',
name='email',
field=models.CharField(blank=True, default='', max_length=100, null=True, verbose_name='e-mail'),
),
migrations.AlterField(
model_name='appregemail',
name='kind',
field=models.IntegerField(blank=True, choices=[(0, 'dostávat novinky'), (2, 'pomáhat jako dobrovolník'), (2, 'pomáhat jako expert'), (1, 'se stát příznivcem'), (2, 'se stát členem'), (3, 'již jsem člen'), (3, 'již pirátím jinak')], default=0, null=True, verbose_name='Chci'),
),
migrations.AlterField(
model_name='appuser',
name='emailToken',
field=models.CharField(blank=True, default=None, max_length=120, null=True, verbose_name='E-mail login token'),
),
migrations.AlterField(
model_name='appuser',
name='email_contact_token',
field=models.CharField(blank=True, default=None, max_length=150, null=True, verbose_name='Ověřovací token pro kontaktní e-mail.'),
),
migrations.AlterField(
model_name='appuser',
name='etStamp',
field=models.DateTimeField(blank=True, default=None, editable=False, null=True, verbose_name='E-mail token timestamp'),
),
migrations.AlterField(
model_name='appuser',
name='kind',
field=models.IntegerField(choices=[(0, 'dostávat novinky'), (2, 'pomáhat jako dobrovolník'), (2, 'pomáhat jako expert'), (1, 'se stát příznivcem'), (2, 'se stát členem'), (3, 'již jsem člen'), (3, 'již pirátím jinak')], default=0, verbose_name='Chci'),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 2.0.3 on 2018-08-18 17:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0031_auto_20180815_0639'),
]
operations = [
migrations.RenameModel(
old_name='Newsletter',
new_name='InterestRegion',
),
migrations.AlterField(
model_name='userform',
name='newsletters',
field=models.ManyToManyField(blank=True, to='nalodeni.InterestRegion', verbose_name='Zájmové regiony'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-08-18 17:29
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0032_auto_20180818_1728'),
]
operations = [
migrations.RenameField(
model_name='userform',
old_name='newsletters',
new_name='regions',
),
]

View File

@ -0,0 +1,81 @@
# Generated by Django 2.0.3 on 2018-08-18 17:30
import datetime
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0033_auto_20180818_1729'),
]
operations = [
migrations.CreateModel(
name='NewsCond',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('group', models.IntegerField(default=False, verbose_name='Skupina podmínek')),
('neg', models.BooleanField(default=False, verbose_name='Negovat podmínku')),
('kind', models.IntegerField(choices=[(0, 'Dovednost'), (1, 'Téma'), (2, 'Území')], default=0, verbose_name='Druh')),
('opt_id', models.IntegerField(verbose_name='ID vybrané možnosti')),
],
),
migrations.CreateModel(
name='Newsletter',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=150, null=True, verbose_name='Název')),
('desc', models.CharField(blank=True, max_length=500, null=True, verbose_name='Popis')),
('enabled', models.BooleanField(default=False, verbose_name='Aktivní')),
('managed_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='newsletter_managed', to=settings.AUTH_USER_MODEL, verbose_name='Správce newsletteru')),
('sent_by', models.ManyToManyField(blank=True, related_name='newsletter_sent', to=settings.AUTH_USER_MODEL, verbose_name='Odesílatelé')),
],
),
migrations.CreateModel(
name='NewsMsg',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_ts', models.DateTimeField(default=datetime.datetime.now, editable=False, verbose_name='Datum vytvoření')),
('delivery_ts', models.DateTimeField(blank=True, null=True, verbose_name='Datum plánovaného rozeslání')),
('sent_ts', models.DateTimeField(blank=True, null=True, verbose_name='Datum skutečného rozeslání')),
('title', models.CharField(max_length=150, null=True, verbose_name='Název, předmět emailu')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Vytvořil')),
('news', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nalodeni.Newsletter', verbose_name='Newsletter')),
],
),
migrations.CreateModel(
name='NewsMsgBlock',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.IntegerField(default=False, verbose_name='Pořadí')),
('heading', models.CharField(max_length=150, null=True, verbose_name='Nadpis')),
('content', models.TextField(null=True, verbose_name='Text (i validované HTML)')),
('link', models.CharField(max_length=500, null=True, verbose_name='Odkaz')),
('link_text', models.CharField(max_length=100, null=True, verbose_name='Text odkazu')),
('img_url', models.CharField(max_length=500, null=True, verbose_name='Obrázek (URL)')),
('img_thumb_url', models.CharField(max_length=500, null=True, verbose_name='Obrázek náhledu (URL)')),
('img_label', models.CharField(max_length=100, null=True, verbose_name='Popis obrázku')),
('nmsg', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nalodeni.NewsMsg', verbose_name='Zpráva')),
],
),
migrations.CreateModel(
name='NewsMsgReply',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('comment', models.CharField(blank=True, max_length=250, null=True, verbose_name='Poznámka')),
('rating_usefullness', models.IntegerField(blank=True, default=0, null=True, verbose_name='Využiji to')),
('rating_interest', models.IntegerField(blank=True, default=0, null=True, verbose_name='Zajímá mě to')),
('rating_action', models.IntegerField(blank=True, default=0, null=True, verbose_name='Zapojím se / Pomůžu')),
('block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nalodeni.NewsMsgBlock', verbose_name='Část zprávy')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Vytvořil')),
],
),
migrations.AddField(
model_name='newscond',
name='news',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='nalodeni.Newsletter', verbose_name='Newsletter'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-08-18 19:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0034_auto_20180818_1730'),
]
operations = [
migrations.AddField(
model_name='newsletter',
name='is_del',
field=models.BooleanField(default=False, editable=False, verbose_name='Smazaný'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-08-18 19:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0035_newsletter_is_del'),
]
operations = [
migrations.AddField(
model_name='newsletter',
name='kind',
field=models.IntegerField(choices=[(0, 'Týdně'), (1, 'Měsíčně'), (2, 'Nepravidelně')], default=1, verbose_name='Perioda zasílání'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-08-18 19:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0036_newsletter_kind'),
]
operations = [
migrations.RenameField(
model_name='newsletter',
old_name='kind',
new_name='period',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-08-18 22:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0037_auto_20180818_1934'),
]
operations = [
migrations.AddField(
model_name='newsmsg',
name='is_del',
field=models.BooleanField(default=False, editable=False, verbose_name='Smazaný'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-08-19 02:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0038_newsmsg_is_del'),
]
operations = [
migrations.RenameField(
model_name='newsmsgblock',
old_name='nmsg',
new_name='newsmsg',
),
]

View File

@ -0,0 +1,43 @@
# Generated by Django 2.0.3 on 2018-08-19 02:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0039_auto_20180819_0221'),
]
operations = [
migrations.AlterField(
model_name='newsmsgblock',
name='content',
field=models.TextField(blank=True, null=True, verbose_name='Text (i validované HTML)'),
),
migrations.AlterField(
model_name='newsmsgblock',
name='img_label',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Popis obrázku'),
),
migrations.AlterField(
model_name='newsmsgblock',
name='img_thumb_url',
field=models.CharField(blank=True, max_length=500, null=True, verbose_name='Obrázek náhledu (URL)'),
),
migrations.AlterField(
model_name='newsmsgblock',
name='img_url',
field=models.CharField(blank=True, max_length=500, null=True, verbose_name='Obrázek (URL)'),
),
migrations.AlterField(
model_name='newsmsgblock',
name='link',
field=models.CharField(blank=True, max_length=500, null=True, verbose_name='Odkaz'),
),
migrations.AlterField(
model_name='newsmsgblock',
name='link_text',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Text odkazu'),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 2.0.3 on 2018-08-19 02:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0040_auto_20180819_0222'),
]
operations = [
migrations.AlterModelOptions(
name='newsmsgblock',
options={'ordering': ['order', 'heading']},
),
migrations.AddField(
model_name='newsmsg',
name='introText',
field=models.CharField(max_length=150, null=True, verbose_name='Úvodník'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-08-19 02:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0041_auto_20180819_0254'),
]
operations = [
migrations.AlterField(
model_name='newsmsg',
name='introText',
field=models.TextField(null=True, verbose_name='Úvodník'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-08-19 02:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0042_auto_20180819_0255'),
]
operations = [
migrations.AlterField(
model_name='newsmsg',
name='introText',
field=models.TextField(blank=True, null=True, verbose_name='Úvodník'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-08-19 08:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0043_auto_20180819_0257'),
]
operations = [
migrations.RenameField(
model_name='newsmsg',
old_name='introText',
new_name='headerText',
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.0.3 on 2018-08-19 08:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0044_auto_20180819_0847'),
]
operations = [
migrations.AddField(
model_name='newsmsg',
name='footerText',
field=models.TextField(blank=True, null=True, verbose_name='Patička'),
),
migrations.AlterField(
model_name='newsmsg',
name='headerText',
field=models.TextField(blank=True, null=True, verbose_name='Hlavička'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-08-19 09:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0045_auto_20180819_0848'),
]
operations = [
migrations.AlterField(
model_name='newsmsgblock',
name='heading',
field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Nadpis'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.3 on 2018-08-19 10:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0046_auto_20180819_0900'),
]
operations = [
migrations.AddField(
model_name='newsletter',
name='recipients',
field=models.CharField(blank=True, max_length=500, null=True, verbose_name='Příjemci (pomocí tagů)'),
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 2.0.3 on 2018-08-19 10:25
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0047_newsletter_recipients'),
]
operations = [
migrations.AlterUniqueTogether(
name='interestregion',
unique_together={('tag',)},
),
migrations.AlterUniqueTogether(
name='userskill',
unique_together={('tag',)},
),
migrations.AlterUniqueTogether(
name='usertopic',
unique_together={('tag',)},
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 2.0.3 on 2018-11-23 16:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0048_auto_20180819_1025'),
]
operations = [
migrations.AlterField(
model_name='appregemail',
name='kind',
field=models.IntegerField(blank=True, choices=[(0, 'dostávat novinky'), (3, 'pomáhat jako dobrovolník'), (4, 'pomáhat jako expert'), (1, 'se stát příznivcem'), (2, 'se stát členem'), (5, 'již jsem člen'), (6, 'již pirátím jinak')], default=0, null=True, verbose_name='Chci'),
),
migrations.AlterField(
model_name='appuser',
name='district',
field=models.IntegerField(blank=True, choices=[(0, 'Hlavní město Praha'), (1, 'Jihočeský kraj'), (2, 'Jihomoravský kraj'), (3, 'Karlovarský kraj'), (4, 'Kraj Vysočina'), (5, 'Královéhradecký kraj'), (6, 'Liberecký kraj'), (7, 'Moravskoslezský kraj'), (8, 'Olomoucký kraj'), (9, 'Pardubický kraj'), (10, 'Plzeňský kraj'), (11, 'Středočeský kraj'), (12, 'Ústecký kraj'), (13, 'Zlínský kraj')], default=None, null=True, verbose_name='Kraj'),
),
migrations.AlterField(
model_name='appuser',
name='kind',
field=models.IntegerField(choices=[(0, 'dostávat novinky'), (3, 'pomáhat jako dobrovolník'), (4, 'pomáhat jako expert'), (1, 'se stát příznivcem'), (2, 'se stát členem'), (5, 'již jsem člen'), (6, 'již pirátím jinak')], default=0, verbose_name='Chci'),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 2.0.3 on 2019-02-20 23:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0049_auto_20181123_1607'),
]
operations = [
migrations.AlterModelOptions(
name='appuser',
options={'ordering': ('username',), 'verbose_name': 'AppUser', 'verbose_name_plural': 'AppUsers'},
),
migrations.AddField(
model_name='newsmsg',
name='testMailRecipients',
field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Cc adresáti testovacího emailu'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.0.3 on 2019-02-21 00:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0050_auto_20190220_2358'),
]
operations = [
migrations.AddField(
model_name='newsletter',
name='replyToEmail',
field=models.CharField(blank=True, max_length=150, null=True, verbose_name='E-mailová adresa pro odpovědi'),
),
migrations.AlterField(
model_name='newsmsg',
name='testMailRecipients',
field=models.CharField(blank=True, max_length=150, null=True, verbose_name='Přidaní adresáti testovacího emailu'),
),
]

View File

@ -0,0 +1,34 @@
# Generated by Django 2.0.3 on 2019-03-03 11:51
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0051_auto_20190221_0035'),
]
operations = [
migrations.CreateModel(
name='Euro2019Interest',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('createdStamp', models.DateTimeField(default=datetime.datetime.now, editable=False, verbose_name='Uživatel vytvořen')),
('email', models.CharField(max_length=100, null=True, verbose_name='e-mail')),
('district', models.IntegerField(blank=True, choices=[(0, 'Hlavní město Praha'), (1, 'Jihočeský kraj'), (2, 'Jihomoravský kraj'), (3, 'Karlovarský kraj'), (4, 'Kraj Vysočina'), (5, 'Královéhradecký kraj'), (6, 'Liberecký kraj'), (7, 'Moravskoslezský kraj'), (8, 'Olomoucký kraj'), (9, 'Pardubický kraj'), (10, 'Plzeňský kraj'), (11, 'Středočeský kraj'), (12, 'Ústecký kraj'), (13, 'Zlínský kraj')], default=None, null=True, verbose_name='Kraj')),
('postcode', models.IntegerField(null=True, verbose_name='PSČ (kvůli dělení do krajů)')),
('want_info', models.BooleanField(default=True, verbose_name='Chci dostávat newslettery')),
('want_campaign', models.BooleanField(default=True, verbose_name='Chci rozdávat Pirátské listy')),
('want_be_active', models.BooleanField(default=True, verbose_name='Chci se podílet chodu kampaně')),
('note', models.CharField(blank=True, max_length=100, null=True, verbose_name='Poznámka (info pro koordinátora)')),
('data_consent', models.BooleanField(default=False, verbose_name='Souhlasím se zpracováním osobních údajů')),
('dc_stamp', models.DateTimeField(blank=True, default=None, editable=False, null=True, verbose_name='Data consent timestamp')),
],
),
migrations.AlterUniqueTogether(
name='euro2019interest',
unique_together={('email',)},
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.0.3 on 2019-03-03 12:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0052_auto_20190303_1151'),
]
operations = [
migrations.AlterField(
model_name='euro2019interest',
name='want_be_active',
field=models.BooleanField(default=False, verbose_name='Chci se podílet chodu kampaně'),
),
migrations.AlterField(
model_name='euro2019interest',
name='want_campaign',
field=models.BooleanField(default=False, verbose_name='Chci rozdávat Pirátské listy'),
),
]

View File

View File

@ -0,0 +1,23 @@
# Generated by Django 2.0.2 on 2018-02-28 16:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='district',
field=models.IntegerField(blank=True, choices=[(0, 'Hlavní město Praha'), (1, 'Jihočeský kraj'), (2, 'Jihomoravský kraj'), (3, 'Karlovarský kraj'), (4, 'Kraj Vysočina'), (5, 'Královéhradecký kraj'), (6, 'Liberecký kraj'), (7, 'Moravskoslezský kraj'), (8, 'Olomoucký kraj'), (9, 'Pardubický kraj'), (10, 'Plzeňský kraj'), (11, 'Středočeský kraj'), (12, 'Ústecký kraj'), (13, 'Zlínský kraj')], null=True, verbose_name='Kraj'),
),
migrations.AlterField(
model_name='appuser',
name='postcode',
field=models.IntegerField(blank=True, null=True, verbose_name='PSČ'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 2.0.2 on 2018-02-28 16:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='appuser',
name='postcode',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-02-28 16:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0002_remove_appuser_postcode'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='district',
field=models.IntegerField(blank=True, choices=[(0, 'Hlavní město Praha'), (1, 'Jihočeský kraj'), (2, 'Jihomoravský kraj'), (3, 'Karlovarský kraj'), (4, 'Kraj Vysočina'), (5, 'Královéhradecký kraj'), (6, 'Liberecký kraj'), (7, 'Moravskoslezský kraj'), (8, 'Olomoucký kraj'), (9, 'Pardubický kraj'), (10, 'Plzeňský kraj'), (11, 'Středočeský kraj'), (12, 'Ústecký kraj'), (13, 'Zlínský kraj')], null=True, verbose_name='Kraj'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.0.2 on 2018-02-28 16:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('nalodeni', '0003_appuser_district'),
]
operations = [
migrations.AddField(
model_name='appuser',
name='postcode',
field=models.IntegerField(blank=True, null=True, verbose_name='PSČ'),
),
]

936
src/nalodeni/models.py Normal file
View File

@ -0,0 +1,936 @@
# -*- 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.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
class AppUser(AbstractUser):
"""
Prepare an empty User class just in case it will be needed later.
"""
### 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_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ý'),
)
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')),
)
##
# 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)
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)
# Stav KIND_MEMBER se nastavuje dle skutečného členství, a bude
# pravidelně aktualizován synchronizací s DB členů.
email_contact = EmailField(_('Kontaktní e-mail'),max_length=100,
default='', blank=True, null=True)
# 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, pokud bude nastaven.
email_contact_token = CharField(_('Ověřovací token pro kontaktní e-mail.'),
max_length=150, default=None, 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_verified = BooleanField(_('Kontaktní e-mail byl ověřen.'),
default=False, blank=True)
# Kontaktni email je potřeba ověřit zaslání ověřovacího emailu.
email_contact_active = EmailField(_('Kontaktní e-mail aktivní'),max_length=100,
default='', blank=True, null=True)
# Kontaktni email, který je ověřen a zapsán v LDAP.
# pole je vyplňováno při přihlášení z SSO.
ts_for_ldap_sync = DateTimeField(_('Timestamp pro LDAP synchronizaci'),
default=datetime.datetime.now, 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 potřeba aktualizovat v LDAP.
dc_stamp = DateTimeField(_('Data consent timestamp'),
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)
##
# Other fields
##
status = IntegerField(_('Stav'), blank=False, null=False,
default = STATUS_NEW, choices=STATUS_CHOICES)
##
# 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)
# Konec
##
##
# Meta information
##
def __str__(self):
return self.username
if self.ssoUid:
return "%s %s" % (self.first_name, self.last_name)
else:
return self.email
class Meta:
verbose_name = _('AppUser')
verbose_name_plural = _('AppUsers')
unique_together = (("email", ),("username",), ("emailToken",),)
ordering = ('username',)
##
# Permission functions
##
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:
# append the ID of the choice
rslt.append(self.DISTRICT_ROLES[p])
spc['dist'] = rslt
return spc
"""
from django.db.models import Count
from django.db.models.functions import TruncMonth
from django.db.models.functions import TruncYear
import nalodeni as n
objs = n.models.USER_MODEL.objects.annotate( month=TruncMonth('createdStamp')).values('month', 'district').annotate(c=Count('id')).order_by('district','month')
district_choices = {
0 : 'PHA',
1 : 'JHC',
2 : 'JHM',
3 : 'KVK',
4 : 'VYS',
5 : 'KHK',
6 : 'LBK',
7 : 'MSK',
8 : 'OLK',
9 : 'PAK',
10 : 'PLK',
11 : 'STC',
12 : 'ULK',
13 : 'ZLK',
}
for o in objs:
print( "%s \t %s \t %s" % (district_choices[o['district']] if o['district'] is not None else "---", o['month'].strftime('%Y-%m'),o['c']))
"""
USER_MODEL = AppUser
#USER_MODEL = get_user_model()
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):
"""
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.
"""
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': 'Martin Rejman'
'preferred_username': 'mr'
'given_name': 'Martin'
'family_name': 'Rejman'
'email': 'martin.rejman@centrum.cz'
}
"""
# 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 = []
#print('* ')
for item in group.split(" "):
item = item.strip()
if item == "":
continue
#print(item)
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)
#print(topic_ids, region_ids, skill_ids)
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)
#print(rslt_u)
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)
## .......+++++++......... ##
# User settings #
# #
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()
# #
# End user settings #
## .......+++++++......... ##
## .......+++++++......... ##
# GLOBAL APP SETTINGS #
# #
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()
# #
# END GLOBAL SETTINGS #
## .......+++++++......... ##

569
src/nalodeni/news.py Normal file
View File

@ -0,0 +1,569 @@
# -*- encoding: utf-8 -*-
from datetime import date, datetime, timedelta # timeSlices
from collections import OrderedDict
import django
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect, resolve_url
from django.template import Template, RequestContext, loader
from django.template.loader import render_to_string
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from django.views.decorators.csrf import ensure_csrf_cookie
from django.forms import ModelForm
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import pgettext, pgettext_lazy
from django.db import transaction
from django.db.models import F
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
import logging
import bleach
import html2text
from django.conf import settings as appSettings
from . import models
from . import forms
from . import auth as nalodeni_auth
# Logger instance
logger = logging.getLogger(__name__)
def role_required(roles=[]):
def decorate(func):
def call(request, *args, **kwargs):
for r in roles:
if not r in request.session['site_perms']:
messages.error(request, "Nedostatečné oprávnění pro přístup. Detaily byly zaznamenány.")
return HttpResponseRedirect('/')
result = func(request, *args, **kwargs)
return result
return call
return decorate
@ensure_csrf_cookie
#@login_required(login_url="/prihlaseni")
#@role_required(['sso_news_list'])
def news_all(request):
newsletters = models.Newsletter.objects.filter(is_del=False)
site_perms = request.session.get('site_perms',[])
actions = set()
if 'sso_news_manage' in site_perms:
actions.add('add_news_list')
actions.add('news_list_actions')
actions.add('news_list_details')
if 'sso_kodo' in site_perms:
actions.add('news_list_details')
if len(actions) == 0:
newsletters = newsletters.filter(enabled=True)
template = 'news/list.html'
context = {
'newsletters' : newsletters,
'actions' : actions,
}
return render(request, template, context)
@ensure_csrf_cookie
@login_required(login_url="/prihlaseni")
@role_required(['sso_news_list'])
def list_edit(request, lid=None):
if lid is not None:
obj = models.Newsletter.objects.filter(pk=lid, is_del=False)
if len(obj) == 1:
obj = obj[0]
elif len(obj) == 0:
obj = None
else:
1/0
else:
obj = None
site_perms = request.session.get('site_perms',[])
passed = False
if (obj is not None and request.user == obj.managed_by) or 'sso_news_manage' in site_perms:
passed = True
if not passed:
messages.error(request, _('Nedostatečné oprávnění.'))
return redirect('nalodeni:news_all')
if request.method == 'GET':
form = forms.NewsletterForm(instance=obj)
elif request.method == 'POST':
form = forms.NewsletterForm(request.POST, instance=obj)
if form.is_valid():
if request.POST.get('del_item', 'ne') == "ano":
form.instance.is_del = True
form.instance.save()
messages.info(request, "Záznam byl smazán.")
return redirect('nalodeni:news_all')
form.save()
messages.info(request, "Údaje byly uloženy.")
return redirect('nalodeni:news_all')
template = 'news/list_edit.html'
context = {
'form' : form,
'regions' : models.InterestRegion.objects.all(),
'topics' : models.UserTopic.objects.all(),
'skills' : models.UserSkill.objects.all(),
}
return render(request, template, context)
@ensure_csrf_cookie
def list_show(request, lid):
"""
Seznam zpráv pro daný newsletter.
"""
obj = models.Newsletter.objects.filter(pk=lid, is_del=False)
if len(obj) == 1:
obj=obj[0]
else:
return redirect('nalodeni:news_all')
site_perms = request.session.get('site_perms',[])
actions = set()
if (request.user == obj.managed_by or 'sso_news_manage' in site_perms):
actions.add('nwsl_edit')
if (request.user in obj.sent_by.all() or request.user == obj.managed_by
or 'sso_news_manage' in site_perms):
actions.add('nwsl_details')
actions.add('nwsl_actions')
if 'sso_news_list_recipients' in site_perms:
actions.add('show_newsletter_recipients')
msgs = obj.newsmsg_set.filter(is_del=False)
if 'nwsl_actions' not in actions:
msgs = msgs.exclude(sent_ts=None)
template = 'news/list_show.html'
context = {
'obj' : obj,
'msgs' : msgs,
'actions' : actions,
}
return render(request, template, context)
@ensure_csrf_cookie
@login_required(login_url="/prihlaseni")
@role_required(['sso_news_list_recipients'])
def list_show_recipients(request, lid):
"""
Seznam příjemců pro daný newsletter.
"""
obj = models.Newsletter.objects.filter(pk=lid, is_del=False)
if len(obj) == 1:
obj=obj[0]
else:
return redirect('nalodeni:news_all')
site_perms = request.session.get('site_perms',[])
actions = set()
if not (request.user in obj.sent_by.all() or request.user == obj.managed_by
or 'sso_news_manage' in site_perms):
messages.error(request, _('Nedostatečné oprávnění.'))
return redirect('nalodeni:news_all')
recipients = obj.get_recip_users()
recipients_cnt = len(recipients)
template = 'news/list_show_recipients.html'
context = {
'obj' : obj,
'msgs' : obj.newsmsg_set.filter(is_del=False),
'recipients' : recipients,
'recipients_cnt' : recipients_cnt,
'actions' : actions,
}
return render(request, template, context)
@ensure_csrf_cookie
def msg_show(request, mid):
"""
Detail zpravy newsletteru.
"""
obj = models.NewsMsg.objects.filter(pk=mid,is_del=False)
if len(obj) == 1:
obj = obj[0]
elif len(obj) == 0:
obj = None
messages.error("Objekt nenalezen.")
return redirect('nalodeni:news_all')
else:
1/0
site_perms = request.session.get('site_perms',[])
actions = set()
passed = False
if (request.user in obj.news.sent_by.all()
or request.user == obj.news.managed_by
or 'sso_news_manage' in site_perms):
actions.add('nwsl_actions')
passed = True
# zobrazit jiz odeslanou zpravu
if obj.sent_ts is not None:
passed = True
if not passed:
messages.error(request, _('Zpráva neexistuje, nebo nemáte dostatečné oprávnění k jejímu zobrazení.'))
return redirect('nalodeni:news_all')
template = 'news/msg_show.html'
context = {
'msg' : obj,
'blocks' : obj.newsmsgblock_set.all(),
'actions' : actions,
}
return render(request, template, context)
@ensure_csrf_cookie
@login_required(login_url="/prihlaseni")
def msg_edit(request, lid, mid=None):
"""
Seznam zpráv pro daný newsletter.
"""
if mid is not None:
obj = models.NewsMsg.objects.filter(pk=mid, is_del=False)
if len(obj) == 1:
obj = obj[0]
elif len(obj) == 0:
obj = None
return redirect('nalodeni:news_msg_create', lid=lid)
else:
1/0
else:
obj = None
if obj is None:
obj = models.NewsMsg(news_id=lid)
site_perms = request.session.get('site_perms',[])
actions = set()
if not (request.user in obj.news.sent_by.all()
or request.user == obj.news.managed_by
or 'sso_news_manage' in site_perms):
messages.error(request, _('Nedostatečné oprávnění pro vytvoření zprávy.'))
return redirect('nalodeni:news_all')
if request.method == 'GET':
form = forms.NewsMsgForm(instance=obj)
elif request.method == 'POST':
form = forms.NewsMsgForm(request.POST, instance=obj)
if form.is_valid():
form.instance.created_by = request.user
form.instance.created_ts = datetime.now()
if request.POST.get('del_item', 'ne') == "ano":
form.instance.is_del = True
form.instance.save()
messages.info(request, "Záznam byl smazán.")
return redirect('nalodeni:news_list_show', lid=lid)
form.instance.headerText = bleach.clean(form.instance.headerText,
tags=['b','ul','ol','li','a','i','hr','br'], strip=True)
form.instance.footerText = bleach.clean(form.instance.footerText,
tags=['b','ul','ol','li','a','i','hr','br'], strip=True)
form.instance.headerText = bleach.linkify(form.instance.headerText)
form.instance.footerText = bleach.linkify(form.instance.footerText)
form.save()
messages.info(request, "Údaje byly uloženy.")
return redirect('nalodeni:news_msg_show', mid=form.instance.id)
template = 'news/msg_edit.html'
context = {
'form' : form,
}
return render(request, template, context)
@ensure_csrf_cookie
@login_required(login_url="/prihlaseni")
def msg_send(request, id, realSend=False):
"""
Odeslani newsletteru.
"""
obj = models.NewsMsg.objects.filter(pk=id,is_del=False)
if len(obj) == 1:
obj = obj[0]
elif len(obj) == 0:
obj = None
messages.error("Objekt nenalezen.")
return redirect('nalodeni:news_all')
else:
1/0
site_perms = request.session.get('site_perms',[])
actions = set()
if not (request.user in obj.news.sent_by.all()
or request.user == obj.news.managed_by
or 'sso_news_manage' in site_perms):
messages.error(request, _('Nedostatečné oprávnění.'))
return redirect('nalodeni:news_all')
if 'sso_news_manage' in site_perms:
recipients_wanted = request.GET.get('recipients','test')
else:
recipients_wanted = "test"
update_sent_ts = False
use_public_to_email = False
recipients_email = []
recip_users = []
if obj.sent_ts is None: # jeste nerozeslano
if recipients_wanted == 'all':
update_sent_ts = True
use_public_to_email = True
recip_users = obj.news.get_recip_users()
else:
recip_users = [ request.user ]
else:
if recipients_wanted == "test":
recip_users = [ request.user ]
else:
messages.error(request, "Zpráva již byla rozeslána.")
return redirect('nalodeni:news_list_show', lid=obj.news_id)
if obj.testMailRecipients is not None and recipients_wanted == "test":
for eml in obj.testMailRecipients.split(","):
try:
eml = eml.strip()
validate_email(eml)
recipients_email.append(eml)
except ValidationError:
messages.error(request, "Testovací email %s není ve tvaru emailu." % (eml,) )
for rcp in recip_users:
# pouzivat KONTAKTNI email misto REGISTRACNIHO
eml = rcp.email_contact_active
if eml is None or len(eml.strip()) == 0:
eml = rcp.email
try:
validate_email(eml)
recipients_email.append(eml)
except ValidationError:
messages.error(request, "Email '%s' uživatele %s není ve správném tvaru." % (eml, rcp.username))
template = 'news/msg_to_email.html'
context = {
'msg' : obj,
'abs_host_url' : '%s://%s' % ( request.scheme, request.get_host()),
'blocks' : obj.newsmsgblock_set.all(),
}
if realSend:
from django.core.mail import get_connection, EmailMultiAlternatives
import quopri
mailgun_backend = get_connection('anymail.backends.mailgun.EmailBackend')
header_from = '%s <%s>' % (
appSettings.ANYMAIL['FROM_NAME'],
appSettings.ANYMAIL['FROM_EMAIL'],
)
html_message = render_to_string(template, context)
plain_message = html2text.html2text(html_message)
ema = EmailMultiAlternatives(
"%s - %s" % (obj.news.name, obj.title),
plain_message, header_from )
ema.connection = mailgun_backend
if use_public_to_email:
ema.to = [ appSettings.ANYMAIL['PUBLIC_TO_EMAIL'] ]
else:
# pouzit kontaktni nebo registracni email uzivatele
eml = rcp.email_contact_active
if eml is None or len(eml.strip()) == 0:
eml = rcp.email
ema.to = [ eml ]
ema.bcc = recipients_email
if obj.news.replyToEmail is not None:
try:
eml = obj.news.replyToEmail.strip()
validate_email(eml)
ema.extra_headers = {
'Reply-To' : eml,
}
except ValidationError:
messages.error(request, "Odpovědní email '%s' není ve tvaru emailu. Odesílání zrušeno." % (eml,) )
messages.info(request, "Chybu může opravit správce tohoto newsletteru." )
return redirect('nalodeni:news_list_show', lid=obj.news_id)
ema.attach_alternative(html_message, "text/html")
ema.send()
if update_sent_ts:
obj.sent_ts = datetime.now()
obj.save()
messages.info(request, "Zpráva odeslána pomocí MailGun celkem %s uživatelům." % len(recipients_email) )
else:
messages.error(request, "Message delivery disabled.")
return redirect('nalodeni:news_list_show', lid=obj.news_id)
def msg_preview(request, id):
"""
Verejny nahled newsletteru.
"""
obj = models.NewsMsg.objects.filter(pk=id,is_del=False)
if len(obj) == 1:
obj = obj[0]
elif len(obj) == 0:
obj = None
messages.error("Objekt nenalezen.")
return redirect('nalodeni:news_all')
else:
1/0
site_perms = request.session.get('site_perms',[])
actions = set()
passed = False
if (request.user in obj.news.sent_by.all()
or request.user == obj.news.managed_by
or 'sso_news_manage' in site_perms):
passed = True
if obj.sent_ts is not None:
passed = True
if not passed:
return redirect('nalodeni:news_all')
template = 'news/msg_to_email.html'
context = {
'msg' : obj,
'abs_host_url' : '%s://%s' % ( request.scheme, request.get_host()),
'blocks' : obj.newsmsgblock_set.all(),
}
return render(request, template, context)
@ensure_csrf_cookie
@login_required(login_url="/prihlaseni")
def block_edit(request, mid, bid=None):
"""
Seznam zpráv pro daný newsletter.
"""
if bid is not None:
obj = models.NewsMsgBlock.objects.filter(pk=bid, newsmsg_id=mid)
if len(obj) == 1:
obj = obj[0]
elif len(obj) == 0:
obj = None
return redirect('nalodeni:news_block_create', mid=mid)
else:
1/0
else:
obj = None
if obj is None:
obj = models.NewsMsgBlock(newsmsg_id=mid)
site_perms = request.session.get('site_perms',[])
actions = set()
if not (request.user in obj.newsmsg.news.sent_by.all()
or request.user == obj.newsmsg.news.managed_by
or 'sso_news_manage' in site_perms):
messages.error(request, _('Nedostatečné oprávnění pro editaci bloku zprávy.'))
return redirect('nalodeni:news_all')
if request.method == 'GET':
form = forms.NewsMsgBlockForm(instance=obj)
elif request.method == 'POST':
form = forms.NewsMsgBlockForm(request.POST, instance=obj)
if form.is_valid():
form.instance.content = bleach.clean(form.instance.content,
tags=['b','ul','ol','li','a','i','hr'], strip=True)
form.instance.content = bleach.linkify(form.instance.content)
form.save()
messages.info(request, "Údaje byly uloženy.")
return redirect('nalodeni:news_msg_show', mid=mid)
template = 'news/block_edit.html'
context = {
'form' : form,
}
return render(request, template, context)
@ensure_csrf_cookie
@login_required(login_url="/prihlaseni")
def block_delete(request, bid):
"""
Seznam zpráv pro daný newsletter.
"""
obj = models.NewsMsgBlock.objects.filter(pk=bid)
if len(obj) == 1:
obj = obj[0]
elif len(obj) == 0:
obj = None
return redirect('nalodeni:news_all')
else:
1/0
site_perms = request.session.get('site_perms',[])
actions = set()
if not (request.user in obj.newsmsg.news.sent_by.all()
or request.user == obj.newsmsg.news.managed_by
or 'sso_news_manage' in site_perms):
messages.error(request, _('Nedostatečné oprávnění pro smazání bloku zprávy.'))
return redirect('nalodeni:news_all')
mid = obj.newsmsg_id
obj.delete()
messages.info(request, "Záznam byl smazán.")
return redirect('nalodeni:news_msg_show', mid=mid)

197
src/nalodeni/people.py Normal file
View File

@ -0,0 +1,197 @@
# -*- encoding: utf-8 -*-
from datetime import date, datetime, timedelta # timeSlices
from collections import OrderedDict
import django
from django.http import HttpResponse, HttpResponseRedirect
from django.template import Template, RequestContext, loader
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from django.views.decorators.csrf import ensure_csrf_cookie
from django import forms
from django.forms import ModelForm
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import pgettext, pgettext_lazy
from django.db import transaction
from django.db.models import F
from django.core.exceptions import ValidationError
import logging
from django.conf import settings as appSettings
from . import models
from . import forms
from . import auth as nalodeni_auth
# Logger instance
logger = logging.getLogger(__name__)
def role_required(roles=[]):
def decorate(func):
def call(request, *args, **kwargs):
for r in roles:
if not r in request.session['site_perms']:
messages.error(request, "Nedostatečné oprávnění pro přístup. Detaily byly zaznamenány.")
return HttpResponseRedirect('/')
result = func(request, *args, **kwargs)
return result
return call
return decorate
def get_AppUser_objects(request):
''' Zkontroluje, že všechny objekty v 'objs' vyhovují přiděleným rolím.
Nevyhovující vyřadí.
'''
sp = request.session['site_perms']
if 'sso_kodo' not in sp:
return models.AppUser.objects.none()
if 'sso_admin' in sp:
return models.AppUser.objects.all()
objs = models.AppUser.objects.filter(district__in=request.session['spc']['dist'])
return objs
def get_AppUser_districts(request):
'''Vrátí dostupné kraje podle rolí.'''
if 'sso_admin' in request.session['site_perms']:
return list(models.AppUser.DISTRICT_CHOICES)
rslt = []
for d in models.AppUser.DISTRICT_CHOICES:
if d[0] in request.session['spc']['dist']:
rslt.append(d)
return rslt
@ensure_csrf_cookie
@login_required(login_url="/prihlaseni")
@role_required(['sso_kodo'])
def confirmed(request, newOnly=False, dist=None):
if dist is None:
dist = int(request.POST.get('dist', -1))
objs = get_AppUser_objects(request).order_by('last_name', 'first_name' ,'email')
if newOnly:
objs = objs.filter(status=models.AppUser.STATUS_NEW)
else:
objs = objs.filter(status=models.AppUser.STATUS_REG)
districts = [(-1, ' -- vše dostupné -- ')] + get_AppUser_districts(request)
# filtrujeme jen povolene kraje, nebo vse pro adminy
if dist != -1 and (dist in request.session['spc']['dist'] or 'sso_admin' in request.session['site_perms']):
selDist = dist
objs = objs.filter(district=selDist)
else:
selDist = -1
template = 'people/list.html'
context = {
'people' : objs,
'newOnly' : newOnly,
'distAvail' : districts,
'selDist' : selDist,
}
return render(request, template, context)
@ensure_csrf_cookie
@login_required(login_url="/prihlaseni")
@role_required(['sso_kodo'])
def pending(request):
''' List pending registrations from AppRegEmail. '''
show_all = request.GET.get("show_all","no") == "yes"
if request.method == "POST":
act = request.POST.get("action", None)
ids = request.POST.getlist('r[]')
objs = models.AppRegEmail.objects.filter(id__in = ids)
if act == "token":
email_counter = 0
skipped_counter = 0
for o in objs:
# kontrola, jestli uživatel již není registrován
rslt = models.AppUser.objects.filter(email__iexact=o.email.strip())
if len(rslt) != 0:
messages.info(request,'Uživatel s emailem %s již je registrován, požadavek na registaci odstraněn.' % o.email)
o.delete()
else:
if not ( o.etStamp and (datetime.now() - o.etStamp).total_seconds() < int(appSettings.TOKEN_VALID_SEC)):
# token not valid or not sent
nalodeni_auth.sendRegisterToken(o.email)
email_counter += 1
else:
skipped_counter += 1
if email_counter > 0:
messages.info(request,'Registrační emaily odeslány, celkem odesláno %s zpráv.' % email_counter)
if skipped_counter > 0:
messages.info(request,'Celkem %s registrací přeskočeno, ještě jsou platné.' % skipped_counter)
elif act == "delete":
objs.delete()
messages.info(request,'Registrace odstraněny.')
tokenValidAfter = datetime.now() - timedelta(seconds=int(appSettings.TOKEN_VALID_SEC))
objs = models.AppRegEmail.objects.all()
if not show_all:
# zobrazovat pouze nové registrace
objs = objs.filter(emailToken = None)
objs = objs.order_by('etStamp')
template = 'people/pending.html'
context = {
'people' : objs,
'tokenValidAfter' : tokenValidAfter,
'show_all' : show_all,
}
return render(request, template, context)
@ensure_csrf_cookie
@login_required(login_url="/prihlaseni")
@role_required(['sso_kodo'])
def update(request):
ids = request.POST.getlist('r[]')
val = request.POST.get('setStatus', None)
if val and val in ['reg','new']:
objs = get_AppUser_objects(request).filter(id__in=ids)
rec = 0
for o in objs:
rec += 1
o.status = o.STATUS_REG if val == 'reg' else o.STATUS_NEW
o.save()
messages.info(request, 'Upraveno celkem %s záznamů.' % rec)
if val == 'new':
return HttpResponseRedirect('/people/list/')
elif val == "reg":
return HttpResponseRedirect('/people/list-new/')
else:
messages.error(request, 'Špatný požadavek.')
return HttpResponseRedirect('/people/list/')

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
.table-list th { text-align: left }
.table-list input[type="checkbox"] {margin:0; vertical-align: bottom;}
.messages .msg {
padding: 0.5em;
border: 1px solid #bababa;
}
.messages .error { background: #ffc9c9;}
.messages .info { background: #ccffc9;}
.messages .critical { background: #ff8a5c;}
ul.errorlist { margin:0.1em 0; }
ul.errorlist li { background: #ffc5b3; list-style: none; margin: 0; padding: 0.2em 0.5em; }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More