From 247c0d909fac1da59326fff644fb8897b34c737e Mon Sep 17 00:00:00 2001
From: Robin Sonnabend <robin@fsmpi.rwth-aachen.de>
Date: Sat, 6 May 2017 21:44:33 +0200
Subject: [PATCH] Add right for publishing

/close #93
---
 decorators.py                        |  3 ++
 migrations/versions/279bfa293885_.py | 46 ++++++++++++++++++++++++++++
 models/database.py                   |  9 ++++++
 server.py                            |  8 ++---
 views/forms.py                       |  2 ++
 views/tables.py                      |  3 +-
 6 files changed, 66 insertions(+), 5 deletions(-)
 create mode 100644 migrations/versions/279bfa293885_.py

diff --git a/decorators.py b/decorators.py
index cdc3e19..30fd142 100644
--- a/decorators.py
+++ b/decorators.py
@@ -73,5 +73,8 @@ def require_private_view_right(require_exist=True):
 def require_modify_right(require_exist=True):
     return require_right("modify", require_exist)
 
+def require_publish_right(require_exist=True):
+    return require_right("publish", require_exist)
+
 def require_admin_right(require_exist=True):
     return require_right("admin", require_exist)
diff --git a/migrations/versions/279bfa293885_.py b/migrations/versions/279bfa293885_.py
new file mode 100644
index 0000000..c6c6dd8
--- /dev/null
+++ b/migrations/versions/279bfa293885_.py
@@ -0,0 +1,46 @@
+"""empty message
+
+Revision ID: 279bfa293885
+Revises: 0686095ee9dd
+Create Date: 2017-05-06 21:28:13.577142
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+
+Session = sessionmaker()
+
+Base = declarative_base()
+
+class ProtocolType(Base):
+    __tablename__ = "protocoltypes"
+    id = sa.Column(sa.Integer, primary_key=True)
+    modify_group = sa.Column(sa.String)
+    publish_group = sa.Column(sa.String)
+
+
+# revision identifiers, used by Alembic.
+revision = '279bfa293885'
+down_revision = '0686095ee9dd'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.add_column('protocoltypes', sa.Column('publish_group', sa.String(), nullable=True))
+    # ### end Alembic commands ###
+
+    bind = op.get_bind()
+    session = Session(bind=bind)
+    for protocoltype in session.query(ProtocolType):
+        protocoltype.publish_group = protocoltype.modify_group
+    session.commit()
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_column('protocoltypes', 'publish_group')
+    # ### end Alembic commands ###
diff --git a/models/database.py b/models/database.py
index 967f053..a60440c 100644
--- a/models/database.py
+++ b/models/database.py
@@ -31,6 +31,9 @@ class DatabaseModel(db.Model):
     def has_modify_right(self, user):
         return self.get_parent().has_modify_right(user)
 
+    def has_publish_right(self, user):
+        return self.get_parent().has_publish_right(user)
+
     def has_admin_right(self, user):
         return self.get_parent().has_admin_right(user)
 
@@ -56,6 +59,7 @@ class ProtocolType(DatabaseModel):
     modify_group = db.Column(db.String)
     private_group = db.Column(db.String)
     public_group = db.Column(db.String)
+    publish_group = db.Column(db.String)
     private_mail = db.Column(db.String)
     public_mail = db.Column(db.String)
     non_reproducible_pad_links = db.Column(db.Boolean)
@@ -104,6 +108,11 @@ class ProtocolType(DatabaseModel):
             and (self.modify_group != "" and self.modify_group in user.groups))
             or self.has_admin_right(user))
 
+    def has_publish_right(self, user):
+        return ((user is not None
+            and (self.publish_group != "" and self.publish_group in user.groups))
+            or self.has_admin_right(user))
+
     def has_admin_right(self, user):
         return (user is not None and config.ADMIN_GROUP in user.groups)
 
diff --git a/server.py b/server.py
index f8181d2..2adc7ae 100755
--- a/server.py
+++ b/server.py
@@ -22,7 +22,7 @@ import mimetypes
 import config
 from shared import db, date_filter, datetime_filter, date_filter_long, date_filter_short, time_filter, time_filter_short, user_manager, security_manager, current_user, check_login, login_required, group_required, class_filter, needs_date_test, todostate_name_filter, code_filter, indent_tab_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, fancy_join
-from decorators import db_lookup, require_public_view_right, require_private_view_right, require_modify_right, require_admin_right
+from decorators import db_lookup, require_public_view_right, require_private_view_right, require_modify_right, require_publish_right, require_admin_right
 from models.database import ProtocolType, Protocol, DefaultTOP, TOP, LocalTOP, Document, Todo, Decision, MeetingReminder, Error, TodoMail, DecisionDocument, TodoState, Meta, DefaultMeta, DecisionCategory, Like
 from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, generate_protocol_form, TopForm, LocalTopForm, SearchForm, DecisionSearchForm, ProtocolSearchForm, TodoSearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm, DefaultMetaForm, MetaForm, MergeTodosForm, DecisionCategoryForm, DocumentEditForm
 from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable, DecisionsTable, TodoTable, ErrorTable, TodoMailsTable, DefaultMetasTable, DecisionCategoriesTable
@@ -688,7 +688,7 @@ def etherpush_protocol(protocol):
 @app.route("/protocol/update/<int:protocol_id>", methods=["GET", "POST"])
 @login_required
 @db_lookup(Protocol)
-@require_modify_right()
+@require_publish_right()
 def update_protocol(protocol):
     upload_form = KnownProtocolSourceUploadForm()
     edit_form = generate_protocol_form(protocol)(obj=protocol)
@@ -706,7 +706,7 @@ def update_protocol(protocol):
 @app.route("/protocol/publish/<int:protocol_id>")
 @login_required
 @db_lookup(Protocol)
-@require_modify_right()
+@require_publish_right()
 def publish_protocol(protocol):
     protocol.public = True
     db.session.commit()
@@ -727,7 +727,7 @@ def send_protocol_private(protocol):
 @app.route("/prococol/send/public/<int:protocol_id>")
 @login_required
 @db_lookup(Protocol)
-@require_modify_right()
+@require_publish_right()
 def send_protocol_public(protocol):
     if not config.MAIL_ACTIVE:
         flash("Die Mailfunktion ist nicht aktiviert.", "alert-error")
diff --git a/views/forms.py b/views/forms.py
index 0fd10ee..7d42f07 100644
--- a/views/forms.py
+++ b/views/forms.py
@@ -109,6 +109,7 @@ class ProtocolTypeForm(FlaskForm):
     organization = StringField("Organisation", validators=[InputRequired("Du musst eine zugehörige Organisation angeben.")])
     usual_time = DateTimeField("Üblicher Beginn", validators=[InputRequired("Bitte gib die Zeit an, zu der die Sitzung beginnt.")], format="%H:%M")
     is_public = BooleanField("Öffentlich sichtbar")
+    publish_group = SelectField("Verwaltungsgruppe", choices=[])
     modify_group = SelectField("Bearbeitungsgruppe", choices=[])
     private_group = SelectField("Interne Gruppe", choices=[])
     public_group = SelectField("Öffentliche Gruppe", choices=[])
@@ -129,6 +130,7 @@ class ProtocolTypeForm(FlaskForm):
         self.calendar.choices = get_calendar_choices(protocoltype=protocoltype)
         self.printer.choices = get_printer_choices()
         group_choices = get_group_choices()
+        self.publish_group.choices = group_choices
         self.modify_group.choices = group_choices
         self.private_group.choices = group_choices
         self.public_group.choices = group_choices
diff --git a/views/tables.py b/views/tables.py
index 9f3054d..75592bf 100644
--- a/views/tables.py
+++ b/views/tables.py
@@ -159,7 +159,7 @@ class ProtocolTypeTable(SingleValueTable):
 
     def headers(self):
         general_headers = ["Name", "Abkürzung", "Organisation", "Beginn",
-            "Öffentlich", "Bearbeitungsgruppe", "Interne Gruppe",
+            "Öffentlich", "Verwaltungsgruppe", "Bearbeitungsgruppe", "Interne Gruppe",
             "Öffentliche Gruppe"]
         etherpad_headers = ["Nicht-reproduzierbare Etherpadlinks"]
         if not config.ETHERPAD_ACTIVE:
@@ -190,6 +190,7 @@ class ProtocolTypeTable(SingleValueTable):
             self.value.organization,
             self.value.usual_time.strftime("%H:%M") if self.value.usual_time is not None else "", # todo: remove if, this field is required
             Table.bool(self.value.is_public),
+            self.value.publish_group,
             self.value.modify_group,
             self.value.private_group,
             self.value.public_group,
-- 
GitLab