meilenstein

This commit is contained in:
Daniel Grams 2020-06-14 19:46:37 +02:00
parent fc64b55dfd
commit a151f3a22a
20 changed files with 1059 additions and 26 deletions

View File

@ -4,6 +4,17 @@ python manage.py db init
python manage.py db migrate
python manage.py db upgrade
## Developemt !!!
python manage.py db history
python manage.py db downgrade
// reset git: migrations/versions
python manage.py db migrate
python manage.py db upgrade
## Kill local detached server
lsof -i :5000
kill -9 PIDNUMBER
# i18n
https://pythonhosted.org/Flask-BabelEx/

369
app.py
View File

@ -1,8 +1,13 @@
import os
from flask import Flask, render_template, request
from flask import Flask, render_template, request, url_for, redirect
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import joinedload
from flask_security import Security, current_user, auth_required, roles_required, hash_password, SQLAlchemySessionUserDatastore
from flask_babelex import Babel, gettext, lazy_gettext
from flask_security.utils import FsPermNeed
from flask_babelex import Babel, gettext, lazy_gettext, format_datetime
from flask_principal import Permission
from datetime import datetime
import pytz
# Create app
app = Flask(__name__)
@ -30,7 +35,7 @@ db = SQLAlchemy(app)
# Setup Flask-Security
# Define models
from models import User, Role
from models import User, Role, AdminUnit, AdminUnitMember, AdminUnitMemberRole, OrgMember, OrgMemberRole, Organization, AdminUnitOrg, AdminUnitOrgRole, Event, EventDate
user_datastore = SQLAlchemySessionUserDatastore(db.session, User, Role)
security = Security(app, user_datastore)
@ -39,32 +44,369 @@ def get_locale():
return request.accept_languages.best_match(app.config['LANGUAGES'])
# Create a user to test with
def upsert_user(email, password="password"):
result = user_datastore.find_user(email=email)
if result is None:
result = user_datastore.create_user(email=email, password=hash_password(password))
return result
def upsert_admin_unit(unit_name):
admin_unit = AdminUnit.query.filter_by(name = unit_name).first()
if admin_unit is None:
admin_unit = AdminUnit(name = unit_name)
db.session.add(admin_unit)
return admin_unit
def get_admin_unit(unit_name):
return AdminUnit.query.filter_by(name = unit_name).first()
def upsert_org_member_role(role_name, permissions):
result = OrgMemberRole.query.filter_by(name = role_name).first()
if result is None:
result = OrgMemberRole(name = role_name)
result.remove_permissions(result.get_permissions())
result.add_permissions(permissions)
db.session.add(result)
return result
def upsert_admin_unit_member_role(role_name, permissions):
result = AdminUnitMemberRole.query.filter_by(name = role_name).first()
if result is None:
result = AdminUnitMemberRole(name = role_name)
db.session.add(result)
result.remove_permissions(result.get_permissions())
result.add_permissions(permissions)
return result
def upsert_admin_unit_org_role(role_name, permissions):
result = AdminUnitOrgRole.query.filter_by(name = role_name).first()
if result is None:
result = AdminUnitOrgRole(name = role_name)
db.session.add(result)
result.remove_permissions(result.get_permissions())
result.add_permissions(permissions)
return result
def add_user_to_organization(user, organization):
result = OrgMember.query.with_parent(organization).filter_by(user_id = user.id).first()
if result is None:
result = OrgMember(user = user, organization_id = organization.id)
organization.members.append(result)
db.session.add(result)
return result
def add_user_to_admin_unit(user, admin_unit):
result = OrgMember.query.with_parent(admin_unit).filter_by(user_id = user.id).first()
if result is None:
result = OrgMember(user = user)
admin_unit.members.append(result)
db.session.add(result)
return result
def add_user_to_admin_unit(user, admin_unit):
result = AdminUnitMember.query.with_parent(admin_unit).filter_by(user_id = user.id).first()
if result is None:
result = AdminUnitMember(user = user, admin_unit_id=admin_unit.id)
admin_unit.members.append(result)
db.session.add(result)
return result
def add_organization_to_admin_unit(organization, admin_unit):
result = AdminUnitOrg.query.with_parent(admin_unit).filter_by(organization_id = organization.id).first()
if result is None:
result = AdminUnitOrg(organization = organization, admin_unit_id=admin_unit.id)
admin_unit.organizations.append(result)
db.session.add(result)
return result
def add_role_to_admin_unit_org(admin_unit_org, role):
if AdminUnitOrgRole.query.with_parent(admin_unit_org).filter_by(name = role.name).first() is None:
admin_unit_org.roles.append(role)
def add_role_to_admin_unit_member(admin_unit_member, role):
if AdminUnitMemberRole.query.with_parent(admin_unit_member).filter_by(name = role.name).first() is None:
admin_unit_member.roles.append(role)
def add_role_to_org_member(org_member, role):
if OrgMemberRole.query.with_parent(org_member).filter_by(name = role.name).first() is None:
org_member.roles.append(role)
def upsert_organization(org_name):
result = Organization.query.filter_by(name = org_name).first()
if result is None:
result = Organization(name = org_name)
db.session.add(result)
return result
def create_berlin_date(year, month, day, hour, minute = 0):
return pytz.timezone('Europe/Berlin').localize(datetime(year, month, day, hour=hour, minute=minute))
def upsert_event(event_name, host, location, start, description, link = None, verified = False):
result = Event().query.filter_by(name = event_name).first()
if result is None:
result = Event()
result.name = event_name
result.host = host
result.location = location
result.description = description
result.external_link = link
result.admin_unit = get_admin_unit('Stadt Goslar')
result.verified = verified
eventDate = EventDate(event_id = result.id, start=start)
result.dates.append(eventDate)
db.session.add(result)
return result
def has_admin_unit_member_permission(admin_unit_id, permission):
admin_unit_member = AdminUnitMember.query.filter_by(admin_unit_id=admin_unit_id, user_id=current_user.id).first()
if admin_unit_member is not None:
for role in admin_unit_member.roles:
if permission in role.get_permissions():
return True
return False
def can_list_admin_unit_members(admin_unit):
if not current_user.is_authenticated:
return False
# User permission, e.g. user is global admin
user_perm = Permission(FsPermNeed('admin_unit.members:read'))
if user_perm.can():
return True
if has_admin_unit_member_permission(admin_unit.id, 'admin_unit.members:read'):
return True
return False
def can_list_org_members(organization):
if not current_user.is_authenticated:
return False
# User permission, e.g. user is global admin
user_perm = Permission(FsPermNeed('organization.members:read'))
if user_perm.can():
return True
return True # todo
def can_verify_event(event):
if not current_user.is_authenticated:
return False
# User permission, e.g. user is global admin
user_perm = Permission(FsPermNeed('event:verify'))
if user_perm.can():
return True
# Admin unit member permissions (Holger, Artur)
if has_admin_unit_member_permission(event.admin_unit_id, 'event:verify'):
return True
# Event has Admin Unit
# Admin Unit has organization members with roles with permission 'event:verify'
# This organization has members with roles with permission 'event:verify'
# Der aktuelle nutzer muss unter diesen nutzern sein
admin_unit_orgs = AdminUnitOrg.query.filter_by(admin_unit_id=event.admin_unit_id).all()
for admin_unit_org in admin_unit_orgs:
for admin_unit_org_role in admin_unit_org.roles:
if 'event:verify' in admin_unit_org_role.get_permissions():
org_member = OrgMember.query.filter_by(organization_id=admin_unit_org.organization_id, user_id=current_user.id).first()
if org_member is not None:
for org_member_role in org_member.roles:
if 'event:verify' in org_member_role.get_permissions():
return True
return False
@app.before_first_request
def create_user():
admin_role = user_datastore.find_or_create_role("admin", permissions=["user_create"])
# Admin units
goslar = upsert_admin_unit('Stadt Goslar')
upsert_admin_unit('Bad Harzburg')
upsert_admin_unit('Clausthal')
upsert_admin_unit('Walkenried')
upsert_admin_unit('Bad Lauterberg')
upsert_admin_unit('Harzgerode')
upsert_admin_unit('Ilsenburg')
upsert_admin_unit('Osterode')
upsert_admin_unit('Quedlinburg')
upsert_admin_unit('Wernigerode')
upsert_admin_unit('Halberstadt')
upsert_admin_unit('Wennigsen')
upsert_admin_unit('Hildesheim')
admin_user = user_datastore.find_user(email="grams.daniel@gmail.com")
if admin_user is None:
admin_user = user_datastore.create_user(email="grams.daniel@gmail.com", password=hash_password("password"))
# Organizations
admin_unit_org_event_verifier_role = upsert_admin_unit_org_role('event_verifier', ['event:verify'])
gmg = upsert_organization("GOSLAR marketing gmbh")
gz = upsert_organization("Goslarsche Zeitung")
user_datastore.add_role_to_user(admin_user, admin_role)
gmg_admin_unit_org = add_organization_to_admin_unit(gmg, goslar)
add_role_to_admin_unit_org(gmg_admin_unit_org, admin_unit_org_event_verifier_role)
normal_user = user_datastore.find_user(email="normal.user@gmail.com")
if normal_user is None:
normal_user = user_datastore.create_user(email="normal.user@gmail.com", password=hash_password("password"))
gz_admin_unit_org = add_organization_to_admin_unit(gz, goslar)
add_role_to_admin_unit_org(gz_admin_unit_org, admin_unit_org_event_verifier_role)
# Users
admin_role = user_datastore.find_or_create_role("admin")
admin_role.add_permissions(["user:create", "event:verify", "admin_unit.members:read", "organization.members:read"])
admin_unit_admin_role = upsert_admin_unit_member_role('admin', ["admin_unit.members:read"])
admin_unit_event_verifier_role = upsert_admin_unit_member_role('event_verifier', ["event:verify"])
org_member_event_verifier_role = upsert_org_member_role('event_verifier', ["event:verify"])
daniel = upsert_user("grams.daniel@gmail.com")
user_datastore.add_role_to_user(daniel, admin_role)
holger = upsert_user("holger@test.de")
holger_goslar_member = add_user_to_admin_unit(holger, goslar)
add_role_to_admin_unit_member(holger_goslar_member, admin_unit_admin_role)
add_role_to_admin_unit_member(holger_goslar_member, admin_unit_event_verifier_role)
artur = upsert_user("artur@test.de")
artur_goslar_member = add_user_to_admin_unit(artur, goslar)
add_role_to_admin_unit_member(artur_goslar_member, admin_unit_event_verifier_role)
mia = upsert_user("mia@test.de")
mia_gmg_member = add_user_to_organization(mia, gmg)
add_role_to_org_member(mia_gmg_member, org_member_event_verifier_role)
tom = upsert_user("tom@test.de")
tom_gz_member = add_user_to_organization(tom, gz)
add_role_to_org_member(tom_gz_member, org_member_event_verifier_role)
# Events
berlin = pytz.timezone('Europe/Berlin')
upsert_event("Vienenburger Seefest",
"Stadt Goslar",
"Vienenburger See",
create_berlin_date(2020, 8, 14, 17, 0),
'Vom 14. bis 16. August 2020 findet im ausgewiesenen Naherholungsgebiet am Fuße des Harzes, dem Goslarer Ortsteil Vienenburg, das Seefest unter dem Motto „Feuer & Wasser“ statt.',
'https://www.goslar.de/kultur-freizeit/veranstaltungen/156-vienenburger-seefest?layout=*',
True)
upsert_event("Tausend Schritte durch die Altstadt",
"Stadt Goslar",
"Tourist-Information Goslar",
create_berlin_date(2020, 9, 1, 10, 0),
'Tausend Schritte durch die Altstadt Erleben Sie einen geführten Stadtrundgang durch den historischen Stadtkern. Lassen Sie sich von Fachwerkromantik und kaiserlichen Bauten inmitten der UNESCO-Welterbestätte verzaubern. ganzjährig (außer 01.01.) täglich 10:00 Uhr Treffpunkt: Tourist-Information am Marktplatz (Dauer ca. 2 Std.) Erwachsene 8,00 Euro Inhaber Gastkarte Goslar/Kurkarte Hahnenklee 7,00 Euro Schüler/Studenten 6,00 Euro')
upsert_event("Spaziergang am Nachmittag",
"Stadt Goslar",
"Tourist-Information Goslar",
create_berlin_date(2020, 9, 1, 13, 30),
'Spaziergang am Nachmittag Begeben Sie sich auf einen geführten Rundgang durch die historische Altstadt. Entdecken Sie malerische Fachwerkgassen und imposante Bauwerke bei einem Streifzug durch das UNESCO-Weltkulturerbe. April Oktober und 25.11. 30.12. Montag Samstag 13:30 Uhr Treffpunkt: Tourist-Information am Marktplatz (Dauer ca. 1,5 Std.) Erwachsene 7,00 Euro Inhaber Gastkarte Goslar/Kurkarte Hahnenklee 6,00 Euro Schüler/Studenten 5,00 Euro')
upsert_event("Ein Blick hinter die Kulissen - Rathausbaustelle",
"Stadt Goslar",
"Nagelkopf am Rathaus",
create_berlin_date(2020, 9, 3, 17, 0),
'Allen interessierten Bürgern wird die Möglichkeit geboten, unter fachkundiger Führung des Goslarer Gebäudemanagement (GGM) einen Blick hinter die Kulissen durch das derzeit gesperrte historische Rathaus und die Baustelle Kulturmarktplatz zu werfen. Da bei beiden Führungen die Anzahl der Teilnehmer auf 16 Personen begrenzt ist, ist eine Anmeldung unbedingt notwendig sowie festes Schuhhwerk. Bitte melden Sie sich bei Interesse in der Tourist-Information (Tel. 05321-78060) an. Kinder unter 18 Jahren sind aus Sicherheitsgründen auf der Baustelle nicht zugelassen.')
upsert_event("Wöltingerode unter Dampf",
"Kloster Wöltingerode",
"Kloster Wöltingerode",
create_berlin_date(2020, 9, 5, 12, 0),
'Mit einem ländlichen Programm rund um historische Trecker und Landmaschinen, köstliche Produkte aus Brennerei und Region, einem Kunsthandwerkermarkt und besonderen Führungen findet das Hoffest auf dem Klostergut statt.',
'https://www.woelti-unter-dampf.de')
upsert_event("Altstadtfest",
"GOSLAR marketing gmbh",
"Goslar",
create_berlin_date(2020, 9, 11, 15, 0),
'Drei Tage lang dürfen sich die Besucher des Goslarer Altstadtfestes auf ein unterhaltsames und abwechslungsreiches Veranstaltungsprogramm freuen. Der Flohmarkt auf der Kaiserpfalzwiese lädt zum Stöbern vor historischer Kulisse ein.',
'https://www.goslar.de/kultur-freizeit/veranstaltungen/altstadtfest')
upsert_event("Adventsmarkt auf der Vienenburg",
"Vienenburg",
"Vienenburg",
create_berlin_date(2020, 12, 13, 12, 0),
'Inmitten der mittelalterlichen Burg mit dem restaurierten Burgfried findet der „Advent auf der Burg“ statt. Der Adventsmarkt wird von gemeinnützigen und sozialen Vereinen sowie den Kirchen ausgerichtet.')
db.session.commit()
# Views
@app.route("/")
def home():
return render_template('home.html')
admin_unit_members = AdminUnitMember.query.filter_by(user_id = current_user.id).all() if current_user.is_authenticated else None
organization_members = OrgMember.query.filter_by(user_id = current_user.id).all() if current_user.is_authenticated else None
return render_template('home.html',
admin_unit_members=admin_unit_members,
organization_members=organization_members)
@app.route("/admin_units")
def admin_units():
return render_template('admin_units.html',
admin_units=AdminUnit.query.all())
@app.route('/admin_unit/<int:admin_unit_id>')
def admin_unit(admin_unit_id):
admin_unit = AdminUnit.query.filter_by(id = admin_unit_id).first()
current_user_member = AdminUnitMember.query.with_parent(admin_unit).filter_by(user_id = current_user.id).first() if current_user.is_authenticated else None
return render_template('admin_unit.html',
admin_unit=admin_unit,
current_user_member=current_user_member,
can_list_admin_unit_members=can_list_admin_unit_members(admin_unit))
@app.route("/organizations")
def organizations():
return render_template('organizations.html',
organizations=Organization.query.all())
@app.route('/organization/<int:organization_id>')
def organization(organization_id):
organization = Organization.query.filter_by(id = organization_id).first()
current_user_member = OrgMember.query.with_parent(organization).filter_by(user_id = current_user.id).first() if current_user.is_authenticated else None
return render_template('organization.html',
organization=organization,
current_user_member=current_user_member,
can_list_members=can_list_org_members(organization))
@app.route("/profile")
@auth_required()
def profile():
return render_template('profile.html')
@app.route("/events")
def events():
events = Event.query.all()
return render_template('events.html', events=events)
@app.route('/event/<int:event_id>', methods=('GET', 'POST'))
def event(event_id):
event = Event.query.filter_by(id = event_id).first()
user_can_verify_event = can_verify_event(event)
if user_can_verify_event and request.method == 'POST':
action = request.form['action']
if action == 'verify':
event.verified = True
elif action == 'unverify':
event.verified = False
db.session.commit()
return render_template('event.html', event=event, user_can_verify_event=user_can_verify_event)
from forms.create_event import CreateEventForm
@app.route("/events/create", methods=('GET', 'POST'))
def create_event():
form = CreateEventForm()
if form.validate_on_submit():
event = Event()
form.populate_obj(event)
event.admin_unit = get_admin_unit('Stadt Goslar')
eventDate = EventDate(event_id = event.id, start=form.start.data)
event.dates.append(eventDate)
db.session.commit()
return redirect(url_for('events'))
return render_template('create_event.html', form=form)
@app.route("/admin")
@roles_required("admin")
def admin():
@ -73,7 +415,8 @@ def admin():
@app.route("/admin/admin_units")
@roles_required("admin")
def admin_admin_units():
return render_template('admin/admin_units.html')
return render_template('admin/admin_units.html',
admin_units=AdminUnit.query.all())
if __name__ == '__main__':
app.run()

0
forms/__init__.py Normal file
View File

13
forms/create_event.py Normal file
View File

@ -0,0 +1,13 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField
from wtforms.fields.html5 import DateTimeLocalField
from wtforms.validators import DataRequired, Optional
class CreateEventForm(FlaskForm):
submit = SubmitField("Create event")
host = StringField('Host', validators=[DataRequired()])
name = StringField('Name', validators=[DataRequired()])
description = TextAreaField('Description', validators=[DataRequired()])
start = DateTimeLocalField('Start', format='%Y-%m-%dT%H:%M', validators=[DataRequired()])
location = StringField('Location', validators=[DataRequired()])
external_link = StringField('Link URL', validators=[Optional()])

View File

@ -1,4 +1,4 @@
from flask_script import Manager
from flask_script import Manager, Command
from flask_migrate import Migrate, MigrateCommand
from app import app, db

View File

@ -0,0 +1,142 @@
"""empty message
Revision ID: a2ba246b23d0
Revises: 36945eb387b4
Create Date: 2020-06-14 19:39:43.809236
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a2ba246b23d0'
down_revision = '36945eb387b4'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('adminunit',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('adminunitmemberrole',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=80), nullable=True),
sa.Column('description', sa.String(length=255), nullable=True),
sa.Column('permissions', sa.UnicodeText(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('adminunitorgrole',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=80), nullable=True),
sa.Column('description', sa.String(length=255), nullable=True),
sa.Column('permissions', sa.UnicodeText(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('organization',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('orgmemberrole',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=80), nullable=True),
sa.Column('description', sa.String(length=255), nullable=True),
sa.Column('permissions', sa.UnicodeText(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('adminunitmember',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('admin_unit_id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['admin_unit_id'], ['adminunit.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('adminunitorg',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('admin_unit_id', sa.Integer(), nullable=False),
sa.Column('organization_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['admin_unit_id'], ['adminunit.id'], ),
sa.ForeignKeyConstraint(['organization_id'], ['organization.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('event',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('admin_unit_id', sa.Integer(), nullable=False),
sa.Column('host', sa.Unicode(length=255), nullable=False),
sa.Column('external_link', sa.String(length=255), nullable=True),
sa.Column('location', sa.Unicode(length=255), nullable=False),
sa.Column('name', sa.Unicode(length=255), nullable=False),
sa.Column('description', sa.UnicodeText(), nullable=False),
sa.Column('verified', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['admin_unit_id'], ['adminunit.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('orgmember',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('organization_id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['organization_id'], ['organization.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('adminunitmemberroles_members',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('member_id', sa.Integer(), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['member_id'], ['adminunitmember.id'], ),
sa.ForeignKeyConstraint(['role_id'], ['adminunitmemberrole.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('adminunitorgroles_organizations',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('admin_unit_org_id', sa.Integer(), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['admin_unit_org_id'], ['adminunitorg.id'], ),
sa.ForeignKeyConstraint(['role_id'], ['adminunitorgrole.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('eventdate',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('event_id', sa.Integer(), nullable=False),
sa.Column('start', sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(['event_id'], ['event.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('orgmemberroles_members',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('member_id', sa.Integer(), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['member_id'], ['orgmember.id'], ),
sa.ForeignKeyConstraint(['role_id'], ['orgmemberrole.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('orgmemberroles_members')
op.drop_table('eventdate')
op.drop_table('adminunitorgroles_organizations')
op.drop_table('adminunitmemberroles_members')
op.drop_table('orgmember')
op.drop_table('event')
op.drop_table('adminunitorg')
op.drop_table('adminunitmember')
op.drop_table('orgmemberrole')
op.drop_table('organization')
op.drop_table('adminunitorgrole')
op.drop_table('adminunitmemberrole')
op.drop_table('adminunit')
# ### end Alembic commands ###

119
models.py
View File

@ -1,8 +1,10 @@
from app import db
from sqlalchemy.orm import relationship, backref
from sqlalchemy import Boolean, DateTime, Column, Integer, String, ForeignKey, UnicodeText
from sqlalchemy import Boolean, DateTime, Column, Integer, String, ForeignKey, Unicode, UnicodeText
from flask_security import UserMixin, RoleMixin
### User
class RolesUsers(db.Model):
__tablename__ = 'roles_users'
id = Column(Integer(), primary_key=True)
@ -31,4 +33,117 @@ class User(db.Model, UserMixin):
fs_uniquifier = Column(String(255))
confirmed_at = Column(DateTime())
roles = relationship('Role', secondary='roles_users',
backref=backref('users', lazy='dynamic'))
backref=backref('users', lazy='dynamic'))
### Organization
class OrgMemberRolesMembers(db.Model):
__tablename__ = 'orgmemberroles_members'
id = Column(Integer(), primary_key=True)
member_id = Column('member_id', Integer(), ForeignKey('orgmember.id'))
role_id = Column('role_id', Integer(), ForeignKey('orgmemberrole.id'))
class OrgMemberRole(db.Model, RoleMixin):
__tablename__ = 'orgmemberrole'
id = Column(Integer(), primary_key=True)
name = Column(String(80), unique=True)
description = Column(String(255))
permissions = Column(UnicodeText())
class OrgMember(db.Model):
__tablename__ = 'orgmember'
id = Column(Integer(), primary_key=True)
organization_id = db.Column(db.Integer, db.ForeignKey('organization.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
user = db.relationship('User', backref=db.backref('orgmembers', lazy=True))
roles = relationship('OrgMemberRole', secondary='orgmemberroles_members',
backref=backref('members', lazy='dynamic'))
class Organization(db.Model):
__tablename__ = 'organization'
id = Column(Integer(), primary_key=True)
name = Column(String(255), unique=True)
members = relationship('OrgMember', backref=backref('organization', lazy=True))
### Admin Unit
class AdminUnitMemberRolesMembers(db.Model):
__tablename__ = 'adminunitmemberroles_members'
id = Column(Integer(), primary_key=True)
member_id = Column('member_id', Integer(), ForeignKey('adminunitmember.id'))
role_id = Column('role_id', Integer(), ForeignKey('adminunitmemberrole.id'))
class AdminUnitMemberRole(db.Model, RoleMixin):
__tablename__ = 'adminunitmemberrole'
id = Column(Integer(), primary_key=True)
name = Column(String(80), unique=True)
description = Column(String(255))
permissions = Column(UnicodeText())
class AdminUnitMember(db.Model):
__tablename__ = 'adminunitmember'
id = Column(Integer(), primary_key=True)
admin_unit_id = db.Column(db.Integer, db.ForeignKey('adminunit.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
user = db.relationship('User', backref=db.backref('adminunitmembers', lazy=True))
roles = relationship('AdminUnitMemberRole', secondary='adminunitmemberroles_members',
backref=backref('members', lazy='dynamic'))
class AdminUnitOrgRoleOrganizations(db.Model):
__tablename__ = 'adminunitorgroles_organizations'
id = Column(Integer(), primary_key=True)
admin_unit_org_id = Column('admin_unit_org_id', Integer(), ForeignKey('adminunitorg.id'))
role_id = Column('role_id', Integer(), ForeignKey('adminunitorgrole.id'))
class AdminUnitOrgRole(db.Model, RoleMixin):
__tablename__ = 'adminunitorgrole'
id = Column(Integer(), primary_key=True)
name = Column(String(80), unique=True)
description = Column(String(255))
permissions = Column(UnicodeText())
class AdminUnitOrg(db.Model):
__tablename__ = 'adminunitorg'
id = Column(Integer(), primary_key=True)
admin_unit_id = db.Column(db.Integer, db.ForeignKey('adminunit.id'), nullable=False)
organization_id = db.Column(db.Integer, db.ForeignKey('organization.id'), nullable=False)
organization = db.relationship('Organization', backref=db.backref('adminunitorgs', lazy=True))
roles = relationship('AdminUnitOrgRole', secondary='adminunitorgroles_organizations',
backref=backref('organizations', lazy='dynamic'))
class AdminUnit(db.Model):
__tablename__ = 'adminunit'
id = Column(Integer(), primary_key=True)
name = Column(String(255), unique=True)
members = relationship('AdminUnitMember', backref=backref('adminunit', lazy=True))
organizations = relationship('AdminUnitOrg', backref=backref('adminunit', lazy=True))
# Events
class Event(db.Model):
__tablename__ = 'event'
id = Column(Integer(), primary_key=True)
admin_unit_id = db.Column(db.Integer, db.ForeignKey('adminunit.id'), nullable=False)
admin_unit = db.relationship('AdminUnit', backref=db.backref('events', lazy=True))
host = Column(Unicode(255), nullable=False) # org|adminunit|string
#co_hosts: -"-
#format: real|online
external_link = Column(String(255))
#ticket_link = Column(String(255))
location = Column(Unicode(255), nullable=False) # place|address
dates = relationship('EventDate', backref=backref('event', lazy=False))
name = Column(Unicode(255), nullable=False)
description = Column(UnicodeText(), nullable=False)
#photo: image(1200x628)
#category: relationship, nullable=False
#keywords = Column(String(255)) oder liste?
#kid_friendly: bool
verified = Column(Boolean())
# (Multiple Events möglich, wiederholend oder frei, dann aber mit endzeit)
class EventDate(db.Model):
__tablename__ = 'eventdate'
id = Column(Integer(), primary_key=True)
event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False)
start = db.Column(db.DateTime(timezone=True), nullable=False)
#end: date_time

View File

@ -1,7 +1,7 @@
h1 {
font-size: 1.8rem;
margin-bottom: 1.8rem;
font-size: 1.6rem;
margin: 1rem 0 2rem;
}
.navbar {
@ -40,4 +40,40 @@ footer {
/* background-color: #009688;
color: white; */
font-size: 1.5rem;
}
tr.collapse.in {
display:table-row;
}
tr.table-borderless td {
border:0;
padding:0;
}
tr.table-line-through td {
text-decoration: line-through;
}
.text-toogle[aria-expanded=false] .text-expanded {
display: none;
}
.text-toogle[aria-expanded=true] .text-collapsed {
display: none;
}
.table td.fit,
.table th.fit {
white-space: nowrap;
width: 1%;
}
.ui-autocomplete {
z-index: 1025 !important;
}
@media (max-width: 320px) {
td, th {
word-break: break-all;
}
}

View File

@ -11,7 +11,10 @@
</nav>
<div class="list-group">
<a href="{{ url_for('admin_admin_units') }}" class="list-group-item list-group-item-action">{{ _('Admin Units') }}</a>
<a href="{{ url_for('admin_admin_units') }}" class="list-group-item list-group-item-action">
{{ _('Admin Units') }}
<i class="fa fa-caret-right"></i>
</a>
</div>
{% endblock %}

View File

@ -11,5 +11,21 @@
</ol>
</nav>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover table-striped">
<thead>
<tr>
<th>{{ _('Name') }}</th>
</tr>
</thead>
<tbody>
{% for admin_unit in admin_units %}
<tr>
<td>{{ admin_unit.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

58
templates/admin_unit.html Normal file
View File

@ -0,0 +1,58 @@
{% extends "layout.html" %}
{% block title %}
{{ admin_unit.name }}
{% endblock %}
{% block content %}
<h1>{{ admin_unit.name }}</h1>
{% if current_user_member %}
<div class="my-4">
{{ _('You are a member of this admin unit.') }}
({% for role in current_user_member.roles %}{{ role.name }}{%if not loop.last %}, {% endif %}{% endfor %})
</div>
{% endif %}
{% if can_list_admin_unit_members %}
<p class="h5 mt-5">{{ _('Members') }}</p>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover table-striped">
<thead>
<tr>
<th>{{ _('Name') }}</th>
<th>{{ _('Roles') }}</th>
</tr>
</thead>
<tbody>
{% for member in admin_unit.members %}
<tr>
<td>{{ member.user.email }}</td>
<td>{% for role in member.roles %}{{ role.name }}{%if not loop.last %}, {% endif %}{% endfor %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<p class="h5 mt-5">{{ _('Organizations') }}</p>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover table-striped">
<thead>
<tr>
<th>{{ _('Name') }}</th>
<th>{{ _('Roles') }}</th>
</tr>
</thead>
<tbody>
{% for admin_unit_org in admin_unit.organizations %}
<tr>
<td><a href="{{ url_for('organization', organization_id=admin_unit_org.organization_id) }}">{{ admin_unit_org.organization.name }}</a></td>
<td>{% for role in admin_unit_org.roles %}{{ role.name }}{%if not loop.last %}, {% endif %}{% endfor %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -0,0 +1,26 @@
{% extends "layout.html" %}
{% block title %}
{{ _('Admin Units') }}
{% endblock %}
{% block content %}
<h1>{{ _('Admin Units') }}</h1>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover table-striped">
<thead>
<tr>
<th>{{ _('Name') }}</th>
</tr>
</thead>
<tbody>
{% for admin_unit in admin_units %}
<tr>
<td><a href="{{ url_for('admin_unit', admin_unit_id=admin_unit.id) }}">{{ admin_unit.name }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field_with_errors, render_field %}
{% block content %}
<h1>{{ _('Create event') }}</h1>
<form action="{{ url_for('create_event') }}" method="POST">
{{ form.hidden_tag() }}
{{ render_field_with_errors(form.host) }}
{{ render_field_with_errors(form.name) }}
{{ render_field_with_errors(form.description) }}
{{ render_field_with_errors(form.start) }}
{{ render_field_with_errors(form.location) }}
{{ render_field_with_errors(form.external_link) }}
{{ render_field(form.submit) }}
</form>
{% endblock %}

44
templates/event.html Normal file
View File

@ -0,0 +1,44 @@
{% extends "layout.html" %}
{% block title %}
{{ event.name }}
{% endblock %}
{% block content %}
<h1>{{ event.name }}</h1>
{% if user_can_verify_event %}
<div class="my-4">
<form action="{{ url_for('event', event_id=event.id) }}" method="POST">
{% if event.verified %}
<input name="action" type="hidden" value="unverify">
<p><input class="btn btn-danger" name="submit" type="submit" value="{{ _('Mark event as unverified') }}"></p>
{% else %}
<input name="action" type="hidden" value="verify">
<p><input class="btn btn-success" name="submit" type="submit" value="{{ _('Mark event as verified') }}"></p>
{% endif %}
</form>
</div>
{% endif %}
<div class="my-4">
<div><i class="fa fa-fw fa-calendar" data-toggle="tooltip" title="{{ _('Date') }}"></i> {{ event.dates[0].start | datetimeformat }}</div>
<div><i class="fa fa-fw fa-map-marker" data-toggle="tooltip" title="{{ _('Location') }}"></i> {{ event.location }}</div>
{% if event.verified %}
<div><i class="fa fa-fw fa-check-circle text-success" data-toggle="tooltip" title="{{ _('Verified') }}"></i> {{ _('Verified') }}</div>
{% endif %}
</div>
<div class="my-4">{{ event.description }}</div>
{% if event.external_link %}
<div class="my-4">
<i class="fa fa-fw fa-external-link" data-toggle="tooltip" title="{{ _('Link') }}"></i>
<a href="{{ event.external_link }}">{{ event.external_link }}</a>
</div>
{% endif %}
<div class="my-4">
<div><i class="fa fa-fw fa-users" data-toggle="tooltip" title="{{ _('Host') }}"></i> {{ event.host }}</div>
</div>
{% endblock %}

41
templates/events.html Normal file
View File

@ -0,0 +1,41 @@
{% extends "layout.html" %}
{% block title %}
{{ _('Events') }}
{% endblock %}
{% block content %}
<h1>{{ _('Events') }}</h1>
<div class="my-4">
<a class="btn btn-primary" href="{{ url_for('create_event') }}" role="button"><i class="fa fa-plus"></i> {{ _('Create event') }}</a>
</div>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover table-striped">
<thead>
<tr>
<th>{{ _('Date') }}</th>
<th>{{ _('Name') }}</th>
<th>{{ _('Host') }}</th>
<th>{{ _('Location') }}</th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr>
<td>{{ event.dates[0].start | datetimeformat }}</td>
<td>
<a href="{{ url_for('event', event_id=event.id) }}">{{ event.name }}</a>
{% if event.verified %}
<i class="fa fa-check-circle text-success" data-toggle="tooltip" title="{{ _('Verified') }}"></i>
{% endif %}
</td>
<td>{{ event.host }}</td>
<td>{{ event.location }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -4,6 +4,58 @@ Prototyp
{% endblock %}
{% block content %}
{{ _('Hi there!') }}
<h1>{{ _('Hi there!') }}</h1>
<!--
<div class="list-group">
<a href="{{ url_for('events') }}" class="list-group-item list-group-item-action">
{{ _('Events') }}
<i class="fa fa-caret-right"></i>
</a>
</div>-->
{% if admin_unit_members %}
<p class="h5 mt-5">{{ _('Your Admin Units') }}</p>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover table-striped">
<thead>
<tr>
<th>{{ _('Name') }}</th>
<th>{{ _('Roles') }}</th>
</tr>
</thead>
<tbody>
{% for member in admin_unit_members %}
<tr>
<td><a href="{{ url_for('admin_unit', admin_unit_id=member.adminunit.id) }}">{{ member.adminunit.name }}</a></td>
<td>{% for role in member.roles %}{{ role.name }}{%if not loop.last %}, {% endif %}{% endfor %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if organization_members %}
<p class="h5 mt-5">{{ _('Your Organizations') }}</p>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover table-striped">
<thead>
<tr>
<th>{{ _('Name') }}</th>
<th>{{ _('Roles') }}</th>
</tr>
</thead>
<tbody>
{% for member in organization_members %}
<tr>
<td>{{ member.organization.name }}</td>
<td>{% for role in member.roles %}{{ role.name }}{%if not loop.last %}, {% endif %}{% endfor %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock %}

View File

@ -27,6 +27,10 @@
<script>
moment.locale('de')
$( function() {
$('[data-toggle="tooltip"]').tooltip();
})
</script>
{% block header %}
@ -36,7 +40,7 @@
<body{% block body_attribs %}{% endblock body_attribs %}>
{% block body -%}
{% block navbar %}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="{{ url_for('home') }}">
Prototyp
</a>
@ -44,15 +48,18 @@
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<div class="navbar-nav mr-auto">
<a class="nav-item nav-link" href="{{ url_for('admin_units') }}">{{ _('Admin Units') }}</a>
<a class="nav-item nav-link" href="{{ url_for('organizations') }}">{{ _('Organizations') }}</a>
<a class="nav-item nav-link" href="{{ url_for('events') }}">{{ _('Events') }}</a>
</div>
<div class="navbar-nav navbar-right">
{% if current_user.is_authenticated %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ _('You') }}
{{ current_user.email }}
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<h6 class="dropdown-header">{{ current_user.email }}</h6>
<a class="dropdown-item" href="{{ url_for('profile') }}">{{ _('Profile') }}</a>
{% if current_user.has_role('admin') %}

View File

@ -0,0 +1,38 @@
{% extends "layout.html" %}
{% block title %}
{{ organization.name }}
{% endblock %}
{% block content %}
<h1>{{ organization.name }}</h1>
{% if current_user_member %}
<div class="my-4">
{{ _('You are a member of this organization.') }}
({% for role in current_user_member.roles %}{{ role.name }}{%if not loop.last %}, {% endif %}{% endfor %})
</div>
{% endif %}
{% if can_list_members %}
<p class="h5 mt-5">{{ _('Members') }}</p>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover table-striped">
<thead>
<tr>
<th>{{ _('Name') }}</th>
<th>{{ _('Roles') }}</th>
</tr>
</thead>
<tbody>
{% for member in organization.members %}
<tr>
<td>{{ member.user.email }}</td>
<td>{% for role in member.roles %}{{ role.name }}{%if not loop.last %}, {% endif %}{% endfor %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,26 @@
{% extends "layout.html" %}
{% block title %}
{{ _('Organizations') }}
{% endblock %}
{% block content %}
<h1>{{ _('Organizations') }}</h1>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover table-striped">
<thead>
<tr>
<th>{{ _('Name') }}</th>
</tr>
</thead>
<tbody>
{% for organization in organizations %}
<tr>
<td><a href="{{ url_for('organization', organization_id=organization.id) }}">{{ organization.name }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -4,6 +4,50 @@
{% endblock %}
{% block content %}
{{ current_user.email }}
<h1>{{ current_user.email }}</h1>
{% if admin_unit_members %}
<p class="h5 mt-5">{{ _('Admin Units') }}</p>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover table-striped">
<thead>
<tr>
<th>{{ _('Name') }}</th>
<th>{{ _('Roles') }}</th>
</tr>
</thead>
<tbody>
{% for member in admin_unit_members %}
<tr>
<td><a href="{{ url_for('admin_unit', admin_unit_id=member.adminunit.id) }}">{{ member.adminunit.name }}</a></td>
<td>{% for role in member.roles %}{{ role.name }}{%if not loop.last %}, {% endif %}{% endfor %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if organization_members %}
<p class="h5 mt-5">{{ _('Organizations') }}</p>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover table-striped">
<thead>
<tr>
<th>{{ _('Name') }}</th>
<th>{{ _('Roles') }}</th>
</tr>
</thead>
<tbody>
{% for member in organization_members %}
<tr>
<td>{{ member.organization.name }}</td>
<td>{% for role in member.roles %}{{ role.name }}{%if not loop.last %}, {% endif %}{% endfor %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock %}