Commit 29f92eef authored by Robin Sonnabend's avatar Robin Sonnabend

Implemented default tops

parent 9399cc95
"""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):
self.public_mail = public_mail
def __repr__(self):
return "<ProtocolType(id={}, short_name={}, name={}, organization={})>".format(
self.id, self.short_name, self.name, self.organization)
return "<ProtocolType(id={}, short_name={}, name={}, organization={}, is_public={}, private_group={}, public_group={})>".format(
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):
__tablename__ = "protocols"
......@@ -53,6 +72,7 @@ class Protocol(db.Model):
author = db.Column(db.String)
participants = 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")
decisions = relationship("Decision", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Decision.id")
......@@ -85,6 +105,9 @@ class Protocol(db.Model):
self.participants = remarks["Anwesende"].value
self.location = remarks["Ort"].value
def is_done(self):
return self.done
class DefaultTOP(db.Model):
__tablename__ = "defaulttops"
......@@ -103,7 +126,7 @@ class DefaultTOP(db.Model):
self.id, self.protocoltype_id, self.name, self.number)
def is_at_end(self):
return self.number < 0
return self.number > 0
class TOP(db.Model):
__tablename__ = "tops"
......
......@@ -13,8 +13,8 @@ import config
from shared import db, date_filter, datetime_filter, ldap_manager, security_manager
from utils import is_past, mail_manager, url_manager
from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error
from views.forms import LoginForm
from views.tables import ProtocolsTable
from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm
from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable
app = Flask(__name__)
app.config.from_object(config)
......@@ -61,16 +61,175 @@ def login_required(function):
return redirect(url_for("login", next=request.url))
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(current_user=current_user)
app.jinja_env.globals.update(zip=zip)
# blueprints here
@app.route("/")
#@login_required
def index():
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")
def list_protocols():
is_logged_in = check_login()
......@@ -82,14 +241,14 @@ def list_protocols():
protocol.protocoltype.public_group in user.groups
or protocol.protocoltype.private_group in user.groups))]
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"])
def login():
if "auth" in session:
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()
if form.validate_on_submit():
user = ldap_manager.login(form.username.data, form.password.data)
......
......@@ -19,4 +19,6 @@ body {
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 @@
<ul class="nav navbar-nav">
<li><a href="{{url_for("index")}}">Zuhause</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 #}
</ul>
<ul class="nav navbar-nav navbar-right">
......
......@@ -42,7 +42,7 @@ to not render a label for the CRSFTokenField -->
<label>
{{ field(type='checkbox', **kwargs) }} {{ field.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 %}
{% for e in field.errors %}
<p class="help-block">{{ e }}</p>
......@@ -109,6 +109,12 @@ to not render a label for the CRSFTokenField -->
{%- endmacro %}
{% 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">
<thead>
<tr>
......@@ -128,3 +134,22 @@ to not render a label for the CRSFTokenField -->
</tbody>
</table>
{%- 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 %}
{% 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 wtforms import StringField, PasswordField
from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField
from wtforms.validators import InputRequired
class LoginForm(FlaskForm):
username = StringField("User", validators=[InputRequired("Please input the username.")])
password = PasswordField("Password", validators=[InputRequired("Please input the password.")])
username = StringField("Benutzer", validators=[InputRequired("Bitte gib deinen Benutzernamen ein.")])
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
from shared import date_filter
class Table:
def __init__(self, title, values, newlink=None):
def __init__(self, title, values, newlink=None, newtext=None):
self.title = title
self.values = values
self.newlink = newlink
self.newtext = "New"
self.newtext = newtext or "Neu"
def rows(self):
return [row for row in [self.row(value) for value in self.values] if row is not None]
......@@ -26,11 +26,11 @@ class Table:
@staticmethod
def bool(value):
return "Yes" if value else "No"
return "Ja" if value else "Nein"
@staticmethod
def concat(values):
return ", ".join(values)
return Markup(", ".join(values))
#if len(values) <= 1:
# return "".join(values)
#else:
......@@ -38,11 +38,11 @@ class Table:
class SingleValueTable:
def __init__(self, title, value, newlink=None):
def __init__(self, title, value, newlink=None, newtext=None):
self.title = title
self.value = value
self.newlink = newlink if newlink else None
self.newtext = "Edit"
self.newtext = newtext or "Ändern"
def rows(self):
return [self.row()]
......@@ -61,3 +61,58 @@ class ProtocolsTable(Table):
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))
])
]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment