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

configurable fonts and document upload

parent d558fb90
SQLALCHEMY_DATABASE_URI = "postgresql://proto3:@/proto3"
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = "abc"
DEBUG = False
MAIL_ACTIVE = True
MAIL_FROM = "protokolle@example.com"
MAIL_HOST = "mail.example.com:465"
MAIL_USER = "user"
MAIL_PASSWORD = "password"
MAIL_PREFIX = "protokolle"
CELERY_BROKER_URL = "redis://localhost:6379/0"
CELERY_TASK_SERIALIZER = "pickle"
CELERY_ACCEPT_CONTENT = ["pickle"]
URL_ROOT = "protokolle.example.com"
URL_PROTO = "https"
URL_PATH = "/"
URL_PARAMS = ""
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
from shared import db
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
......@@ -171,16 +172,28 @@ class Document(db.Model):
name = db.Column(db.String)
filename = db.Column(db.String, unique=True)
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.name = name
self.filename = filename
self.is_compiled = is_compiled
self.is_private = is_private
def __repr__(self):
return "<Document(id={}, protocol_id={}, name={}, filename={}, is_compiled={})>".format(
self.id, self.protocol_id, self.name, self.filename, self.is_compiled)
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.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):
__tablename__ = "todos"
......
......@@ -3,6 +3,7 @@ import locale
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 werkzeug.utils import secure_filename
from flask_script import Manager, prompt
from flask_migrate import Migrate, MigrateCommand
#from flask_socketio import SocketIO
......@@ -10,13 +11,14 @@ from celery import Celery
from functools import wraps
import requests
from io import StringIO, BytesIO
import os
import config
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 models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error
from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm
from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable
from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm
from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable
app = Flask(__name__)
app.config.from_object(config)
......@@ -335,7 +337,9 @@ def show_protocol(protocol_id):
flash("Invalides Protokoll.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
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>")
def etherpull_protocol(protocol_id):
......@@ -345,10 +349,7 @@ def etherpull_protocol(protocol_id):
flash("Invalides Protokoll oder keine Berechtigung.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
source_req = requests.get(protocol.get_etherpad_source_link())
#source = source_req.content.decode("utf-8")
source = source_req.text
print(source.split("\r"))
#print(source.split("\n"))
protocol.source = source
db.session.commit()
tasks.parse_protocol(protocol)
......@@ -386,6 +387,49 @@ def list_todos():
todos_table = TodosTable(todos)
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"])
......
......@@ -69,6 +69,8 @@ def datetime_filter(date):
return date.strftime("%d. %B %Y, %H:%M")
def date_filter_long(date):
return date.strftime("%A, %d.%m.%Y, Kalenderwoche %W")
def date_filter_short(date):
return date.strftime("%d.%m.%Y")
def time_filter(time):
return time.strftime("%H:%m")
......
......@@ -7,7 +7,7 @@ import tempfile
from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP
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 parser import parse, ParserException, Element, Content, Text, Tag, Remark, Fork
......@@ -27,6 +27,7 @@ texenv.lstrip_blocks = True
texenv.filters["url_complete"] = url_manager.complete
texenv.filters["datify"] = date_filter
texenv.filters["datify_long"] = date_filter_long
texenv.filters["datify_short"] = date_filter_short
texenv.filters["datetimify"] = datetime_filter
texenv.filters["timify"] = time_filter
texenv.filters["class"] = class_filter
......@@ -182,7 +183,9 @@ def compile_async(content, protocol_id):
log_filename = "protocol.log"
with open(os.path.join(compile_dir, protocol_source_filename), "w") as source_file:
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)
command = [
"/usr/bin/xelatex",
......@@ -196,12 +199,12 @@ def compile_async(content, protocol_id):
for old_document in [document for document in protocol.documents if document.is_compiled]:
protocol.documents.remove(old_document)
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.commit()
target_filename = "compiled-{}.pdf".format(document.id)
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()
shutil.copy(os.path.join(compile_dir, log_filename), "/tmp")
except subprocess.SubprocessError:
......
......@@ -87,9 +87,9 @@ to not render a label for the CRSFTokenField -->
action_text - text of submit button
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 }}
{% if caller %}
{{ caller() }}
......
{% extends "layout.html" %}
{% from "macros.html" import render_table %}
{% from "macros.html" import render_table, render_form %}
{% block title %}Protokoll{% endblock %}
{% block content %}
......@@ -71,8 +71,10 @@
{{render_table(errors_table)}}
{% endif %}
{% if protocol.documents|length > 0 %}
<h3>Anhang</h3>
{# TODO: render documents table here #}
{{render_table(documents_table)}}
{% 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 %}
</div>
</div>
......
......@@ -14,37 +14,41 @@
\LoadClass[a4paper]{article}
% so ein paar sachen die wir eh immer brauchen
%\RequirePackage[T1]{fontenc}
%\RequirePackage{ngerman}
\RequirePackage[ngerman]{babel}
%\RequirePackage[utf8]{inputenc}
\RequirePackage[vmargin=1.5cm,hmargin={1.5cm,1.2cm},bindingoffset=8mm]{geometry}
%\RequirePackage{lineno}
\RequirePackage{longtable}
\RequirePackage{framed}
\RequirePackage{eurosym}
\RequirePackage[babel]{csquotes}
%\RequirePackage{fontspec}
\RequirePackage{polyglossia}
\setmainlanguage[babelshorthands=true]{german}
\RequirePackage{fontspec}
%\setromanfont[Scale=0.925]{DejaVu Serif}
%\setsansfont[Scale=0.925]{DejaVu Sans}
%\setmonofont[Scale=0.925]{DejaVu Sans Mono}
%\setmainfont[Scale=0.925]{DejaVu Sans}
\setromanfont{Nimbus Roman}
\setsansfont{Nimbus Sans}
\setmonofont{Nimbus Mono PS}
\setromanfont[
BoldFont={\VAR{fonts.roman.bold}},
ItalicFont={\VAR{fonts.roman.italic}},
BoldItalicFont={\VAR{fonts.roman.bolditalic}}
]{\VAR{fonts.roman.regular}}
\setsansfont[
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[
BoldFont={NimbusSans},
ItalicFont={NimbusSans},
BoldItalicFont={NimbusSans}
]{Nimbus Sans} % TODO: make configurable
BoldFont={\VAR{fonts.main.bold}},
ItalicFont={\VAR{fonts.main.italic}},
BoldItalicFont={\VAR{fonts.main.bolditalic}}
]{\VAR{fonts.main.regular}}
% nicht einrcken und benutzerinnendefinierte kopfzeile
% nicht einrücken und benutzerinnendefinierte kopfzeile
\setlength{\parindent}{0cm}
\setlength{\parskip}{1ex}
\pagestyle{myheadings}
......
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
class LoginForm(FlaskForm):
......@@ -32,3 +32,7 @@ class NewProtocolForm(FlaskForm):
def __init__(self, protocoltypes, **kwargs):
super().__init__(**kwargs)
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
from flask import Markup, url_for, request
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:
def __init__(self, title, values, newlink=None, newtext=None):
......@@ -174,3 +174,17 @@ class TodosTable(Table):
todo.who,
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