Commit f62d577e authored by Robin Sonnabend's avatar Robin Sonnabend

Search and paginate todos

parent 6031524d
"""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):
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")
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,
is_public, private_group, public_group, private_mail, public_mail):
......@@ -63,6 +64,14 @@ class ProtocolType(db.Model):
def has_modify_right(self, 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):
__tablename__ = "protocols"
......@@ -244,6 +253,7 @@ def on_document_delete(mapper, connection, document):
class Todo(db.Model):
__tablename__ = "todos"
id = db.Column(db.Integer, primary_key=True)
protocoltype_id = db.Column(db.Integer, db.ForeignKey("protocoltypes.id"))
number = db.Column(db.Integer)
who = db.Column(db.String)
description = db.Column(db.String)
......@@ -253,7 +263,8 @@ class Todo(db.Model):
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.description = description
self.tags = tags
......
......@@ -11,12 +11,13 @@ from celery import Celery
from io import StringIO, BytesIO
import os
from datetime import datetime
import math
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 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 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
app = Flask(__name__)
......@@ -51,6 +52,9 @@ import tasks
app.jinja_env.globals.update(check_login=check_login)
app.jinja_env.globals.update(current_user=current_user)
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
......@@ -289,10 +293,7 @@ def list_protocols():
@login_required
def new_protocol():
user = current_user()
protocoltypes = [
protocoltype for protocoltype in ProtocolType.query.all()
if protocoltype.has_modify_right(user)
]
protocoltypes = ProtocolType.get_available_protocoltypes(user)
form = NewProtocolForm(protocoltypes)
upload_form = NewProtocolSourceUploadForm(protocoltypes)
if form.validate_on_submit():
......@@ -527,16 +528,57 @@ def move_top(top_id, diff):
except ValueError:
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))
@app.route("/todos/list")
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", methods=["GET", "POST"])
def list_todos():
is_logged_in = check_login()
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
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>")
def download_document(document_id):
......
......@@ -23,7 +23,7 @@ h3 > a {
font-size: 18px;
}
form {
form:not(.form-inline) {
max-width: 350px;
margin: 0 auto;
}
......@@ -31,3 +31,12 @@ form {
input[type="file"] {
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):
candidate.number = field_id
todo = candidate
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
else:
candidate = Todo.query.filter_by(who=who, description=what).first()
if candidate is not None:
todo = candidate
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)
todo.protocols.append(protocol)
todo_tags_internal = todo.tags.split(";")
......
......@@ -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_', '') }}">
{% if field.type != 'HiddenField' and field.type !='CSRFTokenField' and label_visible %}
<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 %}
{{ field(title=field.description, class_='form-control', **kwargs) }}
{{ field(title=field.description, placeholder=field.label.text, class_='form-control', **kwargs) }}
{% if field.errors %}
{% for e in field.errors %}
<p class="help-block">{{ e }}</p>
{% endfor %}
{% endif %}
</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 %}
{# Renders checkbox fields since they are represented differently in bootstrap
......@@ -87,7 +87,7 @@ to not render a label for the CRSFTokenField -->
action_text - text of submit button
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.hidden_tag() if form.hidden_tag }}
......@@ -100,7 +100,7 @@ to not render a label for the CRSFTokenField -->
{% elif f.type == 'RadioField' %}
{{ render_radio_field(f) }}
{% else %}
{{ render_field(f) }}
{{ render_field(f, label_visible=labels_visible) }}
{% endif %}
{% endfor %}
{% endif %}
......
{% extends "layout.html" %}
{% from "macros.html" import render_table %}
{% from "macros.html" import render_table, render_form %}
{% 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 %}
<div class="container">
{{render_form(search_form, class_="form-inline", labels_visible=False)}}
{{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>
{% endblock %}
......@@ -124,3 +124,4 @@ 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
......@@ -61,3 +61,12 @@ class TopForm(FlaskForm):
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.")])
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):
super().__init__("Todos", todos)
def headers(self):
return ["ID", "Status", "Sitzung", "Name", "Aufgabe"]
return ["Status", "Sitzung", "Name", "Aufgabe"]
def row(self, todo):
protocol = todo.get_first_protocol()
return [
todo.get_id(),
todo.get_state(),
Table.link(url_for("show_protocol", protocol_id=protocol.id), protocol.get_identifier()) if protocol is not None else "",
todo.who,
......
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