Commit 1e997477 authored by Robin Sonnabend's avatar Robin Sonnabend
Browse files

configurable fonts and document upload

parent d558fb90
SQLALCHEMY_DATABASE_URI = "postgresql://proto3:@/proto3" SQLALCHEMY_DATABASE_URI = "postgresql://proto3:@/proto3"
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = "abc" SECRET_KEY = "abc"
DEBUG = False DEBUG = False
MAIL_ACTIVE = True MAIL_ACTIVE = True
MAIL_FROM = "protokolle@example.com" MAIL_FROM = "protokolle@example.com"
MAIL_HOST = "mail.example.com:465" MAIL_HOST = "mail.example.com:465"
MAIL_USER = "user" MAIL_USER = "user"
MAIL_PASSWORD = "password" MAIL_PASSWORD = "password"
MAIL_PREFIX = "protokolle" MAIL_PREFIX = "protokolle"
CELERY_BROKER_URL = "redis://localhost:6379/0" CELERY_BROKER_URL = "redis://localhost:6379/0"
CELERY_TASK_SERIALIZER = "pickle" CELERY_TASK_SERIALIZER = "pickle"
CELERY_ACCEPT_CONTENT = ["pickle"] CELERY_ACCEPT_CONTENT = ["pickle"]
URL_ROOT = "protokolle.example.com" URL_ROOT = "protokolle.example.com"
URL_PROTO = "https" URL_PROTO = "https"
URL_PATH = "/" URL_PATH = "/"
URL_PARAMS = "" URL_PARAMS = ""
ERROR_CONTEXT_LINES = 3 ERROR_CONTEXT_LINES = 3
# choose something nice from fc-list
FONTS = {
"main": {
"regular": "Nimbus Sans",
"bold": "NimbusSans",
"italic": "NimbusSans",
"bolditalic": "NimbusSans"
},
"roman": {
"regular": "Nimbus Roman",
"bold": "Nimbus Roman",
"italic": "Nimbus Roman",
"bolditalic": "Nimbus Roman"
},
"sans": {
"regular": "Nimbus Sans",
"bold": "NimbusSans",
"italic": "NimbusSans",
"bolditalic": "NimbusSans"
},
"mono": {
"regular": "Nimbus Mono PS",
"bold": "Nimbus Mono PS",
"italic": "Nimbus Mono PS",
"bolditalic": "Nimbus Mono PS"
}
}
"""empty message
Revision ID: b114754024fb
Revises: 97cf1913e60d
Create Date: 2017-02-23 20:33:56.446729
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'b114754024fb'
down_revision = '97cf1913e60d'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('documents', sa.Column('is_private', sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('documents', 'is_private')
# ### end Alembic commands ###
...@@ -6,9 +6,10 @@ import math ...@@ -6,9 +6,10 @@ import math
from shared import db from shared import db
from utils import random_string, url_manager from utils import random_string, url_manager
#from models.tables import TexResponsiblesTable, TexSupportersTable import os
from sqlalchemy.orm import relationship, backref from sqlalchemy import event
from sqlalchemy.orm import relationship, backref, sessionmaker
import config import config
...@@ -171,16 +172,28 @@ class Document(db.Model): ...@@ -171,16 +172,28 @@ class Document(db.Model):
name = db.Column(db.String) name = db.Column(db.String)
filename = db.Column(db.String, unique=True) filename = db.Column(db.String, unique=True)
is_compiled = db.Column(db.Boolean) is_compiled = db.Column(db.Boolean)
is_private = db.Column(db.Boolean)
def __init__(self, protocol_id, name, filename, is_compiled): def __init__(self, protocol_id, name, filename, is_compiled, is_private):
self.protocol_id = protocol_id self.protocol_id = protocol_id
self.name = name self.name = name
self.filename = filename self.filename = filename
self.is_compiled = is_compiled self.is_compiled = is_compiled
self.is_private = is_private
def __repr__(self): def __repr__(self):
return "<Document(id={}, protocol_id={}, name={}, filename={}, is_compiled={})>".format( return "<Document(id={}, protocol_id={}, name={}, filename={}, is_compiled={}, is_private={})>".format(
self.id, self.protocol_id, self.name, self.filename, self.is_compiled) self.id, self.protocol_id, self.name, self.filename, self.is_compiled, self.is_private)
def get_filename(self):
return os.path.join(config.DOCUMENTS_PATH, self.filename)
@event.listens_for(Document, "before_delete")
def on_delete(mapper, connection, document):
if document.filename is not None:
document_path = os.path.join(config.DOCUMENTS_PATH, document.filename)
if os.path.isfile(document_path):
os.remove(document_path)
class Todo(db.Model): class Todo(db.Model):
__tablename__ = "todos" __tablename__ = "todos"
......
...@@ -3,6 +3,7 @@ import locale ...@@ -3,6 +3,7 @@ import locale
locale.setlocale(locale.LC_TIME, "de_DE.utf8") locale.setlocale(locale.LC_TIME, "de_DE.utf8")
from flask import Flask, g, current_app, request, session, flash, redirect, url_for, abort, render_template, Response, send_file from flask import Flask, g, current_app, request, session, flash, redirect, url_for, abort, render_template, Response, send_file
from werkzeug.utils import secure_filename
from flask_script import Manager, prompt from flask_script import Manager, prompt
from flask_migrate import Migrate, MigrateCommand from flask_migrate import Migrate, MigrateCommand
#from flask_socketio import SocketIO #from flask_socketio import SocketIO
...@@ -10,13 +11,14 @@ from celery import Celery ...@@ -10,13 +11,14 @@ from celery import Celery
from functools import wraps from functools import wraps
import requests import requests
from io import StringIO, BytesIO from io import StringIO, BytesIO
import os
import config import config
from shared import db, date_filter, datetime_filter, date_filter_long, time_filter, ldap_manager, security_manager from shared import db, date_filter, datetime_filter, date_filter_long, time_filter, ldap_manager, security_manager
from utils import is_past, mail_manager, url_manager from utils import is_past, mail_manager, url_manager
from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error
from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm
from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(config) app.config.from_object(config)
...@@ -335,7 +337,9 @@ def show_protocol(protocol_id): ...@@ -335,7 +337,9 @@ def show_protocol(protocol_id):
flash("Invalides Protokoll.", "alert-error") flash("Invalides Protokoll.", "alert-error")
return redirect(request.args.get("next") or url_for("index")) return redirect(request.args.get("next") or url_for("index"))
errors_table = ErrorsTable(protocol.errors) errors_table = ErrorsTable(protocol.errors)
return render_template("protocol-show.html", protocol=protocol, errors_table=errors_table) documents_table = DocumentsTable(protocol.documents)
document_upload_form = DocumentUploadForm()
return render_template("protocol-show.html", protocol=protocol, errors_table=errors_table, documents_table=documents_table, document_upload_form=document_upload_form)
@app.route("/protocol/etherpull/<int:protocol_id>") @app.route("/protocol/etherpull/<int:protocol_id>")
def etherpull_protocol(protocol_id): def etherpull_protocol(protocol_id):
...@@ -345,10 +349,7 @@ def etherpull_protocol(protocol_id): ...@@ -345,10 +349,7 @@ def etherpull_protocol(protocol_id):
flash("Invalides Protokoll oder keine Berechtigung.", "alert-error") flash("Invalides Protokoll oder keine Berechtigung.", "alert-error")
return redirect(request.args.get("next") or url_for("index")) return redirect(request.args.get("next") or url_for("index"))
source_req = requests.get(protocol.get_etherpad_source_link()) source_req = requests.get(protocol.get_etherpad_source_link())
#source = source_req.content.decode("utf-8")
source = source_req.text source = source_req.text
print(source.split("\r"))
#print(source.split("\n"))
protocol.source = source protocol.source = source
db.session.commit() db.session.commit()
tasks.parse_protocol(protocol) tasks.parse_protocol(protocol)
...@@ -386,6 +387,49 @@ def list_todos(): ...@@ -386,6 +387,49 @@ def list_todos():
todos_table = TodosTable(todos) todos_table = TodosTable(todos)
return render_template("todos-list.html", todos=todos, todos_table=todos_table) return render_template("todos-list.html", todos=todos, todos_table=todos_table)
@app.route("/document/download/<int:document_id>")
def download_document(document_id):
user = current_user()
document = Document.query.filter_by(id=document_id).first()
if document is None:
flash("Invalides Dokument.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
if ((document.is_private
and not document.protocol.protocoltype.has_private_view_right(user))
or (not document.is_private
and not document.protocol.protocoltype.has_public_view_right(user))):
flash("Keine Berechtigung.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
with open(document.get_filename(), "rb") as file:
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"])
@login_required
def upload_document(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("Insufficient permissions.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
form = DocumentUploadForm()
print(form, form.document.data, form.private.data)
print(request.files)
if form.document.data is None:
flash("No file has been selected.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
file = form.document.data
if file.filename == "":
flash("No file has been selected.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
if file:
filename = secure_filename(file.filename)
internal_filename = "{}-{}".format(protocol.id, filename)
file.save(os.path.join(config.DOCUMENTS_PATH, internal_filename))
document = Document(protocol.id, filename, internal_filename, False, form.private.data)
db.session.add(document)
db.session.commit()
return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id))
@app.route("/login", methods=["GET", "POST"]) @app.route("/login", methods=["GET", "POST"])
......
...@@ -69,6 +69,8 @@ def datetime_filter(date): ...@@ -69,6 +69,8 @@ def datetime_filter(date):
return date.strftime("%d. %B %Y, %H:%M") return date.strftime("%d. %B %Y, %H:%M")
def date_filter_long(date): def date_filter_long(date):
return date.strftime("%A, %d.%m.%Y, Kalenderwoche %W") return date.strftime("%A, %d.%m.%Y, Kalenderwoche %W")
def date_filter_short(date):
return date.strftime("%d.%m.%Y")
def time_filter(time): def time_filter(time):
return time.strftime("%H:%m") return time.strftime("%H:%m")
......
...@@ -7,7 +7,7 @@ import tempfile ...@@ -7,7 +7,7 @@ import tempfile
from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP
from server import celery, app from server import celery, app
from shared import db, escape_tex, unhyphen, date_filter, datetime_filter, date_filter_long, time_filter, class_filter from shared import db, escape_tex, unhyphen, date_filter, datetime_filter, date_filter_long, date_filter_short, time_filter, class_filter
from utils import mail_manager, url_manager, encode_kwargs, decode_kwargs from utils import mail_manager, url_manager, encode_kwargs, decode_kwargs
from parser import parse, ParserException, Element, Content, Text, Tag, Remark, Fork from parser import parse, ParserException, Element, Content, Text, Tag, Remark, Fork
...@@ -27,6 +27,7 @@ texenv.lstrip_blocks = True ...@@ -27,6 +27,7 @@ texenv.lstrip_blocks = True
texenv.filters["url_complete"] = url_manager.complete texenv.filters["url_complete"] = url_manager.complete
texenv.filters["datify"] = date_filter texenv.filters["datify"] = date_filter
texenv.filters["datify_long"] = date_filter_long texenv.filters["datify_long"] = date_filter_long
texenv.filters["datify_short"] = date_filter_short
texenv.filters["datetimify"] = datetime_filter texenv.filters["datetimify"] = datetime_filter
texenv.filters["timify"] = time_filter texenv.filters["timify"] = time_filter
texenv.filters["class"] = class_filter texenv.filters["class"] = class_filter
...@@ -182,7 +183,9 @@ def compile_async(content, protocol_id): ...@@ -182,7 +183,9 @@ def compile_async(content, protocol_id):
log_filename = "protocol.log" log_filename = "protocol.log"
with open(os.path.join(compile_dir, protocol_source_filename), "w") as source_file: with open(os.path.join(compile_dir, protocol_source_filename), "w") as source_file:
source_file.write(content) source_file.write(content)
shutil.copy("static/tex/protokoll2.cls", compile_dir) protocol2_class_source = texenv.get_template("protokoll2.cls").render(fonts=config.FONTS)
with open(os.path.join(compile_dir, "protokoll2.cls"), "w") as protocol2_class_file:
protocol2_class_file.write(protocol2_class_source)
os.chdir(compile_dir) os.chdir(compile_dir)
command = [ command = [
"/usr/bin/xelatex", "/usr/bin/xelatex",
...@@ -196,12 +199,12 @@ def compile_async(content, protocol_id): ...@@ -196,12 +199,12 @@ def compile_async(content, protocol_id):
for old_document in [document for document in protocol.documents if document.is_compiled]: for old_document in [document for document in protocol.documents if document.is_compiled]:
protocol.documents.remove(old_document) protocol.documents.remove(old_document)
db.session.commit() db.session.commit()
document = Document(protocol.id, name="protokoll_{}_{}.pdf".format(protocol.protocoltype.short_name, date_filter(protocol.date)), filename="", is_compiled=True) document = Document(protocol.id, name="protokoll_{}_{}.pdf".format(protocol.protocoltype.short_name, date_filter_short(protocol.date)), filename="", is_compiled=True, is_private=False)
db.session.add(document) db.session.add(document)
db.session.commit() db.session.commit()
target_filename = "compiled-{}.pdf".format(document.id) target_filename = "compiled-{}.pdf".format(document.id)
document.filename = target_filename document.filename = target_filename
shutil.copy(os.path.join(compile_dir, protocol_target_filename), os.path.join("documents", target_filename)) shutil.copy(os.path.join(compile_dir, protocol_target_filename), os.path.join(config.DOCUMENTS_PATH, target_filename))
db.session.commit() db.session.commit()
shutil.copy(os.path.join(compile_dir, log_filename), "/tmp") shutil.copy(os.path.join(compile_dir, log_filename), "/tmp")
except subprocess.SubprocessError: except subprocess.SubprocessError:
......
...@@ -87,9 +87,9 @@ to not render a label for the CRSFTokenField --> ...@@ -87,9 +87,9 @@ to not render a label for the CRSFTokenField -->
action_text - text of submit button action_text - text of submit button
class_ - sets a class for form class_ - sets a class for form
#} #}
{% macro render_form(form, action_url='', action_text='Submit', class_='', btn_class='btn btn-default') -%} {% macro render_form(form, action_url='', action_text='Submit', class_='', btn_class='btn btn-default', enctype=None) -%}
<form method="POST" action="{{ action_url }}" role="form" class="{{ class_ }}"> <form method="POST" action="{{ action_url }}" role="form" class="{{ class_ }}"{% if enctype is not none %}enctype="{{enctype}}"{% endif %}>
{{ form.hidden_tag() if form.hidden_tag }} {{ form.hidden_tag() if form.hidden_tag }}
{% if caller %} {% if caller %}
{{ caller() }} {{ caller() }}
......
{% extends "layout.html" %} {% extends "layout.html" %}
{% from "macros.html" import render_table %} {% from "macros.html" import render_table, render_form %}
{% block title %}Protokoll{% endblock %} {% block title %}Protokoll{% endblock %}
{% block content %} {% block content %}
...@@ -71,8 +71,10 @@ ...@@ -71,8 +71,10 @@
{{render_table(errors_table)}} {{render_table(errors_table)}}
{% endif %} {% endif %}
{% if protocol.documents|length > 0 %} {% if protocol.documents|length > 0 %}
<h3>Anhang</h3> {{render_table(documents_table)}}
{# TODO: render documents table here #} {% endif %}
{% if protocol.is_done() %}
{{render_form(document_upload_form, action_url=url_for("upload_document", protocol_id=protocol.id, next=url_for("show_protocol", protocol_id=protocol.id)), action_text="Hochladen", enctype="multipart/form-data")}}
{% endif %} {% endif %}
</div> </div>
</div> </div>
......
...@@ -14,37 +14,41 @@ ...@@ -14,37 +14,41 @@
\LoadClass[a4paper]{article} \LoadClass[a4paper]{article}
% so ein paar sachen die wir eh immer brauchen % so ein paar sachen die wir eh immer brauchen
%\RequirePackage[T1]{fontenc}
%\RequirePackage{ngerman}
\RequirePackage[ngerman]{babel} \RequirePackage[ngerman]{babel}
%\RequirePackage[utf8]{inputenc}
\RequirePackage[vmargin=1.5cm,hmargin={1.5cm,1.2cm},bindingoffset=8mm]{geometry} \RequirePackage[vmargin=1.5cm,hmargin={1.5cm,1.2cm},bindingoffset=8mm]{geometry}
%\RequirePackage{lineno} %\RequirePackage{lineno}
\RequirePackage{longtable} \RequirePackage{longtable}
\RequirePackage{framed} \RequirePackage{framed}
\RequirePackage{eurosym} \RequirePackage{eurosym}
\RequirePackage[babel]{csquotes} \RequirePackage[babel]{csquotes}
%\RequirePackage{fontspec}
\RequirePackage{polyglossia} \RequirePackage{polyglossia}
\setmainlanguage[babelshorthands=true]{german} \setmainlanguage[babelshorthands=true]{german}
\RequirePackage{fontspec} \RequirePackage{fontspec}
%\setromanfont[Scale=0.925]{DejaVu Serif} \setromanfont[
%\setsansfont[Scale=0.925]{DejaVu Sans} BoldFont={\VAR{fonts.roman.bold}},
%\setmonofont[Scale=0.925]{DejaVu Sans Mono} ItalicFont={\VAR{fonts.roman.italic}},
%\setmainfont[Scale=0.925]{DejaVu Sans} BoldItalicFont={\VAR{fonts.roman.bolditalic}}
\setromanfont{Nimbus Roman} ]{\VAR{fonts.roman.regular}}
\setsansfont{Nimbus Sans} \setsansfont[
\setmonofont{Nimbus Mono PS} BoldFont={\VAR{fonts.sans.bold}},
ItalicFont={\VAR{fonts.sans.italic}},
BoldItalicFont={\VAR{fonts.sans.bolditalic}}
]{\VAR{fonts.sans.regular}}
\setmonofont[
BoldFont={\VAR{fonts.mono.bold}},
ItalicFont={\VAR{fonts.mono.italic}},
BoldItalicFont={\VAR{fonts.mono.bolditalic}}
]{\VAR{fonts.mono.regular}}
\setmainfont[ \setmainfont[
BoldFont={NimbusSans}, BoldFont={\VAR{fonts.main.bold}},
ItalicFont={NimbusSans}, ItalicFont={\VAR{fonts.main.italic}},
BoldItalicFont={NimbusSans} BoldItalicFont={\VAR{fonts.main.bolditalic}}
]{Nimbus Sans} % TODO: make configurable ]{\VAR{fonts.main.regular}}
% nicht einrcken und benutzerinnendefinierte kopfzeile % nicht einrücken und benutzerinnendefinierte kopfzeile
\setlength{\parindent}{0cm} \setlength{\parindent}{0cm}
\setlength{\parskip}{1ex} \setlength{\parskip}{1ex}
\pagestyle{myheadings} \pagestyle{myheadings}
......
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField, SelectField from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField, SelectField, FileField
from wtforms.validators import InputRequired from wtforms.validators import InputRequired
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
...@@ -32,3 +32,7 @@ class NewProtocolForm(FlaskForm): ...@@ -32,3 +32,7 @@ class NewProtocolForm(FlaskForm):
def __init__(self, protocoltypes, **kwargs): def __init__(self, protocoltypes, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.protocoltype.choices = [(protocoltype.id, protocoltype.short_name) for protocoltype in protocoltypes] self.protocoltype.choices = [(protocoltype.id, protocoltype.short_name) for protocoltype in protocoltypes]
class DocumentUploadForm(FlaskForm):
document = FileField("Datei", validators=[InputRequired("Du musst eine Datei angeben.")])
private = BooleanField("Intern")
# coding: utf-8 # coding: utf-8
from flask import Markup, url_for, request from flask import Markup, url_for, request
from models.database import Protocol, ProtocolType, DefaultTOP, TOP, Todo, Decision from models.database import Protocol, ProtocolType, DefaultTOP, TOP, Todo, Decision
from shared import date_filter, datetime_filter from shared import date_filter, datetime_filter, date_filter_short
class Table: class Table:
def __init__(self, title, values, newlink=None, newtext=None): def __init__(self, title, values, newlink=None, newtext=None):
...@@ -174,3 +174,17 @@ class TodosTable(Table): ...@@ -174,3 +174,17 @@ class TodosTable(Table):
todo.who, todo.who,
todo.description todo.description
] ]
class DocumentsTable(Table):
def __init__(self, documents):
super().__init__("Anhang", documents)
def headers(self):
return ["ID", "Name", ""]
def row(self, document):
return [
document.id,
Table.link(url_for("download_document", document_id=document.id), document.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