diff --git a/config.py.example b/config.py.example index 98a011257b0794b1ab142a06a28be3616743044d..5c443f4da4d37864609fbb8607c3b80271f9ef63 100644 --- a/config.py.example +++ b/config.py.example @@ -25,6 +25,14 @@ LDAP_PROVIDER_URL = "ldaps://auth.example.com:389" LDAP_BASE = "dc=example,dc=example,dc=com" LDAP_PROTOCOL_VERSION = 3 # do not change +PRINTING_ACTIVE = True +PRINTING_SERVER = "printsrv.example.com:631" +PRINTING_USER = "protocols" +PRINTING_PRINTERS = [ + "example_printer": ["Duplex=DuplexNoTumble", "option2=value"], + "other_printer": ["list", "of", "options"] +] + ETHERPAD_URL = "https://fachschaften.rwth-aachen.de/etherpad" EMPTY_ETHERPAD = """Welcome to Etherpad! diff --git a/migrations/versions/f91d760158dc_.py b/migrations/versions/f91d760158dc_.py new file mode 100644 index 0000000000000000000000000000000000000000..8a868a58746b47eb43b2370bbdac7a15c3c56b1c --- /dev/null +++ b/migrations/versions/f91d760158dc_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: f91d760158dc +Revises: d8c0c74b88bd +Create Date: 2017-02-25 21:52:07.654276 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f91d760158dc' +down_revision = 'd8c0c74b88bd' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('protocoltypes', sa.Column('printer', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('protocoltypes', 'printer') + # ### end Alembic commands ### diff --git a/models/database.py b/models/database.py index 89d0921cbe8bb33b544e676af572a82a4a459a2e..5c180270fb6dc1df0e571b0d9b437fb8e605745d 100644 --- a/models/database.py +++ b/models/database.py @@ -29,6 +29,7 @@ class ProtocolType(db.Model): use_wiki = db.Column(db.Boolean) wiki_category = db.Column(db.String) wiki_only_public = db.Column(db.Boolean) + printer = db.Column(db.String) protocols = relationship("Protocol", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="Protocol.id") default_tops = relationship("DefaultTOP", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="DefaultTOP.number") @@ -37,7 +38,7 @@ class ProtocolType(db.Model): def __init__(self, name, short_name, organization, is_public, private_group, public_group, private_mail, public_mail, - use_wiki, wiki_category, wiki_only_public): + use_wiki, wiki_category, wiki_only_public, printer): self.name = name self.short_name = short_name self.organization = organization @@ -49,16 +50,17 @@ class ProtocolType(db.Model): self.use_wiki = use_wiki self.wiki_category = wiki_category self.wiki_only_public = wiki_only_public + self.printer = printer def __repr__(self): return ("<ProtocolType(id={}, short_name={}, name={}, " "organization={}, is_public={}, private_group={}, " "public_group={}, use_wiki={}, wiki_category='{}', " - "wiki_only_public={})>".format( + "wiki_only_public={}, printer={})>".format( self.id, self.short_name, self.name, self.organization, self.is_public, self.private_group, self.public_group, self.use_wiki, self.wiki_category, - self.wiki_only_public)) + self.wiki_only_public, self.printer)) def get_latest_protocol(self): candidates = sorted([protocol for protocol in self.protocols if protocol.is_done()], key=lambda p: p.date, reverse=True) diff --git a/server.py b/server.py index 289cdcd3e3e1eda33d889c442d94e59f59563b10..f5c8cb1a2e1796e10724662691657847d471eef8 100755 --- a/server.py +++ b/server.py @@ -90,7 +90,7 @@ def new_type(): form.private_group.data, form.public_group.data, form.private_mail.data, form.public_mail.data, form.use_wiki.data, form.wiki_category.data, - form.wiki_only_public.data) + form.wiki_only_public.data, form.printer.data) db.session.add(protocoltype) db.session.commit() flash("Der Protokolltyp {} wurde angelegt.".format(protocoltype.name), "alert-success") @@ -688,6 +688,17 @@ def delete_document(document_id): flash("Das Dokument {} wurde gelöscht.".format(name), "alert-success") return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id)) +@app.route("/document/print/<int:document_id>") +@login_required +def print_document(document_id): + user = current_user() + document = Document.query.filter_by(id=document_id).first() + if document is None or not document.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.print_file(document.get_filename(), document.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.protocol.id)) @app.route("/login", methods=["GET", "POST"]) def login(): diff --git a/tasks.py b/tasks.py index 0beb9491b30cf845d65312798add25ca07cbd64d..88fdebfd5359541ae9f729c750b816b675d9fb44 100644 --- a/tasks.py +++ b/tasks.py @@ -281,6 +281,33 @@ def compile_async(content, protocol_id, show_private): finally: os.chdir(current) +def print_file(filename, protocol): + if config.PRINTING_ACTIVE: + print_file_async.delay(filename, protocol.id) + +@celery.task +def print_file_async(filename, protocol_id): + with app.app_context(): + protocol = Protocol.query.filter_by(id=protocol_id).first() + if protocol.protocoltype.printer is None: + error = protocol.create_error("Printing", "No printer configured.", "You don't have any printer configured for the protocoltype {}. Please do so before printing a protocol.".format(protocol.protocoltype.name)) + try: + command = [ + "/usr/bin/lpr", + "-H", config.PRINTING_SERVER, + "-P", protocol.protocoltype.printer, + "-U", config.PRINTING_USER, + "-T", protocol.get_identifier(), + ] + for option in config.PRINTING_PRINTERS[protocol.protocoltype.printer]: + command.extend(["-o", '"{}"'.format(option) if " " in option else option]) + command.append(filename) + subprocess.check_call(command, universal_newlines=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except subprocess.SubprocessError: + error = protocol.create_error("Printing", "Printing {} failed.".format(protocol.get_identifier()), "") + db.session.add(error) + db.session.commit() + def send_mail(mail): send_mail_async.delay(mail.id) diff --git a/utils.py b/utils.py index 2c78e153cb36d00aa1f266e1c3acb14374ff6a8c..6d818bc921a1d59084ac33e27c86a0fa30109944 100644 --- a/utils.py +++ b/utils.py @@ -124,4 +124,3 @@ def set_etherpad_text(pad, text, only_if_default=True): req = requests.post(get_etherpad_import_url(pad), files=files) return req.status_code == 200 - diff --git a/views/forms.py b/views/forms.py index 6abc73b0e60a502d53c2210850802e9f180f2916..10ba631f53292059ec115bf744613ba0b89e93ca 100644 --- a/views/forms.py +++ b/views/forms.py @@ -2,6 +2,8 @@ from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField, SelectField, FileField, DateTimeField from wtforms.validators import InputRequired, Optional +import config + class LoginForm(FlaskForm): username = StringField("Benutzer", validators=[InputRequired("Bitte gib deinen Benutzernamen ein.")]) password = PasswordField("Passwort", validators=[InputRequired("Bitte gib dein Passwort ein.")]) @@ -18,6 +20,7 @@ class ProtocolTypeForm(FlaskForm): wiki_category = StringField("Wiki-Kategorie") use_wiki = BooleanField("Wiki benutzen") wiki_only_public = BooleanField("Wiki ist öffentlich") + printer = SelectField("Drucker", choices=list(zip(config.PRINTING_PRINTERS, config.PRINTING_PRINTERS))) class DefaultTopForm(FlaskForm): name = StringField("Name", validators=[InputRequired("Du musst einen Namen angeben.")]) diff --git a/views/tables.py b/views/tables.py index c7372a935d31b8ae6f31811f125182fe00e2266d..778e4235e3c9ae73ab4748a366e6e8e47c869bab 100644 --- a/views/tables.py +++ b/views/tables.py @@ -98,7 +98,7 @@ class ProtocolTypeTable(SingleValueTable): headers = ["Name", "Abkürzung", "Organisation", "Öffentlich", "Interne Gruppe", "Öffentliche Gruppe", "Interner Verteiler", "Öffentlicher Verteiler", - "Wiki"] + "Drucker", "Wiki"] if self.value.use_wiki: headers.append("Wiki-Kategorie") return headers @@ -113,7 +113,8 @@ class ProtocolTypeTable(SingleValueTable): self.value.public_group, self.value.private_mail, self.value.public_mail, - Table.bool(self.value.use_wiki) + (", " + ("Öffentlich" if self.value.wiki_only_public else "Intern")) if self.value.use_wiki else "" + self.value.printer, + (Table.bool(self.value.use_wiki) + ((", " + ("Öffentlich" if self.value.wiki_only_public else "Intern")) if self.value.use_wiki else "")) ] if self.value.use_wiki: row.append(self.value.wiki_category) @@ -221,7 +222,10 @@ class DocumentsTable(Table): return [ document.id, Table.link(url_for("download_document", document_id=document.id), document.name), - (Table.link(url_for("delete_document", document_id=document.id), "Löschen", confirm="Bist du dir sicher, dass du das Dokument {} löschen willst?".format(document.name)) - if document.protocol.protocoltype.has_modify_right(user) - else "") + Table.concat([ + Table.link(url_for("delete_document", document_id=document.id), "Löschen", confirm="Bist du dir sicher, dass du das Dokument {} löschen willst?".format(document.name)), + Table.link(url_for("print_document", document_id=document.id), "Drucken") + ]) + if document.protocol.protocoltype.has_modify_right(user) + else "" ]