Skip to content
Snippets Groups Projects
Commit f62d577e authored by Robin Sonnabend's avatar Robin Sonnabend
Browse files

Search and paginate todos

parent 6031524d
Branches
No related tags found
No related merge requests found
"""empty message
Revision ID: 0131d5776f8d
Revises: 24bd2198a626
Create Date: 2017-02-24 21:03:34.294388
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0131d5776f8d'
down_revision = '24bd2198a626'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('todos', sa.Column('protocoltype_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'todos', 'protocoltypes', ['protocoltype_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'todos', type_='foreignkey')
op.drop_column('todos', 'protocoltype_id')
# ### end Alembic commands ###
...@@ -29,6 +29,7 @@ class ProtocolType(db.Model): ...@@ -29,6 +29,7 @@ class ProtocolType(db.Model):
protocols = relationship("Protocol", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="Protocol.id") 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") default_tops = relationship("DefaultTOP", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="DefaultTOP.number")
reminders = relationship("MeetingReminder", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="MeetingReminder.days_before") reminders = relationship("MeetingReminder", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="MeetingReminder.days_before")
todos = relationship("Todo", backref=backref("protocoltype"), order_by="Todo.id")
def __init__(self, name, short_name, organization, def __init__(self, name, short_name, organization,
is_public, private_group, public_group, private_mail, public_mail): is_public, private_group, public_group, private_mail, public_mail):
...@@ -63,6 +64,14 @@ class ProtocolType(db.Model): ...@@ -63,6 +64,14 @@ class ProtocolType(db.Model):
def has_modify_right(self, user): def has_modify_right(self, user):
return self.has_private_view_right(user) return self.has_private_view_right(user)
@staticmethod
def get_available_protocoltypes(user):
return [
protocoltype for protocoltype in ProtocolType.query.all()
if protocoltype.has_modify_right(user)
]
class Protocol(db.Model): class Protocol(db.Model):
__tablename__ = "protocols" __tablename__ = "protocols"
...@@ -244,6 +253,7 @@ def on_document_delete(mapper, connection, document): ...@@ -244,6 +253,7 @@ def on_document_delete(mapper, connection, document):
class Todo(db.Model): class Todo(db.Model):
__tablename__ = "todos" __tablename__ = "todos"
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
protocoltype_id = db.Column(db.Integer, db.ForeignKey("protocoltypes.id"))
number = db.Column(db.Integer) number = db.Column(db.Integer)
who = db.Column(db.String) who = db.Column(db.String)
description = db.Column(db.String) description = db.Column(db.String)
...@@ -253,7 +263,8 @@ class Todo(db.Model): ...@@ -253,7 +263,8 @@ class Todo(db.Model):
protocols = relationship("Protocol", secondary="todoprotocolassociations", backref="todos") protocols = relationship("Protocol", secondary="todoprotocolassociations", backref="todos")
def __init__(self, who, description, tags, done, number=None): def __init__(self, type_id, who, description, tags, done, number=None):
self.protocoltype_id = type_id
self.who = who self.who = who
self.description = description self.description = description
self.tags = tags self.tags = tags
......
...@@ -11,12 +11,13 @@ from celery import Celery ...@@ -11,12 +11,13 @@ from celery import Celery
from io import StringIO, BytesIO from io import StringIO, BytesIO
import os import os
from datetime import datetime from datetime import datetime
import math
import config import config
from shared import db, date_filter, datetime_filter, date_filter_long, time_filter, ldap_manager, security_manager, current_user, check_login, login_required, group_required from shared import db, date_filter, datetime_filter, date_filter_long, time_filter, ldap_manager, security_manager, current_user, check_login, login_required, group_required
from utils import is_past, mail_manager, url_manager, get_first_unused_int, set_etherpad_text, get_etherpad_text from utils import is_past, mail_manager, url_manager, get_first_unused_int, set_etherpad_text, get_etherpad_text
from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error
from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm, SearchForm
from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable
app = Flask(__name__) app = Flask(__name__)
...@@ -51,6 +52,9 @@ import tasks ...@@ -51,6 +52,9 @@ import tasks
app.jinja_env.globals.update(check_login=check_login) app.jinja_env.globals.update(check_login=check_login)
app.jinja_env.globals.update(current_user=current_user) app.jinja_env.globals.update(current_user=current_user)
app.jinja_env.globals.update(zip=zip) 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)
# blueprints here # blueprints here
...@@ -289,10 +293,7 @@ def list_protocols(): ...@@ -289,10 +293,7 @@ def list_protocols():
@login_required @login_required
def new_protocol(): def new_protocol():
user = current_user() user = current_user()
protocoltypes = [ protocoltypes = ProtocolType.get_available_protocoltypes(user)
protocoltype for protocoltype in ProtocolType.query.all()
if protocoltype.has_modify_right(user)
]
form = NewProtocolForm(protocoltypes) form = NewProtocolForm(protocoltypes)
upload_form = NewProtocolSourceUploadForm(protocoltypes) upload_form = NewProtocolSourceUploadForm(protocoltypes)
if form.validate_on_submit(): if form.validate_on_submit():
...@@ -528,15 +529,56 @@ def move_top(top_id, diff): ...@@ -528,15 +529,56 @@ def move_top(top_id, diff):
flash("Die angegebene Differenz ist keine Zahl.", "alert-error") flash("Die angegebene Differenz ist keine Zahl.", "alert-error")
return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=top.protocol.id)) return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=top.protocol.id))
def _get_page():
try:
page = request.args.get("page")
if page is None:
return 0
return int(page)
except ValueError:
return 0
@app.route("/todos/list") @app.route("/todos/list", methods=["GET", "POST"])
def list_todos(): def list_todos():
is_logged_in = check_login() is_logged_in = check_login()
user = current_user() user = current_user()
todos = Todo.query.all() protocoltype = None
protocoltype_id = None
try:
protocoltype_id = int(request.args.get("type_id"))
except (ValueError, TypeError):
pass
search_term = request.args.get("search")
protocoltypes = ProtocolType.get_available_protocoltypes(user)
search_form = SearchForm(protocoltypes)
if search_form.validate_on_submit():
if search_form.search.data is not None:
search_term = search_form.search.data.strip()
if search_form.protocoltype.data is not None:
protocoltype_id = search_form.protocoltype.data
else:
if protocoltype_id is not None:
search_form.protocoltype.data = protocoltype_id
if search_term is not None:
search_form.search.data = search_term
if protocoltype_id is not None:
protocoltype = ProtocolType.query.filter_by(id=protocoltype_id).first()
base_query = Todo.query
if protocoltype_id is not None and protocoltype_id != -1:
base_query = base_query.filter(ProtocolType.id == protocoltype_id)
print(search_term)
if search_term is not None and len(search_term.strip()) > 0:
base_query = base_query.filter(Todo.description.match("%{}%".format(search_term)))
page = _get_page()
page_count = int(math.ceil(base_query.count() / config.PAGE_LENGTH))
if page >= page_count:
page = 0
begin_index = page * config.PAGE_LENGTH
end_index = (page + 1) * config.PAGE_LENGTH
todos = base_query.slice(begin_index, end_index).all()
# TODO: paginate and search # TODO: paginate and search
todos_table = TodosTable(todos) todos_table = TodosTable(todos)
return render_template("todos-list.html", todos=todos, todos_table=todos_table) return render_template("todos-list.html", todos=todos, todos_table=todos_table, search_form=search_form, page=page, page_count=page_count, page_diff=config.PAGE_DIFF, protocoltype_id=protocoltype_id, search_term=search_term)
@app.route("/document/download/<int:document_id>") @app.route("/document/download/<int:document_id>")
def download_document(document_id): def download_document(document_id):
......
...@@ -23,7 +23,7 @@ h3 > a { ...@@ -23,7 +23,7 @@ h3 > a {
font-size: 18px; font-size: 18px;
} }
form { form:not(.form-inline) {
max-width: 350px; max-width: 350px;
margin: 0 auto; margin: 0 auto;
} }
...@@ -31,3 +31,12 @@ form { ...@@ -31,3 +31,12 @@ form {
input[type="file"] { input[type="file"] {
padding: 0; padding: 0;
} }
.centered {
margin: 0 auto;
max-width: 300px;
}
.centered > a {
margin: 10px;
}
...@@ -145,14 +145,14 @@ def parse_protocol_async(protocol_id, encoded_kwargs): ...@@ -145,14 +145,14 @@ def parse_protocol_async(protocol_id, encoded_kwargs):
candidate.number = field_id candidate.number = field_id
todo = candidate todo = candidate
else: else:
todo = Todo(who=who, description=what, tags="", done=False) todo = Todo(type_id=protocol.protocoltype.id, who=who, description=what, tags="", done=False)
todo.number = field_id todo.number = field_id
else: else:
candidate = Todo.query.filter_by(who=who, description=what).first() candidate = Todo.query.filter_by(who=who, description=what).first()
if candidate is not None: if candidate is not None:
todo = candidate todo = candidate
else: else:
todo = Todo(who=who, description=what, tags="", done=False) todo = Todo(type_id=protocol.protocoltype.id, who=who, description=what, tags="", done=False)
db.session.add(todo) db.session.add(todo)
todo.protocols.append(protocol) todo.protocols.append(protocol)
todo_tags_internal = todo.tags.split(";") todo_tags_internal = todo.tags.split(";")
......
...@@ -16,16 +16,16 @@ to not render a label for the CRSFTokenField --> ...@@ -16,16 +16,16 @@ to not render a label for the CRSFTokenField -->
<div class="form-group {% if field.errors %}has-error{% endif %} {{ kwargs.pop('class_', '') }}"> <div class="form-group {% if field.errors %}has-error{% endif %} {{ kwargs.pop('class_', '') }}">
{% if field.type != 'HiddenField' and field.type !='CSRFTokenField' and label_visible %} {% if field.type != 'HiddenField' and field.type !='CSRFTokenField' and label_visible %}
<label for="{{ field.id }}" class="control-label">{{ field.label }}</label> <label for="{{ field.id }}" class="control-label">{{ field.label }}</label>
<!--<span onclick="el=document.getElementById('{{field.id}}-description');el.style.display=(el.style.display=='none'?'flex':'none')" class="field-description-questionmark">?</span>--> {#<span onclick="el=document.getElementById('{{field.id}}-description');el.style.display=(el.style.display=='none'?'flex':'none')" class="field-description-questionmark">?</span>#}
{% endif %} {% endif %}
{{ field(title=field.description, class_='form-control', **kwargs) }} {{ field(title=field.description, placeholder=field.label.text, class_='form-control', **kwargs) }}
{% if field.errors %} {% if field.errors %}
{% for e in field.errors %} {% for e in field.errors %}
<p class="help-block">{{ e }}</p> <p class="help-block">{{ e }}</p>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</div> </div>
<div id="{{field.id}}-description" style="display:none" class="field-description">{{field.description}}</div> {#<div id="{{field.id}}-description" style="display:none" class="field-description">{{field.description}}</div>#}
{%- endmacro %} {%- endmacro %}
{# Renders checkbox fields since they are represented differently in bootstrap {# Renders checkbox fields since they are represented differently in bootstrap
...@@ -87,7 +87,7 @@ to not render a label for the CRSFTokenField --> ...@@ -87,7 +87,7 @@ to not render a label for the CRSFTokenField -->
action_text - text of submit button action_text - text of submit button
class_ - sets a class for form class_ - sets a class for form
#} #}
{% macro render_form(form, action_url='', action_text='Submit', class_='', btn_class='btn btn-default', enctype=None) -%} {% macro render_form(form, action_url='', action_text='Submit', class_='', btn_class='btn btn-default', enctype=None, labels_visible=True) -%}
<form method="POST" action="{{ action_url }}" role="form" class="{{ class_ }}"{% if enctype is not none %}enctype="{{enctype}}"{% endif %}> <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 }} {{ form.hidden_tag() if form.hidden_tag }}
...@@ -100,7 +100,7 @@ to not render a label for the CRSFTokenField --> ...@@ -100,7 +100,7 @@ to not render a label for the CRSFTokenField -->
{% elif f.type == 'RadioField' %} {% elif f.type == 'RadioField' %}
{{ render_radio_field(f) }} {{ render_radio_field(f) }}
{% else %} {% else %}
{{ render_field(f) }} {{ render_field(f, label_visible=labels_visible) }}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
......
{% extends "layout.html" %} {% extends "layout.html" %}
{% from "macros.html" import render_table %} {% from "macros.html" import render_table, render_form %}
{% block title %}Todos{% endblock %} {% block title %}Todos{% endblock %}
{% macro page_link(page, text) %}
<a href="{{url_for(request.endpoint, page=page, type_id=protocoltype_id, search=search_term)}}">{{text}}</a>
{% endmacro %}
{% block content %} {% block content %}
<div class="container"> <div class="container">
{{render_form(search_form, class_="form-inline", labels_visible=False)}}
{{render_table(todos_table)}} {{render_table(todos_table)}}
<div class="centered">
{% if page > page_diff %}
{{page_link(0, "<<")}}
{% endif %}
{% for p in range(max(0, page - page_diff), min(page_count, page + page_diff)) %}
{% if p != page %}
{{page_link(p, p + 1)}}
{% else %}
Seite {{p + 1}}
{% endif %}
{% endfor %}
{% if page < page_count - page_diff %}
{{page_link(page_count - 1, ">>")}}
{% endif %}
</div>
</div> </div>
{% endblock %} {% endblock %}
...@@ -124,3 +124,4 @@ def set_etherpad_text(pad, text, only_if_default=True): ...@@ -124,3 +124,4 @@ def set_etherpad_text(pad, text, only_if_default=True):
req = requests.post(get_etherpad_import_url(pad), files=files) req = requests.post(get_etherpad_import_url(pad), files=files)
return req.status_code == 200 return req.status_code == 200
...@@ -61,3 +61,12 @@ class TopForm(FlaskForm): ...@@ -61,3 +61,12 @@ class TopForm(FlaskForm):
name = StringField("TOP", validators=[InputRequired("Du musst den Namen des TOPs angeben.")]) name = StringField("TOP", validators=[InputRequired("Du musst den Namen des TOPs angeben.")])
number = IntegerField("Sortierung", validators=[InputRequired("Du musst eine Sortierung in der Reihenfolge angebene.")]) number = IntegerField("Sortierung", validators=[InputRequired("Du musst eine Sortierung in der Reihenfolge angebene.")])
class SearchForm(FlaskForm):
search = StringField("Suchbegriff")
protocoltype = SelectField("Typ", choices=[], coerce=int)
def __init__(self, protocoltypes, **kwargs):
super().__init__(**kwargs)
choices = [(protocoltype.id, protocoltype.short_name) for protocoltype in protocoltypes]
choices.insert(0, (-1, ""))
self.protocoltype.choices = choices
...@@ -177,12 +177,11 @@ class TodosTable(Table): ...@@ -177,12 +177,11 @@ class TodosTable(Table):
super().__init__("Todos", todos) super().__init__("Todos", todos)
def headers(self): def headers(self):
return ["ID", "Status", "Sitzung", "Name", "Aufgabe"] return ["Status", "Sitzung", "Name", "Aufgabe"]
def row(self, todo): def row(self, todo):
protocol = todo.get_first_protocol() protocol = todo.get_first_protocol()
return [ return [
todo.get_id(),
todo.get_state(), todo.get_state(),
Table.link(url_for("show_protocol", protocol_id=protocol.id), protocol.get_identifier()) if protocol is not None else "", Table.link(url_for("show_protocol", protocol_id=protocol.id), protocol.get_identifier()) if protocol is not None else "",
todo.who, todo.who,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment