Commit 9f4adef4 authored by Robin Sonnabend's avatar Robin Sonnabend

/close #1

parent 372cc118
"""empty message
Revision ID: 6a86c1d5682f
Revises: 9845a330ed06
Create Date: 2017-03-31 20:33:32.885639
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6a86c1d5682f'
down_revision = '9845a330ed06'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('likeprotocolassociations',
sa.Column('like_id', sa.Integer(), nullable=False),
sa.Column('protocol_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], ),
sa.ForeignKeyConstraint(['protocol_id'], ['protocols.id'], ),
sa.PrimaryKeyConstraint('like_id', 'protocol_id')
)
op.create_table('liketodoassociations',
sa.Column('like_id', sa.Integer(), nullable=False),
sa.Column('todo_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], ),
sa.ForeignKeyConstraint(['todo_id'], ['todos.id'], ),
sa.PrimaryKeyConstraint('like_id', 'todo_id')
)
op.create_table('likedecisionassociations',
sa.Column('like_id', sa.Integer(), nullable=False),
sa.Column('decision_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['decision_id'], ['decisions.id'], ),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], ),
sa.PrimaryKeyConstraint('like_id', 'decision_id')
)
op.create_table('liketopassociations',
sa.Column('like_id', sa.Integer(), nullable=False),
sa.Column('top_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], ),
sa.ForeignKeyConstraint(['top_id'], ['tops.id'], ),
sa.PrimaryKeyConstraint('like_id', 'top_id')
)
op.drop_table('like_top_association')
op.drop_table('like_protocol_association')
op.drop_table('like_todo_association')
op.drop_table('like_decision_association')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('like_decision_association',
sa.Column('like_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('decision_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['decision_id'], ['decisions.id'], name='like_decision_association_decision_id_fkey'),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], name='like_decision_association_like_id_fkey'),
sa.PrimaryKeyConstraint('like_id', 'decision_id', name='like_decision_association_pkey')
)
op.create_table('like_todo_association',
sa.Column('like_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('todo_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], name='like_todo_association_like_id_fkey'),
sa.ForeignKeyConstraint(['todo_id'], ['todos.id'], name='like_todo_association_todo_id_fkey'),
sa.PrimaryKeyConstraint('like_id', 'todo_id', name='like_todo_association_pkey')
)
op.create_table('like_protocol_association',
sa.Column('like_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('protocol_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], name='like_protocol_association_like_id_fkey'),
sa.ForeignKeyConstraint(['protocol_id'], ['protocols.id'], name='like_protocol_association_protocol_id_fkey'),
sa.PrimaryKeyConstraint('like_id', 'protocol_id', name='like_protocol_association_pkey')
)
op.create_table('like_top_association',
sa.Column('like_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('top_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], name='like_top_association_like_id_fkey'),
sa.ForeignKeyConstraint(['top_id'], ['tops.id'], name='like_top_association_top_id_fkey'),
sa.PrimaryKeyConstraint('like_id', 'top_id', name='like_top_association_pkey')
)
op.drop_table('liketopassociations')
op.drop_table('likedecisionassociations')
op.drop_table('liketodoassociations')
op.drop_table('likeprotocolassociations')
# ### end Alembic commands ###
"""empty message
Revision ID: 9845a330ed06
Revises: 4651698510d7
Create Date: 2017-03-31 20:31:34.199580
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '9845a330ed06'
down_revision = '4651698510d7'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('likes',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('who', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('like_protocol_association',
sa.Column('like_id', sa.Integer(), nullable=False),
sa.Column('protocol_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], ),
sa.ForeignKeyConstraint(['protocol_id'], ['protocols.id'], ),
sa.PrimaryKeyConstraint('like_id', 'protocol_id')
)
op.create_table('like_todo_association',
sa.Column('like_id', sa.Integer(), nullable=False),
sa.Column('todo_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], ),
sa.ForeignKeyConstraint(['todo_id'], ['todos.id'], ),
sa.PrimaryKeyConstraint('like_id', 'todo_id')
)
op.create_table('like_decision_association',
sa.Column('like_id', sa.Integer(), nullable=False),
sa.Column('decision_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['decision_id'], ['decisions.id'], ),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], ),
sa.PrimaryKeyConstraint('like_id', 'decision_id')
)
op.create_table('like_top_association',
sa.Column('like_id', sa.Integer(), nullable=False),
sa.Column('top_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['like_id'], ['likes.id'], ),
sa.ForeignKeyConstraint(['top_id'], ['tops.id'], ),
sa.PrimaryKeyConstraint('like_id', 'top_id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('like_top_association')
op.drop_table('like_decision_association')
op.drop_table('like_todo_association')
op.drop_table('like_protocol_association')
op.drop_table('likes')
# ### end Alembic commands ###
......@@ -157,6 +157,8 @@ class Protocol(DatabaseModel):
metas = relationship("Meta", backref=backref("protocol"), cascade="all, delete-orphan")
localtops = relationship("LocalTOP", backref=backref("protocol"), cascade="all, delete-orphan")
likes = relationship("Like", secondary="likeprotocolassociations")
def get_parent(self):
return self.protocoltype
......@@ -341,6 +343,8 @@ class TOP(DatabaseModel):
planned = db.Column(db.Boolean)
description = db.Column(db.String)
likes = relationship("Like", secondary="liketopassociations")
def get_parent(self):
return self.protocol
......@@ -496,6 +500,7 @@ class Todo(DatabaseModel):
date = db.Column(db.Date, nullable=True)
protocols = relationship("Protocol", secondary="todoprotocolassociations", backref="todos")
likes = relationship("Like", secondary="liketodoassociations")
def get_parent(self):
return self.protocoltype
......@@ -584,6 +589,8 @@ class Decision(DatabaseModel):
document = relationship("DecisionDocument", backref=backref("decision"), cascade="all, delete-orphan", uselist=False)
likes = relationship("Like", secondary="likedecisionassociations")
def get_parent(self):
return self.protocol
......@@ -674,8 +681,34 @@ class Meta(DatabaseModel):
def get_parent(self):
return self.protocol
class Like(DatabaseModel):
__tablename__ = "likes"
__model_name__ = "like"
id = db.Column(db.Integer, primary_key=True)
who = db.Column(db.String)
class LikeProtocolAssociation(DatabaseModel):
__tablename__ = "likeprotocolassociations"
like_id = db.Column(db.Integer, db.ForeignKey("likes.id"), primary_key=True)
protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id"), primary_key=True)
class LikeTodoAssociation(DatabaseModel):
__tablename__ = "liketodoassociations"
like_id = db.Column(db.Integer, db.ForeignKey("likes.id"), primary_key=True)
todo_id = db.Column(db.Integer, db.ForeignKey("todos.id"), primary_key=True)
class LikeDecisionAssociation(DatabaseModel):
__tablename__ = "likedecisionassociations"
like_id = db.Column(db.Integer, db.ForeignKey("likes.id"), primary_key=True)
decision_id = db.Column(db.Integer, db.ForeignKey("decisions.id"), primary_key=True)
class LikeTOPAssociation(DatabaseModel):
__tablename__ = "liketopassociations"
like_id = db.Column(db.Integer, db.ForeignKey("likes.id"), primary_key=True)
top_id = db.Column(db.Integer, db.ForeignKey("tops.id"), primary_key=True)
ALL_MODELS = [
ProtocolType, Protocol, DefaultTOP, TOP, Document, DecisionDocument,
Todo, Decision, MeetingReminder, Error, DefaultMeta, Meta, DecisionCategory
]
......@@ -2,7 +2,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 as flask_send_file
from flask import Flask, g, current_app, request, session, flash, redirect, url_for, abort, render_template, Response, send_file as flask_send_file, Markup
from werkzeug.utils import secure_filename
from flask_script import Manager, prompt
from flask_migrate import Migrate, MigrateCommand
......@@ -21,9 +21,9 @@ import mimetypes
import config
from shared import db, date_filter, datetime_filter, date_filter_long, date_filter_short, time_filter, time_filter_short, ldap_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
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 models.database import ProtocolType, Protocol, DefaultTOP, TOP, LocalTOP, Document, Todo, Decision, MeetingReminder, Error, TodoMail, DecisionDocument, TodoState, Meta, DefaultMeta, DecisionCategory
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, ProtocolForm, TopForm, LocalTopForm, SearchForm, DecisionSearchForm, ProtocolSearchForm, TodoSearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm, DefaultMetaForm, MetaForm, MergeTodosForm, DecisionCategoryForm
from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable, DecisionsTable, TodoTable, ErrorTable, TodoMailsTable, DefaultMetasTable, DecisionCategoriesTable
from legacy import import_old_todos, import_old_protocols, import_old_todomails
......@@ -59,6 +59,7 @@ app.jinja_env.filters["class"] = class_filter
app.jinja_env.filters["todo_get_name"] = todostate_name_filter
app.jinja_env.filters["code"] = code_filter
app.jinja_env.filters["indent_tab"] = indent_tab_filter
app.jinja_env.filters["fancy_join"] = fancy_join
app.jinja_env.tests["auth_valid"] = security_manager.check_user
app.jinja_env.tests["needs_date"] = needs_date_test
......@@ -70,6 +71,7 @@ app.jinja_env.globals.update(zip=zip)
app.jinja_env.globals.update(min=min)
app.jinja_env.globals.update(max=max)
app.jinja_env.globals.update(dir=dir)
app.jinja_env.globals.update(now=datetime.now)
# blueprints here
......@@ -1245,6 +1247,32 @@ def delete_decisioncategory(decisioncategory):
flash("Beschlusskategorie {} gelöscht.".format(name), "alert-success")
return redirect(request.args.get("next") or url_for("show_type", protocoltype_id=type_id))
@app.route("/like/new")
@login_required
def new_like():
user = current_user()
parent = None
if "protocol_id" in request.args:
parent = Protocol.query.filter_by(id=request.args.get("protocol_id")).first()
elif "todo_id" in request.args:
parent = Todo.query.filter_by(id=request.args.get("todo_id")).first()
elif "decision_id" in request.args:
parent = Decision.query.filter_by(id=request.args.get("decision_id")).first()
elif "top_id" in request.args:
parent = TOP.query.filter_by(id=request.args.get("top_id")).first()
if parent is None or not parent.has_public_view_right(user):
flash("Missing object to like.", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
if len([like for like in parent.likes if like.who == user.username]) > 0:
flash("You have liked this already!", "alert-error")
return redirect(request.args.get("next") or url_for("index"))
like = Like(who=user.username)
db.session.add(like)
parent.likes.append(like)
db.session.commit()
flash("Like!", "alert-success")
return redirect(request.args.get("next") or url_for("index"))
@app.route("/login", methods=["GET", "POST"])
def login():
if "auth" in session:
......
......@@ -20,7 +20,7 @@ body {
color: #808000;
}
h3 > a {
h2 > a, h3 > a {
font-size: 18px;
}
......@@ -55,3 +55,21 @@ textarea {
outline: none;
background-color: #d0d0d0;
}
.likes-div {
display: inline-block;
font-size: 10pt;
background-color: #ddddff;
border-radius: 3px;
padding: 1px;
}
.likes-div > p {
display: inline-block;
margin: 0;
padding: 0;
}
.like-sign {
font-size: 16px;
}
{% extends "layout.html" %}
{% from "macros.html" import render_table %}
{% from "macros.html" import render_table, render_likes %}
{% block title %}Startseite{% endblock %}
{% block content %}
......@@ -15,7 +15,10 @@
<ul>
{% if open_protocols|length > 0 %}
{% for protocol in open_protocols %}
<li><a href="{{url_for("show_protocol", protocol_id=protocol.id)}}">{{protocol.protocoltype.name}}</a> am {{protocol.date|datify}}</li>
<li>
<a href="{{url_for("show_protocol", protocol_id=protocol.id)}}">{{protocol.protocoltype.name}}</a> am {{protocol.date|datify}}
{{render_likes(protocol.likes, protocol_id=protocol.id)}}
</li>
{% endfor %}
{% else %}
<li>Keine anstehenden Sitzungen</li>
......@@ -53,7 +56,10 @@
{% if has_public_view_right %}
{% if protocol.decisions|length > 0 %}
{% for decision in protocol.decisions %}
<li>{{decision.content}}</li>
<li>
{{decision.content}}
{{render_likes(decision.likes, decision_id=decision.id)}}
</li>
{% endfor %}
{% else %}
<li>Keine Beschlüsse</li>
......@@ -71,7 +77,11 @@
<ul>
{% if todos|length > 0 %}
{% for todo in todos %}
<li>{{todo.render_html()|safe}} ({{todo.protocoltype.name}})</li>
<li>
{{todo.render_html()|safe}}
({{todo.protocoltype.name}})
{{render_likes(todo.likes, todo_id=todo.id)}}
</li>
{% endfor %}
{% else %}
<li>Keine Todos</li>
......
......@@ -156,3 +156,33 @@ to not render a label for the CRSFTokenField -->
</tbody>
</table>
{%- endmacro %}
{% macro render_likes(likes) -%}
{% set timestamp = now() %}
{% if timestamp.month == 4 and timestamp.day == 1 %}
{% set user=current_user() %}
{% set add_link = user is not none and likes|selectattr("who", "equalto", user.username)|list|length == 0 %}
{% set verb = "like" %}
{% if likes|length == 1 %}
{% set verb = "likes" %}
{% endif %}
{% if add_link %}
<a href="{{url_for("new_like", next=request.url, **kwargs)}}">
{% endif %}
<div class="likes-div">
<p>{{likes|length}} <span class="like-sign">&#x1f44d;</span></p>
{% if user is not none or likes|length > 0 %}
<p>
{% if likes|length == 0 %}
Be the first one to like this!
{% else %}
{{likes|map(attribute="who")|map("capitalize")|fancy_join(" and ")}} {{verb}} this
{% endif %}
</p>
{% endif %}
</div>
{% if add_link %}
</a>
{% endif %}
{% endif %}
{%- endmacro %}
{% extends "layout.html" %}
{% from "macros.html" import render_table, render_form %}
{% from "macros.html" import render_table, render_form, render_likes %}
{% block title %}Protokoll{% endblock %}
{% set logged_in = check_login() %}
......@@ -52,9 +52,15 @@
<div class="row">
<div id="left-column" class="col-lg-6">
{% if protocol.is_done() %}
<h2>Protokoll: {{protocol.protocoltype.name}} {% if protocol.date is not none %}vom {{protocol.date|datify}}{% endif %}</h2>
<h2>
Protokoll: {{protocol.protocoltype.name}} {% if protocol.date is not none %}vom {{protocol.date|datify}}{% endif %}
{{render_likes(protocol.likes, protocol_id=protocol.id)}}</h2>
</h2>
{% else %}
<h2>{{protocol.protocoltype.name}} {% if protocol.date is not none %}am {{protocol.date|datify}}{% endif %}</h2>
<h2>
{{protocol.protocoltype.name}} {% if protocol.date is not none %}am {{protocol.date|datify}}{% endif %}
{{render_likes(protocol.likes, protocol_id=protocol.id)}}
</h2>
{% endif %}
{% if protocol.is_done() %}
{% if protocol.date is not none %}
......@@ -88,6 +94,7 @@
{% if config.PRINTING_ACTIVE and has_private_view_right and decision.document is not none %}
<a href="{{url_for("print_decision", decisiondocument_id=decision.document.id)}}">Drucken</a>
{% endif %}
{{render_likes(decision.likes, decision_id=decision.id)}}</h2>
</li>
{% endfor %}
{% else %}
......@@ -109,7 +116,10 @@
<ul>
{% if protocol.get_originating_todos()|length > 0 %}
{% for todo in protocol.get_originating_todos() %}
<li>{{todo.render_html()|safe}}</li>
<li>
{{todo.render_html()|safe}}
{{render_likes(todo.likes, todo_id=todo.id)}}
</li>
{% endfor %}
{% else %}
<li>Keine Todos</li>
......
{% from "macros.html" import render_likes %}
<ul>
{% if not protocol.has_nonplanned_tops() %}
{% for default_top in protocol.protocoltype.default_tops %}
......@@ -36,6 +37,7 @@
{{-top.description-}}
</pre>
{% endif %}
{{render_likes(top.likes, top_id=top.id)}}
</li>
{% endfor %}
{% if not protocol.has_nonplanned_tops() %}
......
......@@ -187,3 +187,11 @@ def check_ip_in_networks(networks_string):
return False
except ValueError:
return False
def fancy_join(values, sep1=" und ", sep2=", "):
values = list(values)
if len(values) <= 1:
return "".join(values)
last = values[-1]
start = values[:-1]
return "{}{}{}".format(sep2.join(start), sep1, last)
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