Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
protokollsystem
proto3
Commits
f62d577e
Commit
f62d577e
authored
Feb 24, 2017
by
Robin Sonnabend
Browse files
Search and paginate todos
parent
6031524d
Changes
10
Hide whitespace changes
Inline
Side-by-side
migrations/versions/0131d5776f8d_.py
0 → 100644
View file @
f62d577e
"""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 ###
models/database.py
View file @
f62d577e
...
...
@@ -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
...
...
server.py
View file @
f62d577e
...
...
@@ -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
):
...
...
static/css/style.css
View file @
f62d577e
...
...
@@ -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
;
}
tasks.py
View file @
f62d577e
...
...
@@ -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
(
";"
)
...
...
templates/macros.html
View file @
f62d577e
...
...
@@ -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 %}
...
...
templates/todos-list.html
View file @
f62d577e
{% 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 %}
utils.py
View file @
f62d577e
...
...
@@ -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
views/forms.py
View file @
f62d577e
...
...
@@ -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
views/tables.py
View file @
f62d577e
...
...
@@ -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
,
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment