From b03b40da776d30080357b5d4955f352528a33e83 Mon Sep 17 00:00:00 2001 From: Robin Sonnabend <robin@fsmpi.rwth-aachen.de> Date: Fri, 28 Apr 2017 22:14:26 +0200 Subject: [PATCH] Model decision categories as many to many relation /close #53 --- migrations/versions/60a730d37c9a_.py | 30 ++++++++++++++++++++++++ migrations/versions/ac54a2c29610_.py | 34 ++++++++++++++++++++++++++++ models/database.py | 12 +++++++--- parser.py | 15 +++++++----- server.py | 3 +-- tasks.py | 13 +++++------ templates/protocol.tex | 2 +- views/tables.py | 4 ++-- 8 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 migrations/versions/60a730d37c9a_.py create mode 100644 migrations/versions/ac54a2c29610_.py diff --git a/migrations/versions/60a730d37c9a_.py b/migrations/versions/60a730d37c9a_.py new file mode 100644 index 0000000..7342b55 --- /dev/null +++ b/migrations/versions/60a730d37c9a_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 60a730d37c9a +Revises: ac54a2c29610 +Create Date: 2017-04-28 22:10:24.694024 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '60a730d37c9a' +down_revision = 'ac54a2c29610' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('decisions_category_id_fkey', 'decisions', type_='foreignkey') + op.drop_column('decisions', 'category_id') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('decisions', sa.Column('category_id', sa.INTEGER(), autoincrement=False, nullable=True)) + op.create_foreign_key('decisions_category_id_fkey', 'decisions', 'decisioncategories', ['category_id'], ['id']) + # ### end Alembic commands ### diff --git a/migrations/versions/ac54a2c29610_.py b/migrations/versions/ac54a2c29610_.py new file mode 100644 index 0000000..2916bea --- /dev/null +++ b/migrations/versions/ac54a2c29610_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: ac54a2c29610 +Revises: c59998057d39 +Create Date: 2017-04-20 19:30:26.887146 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ac54a2c29610' +down_revision = 'c59998057d39' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('decisioncategoryassociations', + sa.Column('decision_id', sa.Integer(), nullable=False), + sa.Column('decisioncategory_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['decision_id'], ['decisions.id'], ), + sa.ForeignKeyConstraint(['decisioncategory_id'], ['decisioncategories.id'], ), + sa.PrimaryKeyConstraint('decision_id', 'decisioncategory_id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('decisioncategoryassociations') + # ### end Alembic commands ### diff --git a/models/database.py b/models/database.py index 5ecf463..0646ff7 100644 --- a/models/database.py +++ b/models/database.py @@ -610,15 +610,18 @@ class Decision(DatabaseModel): id = db.Column(db.Integer, primary_key=True) protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id")) content = db.Column(db.String) - category_id = db.Column(db.Integer, db.ForeignKey("decisioncategories.id"), nullable=True) document = relationship("DecisionDocument", backref=backref("decision"), cascade="all, delete-orphan", uselist=False) + categories = relationship("DecisionCategory", secondary="decisioncategoryassociations") likes = relationship("Like", secondary="likedecisionassociations") def get_parent(self): return self.protocol + def get_categories_str(self): + return ", ".join(map(lambda c: c.name, self.categories)) + class DecisionCategory(DatabaseModel): __tablename__ = "decisioncategories" __model_name__ = "decisioncategory" @@ -626,11 +629,14 @@ class DecisionCategory(DatabaseModel): protocoltype_id = db.Column(db.Integer, db.ForeignKey("protocoltypes.id")) name = db.Column(db.String) - decisions = relationship("Decision", backref=backref("category"), order_by="Decision.id") - def get_parent(self): return self.protocoltype +class DecisionCategoryAssociation(DatabaseModel): + __tablename__ = "decisioncategoryassociations" + decision_id = db.Column(db.Integer, db.ForeignKey("decisions.id"), primary_key=True) + decisioncategory_id = db.Column(db.Integer, db.ForeignKey("decisioncategories.id"), primary_key=True) + class MeetingReminder(DatabaseModel): __tablename__ = "meetingreminders" __model_name__ = "meetingreminder" diff --git a/parser.py b/parser.py index 6ac3757..369b302 100644 --- a/parser.py +++ b/parser.py @@ -217,10 +217,12 @@ class Tag: return "" return self.todo.render_latex(current_protocol=protocol) elif self.name == "beschluss": - result = r"\textbf{{Beschluss:}} {}".format(self.decision.content) - if self.decision.category is not None: - result = r"{} \textit{{({})}}".format(result, self.decision.category.name) - return result + parts = [r"\textbf{{Beschluss:}} {}".format(self.decision.content)] + if len(self.decision.categories): + parts.append( + r"\textit{{({})}}".format(self.decision.get_categories_str()) + ) + return " ".join(parts) return r"\textbf{{{}:}} {}".format(escape_tex(self.name.capitalize()), escape_tex(";".join(self.values))) elif render_type == RenderType.plaintext: if self.name == "url": @@ -251,8 +253,9 @@ class Tag: elif self.name == "beschluss": if getattr(self, "decision", None) is not None: parts = ["<b>Beschluss:</b>", self.decision.content] - if self.decision.category is not None: - parts.append("<i>{}</i>".format(self.decision.category.name)) + if len(self.decision.categories) > 0: + parts.append("<i>{}</i>".format( + self.decision.get_categories_str())) return " ".join(parts) else: return "<b>Beschluss:</b> {}".format(self.values[0]) diff --git a/server.py b/server.py index de2fbe8..6e91019 100755 --- a/server.py +++ b/server.py @@ -1034,8 +1034,7 @@ def list_decisions(): if decisioncategory_id is not None and decisioncategory_id != -1: decisions = [ decision for decision in decisions - if decision.category is not None - and decision.category.id == decisioncategory_id + if decisioncategory_id in map(lambda d: d.id, decision.categories) ] if search_term is not None and len(search_term.strip()) > 0: decisions = [ diff --git a/tasks.py b/tasks.py index d1eff0d..b9ba248 100644 --- a/tasks.py +++ b/tasks.py @@ -50,8 +50,6 @@ if raw_additional_packages is not None: if "{" not in package: package = "{{{}}}".format(package) additional_packages.append(package) -print(raw_additional_packages) -print(additional_packages) texenv.globals["additional_packages"] = additional_packages latex_pagestyle = getattr(config, "LATEX_PAGESTYLE", None) if latex_pagestyle is not None: @@ -314,9 +312,8 @@ def parse_protocol_async_inner(protocol, encoded_kwargs): db.session.commit() return decision_content = decision_tag.values[0] - decision_category_id = None - if len(decision_tag.values) > 1: - decision_category_name = decision_tag.values[1] + decision_categories = [] + for decision_category_name in decision_tag.values[1:]: decision_category = DecisionCategory.query.filter_by(protocoltype_id=protocol.protocoltype.id, name=decision_category_name).first() if decision_category is None: category_candidates = DecisionCategory.query.filter_by(protocoltype_id=protocol.protocoltype.id).all() @@ -336,11 +333,13 @@ def parse_protocol_async_inner(protocol, encoded_kwargs): db.session.commit() return else: - decision_category_id = decision_category.id + decision_categories.append(decision_category) decision = Decision(protocol_id=protocol.id, - content=decision_content, category_id=decision_category_id) + content=decision_content) db.session.add(decision) db.session.commit() + for decision_category in decision_categories: + decision.categories.append(decision_category) decision_tag.decision = decision decisions_to_render.append((decision, decision_tag)) for decision, decision_tag in decisions_to_render: diff --git a/templates/protocol.tex b/templates/protocol.tex index f29e5a2..c433c9f 100644 --- a/templates/protocol.tex +++ b/templates/protocol.tex @@ -41,7 +41,7 @@ \begin{itemize} \ENV{if protocol.decisions|length > 0} \ENV{for decision in protocol.decisions} - \item \VAR{decision.content|escape_tex}\ENV{if decision.category is not none and show_private} \textit{(\VAR{decision.category.name})}\ENV{endif} + \item \VAR{decision.content|escape_tex}\ENV{if decision.categories|length > 0 and show_private} \textit{(\VAR{decision.get_categories_str()})}\ENV{endif} \ENV{endfor} \ENV{else} \item Keine Beschlüsse diff --git a/views/tables.py b/views/tables.py index 02b71d9..9f3054d 100644 --- a/views/tables.py +++ b/views/tables.py @@ -384,7 +384,7 @@ class DecisionsTable(Table): super().__init__("Beschlüsse", decisions) self.category_present = len([ decision for decision in decisions - if decision.category is not None + if len(decision.categories) > 0 ]) > 0 def headers(self): @@ -401,7 +401,7 @@ class DecisionsTable(Table): Table.link(url_for("show_protocol", protocol_id=decision.protocol.id), decision.protocol.get_short_identifier()), decision.content ] - category_part = [decision.category.name if decision.category is not None else ""] + category_part = [decision.get_categories_str()] if not self.category_present: category_part = [] action_part = [ -- GitLab