Skip to content
Snippets Groups Projects
Commit 29f92eef authored by Robin Sonnabend's avatar Robin Sonnabend
Browse files

Implemented default tops

parent 9399cc95
No related branches found
No related tags found
No related merge requests found
"""empty message
Revision ID: a3d9d1b87ba0
Revises: efaa3b4fd3e8
Create Date: 2017-02-22 16:00:02.816515
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a3d9d1b87ba0'
down_revision = 'efaa3b4fd3e8'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('protocols', sa.Column('done', sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('protocols', 'done')
# ### end Alembic commands ###
...@@ -39,8 +39,27 @@ class ProtocolType(db.Model): ...@@ -39,8 +39,27 @@ class ProtocolType(db.Model):
self.public_mail = public_mail self.public_mail = public_mail
def __repr__(self): def __repr__(self):
return "<ProtocolType(id={}, short_name={}, name={}, organization={})>".format( return "<ProtocolType(id={}, short_name={}, name={}, organization={}, is_public={}, private_group={}, public_group={})>".format(
self.id, self.short_name, self.name, self.organization) self.id, self.short_name, self.name, self.organization, self.is_public, self.private_group, self.public_group)
def get_latest_protocol(self):
candidates = sorted([protocol for protocol in self.protocols if protocol.is_done()], key=lambda p: p.data, reverse=True)
if len(candidates) == 0:
return None
return candidates[0]
def has_public_view_right(self, user):
return (self.is_public
or (user is not None and
((self.public_group != "" and self.public_group in user.groups)
or (self.private_group != "" and self.private_group in user.groups))))
def has_private_view_right(self, user):
return (self.private_group != "" and self.private_group in user.groups)
def has_modify_right(self, user):
return self.has_private_view_right(user)
class Protocol(db.Model): class Protocol(db.Model):
__tablename__ = "protocols" __tablename__ = "protocols"
...@@ -53,6 +72,7 @@ class Protocol(db.Model): ...@@ -53,6 +72,7 @@ class Protocol(db.Model):
author = db.Column(db.String) author = db.Column(db.String)
participants = db.Column(db.String) participants = db.Column(db.String)
location = db.Column(db.String) location = db.Column(db.String)
done = db.Column(db.Boolean)
tops = relationship("TOP", backref=backref("protocol"), cascade="all, delete-orphan", order_by="TOP.number") tops = relationship("TOP", backref=backref("protocol"), cascade="all, delete-orphan", order_by="TOP.number")
decisions = relationship("Decision", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Decision.id") decisions = relationship("Decision", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Decision.id")
...@@ -85,6 +105,9 @@ class Protocol(db.Model): ...@@ -85,6 +105,9 @@ class Protocol(db.Model):
self.participants = remarks["Anwesende"].value self.participants = remarks["Anwesende"].value
self.location = remarks["Ort"].value self.location = remarks["Ort"].value
def is_done(self):
return self.done
class DefaultTOP(db.Model): class DefaultTOP(db.Model):
__tablename__ = "defaulttops" __tablename__ = "defaulttops"
...@@ -103,7 +126,7 @@ class DefaultTOP(db.Model): ...@@ -103,7 +126,7 @@ class DefaultTOP(db.Model):
self.id, self.protocoltype_id, self.name, self.number) self.id, self.protocoltype_id, self.name, self.number)
def is_at_end(self): def is_at_end(self):
return self.number < 0 return self.number > 0
class TOP(db.Model): class TOP(db.Model):
__tablename__ = "tops" __tablename__ = "tops"
......
...@@ -13,8 +13,8 @@ import config ...@@ -13,8 +13,8 @@ import config
from shared import db, date_filter, datetime_filter, ldap_manager, security_manager from shared import db, date_filter, datetime_filter, ldap_manager, security_manager
from utils import is_past, mail_manager, url_manager from utils import is_past, mail_manager, url_manager
from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error
from views.forms import LoginForm from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm
from views.tables import ProtocolsTable from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(config) app.config.from_object(config)
...@@ -61,16 +61,175 @@ def login_required(function): ...@@ -61,16 +61,175 @@ def login_required(function):
return redirect(url_for("login", next=request.url)) return redirect(url_for("login", next=request.url))
return decorated_function return decorated_function
def group_required(function, group):
@wraps(function)
def decorated_function(*args, **kwargs):
if group in current_user.groups:
return function(*args, **kwargs)
else:
flash("You do not have the necessary permissions to view this page.")
return redirect(request.args.get("next") or url_for("index"))
return decorated_function
app.jinja_env.globals.update(check_login=check_login) app.jinja_env.globals.update(check_login=check_login)
app.jinja_env.globals.update(current_user=current_user) app.jinja_env.globals.update(current_user=current_user)
app.jinja_env.globals.update(zip=zip)
# blueprints here # blueprints here
@app.route("/") @app.route("/")
#@login_required
def index(): def index():
return render_template("index.html") return render_template("index.html")
@login_required
@app.route("/types/list")
def list_types():
is_logged_in = check_login()
user = current_user()
types = [
protocoltype for protocoltype in ProtocolType.query.all()
if (protocoltype.public_group in user.groups
or protocoltype.private_group in user.groups
or protocoltype.is_public)]
types_table = ProtocolTypesTable(types)
return render_template("types-list.html", types=types, types_table=types_table)
@app.route("/type/new", methods=["GET", "POST"])
@login_required
def new_type():
form = ProtocolTypeForm()
if form.validate_on_submit():
user = current_user()
if form.private_group.data not in user.groups:
flash("Du kannst keinen internen Protokolltypen anlegen, zu dem du selbst keinen Zugang hast.", "alert-error")
else:
protocoltype = ProtocolType(form.name.data, form.short_name.data,
form.organization.data, form.is_public.data,
form.private_group.data, form.public_group.data,
form.private_mail.data, form.public_mail.data)
db.session.add(protocoltype)
db.session.commit()
flash("Der Protokolltyp {} wurde angelegt.".format(protocoltype.name), "alert-success")
return redirect(request.args.get("next") or url_for("list_types"))
return render_template("type-new.html", form=form)
@app.route("/type/edit/<int:type_id>", methods=["GET", "POST"])
@login_required
def edit_type(type_id):
protocoltype = ProtocolType.query.filter_by(id=type_id).first()
if protocoltype is None:
flash("Dieser Protokolltyp existiert nicht.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
user = current_user()
if not protocoltype.has_private_view_right(user):
flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
form = ProtocolTypeForm(obj=protocoltype)
if form.validate_on_submit():
if form.private_group.data not in user.groups:
flash("Du kannst keinen internen Protokolltypen anlegen, zu dem du selbst keinen Zugang hast.", "alert-error")
else:
form.populate_obj(protocoltype)
db.session.commit()
return redirect(request.args.get("next") or url_for("show_type", type_id=protocoltype.id))
return render_template("type-edit.html", form=form, protocoltype=protocoltype)
@app.route("/type/show/<int:type_id>")
@login_required
def show_type(type_id):
protocoltype = ProtocolType.query.filter_by(id=type_id).first()
if protocoltype is None:
flash("Dieser Protokolltyp existiert nicht.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
user = current_user()
if not protocoltype.has_private_view_right(user):
flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
protocoltype_table = ProtocolTypeTable(protocoltype)
default_tops_table = DefaultTOPsTable(protocoltype.default_tops, protocoltype)
return render_template("type-show.html", protocoltype=protocoltype, protocoltype_table=protocoltype_table, default_tops_table=default_tops_table)
@app.route("/type/tops/new/<int:type_id>", methods=["GET", "POST"])
@login_required
def new_default_top(type_id):
protocoltype = ProtocolType.query.filter_by(id=type_id).first()
if protocoltype is None:
flash("Dieser Protokolltyp existiert nicht.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
user = current_user()
if not protocoltype.has_modify_right(user):
flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
form = DefaultTopForm()
if form.validate_on_submit():
default_top = DefaultTOP(protocoltype.id, form.name.data, form.number.data)
db.session.add(default_top)
db.session.commit()
flash("Der Standard-TOP {} wurde für dem Protokolltyp {} hinzugefügt.".format(default_top.name, protocoltype.name), "alert-success")
return redirect(request.args.get("next") or url_for("index"))
return render_template("default-top-new.html", form=form, protocoltype=protocoltype)
@app.route("/type/tops/edit/<int:type_id>/<int:top_id>", methods=["GET", "POST"])
@login_required
def edit_default_top(type_id, top_id):
protocoltype = ProtocolType.query.filter_by(id=type_id).first()
if protocoltype is None:
flash("Dieser Protokolltyp existiert nicht.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
user = current_user()
if not protocoltype.has_modify_right(user):
flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
default_top = DefaultTOP.query.filter_by(id=top_id).first()
if default_top is None or default_top.protocoltype != protocoltype:
flash("Invalider Standard-TOP.", "alert-error")
return redirect(request.args.get("nexT") or url_for("index"))
form = DefaultTopForm(obj=default_top)
if form.validate_on_submit():
form.populate_obj(default_top)
db.session.commit()
return redirect(request.args.get("next") or url_for("show_type", type_id=protocoltype.id))
return render_template("default-top-edit.html", form=form, protocoltype=protocoltype, default_top=default_top)
@app.route("/type/tops/delete/<int:type_id>/<int:top_id>")
@login_required
def delete_default_top(type_id, top_id):
protocoltype = ProtocolType.query.filter_by(id=type_id).first()
if protocoltype is None:
flash("Dieser Protokolltyp existiert nicht.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
user = current_user()
if not protocoltype.has_modify_right(user):
flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
default_top = DefaultTOP.query.filter_by(id=top_id).first()
if default_top is None or default_top.protocoltype != protocoltype:
flash("Invalider Standard-TOP.", "alert-error")
return redirect(request.args.get("nexT") or url_for("index"))
db.session.delete(default_top)
db.session.commit()
return redirect(request.args.get("next") or url_for("show_type", type_id=protocoltype.id))
@app.route("/type/tops/move/<int:type_id>/<int:top_id>/<diff>/")
@login_required
def move_default_top(type_id, top_id, diff):
protocoltype = ProtocolType.query.filter_by(id=type_id).first()
if protocoltype is None:
flash("Dieser Protokolltyp existiert nicht.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
user = current_user()
if not protocoltype.has_modify_right(user):
flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
default_top = DefaultTOP.query.filter_by(id=top_id).first()
if default_top is None or default_top.protocoltype != protocoltype:
flash("Invalider Standard-TOP.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
default_top.number += int(diff)
db.session.commit()
return redirect(request.args.get("next") or url_for("show_type", type_id=protocoltype.id))
@app.route("/protocol/list") @app.route("/protocol/list")
def list_protocols(): def list_protocols():
is_logged_in = check_login() is_logged_in = check_login()
...@@ -82,14 +241,14 @@ def list_protocols(): ...@@ -82,14 +241,14 @@ def list_protocols():
protocol.protocoltype.public_group in user.groups protocol.protocoltype.public_group in user.groups
or protocol.protocoltype.private_group in user.groups))] or protocol.protocoltype.private_group in user.groups))]
protocols_table = ProtocolsTable(protocols) protocols_table = ProtocolsTable(protocols)
return render_template("protocol-list.html", protocols=protocols, protocols_table=protocols_table) return render_template("protocols-list.html", protocols=protocols, protocols_table=protocols_table)
@app.route("/login", methods=["GET", "POST"]) @app.route("/login", methods=["GET", "POST"])
def login(): def login():
if "auth" in session: if "auth" in session:
flash("You are already logged in.", "alert-success") flash("You are already logged in.", "alert-success")
return redirect(url_for(request.args.get("next") or "index")) return redirect(request.args.get("next") or url_for("index"))
form = LoginForm() form = LoginForm()
if form.validate_on_submit(): if form.validate_on_submit():
user = ldap_manager.login(form.username.data, form.password.data) user = ldap_manager.login(form.username.data, form.password.data)
......
...@@ -19,4 +19,6 @@ body { ...@@ -19,4 +19,6 @@ body {
color: #808000; color: #808000;
} }
h3 > a {
font-size: 18px;
}
{% extends "layout.html" %}
{% from "macros.html" import render_form %}
{% block title %}Standard-TOP bearbeiten{% endblock %}
{% block content %}
<div class="container">
{{render_form(form, action_url=url_for("edit_default_top", type_id=protocoltype.id, top_id=default_top.id, next=url_for("show_type", type_id=protocoltype.id)), action_text="Ändern")}}
</div>
{% endblock %}
{% extends "layout.html" %}
{% from "macros.html" import render_form %}
{% block title %}Standard-TOP hinzufügen{% endblock %}
{% block content %}
<div class="container">
{{render_form(form, action_url=url_for("new_default_top", type_id=protocoltype.id, next=url_for("show_type", type_id=protocoltype.id)), action_text="Anlegen")}}
</div>
{% endblock %}
...@@ -28,6 +28,9 @@ ...@@ -28,6 +28,9 @@
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li><a href="{{url_for("index")}}">Zuhause</a></li> <li><a href="{{url_for("index")}}">Zuhause</a></li>
<li><a href="{{url_for("list_protocols")}}">Protokolle</a></li> <li><a href="{{url_for("list_protocols")}}">Protokolle</a></li>
{% if check_login() %}
<li><a href="{{url_for("list_types")}}">Typen</a></li>
{% endif %}
{# todo: add more links #} {# todo: add more links #}
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
......
...@@ -42,7 +42,7 @@ to not render a label for the CRSFTokenField --> ...@@ -42,7 +42,7 @@ to not render a label for the CRSFTokenField -->
<label> <label>
{{ field(type='checkbox', **kwargs) }} {{ field.label }} {{ field(type='checkbox', **kwargs) }} {{ field.label }}
</label> </label>
<span onclick="el=document.getElementById('{{field.id}}-description');el.style.display=(el.style.display=='none'?'flex':'none')" class="field-description-questionmark">?</span> <!--<span onclick="el=document.getElementById('{{field.id}}-description');el.style.display=(el.style.display=='none'?'flex':'none')" class="field-description-questionmark">?</span>-->
{% if field.errors %} {% if field.errors %}
{% for e in field.errors %} {% for e in field.errors %}
<p class="help-block">{{ e }}</p> <p class="help-block">{{ e }}</p>
...@@ -109,6 +109,12 @@ to not render a label for the CRSFTokenField --> ...@@ -109,6 +109,12 @@ to not render a label for the CRSFTokenField -->
{%- endmacro %} {%- endmacro %}
{% macro render_table(table) -%} {% macro render_table(table) -%}
<h3>
{{table.title}}
{% if table.newlink is not none %}
<a href="{{table.newlink}}">{{table.newtext}}</a>
{% endif %}
</h3>
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
...@@ -128,3 +134,22 @@ to not render a label for the CRSFTokenField --> ...@@ -128,3 +134,22 @@ to not render a label for the CRSFTokenField -->
</tbody> </tbody>
</table> </table>
{%- endmacro %} {%- endmacro %}
{% macro render_single_table(table) -%}
<h3>
{{table.title}}
{% if table.newlink is not none %}
<a href="{{table.newlink}}">{{table.newtext}}</a>
{% endif %}
</h3>
<table class="table table-striped">
<tbody>
{% for key, value in zip(table.headers(), table.row()) %}
<tr>
<td>{{key}}</td>
<td>{{value}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{%- endmacro %}
File moved
{% extends "layout.html" %}
{% from "macros.html" import render_form %}
{% block title %}Protokolltyp ändern{% endblock %}
{% block content %}
<div class="container">
{{render_form(form, action_url=url_for("edit_type", type_id=protocoltype.id), action_text="Ändern")}}
</div>
{% endblock %}
{% extends "layout.html" %}
{% from "macros.html" import render_form %}
{% block title %}Protokolltyp anlegen{% endblock %}
{% block content %}
<div class="container">
{{render_form(form, action_url=url_for("new_type"), action_text="Anlegen")}}
</div>
{% endblock %}
{% extends "layout.html" %}
{% from "macros.html" import render_table, render_single_table %}
{% block title %}Protokolltyp {{protocoltype.short_name}}{% endblock %}
{% block content %}
<div class="container">
{{render_single_table(protocoltype_table)}}
{{render_table(default_tops_table)}}
Standard-TOPs mit negativer Sortierung werden vor und die mit positiver Sortierung nach den TOPs eingefügt.
</div>
{% endblock %}
{% extends "layout.html" %}
{% from "macros.html" import render_table %}
{% block title %}Protokolltypen{% endblock %}
{% block content %}
<div class="container">
{{render_table(types_table)}}
</div>
{% endblock %}
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField
from wtforms.validators import InputRequired from wtforms.validators import InputRequired
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
username = StringField("User", validators=[InputRequired("Please input the username.")]) username = StringField("Benutzer", validators=[InputRequired("Bitte gib deinen Benutzernamen ein.")])
password = PasswordField("Password", validators=[InputRequired("Please input the password.")]) password = PasswordField("Passwort", validators=[InputRequired("Bitte gib dein Passwort ein.")])
class ProtocolTypeForm(FlaskForm):
name = StringField("Name", validators=[InputRequired("Du musst einen Namen angeben.")])
short_name = StringField("Abkürzung", validators=[InputRequired("Du musst eine Abkürzung angebene.")])
organization = StringField("Organisation", validators=[InputRequired("Du musst eine zugehörige Organisation angeben.")])
is_public = BooleanField("Öffentlich sichtbar")
private_group = StringField("Interne Gruppe")
public_group = StringField("Öffentliche Gruppe")
private_mail = StringField("Interner Verteiler")
public_mail = StringField("Öffentlicher Verteiler")
class DefaultTopForm(FlaskForm):
name = StringField("Name", validators=[InputRequired("Du musst einen Namen angeben.")])
number = IntegerField("Nummer", validators=[InputRequired("Du musst eine Nummer angeben.")])
...@@ -4,11 +4,11 @@ from models.database import Protocol, ProtocolType, DefaultTOP, TOP, Todo, Decis ...@@ -4,11 +4,11 @@ from models.database import Protocol, ProtocolType, DefaultTOP, TOP, Todo, Decis
from shared import date_filter from shared import date_filter
class Table: class Table:
def __init__(self, title, values, newlink=None): def __init__(self, title, values, newlink=None, newtext=None):
self.title = title self.title = title
self.values = values self.values = values
self.newlink = newlink self.newlink = newlink
self.newtext = "New" self.newtext = newtext or "Neu"
def rows(self): def rows(self):
return [row for row in [self.row(value) for value in self.values] if row is not None] return [row for row in [self.row(value) for value in self.values] if row is not None]
...@@ -26,11 +26,11 @@ class Table: ...@@ -26,11 +26,11 @@ class Table:
@staticmethod @staticmethod
def bool(value): def bool(value):
return "Yes" if value else "No" return "Ja" if value else "Nein"
@staticmethod @staticmethod
def concat(values): def concat(values):
return ", ".join(values) return Markup(", ".join(values))
#if len(values) <= 1: #if len(values) <= 1:
# return "".join(values) # return "".join(values)
#else: #else:
...@@ -38,11 +38,11 @@ class Table: ...@@ -38,11 +38,11 @@ class Table:
class SingleValueTable: class SingleValueTable:
def __init__(self, title, value, newlink=None): def __init__(self, title, value, newlink=None, newtext=None):
self.title = title self.title = title
self.value = value self.value = value
self.newlink = newlink if newlink else None self.newlink = newlink if newlink else None
self.newtext = "Edit" self.newtext = newtext or "Ändern"
def rows(self): def rows(self):
return [self.row()] return [self.row()]
...@@ -61,3 +61,58 @@ class ProtocolsTable(Table): ...@@ -61,3 +61,58 @@ class ProtocolsTable(Table):
date_filter(protocol.data) date_filter(protocol.data)
] ]
class ProtocolTypesTable(Table):
def __init__(self, types):
super().__init__("Protokolltypen", types, newlink=url_for("new_type"))
def headers(self):
return ["Typ", "Name", "Neuestes Protokoll", ""]
def row(self, protocoltype):
return [
Table.link(url_for("show_type", type_id=protocoltype.id), protocoltype.short_name),
protocoltype.name,
protocoltype.get_latest_protocol() or "Noch kein Protokoll",
"" # TODO: add links for new, modify, delete
]
class ProtocolTypeTable(SingleValueTable):
def __init__(self, protocoltype):
super().__init__(protocoltype.name, protocoltype, newlink=url_for("edit_type", type_id=protocoltype.id))
def headers(self):
return ["Name", "Abkürzung", "Organisation", "Öffentlich",
"Interne Gruppe", "Öffentliche Gruppe",
"Interner Verteiler", "Öffentlicher Verteiler"]
def row(self):
return [
self.value.name,
self.value.short_name,
self.value.organization,
Table.bool(self.value.is_public),
self.value.private_group,
self.value.public_group,
self.value.private_mail,
self.value.public_mail
]
class DefaultTOPsTable(Table):
def __init__(self, tops, protocoltype=None):
super().__init__("Standard-TOPs", tops, newlink=url_for("new_default_top", type_id=protocoltype.id) if protocoltype is not None else None)
self.protocoltype = protocoltype
def headers(self):
return ["TOP", "Sortierung", ""]
def row(self, top):
return [
top.name,
top.number,
Table.concat([
Table.link(url_for("move_default_top", type_id=self.protocoltype.id, top_id=top.id, diff=1), "Runter"),
Table.link(url_for("move_default_top", type_id=self.protocoltype.id, top_id=top.id, diff=-1), "Hoch"),
Table.link(url_for("edit_default_top", type_id=self.protocoltype.id, top_id=top.id), "Ändern"),
Table.link(url_for("delete_default_top", type_id=self.protocoltype.id, top_id=top.id), "Löschen", confirm="Bist du dir sicher, dass du den Standard-TOP {} löschen willst?".format(top.name))
])
]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment