diff --git a/configproxy.py b/configproxy.py index 7d5ce97a16ed8812f2c2b4b2ececec06e928d2ae..7488c42c195d7514bbe7fd7d80ed9bc2dd621230 100755 --- a/configproxy.py +++ b/configproxy.py @@ -766,8 +766,8 @@ CONFIG_SECTIONS = [ "files: e.g. the files for the template 'yourtemplate' " "need to be in the folder named 'yourtemplate', and the " "templates provides the files: 'protokoll2.cls' (class), " - "'protocol.tex' (protocol), 'decision.tex' (decision) and " - "'asta-logo.tex'")), + "'protocol.tex' (protocol), 'decision.tex' (decision)," + "'extra.tex' (exported TOP) and 'asta-logo.tex'")), ], check=check_rendering, description="Settings for rendering protocols to pdf, html, etc."), diff --git a/migrations/versions/7834767242e8_append_extra_column_to_document.py b/migrations/versions/7834767242e8_append_extra_column_to_document.py new file mode 100644 index 0000000000000000000000000000000000000000..d63b141610c1079b80ad5cdb8fd8bde46a39d721 --- /dev/null +++ b/migrations/versions/7834767242e8_append_extra_column_to_document.py @@ -0,0 +1,28 @@ +"""Append extra column to Document + +Revision ID: 7834767242e8 +Revises: ead59eff0835 +Create Date: 2022-03-21 11:46:20.368800 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7834767242e8' +down_revision = 'ead59eff0835' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('documents', sa.Column('is_extra', sa.Boolean(), default=False, nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('documents', 'is_extra') + # ### end Alembic commands ### diff --git a/models/database.py b/models/database.py index 59e71ce22bbf7546963d2455b611f3f427bc2bcf..411ad1b88097cd2a1f80da0ebe5acb16e84b85b2 100644 --- a/models/database.py +++ b/models/database.py @@ -540,6 +540,7 @@ class Document(DatabaseModel): name = db.Column(db.Text) filename = db.Column(db.Text) is_compiled = db.Column(db.Boolean) + is_extra = db.Column(db.Boolean) is_private = db.Column(db.Boolean) def get_parent(self): diff --git a/protoparser.py b/protoparser.py index cf90277614205cdf36487df33031861fea6936a7..45d55a46952df1988a567dee3a04a160890b3f6c 100644 --- a/protoparser.py +++ b/protoparser.py @@ -38,6 +38,7 @@ class RenderType(Enum): plaintext = 2 html = 3 dokuwiki = 4 + extra = 5 def _not_implemented(self, render_type): @@ -187,7 +188,7 @@ class Text: self.fork = fork def render(self, render_type, show_private, level=None, protocol=None): - if render_type == RenderType.latex: + if render_type == RenderType.latex or render_type == RenderType.extra: return escape_tex(self.text) elif render_type == RenderType.wikitext: return self.text @@ -244,6 +245,22 @@ class Tag: return r"\textbf{{{}:}} {}".format( escape_tex(self.name.capitalize()), escape_tex(";".join(self.values))) + elif render_type == RenderType.extra: + if self.name == "url": + return r"\url{{{}}}".format(self.values[0]) + elif self.name == "todo": + return "" + elif self.name == "beschluss": + return ( + r"\begin{tcolorbox}[breakable,title=Beschluss, colframe=red!45!yellow, colback=yellow!10, coltitle=white,]" + "\n" + + r"\begin{itemize}" + + r"\item[] " + self.values[0] + "\n" + + r"\end{itemize} \end{tcolorbox}") + elif self.name == "footnote": + return r"\footnote{{{}}}".format(self.values[0]) + return r"\textbf{{{}:}} {}".format( + escape_tex(self.name.capitalize()), + escape_tex(";".join(self.values))) elif render_type == RenderType.plaintext: if self.name == "url": return self.values[0] @@ -357,7 +374,7 @@ class Remark(Element): self.linenumber = linenumber def render(self, render_type, show_private, level=None, protocol=None): - if render_type == RenderType.latex: + if render_type == RenderType.latex or render_type == RenderType.extra: return r"\textbf{{{}}}: {}".format(self.name, self.value) elif render_type == RenderType.wikitext: return "{}: {}".format(self.name, self.value) @@ -428,27 +445,39 @@ class Fork(Element): name_line = self.name if self.name is not None else "" if level == 0 and self.name == "Todos" and not show_private: return "" - if render_type == RenderType.latex: - if self.is_extra: + if render_type == RenderType.latex or render_type == RenderType.extra: + if render_type == RenderType.latex and self.is_extra: return r"\textit{[Dieser Tagesordnungspunkt wird in einem eigenem PDF exportiert.]}" begin_line = r"\begin{itemize}" end_line = r"\end{itemize}" content_parts = [] + parts = [] for child in self.children: part = child.render( render_type, show_private, level=level + 1, protocol=protocol) + parts.append(part) if len(part.strip()) == 0: continue - if not part.startswith(r"\item"): - part = r"\item {}".format(part) + if render_type == RenderType.latex: + if not part.startswith(r"\item"): + part = r"\item {}".format(part) + elif render_type == RenderType.extra: + if not part.startswith(r"\item") and not part.startswith(r"\footnote") and not part.startswith( + r"\begin{tcolorbox}"): + part = r"\item {}".format(part) + elif part.startswith(r"\begin{tcolorbox}"): + part = r"\item[] {}".format(part) content_parts.append(part) content_lines = "\n".join(content_parts) if len(content_lines.strip()) == 0: content_lines = "\\item Nichts\n" if level == 0: - return "\n".join([begin_line, content_lines, end_line]) + if render_type == RenderType.latex: + return "\n".join([begin_line, content_lines, end_line]) + elif render_type == RenderType.extra: + return "\n".join(parts) elif self.test_private(self.name): if show_private: return (r"\begin{tcolorbox}[breakable,title=Interner " @@ -460,6 +489,12 @@ class Fork(Element): else: return (r"\textit{[An dieser Stelle wurde intern " r"protokolliert.]}") + elif render_type == RenderType.extra and level == 1: + name_escape = escape_tex(name_line) + return "\n".join([ + f"\section{{{name_escape}}}", begin_line, + content_lines, end_line + ]) else: return "\n".join([ escape_tex(name_line), begin_line, diff --git a/tasks.py b/tasks.py index 25b4df1f657b8b9a09c100c358513d783e12528b..7c1e9a2af17339f8052b5789f0f8894b43fde0df 100644 --- a/tasks.py +++ b/tasks.py @@ -70,7 +70,8 @@ def provide_latex_template(template, documenttype): _DOCUMENTTYPE_FILENAME_MAP = { "class": "protokoll2.cls", "protocol": "protocol.tex", - "decision": "decision.tex" + "decision": "decision.tex", + "extra": "extra.tex" } _PROVIDES = "provides" _LOGO_TEMPLATE = "logo_template" @@ -558,6 +559,21 @@ def parse_protocol_async_inner(protocol, ignore_old_date=False): latex_source, protocol, show_private=show_private, maxdepth=maxdepth) + # Export extra TOPs + extra_tops = [child for child in tree.children if isinstance(child, Fork) and child.is_extra] + for top in extra_tops: + for show_private in privacy_states: + latex_source = texenv.get_template(provide_latex_template( + protocol.protocoltype.latex_template, "extra")).render( + render_type=RenderType.extra, + top=top, + show_private=show_private, + **render_kwargs[show_private]) + compile_extra( + latex_source, protocol, show_private=show_private, extra_name=top.name, + maxdepth=maxdepth) + + if protocol.protocoltype.use_wiki: wiki_type = WikiType[getattr(config, "WIKI_TYPE", "MEDIAWIKI")] wiki_template = { @@ -644,10 +660,13 @@ def compile_decision(content, decision, maxdepth): compile_async.delay( content, decision.id, use_decision=True, maxdepth=maxdepth) +def compile_extra(content, protocol, show_private, maxdepth, extra_name): + compile_async.delay( + content, protocol.id, use_decision=False, show_private=show_private, maxdepth=maxdepth, is_extra=True, extra_name=extra_name) @celery.task def compile_async( - content, protocol_id, show_private=False, use_decision=False, + content, protocol_id, show_private=False, use_decision=False, is_extra=False, extra_name="", maxdepth=5): with tempfile.TemporaryDirectory() as compile_dir, app.app_context(): decision = None @@ -692,7 +711,7 @@ def compile_async( stderr=subprocess.DEVNULL) os.chdir(current) document = None - if not use_decision: + if not use_decision and not is_extra: for old_document in [ document for document in protocol.documents if document.is_compiled @@ -708,7 +727,7 @@ def compile_async( filename="", is_compiled=True, is_private=show_private) - else: + elif use_decision and not is_extra: document = DecisionDocument( decision_id=decision.id, name="beschluss_{}_{}_{}.pdf".format( @@ -716,6 +735,20 @@ def compile_async( date_filter_short(protocol.date), decision.id), filename="") + elif is_extra and not use_decision: + document = Document( + protocol_id=protocol.id, + name="extra-{}{}_{}_{}.pdf".format( + extra_name, + "_intern" if show_private else "", + protocol.protocoltype.short_name, + date_filter_short(protocol.date)), + filename="", + is_compiled=True, + is_extra=True, + is_private=show_private) + else: + raise NotImplementedError("Unknown type.") db.session.add(document) db.session.commit() target_filename = "compiled-{}-{}.pdf".format( diff --git a/templates/documentation-syntax-top.html b/templates/documentation-syntax-top.html index 90a5a20dcbd97c44f1a0c714129e2077a6adcef4..e3945f27d44c4a9e742708fcb8a9559fa2609839 100644 --- a/templates/documentation-syntax-top.html +++ b/templates/documentation-syntax-top.html @@ -40,4 +40,19 @@ </div> </div> </div> + <h4>Exportieren</h4> + <p> + Außerdem kann ein Tagesordnungspunkt auch so angelegt werden, dass ein eigenes PDF für diesen angelegt wird, und im Hauptprotokoll auf dieses PDF verwiesen wird. Dies wird durch ein Hinzufügen eines Ausrufezeichens (<span class="highlight"><span class="kr">!</span></span>) vor dem Schlüsselword <span class="highlight"><span class="kr">TOP</span></span> erreicht. + </p> + <div class="panel panel-default"> + <div class="panel-heading"> + <h5 class="panel-title">Tagesordnungspunkt in eigenem PDF</a></h5> + </div> + <figure class="panel-body"> + <pre class="highlight"><code>{<span class="kr">!TOP</span> <span class="sx">Titel des Tagesordnungspunkts</span> + <span class="c1">Hier steht der Inhalt des Tagesordnungspunkts</span> + <span class="c1">Dies wird in einem separaten PDF exportiert</span> +}</code></pre> + </figure> + </div> {% endblock %} diff --git a/templates/extra.tex b/templates/extra.tex new file mode 100644 index 0000000000000000000000000000000000000000..4ed2894901655890cdcdd3a8f53464377fe1b981 --- /dev/null +++ b/templates/extra.tex @@ -0,0 +1,113 @@ +% Template created by Karol Kozioł (www.karol-koziol.net) for ShareLaTeX +% Template adapted by Nils Beyer + +\documentclass[a4paper,ngerman,twocolumn,9pt]{extarticle} +\usepackage[utf8]{inputenc} + +\usepackage{ifthen} +\newboolean{intern} +\newcommand{\intern}[2]{\ifthenelse{\boolean{intern}}{#1}{#2} } + +\usepackage[T1]{fontenc} +\usepackage{verbatim} +\usepackage{graphicx} +\usepackage{xcolor} +\usepackage{pgf,tikz} +\usepackage{mathrsfs} + +\usepackage{csquotes} +\usepackage{tcolorbox} +\tcbuselibrary{listings,breakable} +\usepackage{draftwatermark} +\SetWatermarkText{} +\SetWatermarkScale{1} + +\usetikzlibrary{shapes, calc, shapes, arrows, babel} + +\usepackage{amsmath,amssymb,textcomp} +\everymath{\displaystyle} + +\usepackage{times} +\renewcommand\familydefault{\sfdefault} +\usepackage{tgheros} + +\usepackage{multicol} +\setlength{\columnseprule}{0.5pt} +\setlength{\columnsep}{20.0pt} + +\usepackage[utf8]{inputenc} +\usepackage[ngerman]{babel} +\usepackage{eurosym} + +\usepackage{hyperref} + +\usepackage{geometry} +\geometry{ + a4paper, + total={210mm,297mm}, + left=10mm,right=10mm,top=10mm,bottom=15mm} + +\linespread{1.2} + + +% custom section titles +\usepackage[explicit]{titlesec} +\newcommand*\sectionlabel{} +\titleformat{\section} +% {\gdef\sectionlabel{}\normalfont\sffamily\Large\bfseries\scshape} +{\gdef\sectionlabel{}\normalfont\sffamily\bfseries} +{\gdef\sectionlabel{\thesection }}{0pt} +{ + \noindent + \begin{tikzpicture} + \node[rectangle,rounded corners=1pt,inner sep=4pt,fill=red!45!black,text width=0.95\columnwidth] {\color{white}\sectionlabel #1}; + \end{tikzpicture} +} +\titlespacing*{\section}{0pt}{10pt}{0pt} + +% Numbering style +\renewcommand*{\thesection}{\Roman{section}~: } + + +% custom footer +\usepackage{fancyhdr} +\makeatletter +\pagestyle{fancy} +\fancyhead{} +\renewcommand{\headrulewidth}{0pt} +\renewcommand{\footrulewidth}{0pt} +\makeatother + +% href boxes +\hypersetup{ + colorlinks, + linkcolor={red!50!black}, + citecolor={blue!50!black}, + urlcolor={blue!80!black} +} + +% Line numbers on sides +\usepackage[switch,columnwise]{lineno} + + +\setlength{\parindent}{0em} +\usepackage{enumitem} +\setlist{nosep} + +\newcommand{\Beschluss}[2][]{\textbf{Beschluss:} #2 \def\temp{#1}\ifx\temp\empty\else\textit{(#1)}\fi} + + +\ENV{if show_private}\setboolean{intern}{true}\ENV{endif} +\intern{\SetWatermarkText{INTERN} \SetWatermarkScale{1}}{} +\begin{document} + \small + \begin{center} + \VAR{top.name|escape_tex} zur \textsc{\VAR{protocol.protocoltype.name|escape_tex}} am \VAR{protocol.date|datify_long|escape_tex} + \end{center} + + \ENV{if top|class == "Fork"} + \VAR{top.render(render_type=render_type, level=0, show_private=show_private, protocol=protocol)} + \ENV{endif} + + \runningpagewiselinenumbers \linenumbers +\end{document} \ No newline at end of file