mirror of
https://github.com/lucaspalomodevelop/eventcally.git
synced 2026-03-13 00:07:22 +00:00
zwischenstand
This commit is contained in:
parent
9b189cbd07
commit
664e66bd1f
576
app.py
576
app.py
@ -1,13 +1,15 @@
|
||||
import os
|
||||
from flask import Flask, render_template, request, url_for, redirect
|
||||
from flask import Flask, render_template, request, url_for, redirect, abort, flash
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.sql import asc, func
|
||||
from flask_security import Security, current_user, auth_required, roles_required, hash_password, SQLAlchemySessionUserDatastore
|
||||
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
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
# Create app
|
||||
app = Flask(__name__)
|
||||
@ -30,12 +32,14 @@ app.config['BABEL_DEFAULT_LOCALE'] = 'de'
|
||||
app.config['BABEL_DEFAULT_TIMEZONE'] = 'Europe/Berlin'
|
||||
babel = Babel(app)
|
||||
|
||||
app.jinja_env.filters['quote_plus'] = lambda u: quote_plus(u)
|
||||
|
||||
# create db
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
# Setup Flask-Security
|
||||
# Define models
|
||||
from models import OrgOrAdminUnit, Actor, Place, Location, User, Role, AdminUnit, AdminUnitMember, AdminUnitMemberRole, OrgMember, OrgMemberRole, Organization, AdminUnitOrg, AdminUnitOrgRole, Event, EventDate
|
||||
from models import EventSuggestion, EventSuggestionDate, OrgOrAdminUnit, Actor, Place, Location, User, Role, AdminUnit, AdminUnitMember, AdminUnitMemberRole, OrgMember, OrgMemberRole, Organization, AdminUnitOrg, AdminUnitOrgRole, Event, EventDate
|
||||
user_datastore = SQLAlchemySessionUserDatastore(db.session, User, Role)
|
||||
security = Security(app, user_datastore)
|
||||
|
||||
@ -55,6 +59,8 @@ def upsert_admin_unit(unit_name):
|
||||
if admin_unit is None:
|
||||
admin_unit = AdminUnit(name = unit_name)
|
||||
db.session.add(admin_unit)
|
||||
|
||||
upsert_org_or_admin_unit_for_admin_unit(admin_unit)
|
||||
return admin_unit
|
||||
|
||||
def get_admin_unit(unit_name):
|
||||
@ -64,9 +70,10 @@ 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)
|
||||
|
||||
result.remove_permissions(result.get_permissions())
|
||||
result.add_permissions(permissions)
|
||||
return result
|
||||
|
||||
def upsert_admin_unit_member_role(role_name, permissions):
|
||||
@ -138,6 +145,8 @@ def upsert_organization(org_name):
|
||||
if result is None:
|
||||
result = Organization(name = org_name)
|
||||
db.session.add(result)
|
||||
|
||||
upsert_org_or_admin_unit_for_organization(result)
|
||||
return result
|
||||
|
||||
def create_berlin_date(year, month, day, hour, minute = 0):
|
||||
@ -150,29 +159,78 @@ def upsert_actor_for_admin_unit(admin_unit_id):
|
||||
db.session.add(result)
|
||||
return result
|
||||
|
||||
def upsert_org_or_admin_unit_for_admin_unit(admin_unit):
|
||||
result = OrgOrAdminUnit.query.filter_by(admin_unit_id = admin_unit.id).first()
|
||||
def upsert_org_or_admin_unit_for_admin_unit_id(admin_unit_id):
|
||||
result = OrgOrAdminUnit.query.filter_by(admin_unit_id = admin_unit_id).first()
|
||||
if result is None:
|
||||
result = OrgOrAdminUnit(admin_unit_id = admin_unit.id)
|
||||
result = OrgOrAdminUnit(admin_unit_id = admin_unit_id)
|
||||
db.session.add(result)
|
||||
return result
|
||||
|
||||
def upsert_org_or_admin_unit_for_admin_unit(admin_unit):
|
||||
return upsert_org_or_admin_unit_for_admin_unit_id(admin_unit.id)
|
||||
|
||||
def upsert_org_or_admin_unit_for_organization_id(organization_id):
|
||||
result = OrgOrAdminUnit.query.filter_by(organization_id = organization_id).first()
|
||||
if result is None:
|
||||
result = OrgOrAdminUnit(organization_id = organization_id)
|
||||
db.session.add(result)
|
||||
return result
|
||||
|
||||
def upsert_org_or_admin_unit_for_organization(organization):
|
||||
result = OrgOrAdminUnit.query.filter_by(organization_id = organization.id).first()
|
||||
return upsert_org_or_admin_unit_for_organization_id(organization.id)
|
||||
|
||||
def upsert_location(street, postalCode, city, latitude = 0, longitude = 0):
|
||||
result = Location.query.filter_by(street = street, postalCode=postalCode, city=city).first()
|
||||
if result is None:
|
||||
result = OrgOrAdminUnit(organization_id = organization.id)
|
||||
result = Location(street = street, postalCode=postalCode, city=city)
|
||||
db.session.add(result)
|
||||
|
||||
result.latitude = latitude
|
||||
result.longitude = longitude
|
||||
|
||||
return result
|
||||
|
||||
def upsert_place(name):
|
||||
def upsert_place(name, street = None, postalCode = None, city = None, latitude = 0, longitude = 0):
|
||||
result = Place.query.filter_by(name = name).first()
|
||||
if result is None:
|
||||
result = Place(name = name)
|
||||
db.session.add(result)
|
||||
|
||||
if city is not None:
|
||||
result.location = upsert_location(street, postalCode, city, latitude, longitude)
|
||||
|
||||
return result
|
||||
|
||||
def upsert_event(event_name, host, location_name, start, description, link = None, verified = False):
|
||||
admin_unit = get_admin_unit('Stadt Goslar')
|
||||
def upsert_event_suggestion(event_name, host_name, place_name, start, description, link = None, admin_unit = None):
|
||||
if admin_unit is None:
|
||||
admin_unit = get_admin_unit('Stadt Goslar')
|
||||
|
||||
result = EventSuggestion.query.filter_by(event_name = event_name).first()
|
||||
if result is None:
|
||||
result = EventSuggestion()
|
||||
db.session.add(result)
|
||||
|
||||
result.admin_unit = admin_unit
|
||||
result.event_name = event_name
|
||||
result.description = description
|
||||
result.external_link = link
|
||||
result.place_name = place_name
|
||||
result.host_name = host_name
|
||||
|
||||
result.place_postalCode = "Dummy postal code"
|
||||
result.place_city = "Dummy city"
|
||||
result.contact_name = "Dummy contact name"
|
||||
result.contact_email = "Dummy contact email"
|
||||
|
||||
eventDate = EventSuggestionDate(event_suggestion_id = result.id, start=start)
|
||||
result.dates = []
|
||||
result.dates.append(eventDate)
|
||||
|
||||
return result
|
||||
|
||||
def upsert_event(event_name, host, location_name, start, description, link = None, verified = False, admin_unit = None):
|
||||
if admin_unit is None:
|
||||
admin_unit = get_admin_unit('Stadt Goslar')
|
||||
place = upsert_place(location_name)
|
||||
|
||||
result = Event.query.filter_by(name = event_name).first()
|
||||
@ -194,25 +252,183 @@ def upsert_event(event_name, host, location_name, start, description, link = Non
|
||||
|
||||
return result
|
||||
|
||||
def has_admin_unit_member_permission(admin_unit_id, permission):
|
||||
def get_event_hosts():
|
||||
# User permission, e.g. user is global admin
|
||||
if has_current_user_permission('event:create'):
|
||||
return OrgOrAdminUnit.query.all()
|
||||
|
||||
# Admin unit member permissions (Holger, Artur)
|
||||
admin_units_the_user_is_member_of = admin_units_with_current_user_member_permission('event:create')
|
||||
|
||||
# Admin org permissions (Mia)
|
||||
admin_units_via_orgs = admin_units_with_current_user_org_member_permission('event:create', 'event:create')
|
||||
|
||||
# Org member permissions (Jason kann nur für Celtic Inn eintragen)
|
||||
organizations_the_user_is_member_of = organizations_with_current_user_org_member_permission('event:create')
|
||||
|
||||
# Combine
|
||||
admin_units = admin_units_the_user_is_member_of
|
||||
admin_units.extend(admin_units_via_orgs)
|
||||
|
||||
result = list()
|
||||
for admin_unit in admin_units:
|
||||
if not any(aao.id == admin_unit.org_or_adminunit.id for aao in result):
|
||||
result.append(admin_unit.org_or_adminunit)
|
||||
|
||||
for admin_unit_org in admin_unit.organizations:
|
||||
if not any(aao.id == admin_unit_org.organization.org_or_adminunit.id for aao in result):
|
||||
result.append(admin_unit_org.organization.org_or_adminunit)
|
||||
|
||||
for organization in organizations_the_user_is_member_of:
|
||||
if not any(aao.id == organization.org_or_adminunit.id for aao in result):
|
||||
result.append(organization.org_or_adminunit)
|
||||
|
||||
return result
|
||||
|
||||
def admin_units_from_aaos(aaos):
|
||||
result = list()
|
||||
|
||||
for aao in aaos:
|
||||
if aao.admin_unit is not None:
|
||||
result.append(aao.admin_unit)
|
||||
|
||||
return result
|
||||
|
||||
# General permission checks
|
||||
|
||||
def has_admin_unit_member_permission(admin_unit_member, permission):
|
||||
for role in admin_unit_member.roles:
|
||||
if permission in role.get_permissions():
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_admin_unit_org_permission(admin_unit_org, permission):
|
||||
for admin_unit_org_role in admin_unit_org.roles:
|
||||
if permission in admin_unit_org_role.get_permissions():
|
||||
return True
|
||||
|
||||
def has_org_member_permission(org_member, permission):
|
||||
for org_member_role in org_member.roles:
|
||||
if permission in org_member_role.get_permissions():
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_any_admin_unit_permission_for_organization(organization_id, permission):
|
||||
admin_unit_orgs = AdminUnitOrg.query.filter_by(organization_id=organization_id).all()
|
||||
for admin_unit_org in admin_unit_orgs:
|
||||
if has_admin_unit_org_permission(admin_unit_org, permission):
|
||||
return True
|
||||
return False
|
||||
|
||||
def admin_units_with_permission_for_organization(organization_id, permission):
|
||||
result = list()
|
||||
|
||||
admin_unit_orgs = AdminUnitOrg.query.filter_by(organization_id=organization_id).all()
|
||||
for admin_unit_org in admin_unit_orgs:
|
||||
if has_admin_unit_org_permission(admin_unit_org, permission):
|
||||
result.append(admin_unit_org.adminunit)
|
||||
|
||||
return result
|
||||
|
||||
def admin_units_for_organization(organization_id):
|
||||
result = list()
|
||||
|
||||
admin_unit_orgs = AdminUnitOrg.query.filter_by(organization_id=organization_id).all()
|
||||
for admin_unit_org in admin_unit_orgs:
|
||||
result.append(admin_unit_org.adminunit)
|
||||
|
||||
return result
|
||||
|
||||
# Current User permission
|
||||
|
||||
# User permission, e.g. user is global admin
|
||||
def has_current_user_permission(permission):
|
||||
user_perm = Permission(FsPermNeed(permission))
|
||||
if user_perm.can():
|
||||
return True
|
||||
|
||||
def has_current_user_member_permission_for_admin_unit(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
|
||||
if has_admin_unit_member_permission(admin_unit_member, permission):
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_current_user_member_permission_for_any_admin_unit(permission):
|
||||
admin_unit_members = AdminUnitMember.query.filter_by(user_id=current_user.id).all()
|
||||
for admin_unit_member in admin_unit_members:
|
||||
if has_admin_unit_member_permission(admin_unit_member, permission):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def admin_units_with_current_user_member_permission(permission):
|
||||
result = list()
|
||||
admin_unit_members = AdminUnitMember.query.filter_by(user_id=current_user.id).all()
|
||||
for admin_unit_member in admin_unit_members:
|
||||
if has_admin_unit_member_permission(admin_unit_member, permission):
|
||||
result.append(admin_unit_member.adminunit)
|
||||
|
||||
return result
|
||||
|
||||
def organizations_with_current_user_org_member_permission(permission):
|
||||
result = list()
|
||||
org_members = OrgMember.query.filter_by(user_id=current_user.id).all()
|
||||
for org_member in org_members:
|
||||
if has_org_member_permission(org_member, permission):
|
||||
result.append(org_member.organization)
|
||||
|
||||
return result
|
||||
|
||||
def is_current_user_member_of_organization(organization_id, permission = None):
|
||||
org_member = OrgMember.query.filter_by(user_id=current_user.id, organization_id=organization_id).first()
|
||||
return (org_member is not None) and (permission is None or has_org_member_permission(org_member, permission))
|
||||
|
||||
def admin_units_with_current_user_org_member_permission(org_member_permission, admit_unit_org_permission):
|
||||
result = list()
|
||||
|
||||
organizations = organizations_with_current_user_org_member_permission(org_member_permission)
|
||||
for organization in organizations:
|
||||
admin_units = admin_units_with_permission_for_organization(organization.id, admit_unit_org_permission)
|
||||
result.extend(admin_units)
|
||||
|
||||
return result
|
||||
|
||||
def has_current_user_admin_unit_member_permission_for_organization(organization_id, admin_unit_member_permission):
|
||||
admin_units = admin_units_for_organization(organization_id)
|
||||
for admin_unit in admin_units:
|
||||
if has_current_user_member_permission_for_admin_unit(admin_unit.id, admin_unit_member_permission):
|
||||
return True
|
||||
return False
|
||||
|
||||
# Ist der Nutzer in einer Organisation mit entsprechender Permission und
|
||||
# ist diese Organisation Teil einer Admin Unit mit der entsprechenden Permission?
|
||||
def has_current_user_permissions_for_any_org_and_any_admin_unit(org_member_permission, admit_unit_org_permission):
|
||||
org_members = OrgMember.query.filter_by(user_id=current_user.id).all()
|
||||
for org_member in org_members:
|
||||
if has_org_member_permission(org_member, org_member_permission) and has_any_admin_unit_permission_for_organization(org_member.organization_id, admit_unit_org_permission):
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_current_user_permissions_for_admin_unit_and_any_org(admin_unit_id, org_member_permission, admit_unit_org_permission):
|
||||
admin_unit_orgs = AdminUnitOrg.query.filter_by(admin_unit_id=admin_unit_id).all()
|
||||
for admin_unit_org in admin_unit_orgs:
|
||||
if has_admin_unit_org_permission(admin_unit_org, admit_unit_org_permission):
|
||||
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 and has_org_member_permission(org_member, org_member_permission):
|
||||
return True
|
||||
return False
|
||||
|
||||
# Type permissions
|
||||
|
||||
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():
|
||||
if has_current_user_permission('admin_unit.members:read'):
|
||||
return True
|
||||
|
||||
if has_admin_unit_member_permission(admin_unit.id, 'admin_unit.members:read'):
|
||||
if has_current_user_member_permission_for_admin_unit(admin_unit.id, 'admin_unit.members:read'):
|
||||
return True
|
||||
|
||||
return False
|
||||
@ -221,63 +437,118 @@ 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():
|
||||
if has_current_user_permission('organization.members:read'):
|
||||
return True
|
||||
|
||||
return True # todo
|
||||
if has_current_user_admin_unit_member_permission_for_organization(organization.id, 'admin_unit.organizations.members:read'):
|
||||
return True
|
||||
|
||||
if is_current_user_member_of_organization(organization.id):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def has_current_user_any_permission(user_permission, admin_unit_member_permission = None, org_member_permission = None, admit_unit_org_permission = None):
|
||||
if admin_unit_member_permission == None:
|
||||
admin_unit_member_permission = user_permission
|
||||
|
||||
if org_member_permission == None:
|
||||
org_member_permission = user_permission
|
||||
|
||||
if admit_unit_org_permission == None:
|
||||
admit_unit_org_permission = user_permission
|
||||
|
||||
if not current_user.is_authenticated:
|
||||
return False
|
||||
|
||||
# User permission, e.g. user is global admin
|
||||
if has_current_user_permission(user_permission):
|
||||
return True
|
||||
|
||||
# Admin unit member permissions (Holger, Artur)
|
||||
if has_current_user_member_permission_for_any_admin_unit(admin_unit_member_permission):
|
||||
return True
|
||||
|
||||
# Org member permissions (Mia)
|
||||
if has_current_user_permissions_for_any_org_and_any_admin_unit(org_member_permission, admit_unit_org_permission):
|
||||
return True
|
||||
|
||||
# Org member permissions (Jason kann nur für Celtic Inn eintragen)
|
||||
if len(organizations_with_current_user_org_member_permission(org_member_permission)) > 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def can_create_event():
|
||||
return has_current_user_any_permission('event:create')
|
||||
|
||||
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():
|
||||
if has_current_user_permission('event:verify'):
|
||||
return True
|
||||
|
||||
# Admin unit member permissions (Holger, Artur)
|
||||
if has_admin_unit_member_permission(event.admin_unit_id, 'event:verify'):
|
||||
if has_current_user_member_permission_for_admin_unit(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
|
||||
if has_current_user_permissions_for_admin_unit_and_any_org(event.admin_unit_id, 'event:verify', 'event:verify'):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def can_list_event_suggestion():
|
||||
return has_current_user_any_permission('event:verify')
|
||||
|
||||
def can_read_event_suggestion(suggestion):
|
||||
allowed_admin_units = admin_units_from_aaos(get_event_hosts())
|
||||
allowed_admin_unit_ids = [a.id for a in allowed_admin_units]
|
||||
|
||||
return suggestion.admin_unit_id in allowed_admin_unit_ids
|
||||
|
||||
def get_event_suggestions_for_current_user():
|
||||
result = list()
|
||||
|
||||
allowed_admin_units = admin_units_from_aaos(get_event_hosts())
|
||||
allowed_admin_unit_ids = [a.id for a in allowed_admin_units]
|
||||
|
||||
suggestions = EventSuggestion.query.all()
|
||||
for suggestion in suggestions:
|
||||
if suggestion.admin_unit_id in allowed_admin_unit_ids:
|
||||
result.append(suggestion)
|
||||
|
||||
return result
|
||||
|
||||
# Routes
|
||||
|
||||
@app.before_first_request
|
||||
def create_user():
|
||||
# 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')
|
||||
harzburg = upsert_admin_unit('Stadt Bad Harzburg')
|
||||
upsert_admin_unit('Stadt Clausthal')
|
||||
upsert_admin_unit('Gemeinde Walkenried')
|
||||
upsert_admin_unit('Stadt Bad Lauterberg')
|
||||
upsert_admin_unit('Stadt Harzgerode')
|
||||
upsert_admin_unit('Stadt Ilsenburg')
|
||||
upsert_admin_unit('Stadt Osterode')
|
||||
upsert_admin_unit('Stadt Quedlinburg')
|
||||
upsert_admin_unit('Stadt Wernigerode')
|
||||
upsert_admin_unit('Stadt Halberstadt')
|
||||
upsert_admin_unit('Gemeinde Wennigsen')
|
||||
upsert_admin_unit('Stadt Hildesheim')
|
||||
|
||||
# Organizations
|
||||
admin_unit_org_event_verifier_role = upsert_admin_unit_org_role('event_verifier', ['event:verify'])
|
||||
admin_unit_org_event_verifier_role = upsert_admin_unit_org_role('event_verifier', ['event:verify', "event:create"])
|
||||
gmg = upsert_organization("GOSLAR marketing gmbh")
|
||||
gz = upsert_organization("Goslarsche Zeitung")
|
||||
celtic_inn = upsert_organization("Celtic Inn")
|
||||
kloster_woelteringerode = upsert_organization("Kloster Wöltingerode")
|
||||
|
||||
gmg_admin_unit_org = add_organization_to_admin_unit(gmg, goslar)
|
||||
@ -286,24 +557,124 @@ def create_user():
|
||||
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)
|
||||
|
||||
add_organization_to_admin_unit(celtic_inn, goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Aids-Hilfe Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Akademie St. Jakobushaus"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Aktiv für Hahndorf e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Aquantic Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Arbeitsgemeinschaft Hahndorfer Vereine und Verbände"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("attac-Regionalgruppe Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Bildungshaus Zeppelin e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Brauhaus Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Buchhandlung Brumby"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Bühnenreif Goslar e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Bürgerstiftung Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Celtic-Inn Irish-Pub im Bahnhof"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Cineplex"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Der Zwinger zu Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("DERPART Reisebüro Goslar GmbH"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Deutscher Alpenverein Sektion Goslar e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("DGB - Frauen Goslar in der Region Südniedersachsen/Harz"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("DGB-Kreisvorstand Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Die Butterhanne"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("E-Bike Kasten"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Energie-Forschungszentrum der TU Clausthal - Geschäftsstelle Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("engesser marketing GmbH"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Ev. Kirchengemeinde St. Georg"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Förderkreis Goslarer Kleinkunsttage e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Frankenberger Kirche"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Frankenberger Winterabend"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Frauen-Arbeitsgemeinschaft im Landkreis Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("FreiwilligenAgentur Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Galerie Stoetzel-Tiedt"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Geschichtsverein GS e.V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Gesellschaft der Freunde und Förderer des Internationalen Musikfestes Goslar - Harz e.V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("GOSLAR marketing gmbh"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Goslarer Museum"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Goslarer Theater"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Goslarsche Höfe"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Goslarsche Zeitung"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Große Karnevalsgesellschaft Goslar e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Großes Heiliges Kreuz"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Hahndorfer Tennis Club 77 e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("HAHNENKLEE tourismus marketing gmbh"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("HANSA Seniorenzentrum Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Harz Hochzeit"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Hotel Der Achtermann"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Internationale Goslarer Klaviertage "), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Judo-Karate-Club Sportschule Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Kloster Wöltingerode Brennen & Brauen GmbH"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Klosterhotel Wöltingerode"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Kontaktstelle Musik - Stadtmusikrat Goslar e.V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Kreisjugendpflege Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Kreismusikschule Goslar e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("KUBIK Event- und Musikklub"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Kulturgemeinschaft Vienenburg"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Kulturinitiative Goslar e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Lions Club Goslar Rammelsberg"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Marketing Club Harz"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Marktkirche Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Media Markt "), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("MGV Juventa von 1877 e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Miners' Rock!"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Mönchehaus Museum"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("MTV Goslar e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Museumsverein Goslar e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("NABU Kreisgruppe Goslar e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Naturwissenschaftlicher Verein Goslar e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("PT Lounge Christian Brink"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Rampen für Goslar e.V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Residenz Schwiecheldthaus"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Romantik Hotel Alte Münze"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Schiefer"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Schwimmpark Aquantic"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Seniorenvertretung der Stadt Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Soup & Soul Kitchen"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Stadtbibliothek Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Stadtjugendpflege Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Stadtteilverein Jerstedt e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Stiftung \"Maria in Horto\""), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Verlag Goslarsche Zeitung"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Vienenburger Bürgerverein e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Volksfest Goslar e. V."), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Weltkulturerbe Rammelsberg"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Zinnfigurenmuseum Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Zonta Club Goslar"), goslar)
|
||||
add_organization_to_admin_unit(upsert_organization("Zontaclub Goslar St. Barbar"), goslar)
|
||||
|
||||
# Places
|
||||
upsert_place('Vienenburger See')
|
||||
upsert_place('Vienenburger See', 'In der Siedlung 4', '38690', 'Goslar', 51.958375, 10.559621)
|
||||
upsert_place('Feuerwehrhaus Hahndorf')
|
||||
upsert_place('Gemeindehaus St. Kilian Hahndorf')
|
||||
upsert_place('Grundschule Hahndorf')
|
||||
upsert_place('Mehrzweckhalle Hahndorf')
|
||||
upsert_place('Sportplatz Hahndorf')
|
||||
upsert_place('St. Kilian Hahndorf')
|
||||
upsert_place("Tourist-Information Goslar", 'Markt 7', '38644', 'Goslar', 51.906172, 10.429346)
|
||||
upsert_place("Nagelkopf am Rathaus Goslar", 'Marktkirchhof 3', '38640', 'Goslar', 51.9055939, 10.4263286)
|
||||
upsert_place("Kloster Wöltingerode", 'Wöltingerode 3', '38690', 'Goslar', 51.9591156, 10.5371815)
|
||||
upsert_place("Marktplatz Goslar", 'Markt 6', '38640', 'Goslar', 51.9063601, 10.4249433)
|
||||
upsert_place("Burg Vienenburg", 'Burgweg 2', '38690', 'Goslar', 51.9476558, 10.5617368)
|
||||
upsert_place("Kurhaus Bad Harzburg", 'Kurhausstraße 11', '38667', 'Bad Harzburg', 51.8758165, 10.5593392)
|
||||
upsert_place("Goslarsche Höfe", 'Okerstraße 32', '38640', 'Goslar', 51.911571, 10.4391331)
|
||||
|
||||
# Org or admins
|
||||
goslar_ooa = upsert_org_or_admin_unit_for_admin_unit(goslar)
|
||||
kloster_woelteringerode_ooa = upsert_org_or_admin_unit_for_organization(kloster_woelteringerode)
|
||||
gmg_ooa = upsert_org_or_admin_unit_for_organization(gmg)
|
||||
harzburg_ooa = upsert_org_or_admin_unit_for_admin_unit(harzburg)
|
||||
|
||||
# Commit
|
||||
db.session.commit()
|
||||
|
||||
# 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_role.add_permissions(["user:create", "event:verify", "event:create", "event_suggestion:read", "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"])
|
||||
admin_unit_admin_role = upsert_admin_unit_member_role('admin', ["admin_unit.members:read", "admin_unit.organizations.members:read"])
|
||||
admin_unit_event_verifier_role = upsert_admin_unit_member_role('event_verifier', ["event:verify", "event:create", "event_suggestion:read"])
|
||||
org_member_event_verifier_role = upsert_org_member_role('event_verifier', ["event:verify", "event:create", "event_suggestion:read"])
|
||||
org_member_event_creator_role = upsert_org_member_role('event_creator', ["event:create"])
|
||||
|
||||
daniel = upsert_user("grams.daniel@gmail.com")
|
||||
user_datastore.add_role_to_user(daniel, admin_role)
|
||||
@ -325,6 +696,10 @@ def create_user():
|
||||
tom_gz_member = add_user_to_organization(tom, gz)
|
||||
add_role_to_org_member(tom_gz_member, org_member_event_verifier_role)
|
||||
|
||||
jason = upsert_user("jason@test.de")
|
||||
jason_celtic_inn_member = add_user_to_organization(jason, celtic_inn)
|
||||
add_role_to_org_member(jason_celtic_inn_member, org_member_event_creator_role)
|
||||
|
||||
# Events
|
||||
berlin = pytz.timezone('Europe/Berlin')
|
||||
upsert_event("Vienenburger Seefest",
|
||||
@ -349,7 +724,7 @@ def create_user():
|
||||
|
||||
upsert_event("Ein Blick hinter die Kulissen - Rathausbaustelle",
|
||||
goslar_ooa,
|
||||
"Nagelkopf am Rathaus",
|
||||
"Nagelkopf am Rathaus Goslar",
|
||||
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.')
|
||||
|
||||
@ -362,17 +737,32 @@ def create_user():
|
||||
|
||||
upsert_event("Altstadtfest",
|
||||
gmg_ooa,
|
||||
"Goslar",
|
||||
"Marktplatz 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",
|
||||
goslar_ooa,
|
||||
"Vienenburg",
|
||||
"Burg 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.')
|
||||
|
||||
upsert_event_suggestion("Der Blaue Vogel",
|
||||
"Freese-Baus Ballettschule",
|
||||
"Kurhaus Bad Harzburg",
|
||||
create_berlin_date(2020, 9, 12, 16, 0),
|
||||
'Die Freese-Baus Ballettschule zeigt 2020 ein wenig bekanntes französisches Märchen nach einer Erzählung Maurice Maeterlinck. Die Leiterin der Schule, Hanna-Sibylle Werner, hat die Grundidee übernommen, aber für ein Ballett überarbeitet, verändert und einstudiert. Das Märchen handelt von zwei Mädchen, die einen kleinen blauen Vogel lieb gewonnen haben, der über Nacht verschwunden ist. Mit Hilfe der Berylune und der Fee des Lichts machen sie sich auf den Weg, den Vogel wieder zu finden. Ihre Reise führt sie ins Reich der Erinnerung, des Glücks, der Phantasie und der Elemente. ( Die Einstudierung der Elemente: Luft, Wasser, Feuer übernahm der Choreograf Marco Barbieri, der auch zum Team der Ballettschule gehört ). Im Reich der Königin der Nacht finden sie ihren kleinen Vogel wieder, aber ob sie ihn mit nehmen dürfen, wird nicht verraten. Die Aufführungen werden durch farbenfrohe Kostüme ( Eigentum der Schule ) und Bühnenbilder ( Günter Werner ) ergänzt. Die Musik stammt von verschiedenen Komponisten. Die Tänzer*innen sind im Alter von 3 bis 40 Jahren, die Teilnahme ist für die älteren Schüler*innen freiwillig.',
|
||||
'https://veranstaltungen.meinestadt.de/bad-harzburg/event-detail/35486361/98831674',
|
||||
admin_unit = get_admin_unit('Stadt Bad Harzburg'))
|
||||
|
||||
upsert_event_suggestion("Herbst-Flohmarkt",
|
||||
"Goslarsche Höfe - Integrationsbetrieb - gGmbH",
|
||||
"Goslarsche Höfe",
|
||||
create_berlin_date(2020, 10, 10, 10, 0),
|
||||
'Zum letzten Mal in dieser Saison gibt es einen Hof-Flohmarkt. Wir bieten zwar nicht den größten, aber vielleicht den gemütlichsten Flohmarkt in der Region. Frei von gewerblichen Anbietern, dafür mit Kaffee, Kuchen, Bier und Bratwurst, alles auf unserem schönen Hofgelände.',
|
||||
'https://www.goslarsche-hoefe.de/veranstaltungen/10/2175252/2020/10/10/herbst-flohmarkt.html')
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Views
|
||||
@ -387,7 +777,7 @@ def home():
|
||||
@app.route("/admin_units")
|
||||
def admin_units():
|
||||
return render_template('admin_units.html',
|
||||
admin_units=AdminUnit.query.all())
|
||||
admin_units=AdminUnit.query.order_by(asc(func.lower(AdminUnit.name))).all())
|
||||
|
||||
@app.route('/admin_unit/<int:admin_unit_id>')
|
||||
def admin_unit(admin_unit_id):
|
||||
@ -402,27 +792,41 @@ def admin_unit(admin_unit_id):
|
||||
@app.route("/organizations")
|
||||
def organizations():
|
||||
return render_template('organizations.html',
|
||||
organizations=Organization.query.all())
|
||||
organizations=Organization.query.order_by(asc(func.lower(Organization.name))).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
|
||||
|
||||
ooa = upsert_org_or_admin_unit_for_organization(organization)
|
||||
events = ooa.events
|
||||
|
||||
return render_template('organization.html',
|
||||
organization=organization,
|
||||
current_user_member=current_user_member,
|
||||
can_list_members=can_list_org_members(organization))
|
||||
can_list_members=can_list_org_members(organization),
|
||||
events=events)
|
||||
|
||||
@app.route("/profile")
|
||||
@auth_required()
|
||||
def profile():
|
||||
return render_template('profile.html')
|
||||
|
||||
@app.route('/place/<int:place_id>')
|
||||
def place(place_id):
|
||||
place = Place.query.filter_by(id = place_id).first()
|
||||
|
||||
return render_template('place/read.html',
|
||||
place=place)
|
||||
|
||||
@app.route("/events")
|
||||
def events():
|
||||
events = Event.query.all()
|
||||
return render_template('events.html', events=events)
|
||||
return render_template('events.html',
|
||||
events=events,
|
||||
user_can_create_event=can_create_event(),
|
||||
user_can_list_event_suggestion=can_list_event_suggestion())
|
||||
|
||||
@app.route('/event/<int:event_id>', methods=('GET', 'POST'))
|
||||
def event(event_id):
|
||||
@ -439,11 +843,22 @@ def event(event_id):
|
||||
|
||||
return render_template('event.html', event=event, user_can_verify_event=user_can_verify_event)
|
||||
|
||||
from forms.create_event import CreateEventForm
|
||||
from forms.event import CreateEventForm
|
||||
from forms.event_suggestion import CreateEventSuggestionForm
|
||||
|
||||
@app.route("/events/create", methods=('GET', 'POST'))
|
||||
def create_event():
|
||||
@auth_required()
|
||||
def event_create():
|
||||
if not can_create_event():
|
||||
abort(401)
|
||||
|
||||
form = CreateEventForm()
|
||||
|
||||
form.host_id.choices = sorted([(ooa.id, ooa.organization.name if ooa.organization is not None else ooa.admin_unit.name) for ooa in get_event_hosts()], key=lambda ooa: ooa[1])
|
||||
form.host_id.choices.insert(0, (0, ''))
|
||||
form.place_id.choices = [(p.id, p.name) for p in Place.query.order_by('name')]
|
||||
form.place_id.choices.insert(0, (0, ''))
|
||||
|
||||
if form.validate_on_submit():
|
||||
event = Event()
|
||||
form.populate_obj(event)
|
||||
@ -451,8 +866,41 @@ def create_event():
|
||||
eventDate = EventDate(event_id = event.id, start=form.start.data)
|
||||
event.dates.append(eventDate)
|
||||
db.session.commit()
|
||||
flash(gettext('Event successfully created'))
|
||||
return redirect(url_for('events'))
|
||||
return render_template('create_event.html', form=form)
|
||||
return render_template('event/create.html', form=form)
|
||||
|
||||
@app.route("/eventsuggestions")
|
||||
@auth_required()
|
||||
def eventsuggestions():
|
||||
if not can_list_event_suggestion():
|
||||
abort(401)
|
||||
|
||||
return render_template('event_suggestion/list.html',
|
||||
suggestions=get_event_suggestions_for_current_user())
|
||||
|
||||
@app.route('/eventsuggestion/<int:event_suggestion_id>')
|
||||
@auth_required()
|
||||
def eventsuggestion(event_suggestion_id):
|
||||
suggestion = EventSuggestion.query.filter_by(id = event_suggestion_id).first()
|
||||
if not can_read_event_suggestion(suggestion):
|
||||
abort(401)
|
||||
|
||||
return render_template('event_suggestion/read.html', suggestion=suggestion)
|
||||
|
||||
@app.route("/eventsuggestions/create", methods=('GET', 'POST'))
|
||||
def event_suggestion_create():
|
||||
form = CreateEventSuggestionForm()
|
||||
if form.validate_on_submit():
|
||||
event = EventSuggestion()
|
||||
form.populate_obj(event)
|
||||
event.admin_unit = get_admin_unit('Stadt Goslar')
|
||||
eventDate = EventSuggestionDate(event_suggestion_id = event.id, start=form.start.data)
|
||||
event.dates.append(eventDate)
|
||||
db.session.commit()
|
||||
flash(gettext('Event suggestion successfully created'))
|
||||
return redirect(url_for('home'))
|
||||
return render_template('event_suggestion/create.html', form=form)
|
||||
|
||||
@app.route("/admin")
|
||||
@roles_required("admin")
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField, TextAreaField
|
||||
from wtforms import StringField, SubmitField, TextAreaField, SelectField
|
||||
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()])
|
||||
external_link = StringField('Link URL', validators=[Optional()])
|
||||
ticket_link = StringField('Ticket Link URL', validators=[Optional()])
|
||||
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()])
|
||||
|
||||
place_id = SelectField('Place', validators=[DataRequired()], coerce=int)
|
||||
host_id = SelectField('Host', validators=[DataRequired()], coerce=int)
|
||||
23
forms/event_suggestion.py
Normal file
23
forms/event_suggestion.py
Normal file
@ -0,0 +1,23 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField, TextAreaField
|
||||
from wtforms.fields.html5 import DateTimeLocalField
|
||||
from wtforms.validators import DataRequired, Optional
|
||||
|
||||
class CreateEventSuggestionForm(FlaskForm):
|
||||
submit = SubmitField("Suggest event")
|
||||
event_name = StringField('Name', validators=[DataRequired()])
|
||||
description = TextAreaField('Description', validators=[DataRequired()])
|
||||
start = DateTimeLocalField('Start', format='%Y-%m-%dT%H:%M', validators=[DataRequired()])
|
||||
external_link = StringField('Link URL', validators=[Optional()])
|
||||
|
||||
place_name = StringField('Event place', validators=[DataRequired()])
|
||||
place_street = StringField('Street', validators=[Optional()])
|
||||
place_postalCode = StringField('Postal code', validators=[DataRequired()])
|
||||
place_city = StringField('City', validators=[DataRequired()])
|
||||
|
||||
host_name = StringField('Event host', validators=[DataRequired()])
|
||||
contact_name = StringField('Contact name', validators=[DataRequired()])
|
||||
contact_email = StringField('Contact email', validators=[DataRequired()])
|
||||
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: db0f9d2e8110
|
||||
Revision ID: 65a924cebc99
|
||||
Revises:
|
||||
Create Date: 2020-06-17 10:51:59.048457
|
||||
Create Date: 2020-06-19 09:53:29.337178
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'db0f9d2e8110'
|
||||
revision = '65a924cebc99'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
@ -133,6 +133,25 @@ def upgrade():
|
||||
sa.ForeignKeyConstraint(['organization_id'], ['organization.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('eventsuggestion',
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('admin_unit_id', sa.Integer(), nullable=False),
|
||||
sa.Column('host_name', sa.Unicode(length=255), nullable=False),
|
||||
sa.Column('event_name', sa.Unicode(length=255), nullable=False),
|
||||
sa.Column('description', sa.UnicodeText(), nullable=False),
|
||||
sa.Column('place_name', sa.Unicode(length=255), nullable=False),
|
||||
sa.Column('place_street', sa.Unicode(length=255), nullable=True),
|
||||
sa.Column('place_postalCode', sa.Unicode(length=255), nullable=False),
|
||||
sa.Column('place_city', sa.Unicode(length=255), nullable=False),
|
||||
sa.Column('contact_name', sa.Unicode(length=255), nullable=False),
|
||||
sa.Column('contact_email', sa.Unicode(length=255), nullable=False),
|
||||
sa.Column('external_link', sa.String(length=255), nullable=True),
|
||||
sa.Column('created_by_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['admin_unit_id'], ['adminunit.id'], ),
|
||||
sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('org_or_adminunit',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('organization_id', sa.Integer(), nullable=True),
|
||||
@ -195,6 +214,13 @@ def upgrade():
|
||||
sa.ForeignKeyConstraint(['place_id'], ['place.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('eventsuggestiondate',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('event_suggestion_id', sa.Integer(), nullable=False),
|
||||
sa.Column('start', sa.DateTime(timezone=True), nullable=False),
|
||||
sa.ForeignKeyConstraint(['event_suggestion_id'], ['eventsuggestion.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('orgmemberroles_members',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('member_id', sa.Integer(), nullable=True),
|
||||
@ -217,12 +243,14 @@ def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('eventdate')
|
||||
op.drop_table('orgmemberroles_members')
|
||||
op.drop_table('eventsuggestiondate')
|
||||
op.drop_table('event')
|
||||
op.drop_table('adminunitorgroles_organizations')
|
||||
op.drop_table('adminunitmemberroles_members')
|
||||
op.drop_table('place')
|
||||
op.drop_table('orgmember')
|
||||
op.drop_table('org_or_adminunit')
|
||||
op.drop_table('eventsuggestion')
|
||||
op.drop_table('adminunitorg')
|
||||
op.drop_table('adminunitmember')
|
||||
op.drop_table('actor')
|
||||
32
models.py
32
models.py
@ -77,7 +77,7 @@ class OrgMember(db.Model):
|
||||
class Organization(db.Model, TrackableMixin):
|
||||
__tablename__ = 'organization'
|
||||
id = Column(Integer(), primary_key=True)
|
||||
name = Column(String(255), unique=True)
|
||||
name = Column(Unicode(255), unique=True)
|
||||
members = relationship('OrgMember', backref=backref('organization', lazy=True))
|
||||
|
||||
### Admin Unit
|
||||
@ -129,7 +129,7 @@ class AdminUnitOrg(db.Model):
|
||||
class AdminUnit(db.Model, TrackableMixin):
|
||||
__tablename__ = 'adminunit'
|
||||
id = Column(Integer(), primary_key=True)
|
||||
name = Column(String(255), unique=True)
|
||||
name = Column(Unicode(255), unique=True)
|
||||
members = relationship('AdminUnitMember', backref=backref('adminunit', lazy=True))
|
||||
organizations = relationship('AdminUnitOrg', backref=backref('adminunit', lazy=True))
|
||||
|
||||
@ -150,9 +150,9 @@ class OrgOrAdminUnit(db.Model):
|
||||
__table_args__ = (UniqueConstraint('organization_id', 'admin_unit_id'),)
|
||||
id = Column(Integer(), primary_key=True)
|
||||
organization_id = db.Column(db.Integer, db.ForeignKey('organization.id'))
|
||||
organization = db.relationship('Organization')
|
||||
organization = db.relationship('Organization', lazy="joined", backref=backref('org_or_adminunit', uselist=False, lazy=True))
|
||||
admin_unit_id = db.Column(db.Integer, db.ForeignKey('adminunit.id'))
|
||||
admin_unit = db.relationship('AdminUnit')
|
||||
admin_unit = db.relationship('AdminUnit', lazy="joined", backref=backref('org_or_adminunit', uselist=False, lazy=True))
|
||||
|
||||
class Location(db.Model, TrackableMixin):
|
||||
__tablename__ = 'location'
|
||||
@ -173,6 +173,30 @@ class Place(db.Model, TrackableMixin):
|
||||
location = db.relationship('Location')
|
||||
|
||||
# Events
|
||||
class EventSuggestion(db.Model, TrackableMixin):
|
||||
__tablename__ = 'eventsuggestion'
|
||||
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('eventsuggestions', lazy=True))
|
||||
host_name = Column(Unicode(255), nullable=False)
|
||||
event_name = Column(Unicode(255), nullable=False)
|
||||
description = Column(UnicodeText(), nullable=False)
|
||||
place_name = Column(Unicode(255), nullable=False)
|
||||
place_street = Column(Unicode(255))
|
||||
place_postalCode = Column(Unicode(255), nullable=False)
|
||||
place_city = Column(Unicode(255), nullable=False)
|
||||
contact_name = Column(Unicode(255), nullable=False)
|
||||
contact_email = Column(Unicode(255), nullable=False)
|
||||
external_link = Column(String(255))
|
||||
dates = relationship('EventSuggestionDate', backref=backref('eventsuggestion', lazy=False), cascade="all, delete-orphan")
|
||||
|
||||
class EventSuggestionDate(db.Model):
|
||||
__tablename__ = 'eventsuggestiondate'
|
||||
id = Column(Integer(), primary_key=True)
|
||||
event_suggestion_id = db.Column(db.Integer, db.ForeignKey('eventsuggestion.id'), nullable=False)
|
||||
start = db.Column(db.DateTime(timezone=True), nullable=False)
|
||||
#end: date_time
|
||||
|
||||
class Event(db.Model, TrackableMixin):
|
||||
__tablename__ = 'event'
|
||||
id = Column(Integer(), primary_key=True)
|
||||
|
||||
@ -4,6 +4,11 @@ h1 {
|
||||
margin: 1rem 0 2rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.2rem;
|
||||
margin: 2rem 0 1rem;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: lightslategray;
|
||||
font-size: 1em;
|
||||
|
||||
@ -1,19 +1,23 @@
|
||||
{% macro render_field_with_errors(field) %}
|
||||
{% set label_text = field.label.text + ' *' if field.flags.required else field.label.text %}
|
||||
<div class="form-group row {% if field.errors %} has-error{% endif -%}">
|
||||
{{ field.label(class="col-sm-2 col-form-label") }}
|
||||
{{ field.label(text=label_text, class="col-sm-2 col-form-label") }}
|
||||
<div class="input-group col-sm-10">
|
||||
{% if field.errors %}
|
||||
{{ field(class="form-control is-invalid", **kwargs)|safe }}
|
||||
{% else %}
|
||||
{{ field(class="form-control", **kwargs)|safe }}
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<div class="invalid-feedback">
|
||||
{% for error in field.errors %}
|
||||
<div>{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% set field_class = kwargs['class'] if 'class' in kwargs else '' %}
|
||||
{% set field_class = field_class + ' form-control' %}
|
||||
{% if field.errors %}
|
||||
{% set field_class = field_class + ' is-invalid' %}
|
||||
{% endif %}
|
||||
|
||||
{{ field(class=field_class, **kwargs)|safe }}
|
||||
|
||||
{% if field.errors %}
|
||||
<div class="invalid-feedback">
|
||||
{% for error in field.errors %}
|
||||
<div>{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@ -43,6 +47,77 @@
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_place(place) %}
|
||||
{{ place.name }}
|
||||
{% macro render_ooa_with_link(ooa) %}
|
||||
{% if ooa.admin_unit %}
|
||||
<a href="{{ url_for('admin_unit', admin_unit_id=ooa.admin_unit.id) }}">{{ ooa.admin_unit.name }}</a>
|
||||
{% endif %}
|
||||
{% if ooa.organization %}
|
||||
<a href="{{ url_for('organization', organization_id=ooa.organization.id) }}">{{ ooa.organization.name }}</a>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_location(location) %}
|
||||
{{ location.street }}, {{ location.postalCode }} {{ location.city }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_place(place) %}
|
||||
{% if place.location %}
|
||||
{{ place.name }}, {{render_location(place.location)}}
|
||||
{% else %}
|
||||
{{ place.name }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_events_sub_menu(user_can_create_event, user_can_list_event_suggestion) %}
|
||||
<div class="my-4">
|
||||
{% if user_can_create_event %}
|
||||
<a class="btn btn-primary" href="{{ url_for('event_create') }}" role="button"><i class="fa fa-lock"></i> {{ _('Create event') }}</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-secondary" href="{{ url_for('event_suggestion_create') }}" role="button"><i class="fa fa-plus"></i> {{ _('Suggest event') }}</a>
|
||||
</div>
|
||||
|
||||
{% if user_can_list_event_suggestion %}
|
||||
<div class="list-group my-4">
|
||||
<a href="{{ url_for('eventsuggestions') }}" class="list-group-item list-group-item-action">
|
||||
{{ _('Event suggestions') }}
|
||||
<i class="fa fa-caret-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_events(events) %}
|
||||
<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>
|
||||
</td>
|
||||
<td>{{ render_ooa(event.host) }}</td>
|
||||
<td>{{ render_place(event.place) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="list-group my-4">
|
||||
<a href="{{ url_for('events') }}" class="list-group-item list-group-item-action list-group-item-primary">
|
||||
{{ _('Show all events') }}
|
||||
<i class="fa fa-caret-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% endmacro %}
|
||||
@ -1,4 +1,5 @@
|
||||
{% extends "layout.html" %}
|
||||
{% from "_macros.html" import render_events %}
|
||||
{% block title %}
|
||||
{{ admin_unit.name }}
|
||||
{% endblock %}
|
||||
@ -6,53 +7,81 @@
|
||||
|
||||
<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 %})
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
{% if current_user_member or can_list_admin_unit_members %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#members" role="tab" area-selected="true">{{ _('Members') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#organizations" role="tab">{{ _('Organizations') }}</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#organizations" role="tab" area-selected="true">{{ _('Organizations') }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#events" role="tab">{{ _('Events') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
{% if current_user_member or can_list_admin_unit_members %}
|
||||
<div class="tab-pane pt-4 active" id="members" role="tabpanel">
|
||||
{% 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 %}
|
||||
<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 %}
|
||||
</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 %}
|
||||
<div class="tab-pane pt-4{% if not current_user_member and not can_list_admin_unit_members %} active{% endif %}" id="organizations" role="tabpanel">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{{ member.user.email }}</td>
|
||||
<td>{% for role in member.roles %}{{ role.name }}{%if not loop.last %}, {% endif %}{% endfor %}</td>
|
||||
<th>{{ _('Name') }}</th>
|
||||
<th>{{ _('Roles') }}</th>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
</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 class="tab-pane pt-4" id="events" role="tabpanel">
|
||||
{{ render_events(admin_unit.events) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@ -1,18 +0,0 @@
|
||||
{% 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 %}
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "layout.html" %}
|
||||
{% from "_macros.html" import render_ooa, render_place %}
|
||||
{% from "_macros.html" import render_ooa_with_link, render_place %}
|
||||
{% block title %}
|
||||
{{ event.name }}
|
||||
{% endblock %}
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
<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> {{ render_place(event.place) }}</div>
|
||||
<div><i class="fa fa-fw fa-map-marker" data-toggle="tooltip" title="{{ _('Location') }}"></i> <a href="{{ url_for('place', place_id=event.place.id) }}">{{ render_place(event.place) }}</a></div>
|
||||
{% if event.verified %}
|
||||
<div><i class="fa fa-fw fa-check-circle text-success" data-toggle="tooltip" title="{{ _('Verified') }}"></i> {{ _('Verified') }}</div>
|
||||
{% endif %}
|
||||
@ -39,7 +39,7 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="my-4">
|
||||
<div><i class="fa fa-fw fa-users" data-toggle="tooltip" title="{{ _('Host') }}"></i> {{ render_ooa(event.host) }}</div>
|
||||
<div><i class="fa fa-fw fa-users" data-toggle="tooltip" title="{{ _('Host') }}"></i> {{ render_ooa_with_link(event.host) }}</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
53
templates/event/create.html
Normal file
53
templates/event/create.html
Normal file
@ -0,0 +1,53 @@
|
||||
{% extends "layout.html" %}
|
||||
{% from "_macros.html" import render_field_with_errors, render_field %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ _('Create event') }}</h1>
|
||||
<form action="{{ url_for('event_create') }}" method="POST">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
{{ _('Event') }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ render_field_with_errors(form.name) }}
|
||||
{{ render_field_with_errors(form.description) }}
|
||||
{{ render_field_with_errors(form.start) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
{{ _('Host') }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ render_field_with_errors(form.host_id, class="autocomplete w-100") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
{{ _('Place') }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ render_field_with_errors(form.place_id, class="autocomplete w-100") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
{{ _('Additional information') }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ render_field_with_errors(form.external_link) }}
|
||||
{{ render_field_with_errors(form.ticket_link) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ render_field(form.submit) }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
26
templates/event_suggestion/create.html
Normal file
26
templates/event_suggestion/create.html
Normal file
@ -0,0 +1,26 @@
|
||||
{% extends "layout.html" %}
|
||||
{% from "_macros.html" import render_field_with_errors, render_field %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ _('Suggest event') }}</h1>
|
||||
<form action="{{ url_for('event_suggestion_create') }}" method="POST">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ render_field_with_errors(form.event_name) }}
|
||||
{{ render_field_with_errors(form.description) }}
|
||||
{{ render_field_with_errors(form.external_link) }}
|
||||
{{ render_field_with_errors(form.start) }}
|
||||
|
||||
{{ render_field_with_errors(form.place_name) }}
|
||||
{{ render_field_with_errors(form.place_street) }}
|
||||
{{ render_field_with_errors(form.place_postalCode) }}
|
||||
{{ render_field_with_errors(form.place_city) }}
|
||||
|
||||
{{ render_field_with_errors(form.host_name) }}
|
||||
{{ render_field_with_errors(form.contact_name) }}
|
||||
{{ render_field_with_errors(form.contact_email) }}
|
||||
|
||||
{{ render_field(form.submit) }}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
36
templates/event_suggestion/list.html
Normal file
36
templates/event_suggestion/list.html
Normal file
@ -0,0 +1,36 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}
|
||||
{{ _('Event suggestions') }}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ _('Event suggestions') }}</h1>
|
||||
|
||||
<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>
|
||||
<th>{{ _('Created at') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for suggestion in suggestions %}
|
||||
<tr>
|
||||
<td>{{ suggestion.dates[0].start | datetimeformat }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('eventsuggestion', event_suggestion_id=suggestion.id) }}">{{ suggestion.event_name }}</a>
|
||||
</td>
|
||||
<td>{{ suggestion.host_name }}</td>
|
||||
<td>{{ suggestion.place_name }}</td>
|
||||
<td>{{ suggestion.created_at | datetimeformat }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
93
templates/event_suggestion/read.html
Normal file
93
templates/event_suggestion/read.html
Normal file
@ -0,0 +1,93 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}
|
||||
{{ suggestion.event_name }}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ suggestion.event_name }}</h1>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ _('Event') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ _('Date') }}</td>
|
||||
<td>{{ suggestion.dates[0].start | datetimeformat }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ _('Event name') }}</td>
|
||||
<td>{{ suggestion.event_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ _('Description') }}</td>
|
||||
<td>{{ suggestion.description }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ _('Link URL') }}</td>
|
||||
<td>{% if suggestion.external_link %}<a href="{{ suggestion.external_link }}">{{ suggestion.external_link }}</a>{% endif %}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ _('Location') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ _('Event place') }}</td>
|
||||
<td>{{ suggestion.place_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ _('Street') }}</td>
|
||||
<td>{{ suggestion.place_street }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ _('Postal code') }}</td>
|
||||
<td>{{ suggestion.place_postalCode }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ _('City') }}</td>
|
||||
<td>{{ suggestion.place_city }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">{{ _('Contact') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ _('Event host') }}</td>
|
||||
<td>{{ suggestion.host_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ _('Contact name') }}</td>
|
||||
<td>{{ suggestion.contact_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ _('Contact email') }}</td>
|
||||
<td>{% if suggestion.contact_email %}<a href="mailto:{{ suggestion.contact_email }}">{{ suggestion.contact_email }}</a>{% endif %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ _('Created at') }}</td>
|
||||
<td>{{ suggestion.created_at | datetimeformat }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "layout.html" %}
|
||||
{% from "_macros.html" import render_ooa, render_place %}
|
||||
{% from "_macros.html" import render_ooa, render_place, render_events_sub_menu %}
|
||||
{% block title %}
|
||||
{{ _('Events') }}
|
||||
{% endblock %}
|
||||
@ -7,9 +7,7 @@
|
||||
|
||||
<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>
|
||||
{{ render_events_sub_menu(user_can_create_event, user_can_list_event_suggestion) }}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-hover table-striped">
|
||||
|
||||
@ -15,7 +15,7 @@ Prototyp
|
||||
</div>-->
|
||||
|
||||
{% if admin_unit_members %}
|
||||
<p class="h5 mt-5">{{ _('Your Admin Units') }}</p>
|
||||
<h2>{{ _('Your Admin Units') }}</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
@ -37,7 +37,7 @@ Prototyp
|
||||
{% endif %}
|
||||
|
||||
{% if organization_members %}
|
||||
<p class="h5 mt-5">{{ _('Your Organizations') }}</p>
|
||||
<h2>{{ _('Your Organizations') }}</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
@ -49,7 +49,7 @@ Prototyp
|
||||
<tbody>
|
||||
{% for member in organization_members %}
|
||||
<tr>
|
||||
<td>{{ member.organization.name }}</td>
|
||||
<td><a href="{{ url_for('organization', organization_id=member.organization.id) }}">{{ member.organization.name }}</a></td>
|
||||
<td>{% for role in member.roles %}{{ role.name }}{%if not loop.last %}, {% endif %}{% endfor %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='site.css')}}" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-beta.1/dist/css/select2.min.css" rel="stylesheet" />
|
||||
{%- block styles %}
|
||||
{%- endblock styles %}
|
||||
|
||||
@ -22,6 +23,7 @@
|
||||
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/i18n/jquery-ui-i18n.js" integrity="sha256-HwXB0HPJCXhungCtZoScc1a53ngQOXua64nb0cI6PHY=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js" integrity="sha256-AdQN98MVZs44Eq2yTwtoKufhnU+uZ7v2kXnD5vqzZVo=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-beta.1/dist/js/select2.min.js"></script>
|
||||
|
||||
<title>{% block title %}{{ title|default }}{% endblock title %}</title>
|
||||
|
||||
@ -29,8 +31,10 @@
|
||||
moment.locale('de')
|
||||
|
||||
$( function() {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
})
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.autocomplete').select2();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% block header %}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{% extends "layout.html" %}
|
||||
{% from "_macros.html" import render_events %}
|
||||
{% block title %}
|
||||
{{ organization.name }}
|
||||
{% endblock %}
|
||||
@ -6,33 +7,60 @@
|
||||
|
||||
<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 %}
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
{% if current_user_member or can_list_members %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#members" role="tab" area-selected="true">{{ _('Members') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#events" role="tab">{{ _('Events') }}</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#events" role="tab" area-selected="true">{{ _('Events') }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
{% 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>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
{% if current_user_member or can_list_members %}
|
||||
<div class="tab-pane pt-4 active" id="members" role="tabpanel">
|
||||
{% 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 %}
|
||||
<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 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="tab-pane pt-4{% if not current_user_member and not can_list_members %} active{% endif %}" id="events" role="tabpanel">
|
||||
{{ render_events(events) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
27
templates/place/read.html
Normal file
27
templates/place/read.html
Normal file
@ -0,0 +1,27 @@
|
||||
{% extends "layout.html" %}
|
||||
{% from "_macros.html" import render_place, render_events %}
|
||||
{% block title %}
|
||||
{{ place.name }}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ place.name }}</h1>
|
||||
|
||||
{% if place.location %}
|
||||
<div class="card card-body">
|
||||
<p>
|
||||
{{ place.location.street }}<br />
|
||||
{{ place.location.postalCode }} {{ place.location.city }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="http://www.google.com/maps?q={{ render_place(place) | quote_plus }}">{{ _('Show on Google Maps') }}</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>{{ _('Events') }}</h2>
|
||||
{{ render_events(place.events) }}
|
||||
|
||||
{% endblock %}
|
||||
@ -7,7 +7,7 @@
|
||||
<h1>{{ current_user.email }}</h1>
|
||||
|
||||
{% if admin_unit_members %}
|
||||
<p class="h5 mt-5">{{ _('Admin Units') }}</p>
|
||||
<h2>{{ _('Admin Units') }}</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
@ -29,7 +29,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if organization_members %}
|
||||
<p class="h5 mt-5">{{ _('Organizations') }}</p>
|
||||
<h2>{{ _('Organizations') }}</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user