Commit 5ed297b0 authored by Robin Sonnabend's avatar Robin Sonnabend
Browse files

Printing decisions

parent 22841431
"""empty message
Revision ID: 0e5220a9f169
Revises: 188f389b2286
Create Date: 2017-02-26 15:53:41.410353
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0e5220a9f169'
down_revision = '188f389b2286'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('decisiondocuments',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('decision_id', sa.Integer(), nullable=True),
sa.Column('name', sa.String(), nullable=True),
sa.Column('filename', sa.String(), nullable=True),
sa.Column('is_compiled', sa.Boolean(), nullable=True),
sa.Column('is_private', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['decision_id'], ['decisions.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('decisiondocuments')
# ### end Alembic commands ###
......@@ -289,10 +289,42 @@ class Document(db.Model):
@event.listens_for(Document, "before_delete")
def on_document_delete(mapper, connection, document):
if document.filename is not None:
document_path = os.path.join(config.DOCUMENTS_PATH, document.filename)
document_path = document.get_filename()
if os.path.isfile(document_path):
os.remove(document_path)
class DecisionDocument(db.Model):
__tablename__ = "decisiondocuments"
id = db.Column(db.Integer, primary_key=True)
decision_id = db.Column(db.Integer, db.ForeignKey("decisions.id"))
name = db.Column(db.String)
filename = db.Column(db.String)
def __init__(self, decision_id, name, filename):
self.decision_id = decision_id
self.name = name
self.filename = filename
def __repr__(self):
return "<DecisionDocument(id={}, decision_id={}, name={}, filename={})>".format(
self.id, self.decision_id, self.name, self.filename)
def get_filename(self):
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(DecisionDocument, "before_delete")
def on_decisions_document_delete(mapper, connection, document):
if document.filename is not None:
document_path = document.get_filename()
if os.path.isfile(document_path):
os.remove(document_path)
class Todo(db.Model):
__tablename__ = "todos"
id = db.Column(db.Integer, primary_key=True)
......@@ -381,6 +413,8 @@ class Decision(db.Model):
protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id"))
content = db.Column(db.String)
document = relationship("DecisionDocument", backref=backref("decision"), cascade="all, delete-orphan", uselist=False)
def __init__(self, protocol_id, content):
self.protocol_id = protocol_id
self.content = content
......@@ -433,7 +467,7 @@ class Error(db.Model):
lines = self.description.splitlines()
if len(lines) <= 4:
return "\n".join(lines)
return "\n".join(lines[:2], "…", lines[-2:])
return "\n".join([*lines[:2], "…", *lines[-2:]])
class TodoMail(db.Model):
__tablename__ = "todomails"
......
......@@ -94,7 +94,9 @@ class Element:
current.append(element)
if isinstance(element, Fork):
return element
return current
else:
element.fork = current
return current
PATTERN = r"x(?<!x)" # yes, a master piece, but it should never be called
......@@ -123,14 +125,14 @@ class Content(Element):
if match.group("content") is None:
raise ParserException("Content is missing its content!", linenumber)
content = match.group("content")
element = Content.from_content(content, linenumber)
element = Content.from_content(content, current, linenumber)
if len(content) == 0:
return current, linenumber
current = Element.parse_outer(element, current)
return current, linenumber
@staticmethod
def from_content(content, linenumber):
def from_content(content, current, linenumber):
children = []
while len(content) > 0:
matched = False
......@@ -138,7 +140,7 @@ class Content(Element):
match = pattern.match(content)
if match is not None:
matched = True
children.append(TEXT_PATTERNS[pattern](match, linenumber))
children.append(TEXT_PATTERNS[pattern](match, current, linenumber))
content = content[len(match.group()):]
break
if not matched:
......@@ -151,9 +153,10 @@ class Content(Element):
PATTERN = r"\s*(?<content>(?:[^\[\];\r\n]+)?(?:\[[^\]\r\n]+\][^;\[\]\r\n]*)*);?"
class Text:
def __init__(self, text, linenumber):
def __init__(self, text, linenumber, fork):
self.text = text
self.linenumber = linenumber
self.fork = fork
def render(self, render_type, show_private, level=None, protocol=None):
if render_type == RenderType.latex:
......@@ -171,22 +174,23 @@ class Text:
print("{}text: {}".format(" " * level, self.text))
@staticmethod
def parse(match, linenumber):
def parse(match, current, linenumber):
if match is None:
raise ParserException("Text is not actually a text!", linenumber)
content = match.group("text")
if content is None:
raise ParserException("Text is empty!", linenumber)
return Text(content, linenumber)
return Text(content, linenumber, current)
PATTERN = r"(?<text>[^\[]+)(?:(?=\[)|$)"
class Tag:
def __init__(self, name, values, linenumber):
def __init__(self, name, values, linenumber, fork):
self.name = name
self.values = values
self.linenumber = linenumber
self.fork = fork
def render(self, render_type, show_private, level=None, protocol=None):
if render_type == RenderType.latex:
......@@ -222,14 +226,14 @@ class Tag:
print("{}tag: {}: {}".format(" " * level, self.name, "; ".join(self.values)))
@staticmethod
def parse(match, linenumber):
def parse(match, current, linenumber):
if match is None:
raise ParserException("Tag is not actually a tag!", linenumber)
content = match.group("content")
if content is None:
raise ParserException("Tag is empty!", linenumber)
parts = content.split(";")
return Tag(parts[0], parts[1:], linenumber)
return Tag(parts[0], parts[1:], linenumber, current)
PATTERN = r"\[(?<content>(?:[^;\]]*;)*(?:[^;\]]*))\]"
......@@ -377,6 +381,11 @@ class Fork(Element):
def is_root(self):
return self.parent is None
def get_top(self):
if self.is_root() or self.parent.is_root():
return self
return self.parent.get_top()
@staticmethod
def create_root():
return Fork(None, None, None, 0)
......
......@@ -21,7 +21,7 @@ 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, TodoMail
from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error, TodoMail, DecisionDocument
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
......@@ -86,8 +86,13 @@ def index():
if protocol.date is not None:
return protocol.date
return datetime.now().date()
current_day = datetime.now().date()
open_protocols = sorted(
[protocol for protocol in protocols if not protocol.done],
[
protocol for protocol in protocols
if not protocol.done
and (protocol.date - current_day).days < config.MAX_INDEX_DAYS
],
key=_sort_key
)
finished_protocols = sorted(
......@@ -660,7 +665,6 @@ def new_top(protocol_id):
db.session.commit()
return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
else:
print(form.number.data)
current_numbers = list(map(lambda t: t.number, protocol.tops))
suggested_number = get_first_unused_int(current_numbers)
form.number.data = suggested_number
......@@ -967,6 +971,21 @@ def print_document(document_id):
flash("Das Dokument {} wird gedruckt.".format(document.name), "alert-success")
return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=document.protocol.id))
@app.route("/decision/print/<int:document_id>")
@login_required
def print_decision(document_id):
user = current_user()
document = DecisionDocument.query.filter_by(id=document_id).first()
if document is None or not document.decision.protocol.protocoltype.has_modify_right(user):
flash("Invalides Dokument oder keine Berechtigung.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
if not config.PRINTING_ACTIVE:
flash("Die Druckfunktion ist nicht aktiviert.", "alert-error")
return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=document.decision.protocol.id))
tasks.print_file(document.get_filename(), document.decision.protocol)
flash("Das Dokument {} wird gedruckt.".format(document.name), "alert-success")
return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=document.decision.protocol.id))
@app.route("/errors/list")
@login_required
def list_errors():
......
......@@ -5,7 +5,7 @@ import subprocess
import shutil
import tempfile
from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP, MeetingReminder, TodoMail
from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP, MeetingReminder, TodoMail, DecisionDocument
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
......@@ -182,6 +182,9 @@ def parse_protocol_async(protocol_id, encoded_kwargs):
decision = Decision(protocol_id=protocol.id, content=decision_tag.values[0])
db.session.add(decision)
db.session.commit()
decision_content = texenv.get_template("decision.tex").render(render_type=RenderType.latex, decision=decision, protocol=protocol, top=decision_tag.fork.get_top(), show_private=False)
print(decision_content)
compile_decision(decision_content, decision)
old_tops = list(protocol.tops)
for top in old_tops:
protocol.tops.remove(top)
......@@ -229,12 +232,21 @@ def push_to_wiki_async(protocol_id, content, summary):
error = protocol.create_error("Pushing to Wiki", "Pushing to Wiki failed.", str(exc))
def compile(content, protocol, show_private):
compile_async.delay(content, protocol.id, show_private)
compile_async.delay(content, protocol.id, show_private=show_private)
def compile_decision(content, decision):
compile_async.delay(content, decision.id, use_decision=True)
@celery.task
def compile_async(content, protocol_id, show_private):
def compile_async(content, protocol_id, show_private=False, use_decision=False):
with tempfile.TemporaryDirectory() as compile_dir, app.app_context():
protocol = Protocol.query.filter_by(id=protocol_id).first()
decision = None
protocol = None
if use_decision:
decision = Decision.query.filter_by(id=protocol_id).first()
protocol = decision.protocol
else:
protocol = Protocol.query.filter_by(id=protocol_id).first()
try:
current = os.getcwd()
protocol_source_filename = "protocol.tex"
......@@ -255,17 +267,22 @@ def compile_async(content, protocol_id, show_private):
subprocess.check_call(command, universal_newlines=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
subprocess.check_call(command, universal_newlines=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
os.chdir(current)
for old_document in [document for document in protocol.documents if document.is_compiled and document.is_private == show_private]:
protocol.documents.remove(old_document)
db.session.commit()
document = Document(protocol.id, name="protokoll{}_{}_{}.pdf".format("_intern" if show_private else "", protocol.protocoltype.short_name, date_filter_short(protocol.date)), filename="", is_compiled=True, is_private=show_private)
document = None
if not use_decision:
for old_document in [document for document in protocol.documents if document.is_compiled and document.is_private == show_private]:
protocol.documents.remove(old_document)
db.session.commit()
document = Document(protocol.id, name="protokoll{}_{}_{}.pdf".format("_intern" if show_private else "", protocol.protocoltype.short_name, date_filter_short(protocol.date)), filename="", is_compiled=True, is_private=show_private)
else:
document = DecisionDocument(decision.id, name="beschluss_{}_{}_{}.pdf".format(protocol.protocoltype.short_name, date_filter_short(protocol.date), decision.id), filename="")
db.session.add(document)
db.session.commit()
target_filename = "compiled-{}-{}.pdf".format(document.id, "internal" if show_private else "public")
if use_decision:
target_filename = "decision-{}-{}-{}.pdf".format(protocol.id, decision.id, document.id)
document.filename = target_filename
shutil.copy(os.path.join(compile_dir, protocol_target_filename), os.path.join(config.DOCUMENTS_PATH, target_filename))
db.session.commit()
#shutil.copy(os.path.join(compile_dir, log_filename), "/tmp")
except subprocess.SubprocessError:
log = ""
total_log_filename = os.path.join(compile_dir, log_filename)
......
\documentclass[11pt,twoside]{protokoll2}
%\usepackage{bookman}
%\usepackage{newcent}
%\usepackage{palatino}
\usepackage{pdfpages}
\usepackage{eurosym}
%\usepackage[utf8]{inputenc}
\usepackage[pdfborder={0 0 0}]{hyperref}
%\usepackage{ngerman}
% \usepackage[left]{lineno}
%\usepackage{footnote}
%\usepackage{times}
\renewcommand{\thefootnote}{\fnsymbol{footnote}}
\renewcommand{\thempfootnote}{\fnsymbol{mpfootnote}}
%\renewcommand{\familydefault}{\sfdefault}
\newcommand{\einrueck}[1]{\hfill\begin{minipage}{0.95\linewidth}#1\end{minipage}}
\begin{document}
%\thispagestyle{plain} %ggf kommentarzeichen entfernen
\Titel{
\large Protokoll: \VAR{protocol.protocoltype.name|escape_tex}
\\\normalsize \VAR{protocol.protocoltype.organization|escape_tex}
}{}
\begin{tabular}{rp{14cm}}
{\bf Datum:} & \VAR{protocol.date|datify_long|escape_tex}\\
{\bf Ort:} & \VAR{protocol.location|escape_tex}\\
{\bf Protokollant:} & \VAR{protocol.author|escape_tex}\\
{\bf Anwesend:} & \VAR{protocol.participants|escape_tex}\\
\end{tabular}
\normalsize
\section*{Beschluss}
\begin{itemize}
\item \VAR{decision.content|escape_tex}
\end{itemize}
\TOP{\VAR{top.name|escape_tex}}
\VAR{top.render(render_type=render_type, level=0, show_private=show_private, protocol=protocol)}
\end{document}
......@@ -74,7 +74,12 @@
<ul>
{% if protocol.decisions|length > 0 %}
{% for decision in protocol.decisions %}
<li>{{decision.content}}</li>
<li>
{{decision.content}}
{% if config.PRINTING_ACTIVE and has_private_view_right and decision.document is not none %}
<a href="{{url_for("print_decision", document_id=decision.document.id)}}">Drucken</a>
{% endif %}
</li>
{% endfor %}
{% else %}
<li>Keine Beschlüsse</li>
......
......@@ -209,7 +209,7 @@ class ErrorsTable(Table):
Table.link(url_for("show_error", error_id=error.id), error.name),
datetime_filter(error.datetime),
error.get_short_description(),
Table.link(url_for("delete_error", error_id=error.id), "Löschen", confirm="Bist du dir sicher, dass du den Fehler löschen möchtest?")
Table.link(url_for("delete_error", error_id=error.id, next=request.path), "Löschen", confirm="Bist du dir sicher, dass du den Fehler löschen möchtest?")
]
class ErrorTable(SingleValueTable):
......@@ -287,12 +287,18 @@ class DecisionsTable(Table):
super().__init__("Beschlüsse", decisions)
def headers(self):
return ["Sitzung", "Beschluss"]
return ["Sitzung", "Beschluss", ""]
def row(self, decision):
user = current_user()
return [
Table.link(url_for("show_protocol", protocol_id=decision.protocol.id), decision.protocol.get_identifier()),
decision.content
decision.content,
Table.link(url_for("print_decision", document_id=decision.document.id), "Drucken")
if config.PRINTING_ACTIVE
and decision.protocol.protocoltype.has_modify_right(user)
and decision.document is not None
else ""
]
class DocumentsTable(Table):
......
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