Commit 12259818 authored by Robin Sonnabend's avatar Robin Sonnabend
Browse files

Sending mail with attached protocol

parent 4f136efa
...@@ -2,6 +2,7 @@ from flask import render_template, send_file, url_for, redirect, flash, request ...@@ -2,6 +2,7 @@ from flask import render_template, send_file, url_for, redirect, flash, request
from datetime import datetime, time, date, timedelta from datetime import datetime, time, date, timedelta
import math import math
from io import StringIO, BytesIO
from shared import db from shared import db
from utils import random_string, url_manager, get_etherpad_url from utils import random_string, url_manager, get_etherpad_url
...@@ -104,8 +105,6 @@ class ProtocolType(db.Model): ...@@ -104,8 +105,6 @@ class ProtocolType(db.Model):
if protocoltype.has_private_view_right(user) if protocoltype.has_private_view_right(user)
] ]
class Protocol(db.Model): class Protocol(db.Model):
__tablename__ = "protocols" __tablename__ = "protocols"
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
...@@ -283,6 +282,10 @@ class Document(db.Model): ...@@ -283,6 +282,10 @@ class Document(db.Model):
def get_filename(self): def get_filename(self):
return os.path.join(config.DOCUMENTS_PATH, self.filename) return os.path.join(config.DOCUMENTS_PATH, self.filename)
def as_file_like(self):
with open(self.get_filename(), "rb") as file:
return BytesIO(file.read())
@event.listens_for(Document, "before_delete") @event.listens_for(Document, "before_delete")
def on_document_delete(mapper, connection, document): def on_document_delete(mapper, connection, document):
if document.filename is not None: if document.filename is not None:
......
...@@ -565,6 +565,19 @@ def update_protocol(protocol_id): ...@@ -565,6 +565,19 @@ def update_protocol(protocol_id):
return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id)) return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
return render_template("protocol-update.html", upload_form=upload_form, edit_form=edit_form, protocol=protocol) return render_template("protocol-update.html", upload_form=upload_form, edit_form=edit_form, protocol=protocol)
@app.route("/prococol/send/<int:protocol_id>")
@login_required
def send_protocol(protocol_id):
user = current_user()
protocol = Protocol.query.filter_by(id=protocol_id).first()
if protocol is None or not protocol.protocoltype.has_modify_right(user):
flash("Invalides Protokoll oder keine Berechtigung.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
tasks.send_protocol(protocol)
flash("Das Protokoll wurde versandt.", "alert-success")
return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
@app.route("/protocol/tops/new/<int:protocol_id>", methods=["GET", "POST"]) @app.route("/protocol/tops/new/<int:protocol_id>", methods=["GET", "POST"])
@login_required @login_required
def new_top(protocol_id): def new_top(protocol_id):
...@@ -738,9 +751,7 @@ def download_document(document_id): ...@@ -738,9 +751,7 @@ def download_document(document_id):
and not document.protocol.protocoltype.has_public_view_right(user))): and not document.protocol.protocoltype.has_public_view_right(user))):
flash("Keine Berechtigung.", "alert-error") flash("Keine Berechtigung.", "alert-error")
return redirect(request.args.get("next") or url_for("index")) return redirect(request.args.get("next") or url_for("index"))
with open(document.get_filename(), "rb") as file: return send_file(document.as_file_like(), cache_timeout=1, as_attachment=True, attachment_filename=document.name)
file_like = BytesIO(file.read())
return send_file(file_like, cache_timeout=1, as_attachment=True, attachment_filename=document.name)
@app.route("/document/upload/<int:protocol_id>", methods=["POST"]) @app.route("/document/upload/<int:protocol_id>", methods=["POST"])
@login_required @login_required
......
...@@ -199,7 +199,6 @@ def parse_protocol_async(protocol_id, encoded_kwargs): ...@@ -199,7 +199,6 @@ def parse_protocol_async(protocol_id, encoded_kwargs):
content_private = render_template("protocol.txt", render_type=RenderType.plaintext, show_private=True, **render_kwargs) content_private = render_template("protocol.txt", render_type=RenderType.plaintext, show_private=True, **render_kwargs)
content_public = render_template("protocol.txt", render_type=RenderType.plaintext, show_private=False, **render_kwargs) content_public = render_template("protocol.txt", render_type=RenderType.plaintext, show_private=False, **render_kwargs)
if content_private != content_public: if content_private != content_public:
print("different")
privacy_states.append(True) privacy_states.append(True)
protocol.content_private = content_private protocol.content_private = content_private
protocol.content_public = content_public protocol.content_public = content_public
...@@ -316,23 +315,40 @@ def send_reminder_async(reminder_id, protocol_id): ...@@ -316,23 +315,40 @@ def send_reminder_async(reminder_id, protocol_id):
with app.app_context(): with app.app_context():
reminder = MeetingReminder.query.filter_by(id=reminder_id).first() reminder = MeetingReminder.query.filter_by(id=reminder_id).first()
protocol = Protocol.query.filter_by(id=protocol_id).first() protocol = Protocol.query.filter_by(id=protocol_id).first()
reminder_text = render_template("reminder.txt", reminder=reminder, protocol=protocol) reminder_text = render_template("reminder-mail.txt", reminder=reminder, protocol=protocol)
if reminder.send_public: if reminder.send_public:
send_mail(protocol, protocol.protocoltype.public_mail, "Tagesordnung der {}".format(protocol.protocoltype.name), reminder_text) send_mail(protocol, protocol.protocoltype.public_mail, "Tagesordnung der {}".format(protocol.protocoltype.name), reminder_text)
if reminder.send_private: if reminder.send_private:
send_mail(protocol, protocol.protocoltype.private_mail, "Tagesordnung der {}".format(protocol.protocoltype.name), reminder_text) send_mail(protocol, protocol.protocoltype.private_mail, "Tagesordnung der {}".format(protocol.protocoltype.name), reminder_text)
def send_mail(protocol, to_addr, subject, content): def send_protocol(protocol):
send_protocol_async.delay(protocol.id, show_private=True)
send_protocol_async.delay(protocol.id, show_private=False)
@celery.task
def send_protocol_async(protocol_id, show_private):
with app.app_context():
protocol = Protocol.query.filter_by(id=protocol_id).first()
to_addr = protocol.protocoltype.private_mail if show_private else protocol.protocoltype.public_mail
subject = "{}{}-Protokoll vom {}".format("Internes " if show_private else "", protocol.protocoltype.short_name, date_filter(protocol.date))
mail_content = render_template("protocol-mail.txt", protocol=protocol)
appendix = [(document.name, document.as_file_like())
for document in protocol.documents
if show_private or not document.is_private
]
send_mail(protocol, to_addr, subject, mail_content, appendix)
def send_mail(protocol, to_addr, subject, content, appendix=None):
if to_addr is not None and len(to_addr.strip()) > 0: if to_addr is not None and len(to_addr.strip()) > 0:
send_mail_async.delay(protocol.id, to_addr, subject, content) send_mail_async.delay(protocol.id, to_addr, subject, content, appendix)
@celery.task @celery.task
def send_mail_async(protocol_id, to_addr, subject, content): def send_mail_async(protocol_id, to_addr, subject, content, appendix):
with app.app_context(): with app.app_context():
protocol = Protocol.query.filter_by(id=protocol_id).first() protocol = Protocol.query.filter_by(id=protocol_id).first()
try: try:
print("sending {} to {}".format(subject, to_addr)) mail_manager.send(to_addr, subject, content, appendix)
mail_manager.send(to_addr, subject, content)
except Exception as exc: except Exception as exc:
error = protocol.create_error("Sending Mail", "Sending mail failed", str(exc)) error = protocol.create_error("Sending Mail", "Sending mail failed", str(exc))
db.session.add(error) db.session.add(error)
......
Protocol der {{protocol.name}} vom {{protocol.date|datify}}
Datum: {{protocol.date|datify_long}}
Zeit: von {{protocol.start_time|timify}} bis {{protocol.end_time|timify}}
Protokollant: {{protocol.author}}
Anwesende: {{protocol.participants}}
Die Tagesordnung ist:
{% if not protocol.has_nonplanned_tops() %}
{% for default_top in protocol.protocoltype.default_tops %}
{% if not default_top.is_at_end() %}
* {{default_top.name}}
{% endif %}
{% endfor %}
{% endif %}
{% for top in protocol.tops %}
* {{top.name }}
{% endfor %}
{% if not protocol.has_nonplanned_tops() %}
{% for default_top in protocol.protocoltype.default_tops %}
{% if default_top.is_at_end() %}
* {{default_top.name}}
{% endif %}
{% endfor %}
{% endif %}
Beschlüsse:
{% if protocol.decisions|length > 0 %}
{% for decision in protocol.decisions %}
* {{decision.content}}
{% endfor %}
{% else %}
* Keine Beschlüsse
{% endif %}
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
{% if not protocol.is_done() %} {% if not protocol.is_done() %}
<a class="btn btn-default" href="{{url_for("get_protocol_template", protocol_id=protocol.id)}}">Vorlage</a> <a class="btn btn-default" href="{{url_for("get_protocol_template", protocol_id=protocol.id)}}">Vorlage</a>
<a class="btn btn-primary" href="{{url_for("etherpush_protocol", protocol_id=protocol.id)}}">In Etherpad</a> <a class="btn btn-primary" href="{{url_for("etherpush_protocol", protocol_id=protocol.id)}}">In Etherpad</a>
{% else %}
<a class="btn btn-default" href="{{url_for("send_protocol", protocol_id=protocol.id)}}">Per Mail versenden</a>
{% endif %} {% endif %}
<a class="btn btn-default" href="{{protocol.get_etherpad_link()}}" target="_blank">Etherpad</a> <a class="btn btn-default" href="{{protocol.get_etherpad_link()}}" target="_blank">Etherpad</a>
<a class="btn btn-default" href="{{url_for("show_type", type_id=protocol.protocoltype.id)}}">Typ</a> <a class="btn btn-default" href="{{url_for("show_type", type_id=protocol.protocoltype.id)}}">Typ</a>
......
...@@ -6,6 +6,7 @@ import regex ...@@ -6,6 +6,7 @@ import regex
import smtplib import smtplib
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from datetime import datetime, date, timedelta from datetime import datetime, date, timedelta
import requests import requests
from io import BytesIO from io import BytesIO
...@@ -68,18 +69,23 @@ class MailManager: ...@@ -68,18 +69,23 @@ class MailManager:
self.username = getattr(config, "MAIL_USER", "") self.username = getattr(config, "MAIL_USER", "")
self.password = getattr(config, "MAIL_PASSWORD", "") self.password = getattr(config, "MAIL_PASSWORD", "")
def send(self, to_addr, subject, content): def send(self, to_addr, subject, content, appendix=None):
if (not self.active if (not self.active
or not self.hostname or not self.hostname
or not self.username or not self.username
or not self.password or not self.password
or not self.from_addr): or not self.from_addr):
return return
msg = MIMEMultipart("alternative") msg = MIMEMultipart("mixed") # todo: test if clients accept attachment-free mails set to multipart/mixed
msg["From"] = self.from_addr msg["From"] = self.from_addr
msg["To"] = to_addr msg["To"] = to_addr
msg["Subject"] = subject msg["Subject"] = subject
msg.attach(MIMEText(content, _charset="utf-8")) msg.attach(MIMEText(content, _charset="utf-8"))
if appendix is not None:
for name, file_like in appendix:
part = MIMEApplication(file_like.read(), "octet-stream")
part["Content-Disposition"] = 'attachment; filename="{}"'.format(name)
msg.attach(part)
server = smtplib.SMTP_SSL(self.hostname) server = smtplib.SMTP_SSL(self.hostname)
server.login(self.username, self.password) server.login(self.username, self.password)
server.sendmail(self.from_addr, to_addr, msg.as_string()) server.sendmail(self.from_addr, to_addr, msg.as_string())
......
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