Commit 8685fc76 authored by Robin Sonnabend's avatar Robin Sonnabend
Browse files

Todomails

parent 104cb90c
"""empty message
Revision ID: 188f389b2286
Revises: 515d261a624b
Create Date: 2017-02-26 12:55:43.761405
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '188f389b2286'
down_revision = '515d261a624b'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('todomails',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('mail', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('todomails')
# ### end Alembic commands ###
......@@ -5,7 +5,7 @@ import math
from io import StringIO, BytesIO
from shared import db
from utils import random_string, url_manager, get_etherpad_url
from utils import random_string, url_manager, get_etherpad_url, split_terms
from models.errors import DateNotMatchingException
import os
......@@ -326,6 +326,12 @@ class Todo(db.Model):
return None
return candidates[0]
def get_users(self):
return [
user.lower().strip()
for user in split_terms(self.who, separators=" ,\t")
]
def get_state(self):
return "[Erledigt]" if self.done else "[Offen]"
def get_state_plain(self):
......@@ -428,3 +434,20 @@ class Error(db.Model):
if len(lines) <= 4:
return "\n".join(lines)
return "\n".join(lines[:2], "…", lines[-2:])
class TodoMail(db.Model):
__tablename__ = "todomails"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True)
mail = db.Column(db.String)
def __init__(self, name, mail):
self.name = name
self.mail = mail
def __repr__(self):
return "<TodoMail(name='{}', mail='{}')>".format(
self.name, self.mail)
def get_formatted_mail(self):
return "{} <{}>".format(self.name, self.mail)
......@@ -21,9 +21,9 @@ import math
import config
from shared import db, date_filter, datetime_filter, date_filter_long, time_filter, ldap_manager, security_manager, current_user, check_login, login_required, group_required, class_filter
from utils import is_past, mail_manager, url_manager, get_first_unused_int, set_etherpad_text, get_etherpad_text, split_terms, optional_int_arg
from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error
from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm, SearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm
from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable, DecisionsTable, TodoTable, ErrorTable
from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error, TodoMail
from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm, SearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm
from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable, DecisionsTable, TodoTable, ErrorTable, TodoMailsTable
app = Flask(__name__)
app.config.from_object(config)
......@@ -977,6 +977,53 @@ def delete_error(error_id):
db.session.commit()
flash("Fehler {} gelöscht.".format(name), "alert-success")
return redirect(request.args.get("next") or url_for("list_errors"))
@app.route("/todomails/list")
@login_required
def list_todomails():
todomails = TodoMail.query.all()
todomails_table = TodoMailsTable(todomails)
return render_template("todomails-list.html", todomails=todomails, todomails_table=todomails_table)
@app.route("/todomail/new", methods=["GET", "POST"])
@login_required
def new_todomail():
form = TodoMailForm()
if form.validate_on_submit():
todomail = TodoMail(form.name.data, form.mail.data)
db.session.add(todomail)
db.session.commit()
flash("Die Todomailzuordnung für {} wurde angelegt.".format(todomail.name), "alert-success")
return redirect(request.args.get("next") or url_for("list_todomails"))
return render_template("todomail-new.html", form=form)
@app.route("/todomail/edit/<int:todomail_id>", methods=["GET", "POST"])
@login_required
def edit_todomail(todomail_id):
todomail = TodoMail.query.filter_by(id=todomail_id).first()
if todomail is None:
flash("Invalide Todo-Mail-Zuordnung.", "alert-error")
return redirect(request.args.get("next") or url_for("list_todomails"))
form = TodoMailForm(obj=todomail)
if form.validate_on_submit():
form.populate_obj(todomail)
db.session.commit()
flash("Die Todo-Mail-Zuordnung wurde geändert.", "alert-success")
return redirect(request.args.get("next") or url_for("list_todomails"))
return render_template("todomail-edit.html", todomail=todomail, form=form)
@app.route("/todomail/delete/<int:todomail_id>")
@login_required
def delete_todomail(todomail_id):
todomail = TodoMail.query.filter_by(id=todomail_id).first()
if todomail is None:
flash("Invalide Todomailzuordnung.", "alert-error")
return redirect(request.args.get("next") or url_for("list_todomails"))
name = todomail.name
db.session.delete(todomail)
db.session.commit()
flash("Die Todo-Mail-Zuordnung für {} wurde gelöscht.".format(name), "alert-success")
return redirect(request.args.get("next") or url_for("list_todomails"))
@app.route("/login", methods=["GET", "POST"])
......
......@@ -5,7 +5,7 @@ import subprocess
import shutil
import tempfile
from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP, MeetingReminder
from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP, MeetingReminder, TodoMail
from models.errors import DateNotMatchingException
from server import celery, app
from shared import db, escape_tex, unhyphen, date_filter, datetime_filter, date_filter_long, date_filter_short, time_filter, class_filter
......@@ -324,6 +324,7 @@ def send_reminder_async(reminder_id, protocol_id):
def send_protocol(protocol):
send_protocol_async.delay(protocol.id, show_private=True)
send_protocol_async.delay(protocol.id, show_private=False)
send_todomails_async.delay(protocol.id)
@celery.task
def send_protocol_async(protocol_id, show_private):
......@@ -338,6 +339,27 @@ def send_protocol_async(protocol_id, show_private):
]
send_mail(protocol, to_addr, subject, mail_content, appendix)
@celery.task
def send_todomails_async(protocol_id):
with app.app_context():
protocol = Protocol.query.filter_by(id=protocol_id).first()
all_todos = Todo.query.filter(Todo.done == False).all()
users = {user for todo in all_todos for user in todo.get_users()}
grouped_todos = {
user: [todo for todo in all_todos if user in todo.get_users()]
for user in users
}
subject = "Du hast noch was zu tun!"
for user in users:
todomail = TodoMail.query.filter(TodoMail.name.ilike(user)).first()
if todomail is None:
error = protocol.create_error("Sending Todomail", "Sending Todomail failed.", "User {} has no Todo-Mail-Assignment.".format(user))
db.session.add(error)
db.session.commit()
continue
to_addr = todomail.get_formatted_mail()
mail_content = render_template("todo-mail.txt", protocol=protocol, todomail=todomail, todos=grouped_todos[user])
send_mail(protocol, to_addr, subject, mail_content)
def send_mail(protocol, to_addr, subject, content, appendix=None):
if to_addr is not None and len(to_addr.strip()) > 0:
......
......@@ -27,7 +27,7 @@
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
{% if check_login() %}
<li><a href="{{url_for("new_protocol")}}">Neues Protokoll</a></li>
<li><a href="{{url_for("new_protocol")}}">Neu</a></li>
{% endif %}
<li><a href="{{url_for("list_protocols")}}">Protokolle</a></li>
{% if check_login() %}
......@@ -37,6 +37,7 @@
{% if check_login() %}
<li><a href="{{url_for("list_types")}}">Typen</a></li>
<li><a href="{{url_for("list_errors")}}">Fehler</a></li>
<li><a href="{{url_for("list_todomails")}}">Todo Mails</a></li>
{% endif %}
{# todo: add more links #}
</ul>
......
Hallo {{todomail.name}},
Du hast für "{{protocol.protocoltype.name}}" noch offene Todos:
{% for todo in todos %}
{{todo.who}}:
{{todo.description}}
{% endfor %}
Fühle die hiermit daran erinnert!
Viele Grüße,
Dein Protokollsystem
{% extends "layout.html" %}
{% from "macros.html" import render_form %}
{% block title %}Todomail ändern{% endblock %}
{% block content %}
<div class="container">
{{render_form(form, action_url=url_for("edit_todomail", todomail_id=todomail.id), action_text="Ändern")}}
</div>
{% endblock %}
{% extends "layout.html" %}
{% from "macros.html" import render_form %}
{% block title %}Neue Todomail{% endblock %}
{% block content %}
<div class="container">
{{render_form(form, action_url=url_for("new_todomail"), action_text="Ändern")}}
</div>
{% endblock %}
{% extends "layout.html" %}
{% from "macros.html" import render_table %}
{% block title %}Todomails{% endblock %}
{% block content %}
<div class="container">
{{render_table(todomails_table)}}
</div>
{% endblock %}
......@@ -75,6 +75,7 @@ class MailManager:
or not self.username
or not self.password
or not self.from_addr):
print("Not sending mail {} to {}".format(subject, to_addr))
return
msg = MIMEMultipart("mixed") # todo: test if clients accept attachment-free mails set to multipart/mixed
msg["From"] = self.from_addr
......
......@@ -108,3 +108,7 @@ class TodoForm(FlaskForm):
description = StringField("Aufgabe", validators=[InputRequired("Bitte gib an, was erledigt werden soll.")])
tags = StringField("Weitere Tags")
done = BooleanField("Erledigt")
class TodoMailForm(FlaskForm):
name = StringField("Name", validators=[InputRequired("Du musst den Namen angeben, der zugeordnet werden soll.")])
mail = StringField("Mail", validators=[InputRequired("Du musst die Mailadresse angeben, die zugeordnet werden soll.")])
......@@ -314,3 +314,21 @@ class DocumentsTable(Table):
if document.protocol.protocoltype.has_modify_right(user)
else ""
]
class TodoMailsTable(Table):
def __init__(self, todomails):
super().__init__("Todo-Mail-Zuordnungen", todomails, url_for("new_todomail"))
def headers(self):
return ["Name", "Mail", ""]
def row(self, todomail):
return [
todomail.name,
todomail.mail,
Table.concat([
Table.link(url_for("edit_todomail", todomail_id=todomail.id), "Ändern"),
Table.link(url_for("delete_todomail", todomail_id=todomail.id), "Löschen", confirm="Bist du dir sicher, dass du die Todomailzuordnung {} zu {} löschen willst?".format(todomail.name, todomail.mail))
])
]
Supports Markdown
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