tables.py 26.9 KB
Newer Older
Robin Sonnabend's avatar
Robin Sonnabend committed
1
from flask import Markup, url_for
marco's avatar
marco committed
2
from shared import date_filter, datetime_filter, time_filter, current_user
3
from common.csrf import get_csrf_token
4

5
from shared import config
6

7

8
class Table:
Robin Sonnabend's avatar
Robin Sonnabend committed
9
    def __init__(self, title, values, newlink=None, newtext=None):
10
11
12
        self.title = title
        self.values = values
        self.newlink = newlink
Robin Sonnabend's avatar
Robin Sonnabend committed
13
        self.newtext = newtext or "Neu"
14
15

    def rows(self):
16
17
18
        return [
            row for row in [self.row(value) for value in self.values]
            if row is not None]
19

20
21
22
    def classes(self):
        return [None for header in self.headers()]

23
    @staticmethod
24
25
26
27
    def link(target, text, confirm=None, css_class=None):
        attributes = [
            "href=\"{}\"".format(target)
        ]
28
        if confirm:
29
30
31
32
33
            attributes.append(
                "onclick=\"return confirm('{}');\"".format(confirm))
        if css_class:
            attributes.append("class=\"{}\"".format(css_class))
        return Markup("<a {}>{}</a>".format(" ".join(attributes), text))
34

marco's avatar
marco committed
35
    @staticmethod
36
37
38
39
40
    def glyphicon(name, text=None):
        if text is None:
            text = ""
        else:
            text = " {}".format(text)
marco's avatar
marco committed
41
        return Markup(
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
            "<span class=\"glyphicon glyphicon-{}\"></span>{}".format(
                name, text))

    @staticmethod
    def button(target, icon, style, confirm=None):
        return Table.link(
            target=target,
            text=Table.glyphicon(icon),
            css_class="btn btn-{}".format(style))

    @staticmethod
    def button_group(buttons, size="xs"):
        return Markup("".join([
            Markup("<div class=\"btn-group btn-group-{}\">").format(size),
            "".join(buttons),
            Markup("</div>"),
        ]))

60
61
    @staticmethod
    def mail(target):
62
        return Table.link("mailto:{}".format(target), target)
63
64
65

    @staticmethod
    def bool(value):
Robin Sonnabend's avatar
Robin Sonnabend committed
66
        return "Ja" if value else "Nein"
67
68
69

    @staticmethod
    def concat(values):
Robin Sonnabend's avatar
Robin Sonnabend committed
70
        return Markup(", ".join(values))
71
72
73
74
75

    @staticmethod
    def concat_lines(values):
        return Markup("<br>".join(values))

76
77

class SingleValueTable:
Robin Sonnabend's avatar
Robin Sonnabend committed
78
    def __init__(self, title, value, newlink=None, newtext=None):
79
80
81
        self.title = title
        self.value = value
        self.newlink = newlink if newlink else None
Robin Sonnabend's avatar
Robin Sonnabend committed
82
        self.newtext = newtext or "Ändern"
83
84
85
86

    def rows(self):
        return [self.row()]

87

88
class ProtocolsTable(Table):
Robin Sonnabend's avatar
Robin Sonnabend committed
89
    def __init__(self, protocols, search_results=None):
90
91
        super().__init__(
            "Protokolle", protocols, newlink=url_for("new_protocol"))
Robin Sonnabend's avatar
Robin Sonnabend committed
92
        self.search_results = search_results
93
94

    def headers(self):
marco's avatar
marco committed
95
        result = ["Sitzung", "Sitzung", "Datum"]
marco's avatar
marco committed
96
        state_part = ["Uhrzeit", "Status", "Status", ""]
97
        search_part = ["Suchergebnis", ""]
Robin Sonnabend's avatar
Robin Sonnabend committed
98
99
100
101
        if self.search_results is None:
            result.extend(state_part)
        else:
            result.extend(search_part)
Robin Sonnabend's avatar
Robin Sonnabend committed
102
        return result
103

104
    def classes(self):
Robin Sonnabend's avatar
Robin Sonnabend committed
105
106
107
        _MOBILE = ["hidden-sm hidden-md hidden-lg"]
        _STANDARD = ["hidden-xs"]
        _ALL = [""]
marco's avatar
marco committed
108
        if self.search_results is None:
Robin Sonnabend's avatar
Robin Sonnabend committed
109
            return _MOBILE + 3 * _STANDARD + _MOBILE + _STANDARD + _ALL
marco's avatar
marco committed
110
        else:
Robin Sonnabend's avatar
Robin Sonnabend committed
111
            return _MOBILE + 2 * _STANDARD + _ALL + 2 * _STANDARD
112

113
    def row(self, protocol):
Robin Sonnabend's avatar
Robin Sonnabend committed
114
        user = current_user()
115
        protocol_link = url_for("show_protocol", protocol_id=protocol.id)
Robin Sonnabend's avatar
Robin Sonnabend committed
116
        result = [
Robin Sonnabend's avatar
Robin Sonnabend committed
117
118
            # Protocol (mobile)
            Table.concat_lines([
119
120
                Table.link(protocol_link, protocol.protocoltype.name),
                date_filter(protocol.date)]),
Robin Sonnabend's avatar
Robin Sonnabend committed
121
122
123
            # Protocol (standard)
            Table.link(protocol_link, protocol.protocoltype.name),
            date_filter(protocol.date)
124
        ]
Robin Sonnabend's avatar
Robin Sonnabend committed
125
        if self.search_results is None:
Robin Sonnabend's avatar
Robin Sonnabend committed
126
127
128
129
130
131
            result.append(Markup(time_filter(protocol.start_time)))
            # State (mobile)
            result.append(Table.glyphicon(protocol.get_state_glyph()))
            # State (standard)
            result.append(Table.glyphicon(
                protocol.get_state_glyph(), protocol.get_state_name()))
Robin Sonnabend's avatar
Robin Sonnabend committed
132
        elif protocol in self.search_results:
Robin Sonnabend's avatar
Robin Sonnabend committed
133
134
            result.append(Markup(self.search_results[protocol]))
            result.append(Table.glyphicon(protocol.get_state_glyph()))
135
136

        buttons = []
marco's avatar
marco committed
137
138
139
        if protocol.has_public_view_right(user):
            user_right = protocol.has_private_view_right(user)
            document = protocol.get_compiled_document(user_right)
Robin Sonnabend's avatar
Robin Sonnabend committed
140
            if document is not None:
141
                buttons.append(Table.button(
Robin Sonnabend's avatar
Robin Sonnabend committed
142
                    url_for("download_document", document_id=document.id),
143
                    icon="download", style="success"))
marco's avatar
marco committed
144

Robin Sonnabend's avatar
Robin Sonnabend committed
145
        if protocol.protocoltype.has_admin_right(user):
146
            buttons.append(Table.button(
147
148
149
                url_for(
                    "delete_protocol", protocol_id=protocol.id,
                    csrf_token=get_csrf_token()),
Robin Sonnabend's avatar
Robin Sonnabend committed
150
151
                icon="trash",
                style="danger",
152
153
                confirm="Bist du dir sicher, dass du das Protokoll {} "
                        "löschen möchtest?"))
marco's avatar
marco committed
154

Robin Sonnabend's avatar
Robin Sonnabend committed
155
        result.append(Table.button_group(buttons))
Robin Sonnabend's avatar
Robin Sonnabend committed
156
        return result
157

158

Robin Sonnabend's avatar
Robin Sonnabend committed
159
160
161
162
163
class ProtocolTypesTable(Table):
    def __init__(self, types):
        super().__init__("Protokolltypen", types, newlink=url_for("new_type"))

    def headers(self):
164
165
166
167
168
169
170
171
172
173
        return [
            "Typ", "Protokoll",
            "Typ", "Name", "Neuestes Protokoll", ""
        ]

    def classes(self):
        return [
            "hidden-sm hidden-md hidden-lg", "hidden-sm hidden-md hidden-lg",
            "hidden-xs", "hidden-xs", "hidden-xs", "hidden-xs"
        ]
Robin Sonnabend's avatar
Robin Sonnabend committed
174
175

    def row(self, protocoltype):
Robin Sonnabend's avatar
Robin Sonnabend committed
176
177
        protocol = protocoltype.get_latest_protocol()
        user = current_user()
178
        has_private_view_right = protocoltype.has_private_view_right(user)
Robin Sonnabend's avatar
Robin Sonnabend committed
179
        has_modify_right = protocoltype.has_modify_right(user)
180
181
182
183
        protocoltype_link = url_for(
            "show_type", protocoltype_id=protocoltype.id)
        protocol_link = (
            url_for("show_protocol", protocol_id=protocol.id)
184
            if protocol is not None else "")
185
186
187
188
        new_protocol_link = url_for(
            "new_protocol", protocoltype_id=protocoltype.id)
        mobile_name = "{} ({})".format(
            protocoltype.name, protocoltype.short_name)
189
190
        mobile_links = []
        if protocol is not None:
191
192
            mobile_links.append(Table.link(
                protocol_link, protocol.get_short_identifier()))
193
        if has_modify_right:
194
195
            mobile_links.append(
                Table.link(new_protocol_link, "Neues Protokoll"))
196
        mobile_part = [
197
198
            Table.link(protocoltype_link, mobile_name)
            if has_private_view_right else mobile_name,
199
200
201
            Markup("<br>".join(mobile_links))
        ]
        desktop_part = [
202
203
            Table.link(protocoltype_link, protocoltype.short_name)
            if has_private_view_right else protocoltype.short_name,
Robin Sonnabend's avatar
Robin Sonnabend committed
204
            protocoltype.name,
205
206
207
208
209
            Table.link(protocol_link, protocol.get_short_identifier())
            if protocol is not None else "Noch kein Protokoll",
            Table.link(new_protocol_link, "Neues Protokoll")
            if has_modify_right else ""
            ""  # TODO: add link for modify, delete
Robin Sonnabend's avatar
Robin Sonnabend committed
210
        ]
211
        return mobile_part + desktop_part
Robin Sonnabend's avatar
Robin Sonnabend committed
212

213

Robin Sonnabend's avatar
Robin Sonnabend committed
214
215
class ProtocolTypeTable(SingleValueTable):
    def __init__(self, protocoltype):
216
217
218
        super().__init__(
            protocoltype.name, protocoltype,
            newlink=url_for("edit_type", protocoltype_id=protocoltype.id))
Robin Sonnabend's avatar
Robin Sonnabend committed
219
220

    def headers(self):
221
222
223
224
        general_headers = [
            "Name", "Abkürzung", "Organisation", "Beginn",
            "Öffentlich", "Verwaltungsgruppe", "Bearbeitungsgruppe",
            "Interne Gruppe", "Öffentliche Gruppe"]
225
226
227
        etherpad_headers = ["Nicht-reproduzierbare Etherpadlinks"]
        if not config.ETHERPAD_ACTIVE:
            etherpad_headers = []
228
229
230
231
232
        mail_headers = ["Interner Verteiler", "Öffentlicher Verteiler"]
        if not config.MAIL_ACTIVE:
            mail_headers = []
        printing_headers = ["Drucker"] if config.PRINTING_ACTIVE else []
        wiki_headers = ["Wiki"]
Robin Sonnabend's avatar
Robin Sonnabend committed
233
        if self.value.use_wiki:
234
235
236
            wiki_headers.append("Wiki-Kategorie")
        if not config.WIKI_ACTIVE:
            wiki_headers = []
Robin Sonnabend's avatar
Robin Sonnabend committed
237
238
239
        calendar_headers = ["Kalender"]
        if not config.CALENDAR_ACTIVE:
            calendar_headers = []
240
        network_headers = ["Netzwerke einschränken", "Erlaubte Netzwerke"]
241
        action_headers = ["Aktion"]
Robin Sonnabend's avatar
Robin Sonnabend committed
242
        feed_headers = []
243
244
        latex_template_headers = ["LaTeX Vorlage"] if getattr(
            config, "LATEX_TEMPLATES", None) is not None else []
Robin Sonnabend's avatar
Robin Sonnabend committed
245
        if self.value.has_public_anonymous_view_right():
246
247
248
249
250
            feed_headers = [
                Markup("<img height=\"18px\" src=\"{}\" /> Feed".format(
                    url_for("static", filename="images/feed-icon.svg")))]
        return (
            general_headers + etherpad_headers + mail_headers
251
            + printing_headers + wiki_headers + calendar_headers
252
253
            + network_headers + latex_template_headers + feed_headers
            + action_headers)
Robin Sonnabend's avatar
Robin Sonnabend committed
254
255

    def row(self):
256
        user = current_user()
257
        general_part = [
Robin Sonnabend's avatar
Robin Sonnabend committed
258
259
260
            self.value.name,
            self.value.short_name,
            self.value.organization,
261
262
            self.value.usual_time.strftime("%H:%M")
            if self.value.usual_time is not None else "",
Robin Sonnabend's avatar
Robin Sonnabend committed
263
            Table.bool(self.value.is_public),
Robin Sonnabend's avatar
Robin Sonnabend committed
264
            self.value.publish_group,
265
            self.value.modify_group,
Robin Sonnabend's avatar
Robin Sonnabend committed
266
267
            self.value.private_group,
            self.value.public_group,
268
        ]
269
270
271
272
        etherpad_part = [
            Table.bool(self.value.non_reproducible_pad_links)
        ]
        if not config.ETHERPAD_ACTIVE:
273
            etherpad_part = []
274
        mail_part = [
Robin Sonnabend's avatar
Robin Sonnabend committed
275
            self.value.private_mail,
Robin Sonnabend's avatar
Robin Sonnabend committed
276
            self.value.public_mail,
277
278
279
280
281
282
283
        ]
        if not config.MAIL_ACTIVE:
            mail_part = []
        printing_part = [self.value.printer]
        if not config.PRINTING_ACTIVE:
            printing_part = []
        wiki_part = [
284
285
286
287
288
289
            (Table.bool(self.value.use_wiki)
                + ((", "
                    + ("Öffentlich"
                        if self.value.wiki_only_public
                        else "Intern")
                    ) if self.value.use_wiki else ""))
Robin Sonnabend's avatar
Robin Sonnabend committed
290
        ]
Robin Sonnabend's avatar
Robin Sonnabend committed
291
        if self.value.use_wiki:
292
293
294
            wiki_part.append(self.value.wiki_category)
        if not config.WIKI_ACTIVE:
            wiki_part = []
295
296
297
        calendar_part = [
            self.value.calendar
            if self.value.calendar is not None else ""]
Robin Sonnabend's avatar
Robin Sonnabend committed
298
299
        if not config.CALENDAR_ACTIVE:
            calendar_part = []
300
301
        network_part = [Table.bool(self.value.restrict_networks)]
        if self.value.allowed_networks is not None:
302
303
304
            network_part.append(
                ", ".join(map(
                    str.strip, self.value.allowed_networks.split(","))))
305
306
        else:
            network_part.append("")
307
308
        _latex_templates = getattr(config, "LATEX_TEMPLATES", None)
        if _latex_templates is not None:
309
310
            latex_template_part = [
                _latex_templates[self.value.latex_template]['name']
311
312
                if (self.value.latex_template is not None
                    and self.value.latex_template != "")
313
                else "Standardvorlage"]
314
315
        else:
            latex_template_part = []
Robin Sonnabend's avatar
Robin Sonnabend committed
316
317
318
        feed_part = []
        if self.value.has_public_anonymous_view_right():
            feed_part = [Markup(", ".join([
319
320
                Table.link(url_for(
                    "feed_protocols_rss",
Robin Sonnabend's avatar
Robin Sonnabend committed
321
                    protocoltype_id=self.value.id), "Protokolle (RSS)"),
322
323
                Table.link(url_for(
                    "feed_protocols_atom",
Robin Sonnabend's avatar
Robin Sonnabend committed
324
                    protocoltype_id=self.value.id), "Protokolle (Atom)"),
325
326
                Table.link(url_for(
                    "feed_appointments_rss",
Robin Sonnabend's avatar
Robin Sonnabend committed
327
                    protocoltype_id=self.value.id), "Sitzungen (RSS)"),
328
329
                Table.link(url_for(
                    "feed_appointments_atom",
Robin Sonnabend's avatar
Robin Sonnabend committed
330
                    protocoltype_id=self.value.id), "Sitzungen (Atom)"),
331
332
                Table.link(url_for(
                    "feed_appointments_ical",
Robin Sonnabend's avatar
Robin Sonnabend committed
333
                    protocoltype_id=self.value.id), "Sitzungen (iCal)"),
Robin Sonnabend's avatar
Robin Sonnabend committed
334
            ]))]
335
336
        action_part = [
            Table.link(
337
338
                url_for("delete_type", protocoltype_id=self.value.id,
                        csrf_token=get_csrf_token()),
339
340
341
342
                "Löschen",
                confirm="Bist du dir sicher, dass du den Protokolltype "
                        "{} löschen möchtest?".format(self.value.name))
        ]
343
344
        if not self.value.has_admin_right(user):
            action_part = [""]
345
346
347
348
349
        return (
            general_part + etherpad_part + mail_part + printing_part
            + wiki_part + calendar_part + network_part + latex_template_part
            + feed_part + action_part)

Robin Sonnabend's avatar
Robin Sonnabend committed
350
351
352

class DefaultTOPsTable(Table):
    def __init__(self, tops, protocoltype=None):
353
354
355
356
        super().__init__(
            "Standard-TOPs", tops,
            newlink=url_for("new_default_top", protocoltype_id=protocoltype.id)
            if protocoltype is not None else None)
Robin Sonnabend's avatar
Robin Sonnabend committed
357
358
359
360
361
362
363
364
365
366
        self.protocoltype = protocoltype

    def headers(self):
        return ["TOP", "Sortierung", ""]

    def row(self, top):
        return [
            top.name,
            top.number,
            Table.concat([
367
                Table.link(
368
369
                    url_for("move_default_top", defaulttop_id=top.id, diff=1,
                            csrf_token=get_csrf_token()),
370
371
                    "Runter"),
                Table.link(
372
373
                    url_for("move_default_top", defaulttop_id=top.id, diff=-1,
                            csrf_token=get_csrf_token()),
374
375
376
377
378
379
380
381
                    "Hoch"),
                Table.link(
                    url_for(
                        "edit_default_top",
                        protocoltype_id=self.protocoltype.id,
                        defaulttop_id=top.id),
                    "Ändern"),
                Table.link(
382
383
                    url_for("delete_default_top", defaulttop_id=top.id,
                            csrf_token=get_csrf_token()),
384
385
386
                    "Löschen",
                    confirm="Bist du dir sicher, dass du den Standard-TOP "
                            "{} löschen willst?".format(top.name))
Robin Sonnabend's avatar
Robin Sonnabend committed
387
388
            ])
        ]
389

390

391
392
class MeetingRemindersTable(Table):
    def __init__(self, reminders, protocoltype=None):
393
394
395
396
        super().__init__(
            "Einladungsmails", reminders,
            newlink=url_for("new_reminder", protocoltype_id=protocoltype.id)
            if protocoltype is not None else None)
397
398
399
        self.protocoltype = protocoltype

    def headers(self):
Robin Sonnabend's avatar
Robin Sonnabend committed
400
        return ["Zeit", "Einladen", "Zusätzlicher Mailinhalt", ""]
401
402

    def row(self, reminder):
403
        general_part = [
404
405
            "{} Tage".format(reminder.days_before),
            self.get_send_summary(reminder),
406
            reminder.additional_text or ""
407
        ]
408
        action_links = [
409
410
411
412
            Table.link(
                url_for("edit_reminder", meetingreminder_id=reminder.id),
                "Ändern"),
            Table.link(
413
414
                url_for("delete_reminder", meetingreminder_id=reminder.id,
                        csrf_token=get_csrf_token()),
415
416
417
418
                "Löschen",
                confirm="Bist du dir sicher, dass du die Einladungsmail {} "
                        "Tage vor der Sitzung löschen willst?".format(
                    reminder.days_before))
419
420
421
        ]
        action_part = [Table.concat(action_links)]
        return general_part + action_part
422
423
424
425
426
427
428
429

    def get_send_summary(self, reminder):
        parts = []
        if reminder.send_public:
            parts.append("Öffentlich")
        if reminder.send_private:
            parts.append("Intern")
        return " und ".join(parts)
Robin Sonnabend's avatar
Robin Sonnabend committed
430

431

Robin Sonnabend's avatar
Robin Sonnabend committed
432
433
434
435
436
class ErrorsTable(Table):
    def __init__(self, errors):
        super().__init__("Fehler", errors)

    def headers(self):
437
438
        return [
            "Protokoll", "Aktion", "Fehler", "Zeitpunkt", "Beschreibung", ""]
Robin Sonnabend's avatar
Robin Sonnabend committed
439

440
    def classes(self):
441
        return [None, None, None, None, "hidden-xs", "hidden-xs"]
442

Robin Sonnabend's avatar
Robin Sonnabend committed
443
444
    def row(self, error):
        return [
445
446
447
            Table.link(
                url_for("show_protocol", protocol_id=error.protocol.id),
                error.protocol.get_short_identifier()),
Robin Sonnabend's avatar
Robin Sonnabend committed
448
449
            error.action,
            Table.link(url_for("show_error", error_id=error.id), error.name),
Robin Sonnabend's avatar
Robin Sonnabend committed
450
            datetime_filter(error.datetime),
Robin Sonnabend's avatar
Robin Sonnabend committed
451
            error.get_short_description(),
452
            Table.link(
453
454
                url_for("delete_error", error_id=error.id,
                        csrf_token=get_csrf_token()),
455
456
457
                "Löschen",
                confirm="Bist du dir sicher, dass du den Fehler löschen "
                        "möchtest?")
Robin Sonnabend's avatar
Robin Sonnabend committed
458
459
        ]

460

Robin Sonnabend's avatar
Robin Sonnabend committed
461
462
463
464
465
class ErrorTable(SingleValueTable):
    def __init__(self, error):
        super().__init__(error.action, error)

    def headers(self):
466
        return ["Protokoll", "Aktion", "Fehler", "Zeitpunkt"]
Robin Sonnabend's avatar
Robin Sonnabend committed
467
468
469

    def row(self):
        return [
470
471
472
            Table.link(
                url_for("show_protocol", protocol_id=self.value.protocol.id),
                self.value.protocol.get_short_identifier()),
Robin Sonnabend's avatar
Robin Sonnabend committed
473
474
            self.value.action,
            self.value.name,
475
            datetime_filter(self.value.datetime)
Robin Sonnabend's avatar
Robin Sonnabend committed
476
477
        ]

478

Robin Sonnabend's avatar
Robin Sonnabend committed
479
480
class TodosTable(Table):
    def __init__(self, todos):
Robin Sonnabend's avatar
Robin Sonnabend committed
481
        super().__init__("Todos", todos, newlink=url_for("new_todo"))
Robin Sonnabend's avatar
Robin Sonnabend committed
482
483

    def headers(self):
484
        return ["Todo", "Status", "Name", "Aufgabe", "Sitzung", ""]
485
486

    def classes(self):
487
488
        return [
            "hidden-sm hidden-md hidden-lg",
489
490
            "hidden-xs", "hidden-xs", None,
            "hidden-xs", "hidden-xs"]
Robin Sonnabend's avatar
Robin Sonnabend committed
491
492

    def row(self, todo):
Robin Sonnabend's avatar
Robin Sonnabend committed
493
        user = current_user()
Robin Sonnabend's avatar
Robin Sonnabend committed
494
        protocol = todo.get_first_protocol()
495
496
        mobile_parts = [Table.glyphicon(todo.get_state_glyph())]
        mobile_parts.append(todo.who)
497
        if protocol is not None:
498
499
500
            mobile_parts.append(Table.link(
                url_for("show_protocol", protocol_id=protocol.id),
                todo.protocoltype.short_name))
Robin Sonnabend's avatar
Robin Sonnabend committed
501
        row = [
502
            Markup("<br>").join(mobile_parts),
503
504
505
            Table.glyphicon(todo.get_state_glyph(),todo.get_state_plain()),
            todo.who,
            Table.link(url_for("show_todo", todo_id=todo.id), todo.description),
506
507
508
509
510
511
512
513
            Table.link(
                url_for("show_protocol", protocol_id=protocol.id),
                protocol.get_short_identifier())
            if protocol is not None
            else Table.link(
                url_for(
                    "list_protocols", protocoltype_id=todo.protocoltype.id),
                todo.protocoltype.short_name),
Robin Sonnabend's avatar
Robin Sonnabend committed
514
        ]
Robin Sonnabend's avatar
Robin Sonnabend committed
515
        if todo.protocoltype.has_modify_right(user):
516
517
518
519
520
521
522
523
524
525
526
527
528
529
            buttons = []
            buttons.append(Table.button(
                url_for(
                    "edit_todo", todo_id=todo.id,
                    csrf_token=get_csrf_token()),
                icon="pencil",
                style="success"))
            buttons.append(Table.button(
                url_for(
                    "delete_todo", todo_id=todo.id,
                    csrf_token=get_csrf_token()),
                icon="trash",
                style="danger"))
            row.append(Table.button_group(buttons))
Robin Sonnabend's avatar
Robin Sonnabend committed
530
531
532
533
        else:
            row.append("")
        return row

534

Robin Sonnabend's avatar
Robin Sonnabend committed
535
536
537
538
539
class TodoTable(SingleValueTable):
    def __init__(self, todo):
        super().__init__("Todo", todo)

    def headers(self):
540
        return ["ID", "Status", "Sitzung", "Name", "Aufgabe", ""]
Robin Sonnabend's avatar
Robin Sonnabend committed
541
542
543
544
545
546

    def row(self):
        user = current_user()
        row = [
            self.value.get_id(),
            self.value.get_state_plain(),
547
            Table.concat([
548
549
550
551
                Table.link(
                    url_for("show_protocol", protocol_id=protocol.id),
                    protocol.get_short_identifier())
                for protocol in self.value.protocols
552
            ]),
Robin Sonnabend's avatar
Robin Sonnabend committed
553
            self.value.who,
554
            self.value.description
Robin Sonnabend's avatar
Robin Sonnabend committed
555
556
557
        ]
        if self.value.protocoltype.has_modify_right(user):
            row.append(Table.concat([
558
559
560
                Table.link(
                    url_for("edit_todo", todo_id=self.value.id), "Ändern"),
                Table.link(
561
562
                    url_for("delete_todo", todo_id=self.value.id,
                            csrf_token=get_csrf_token()), "Löschen",
563
564
                    confirm="Bist du dir sicher, dass du das Todo löschen "
                            "willst?")
Robin Sonnabend's avatar
Robin Sonnabend committed
565
566
567
568
            ]))
        else:
            row.append("")
        return row
569

570

Robin Sonnabend's avatar
Robin Sonnabend committed
571
572
573
class DecisionsTable(Table):
    def __init__(self, decisions):
        super().__init__("Beschlüsse", decisions)
574
575
        self.category_present = len([
            decision for decision in decisions
576
            if len(decision.categories) > 0
577
        ]) > 0
Robin Sonnabend's avatar
Robin Sonnabend committed
578
579

    def headers(self):
580
581
582
583
584
585
        content_part = ["Sitzung", "Beschluss"]
        category_part = ["Kategorie"]
        if not self.category_present:
            category_part = []
        action_part = [""]
        return content_part + category_part + action_part
Robin Sonnabend's avatar
Robin Sonnabend committed
586
587

    def row(self, decision):
Robin Sonnabend's avatar
Robin Sonnabend committed
588
        user = current_user()
589
        content_part = [
590
591
592
            Table.link(
                url_for("show_protocol", protocol_id=decision.protocol.id),
                decision.protocol.get_short_identifier()),
593
594
            decision.content
        ]
595
        category_part = [decision.get_categories_str()]
596
597
598
        if not self.category_present:
            category_part = []
        action_part = [
599
600
601
            Table.link(
                url_for(
                    "print_decision",
602
603
                    decisiondocument_id=decision.document.id,
                    csrf_token=get_csrf_token()),
604
605
                "Drucken")
            if (config.PRINTING_ACTIVE
Robin Sonnabend's avatar
Robin Sonnabend committed
606
                and decision.protocol.protocoltype.has_modify_right(user)
607
608
                and decision.document is not None)
            else ""
Robin Sonnabend's avatar
Robin Sonnabend committed
609
        ]
610
        return content_part + category_part + action_part
Robin Sonnabend's avatar
Robin Sonnabend committed
611

612

613
class DocumentsTable(Table):
614
    def __init__(self, documents, protocol):
615
        super().__init__("Anhang", documents)
616
        self.protocol = protocol
617
618

    def headers(self):
619
620
621
622
623
        user = current_user()
        general_headers = ["ID", "Name"]
        visibility_headers = []
        if self.protocol.has_private_view_right(user):
            visibility_headers = ["Sichtbarkeit"]
624
        action_headers = [""]
625
        return general_headers + visibility_headers + action_headers
626

627
    def classes(self):
628
629
630
631
632
633
634
        user = current_user()
        general_part = [None, None]
        visibility_part = []
        if self.protocol.has_private_view_right(user):
            visibility_part = [None]
        action_part = ["hidden-xs"]
        return general_part + visibility_part + action_part
635

636
    def row(self, document):
Robin Sonnabend's avatar
Robin Sonnabend committed
637
        user = current_user()
638
        links = []
Robin Sonnabend's avatar
Robin Sonnabend committed
639
        if document.protocol.has_modify_right(user):
640
641
642
            links.append(Table.link(
                url_for("edit_document", document_id=document.id),
                "Bearbeiten"))
643
        if config.PRINTING_ACTIVE and document.protocol.has_modify_right(user):
644
            links.append(Table.link(
645
646
                url_for("print_document", document_id=document.id,
                        csrf_token=get_csrf_token()),
647
                "Drucken"))
648
        if document.protocol.protocoltype.has_admin_right(user):
649
            links.append(Table.link(
650
651
                url_for("delete_document", document_id=document.id,
                        csrf_token=get_csrf_token()),
652
653
654
                "Löschen",
                confirm="Bist du dir sicher, dass du das Dokument {} löschen "
                        "willst?".format(document.name)))
655
        general_part = [
656
            document.id,
657
658
659
            Table.link(
                url_for("download_document", document_id=document.id),
                document.name),
660
        ]
661
662
        visibility_part = []
        if document.protocol.has_private_view_right(user):
663
664
665
666
            visibility_part = [
                "Intern"
                if document.is_private
                else "Öffentlich"]
667
668
        action_part = [Table.concat(links)]
        return general_part + visibility_part + action_part
Robin Sonnabend's avatar
Robin Sonnabend committed
669

670

Robin Sonnabend's avatar
Robin Sonnabend committed
671
672
class TodoMailsTable(Table):
    def __init__(self, todomails):
673
674
        super().__init__(
            "Todo-Mail-Zuordnungen", todomails, url_for("new_todomail"))
Robin Sonnabend's avatar
Robin Sonnabend committed
675
676
677
678
679
680
681
682
683

    def headers(self):
        return ["Name", "Mail", ""]

    def row(self, todomail):
        return [
            todomail.name,
            todomail.mail,
            Table.concat([
684
685
686
687
                Table.link(
                    url_for("edit_todomail", todomail_id=todomail.id),
                    "Ändern"),
                Table.link(
688
689
                    url_for("delete_todomail", todomail_id=todomail.id,
                            csrf_token=get_csrf_token()),
690
691
692
693
                    "Löschen",
                    confirm="Bist du dir sicher, dass du die "
                            "Todomailzuordnung {} zu {} löschen "
                            "willst?".format(todomail.name, todomail.mail))
Robin Sonnabend's avatar
Robin Sonnabend committed
694
695
696
            ])
        ]

697

Robin Sonnabend's avatar
Robin Sonnabend committed
698
699
700
701
702
class DefaultMetasTable(Table):
    def __init__(self, metas, protocoltype):
        super().__init__(
            "Metadatenfelder",
            metas,
Robin Sonnabend's avatar
Robin Sonnabend committed
703
            url_for("new_defaultmeta", protocoltype_id=protocoltype.id)
Robin Sonnabend's avatar
Robin Sonnabend committed
704
705
706
        )

    def headers(self):
707
        return ["Name", "Key", "Standardwert", "Intern", "Vorher", ""]
Robin Sonnabend's avatar
Robin Sonnabend committed
708
709

    def row(self, meta):
710
        general_part = [
Robin Sonnabend's avatar
Robin Sonnabend committed
711
712
            meta.name,
            meta.key,
713
714
715
            meta.value,
            Table.bool(meta.internal),
            Table.bool(meta.prior)
Robin Sonnabend's avatar
Robin Sonnabend committed
716
        ]
717
        links = [
718
719
720
            Table.link(
                url_for("edit_defaultmeta", defaultmeta_id=meta.id), "Ändern"),
            Table.link(
721
722
                url_for("delete_defaultmeta", defaultmeta_id=meta.id,
                        csrf_token=get_csrf_token()),
723
724
725
                "Löschen",
                confirm="Bist du dir sicher, dass du das Metadatenfeld {} "
                        "löschen willst?".format(meta.name))
726
727
728
729
        ]
        link_part = [Table.concat(links)]
        return general_part + link_part

730

731
732
733
734
class DecisionCategoriesTable(Table):
    def __init__(self, categories, protocoltype):
        super().__init__(
            "Beschlusskategorien",
735
            categories,
736
737
738
739
740
741
742
743
744
745
            url_for("new_decisioncategory", protocoltype_id=protocoltype.id)
        )

    def headers(self):
        return ["Name", ""]

    def row(self, category):
        general_part = [category.name]
        action_part = [
            Table.concat([
746
747
748
749
750
751
752
753
                Table.link(
                    url_for(
                        "edit_decisioncategory",
                        decisioncategory_id=category.id),
                    "Ändern"),
                Table.link(
                    url_for(
                        "delete_decisioncategory",
754
755
                        decisioncategory_id=category.id,
                        csrf_token=get_csrf_token()),
756
757
758
759
                    "Löschen",
                    confirm="Bist du dir sicher, dass du die "
                            "Beschlusskategorie {} löschen "
                            "willst?".format(category.name))
760
761
762
            ])
        ]
        return general_part + action_part