database.py 23.8 KB
Newer Older
1
2
from flask import render_template, send_file, url_for, redirect, flash, request

Robin Sonnabend's avatar
Robin Sonnabend committed
3
from datetime import datetime, time, date, timedelta
4
import math
5
from io import StringIO, BytesIO
6
from enum import Enum
7

8
from shared import db, date_filter, date_filter_short, escape_tex, DATE_KEY, START_TIME_KEY, END_TIME_KEY, AUTHOR_KEY, PARTICIPANTS_KEY, LOCATION_KEY
Robin Sonnabend's avatar
Robin Sonnabend committed
9
from utils import random_string, url_manager, get_etherpad_url, split_terms
Robin Sonnabend's avatar
Robin Sonnabend committed
10
from models.errors import DateNotMatchingException
11

12
import os
13

14
15
from sqlalchemy import event
from sqlalchemy.orm import relationship, backref, sessionmaker
Robin Sonnabend's avatar
Robin Sonnabend committed
16
from sqlalchemy.ext.hybrid import hybrid_method
17

Robin Sonnabend's avatar
Robin Sonnabend committed
18
import config
19
from todostates import make_states
Robin Sonnabend's avatar
Robin Sonnabend committed
20

21
22
23
24
25
26
class ProtocolType(db.Model):
    __tablename__ = "protocoltypes"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, unique=True)
    short_name = db.Column(db.String, unique=True)
    organization = db.Column(db.String)
Robin Sonnabend's avatar
Robin Sonnabend committed
27
    usual_time = db.Column(db.Time)
28
29
30
31
32
    is_public = db.Column(db.Boolean)
    private_group = db.Column(db.String)
    public_group = db.Column(db.String)
    private_mail = db.Column(db.String)
    public_mail = db.Column(db.String)
Robin Sonnabend's avatar
Robin Sonnabend committed
33
34
35
    use_wiki = db.Column(db.Boolean)
    wiki_category = db.Column(db.String)
    wiki_only_public = db.Column(db.Boolean)
Robin Sonnabend's avatar
Robin Sonnabend committed
36
    printer = db.Column(db.String)
Robin Sonnabend's avatar
Robin Sonnabend committed
37
    calendar = db.Column(db.String)
38
39
40

    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")
41
    reminders = relationship("MeetingReminder", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="MeetingReminder.days_before")
Robin Sonnabend's avatar
Robin Sonnabend committed
42
    todos = relationship("Todo", backref=backref("protocoltype"), order_by="Todo.id")
43

Robin Sonnabend's avatar
Robin Sonnabend committed
44
    def __init__(self, name, short_name, organization, usual_time,
Robin Sonnabend's avatar
Robin Sonnabend committed
45
            is_public, private_group, public_group, private_mail, public_mail,
46
            use_wiki, wiki_category, wiki_only_public, printer, calendar):
47
48
49
        self.name = name
        self.short_name = short_name
        self.organization = organization
Robin Sonnabend's avatar
Robin Sonnabend committed
50
        self.usual_time = usual_time
51
52
53
54
55
        self.is_public = is_public
        self.private_group = private_group
        self.public_group = public_group
        self.private_mail = private_mail
        self.public_mail = public_mail
Robin Sonnabend's avatar
Robin Sonnabend committed
56
57
58
        self.use_wiki = use_wiki
        self.wiki_category = wiki_category
        self.wiki_only_public = wiki_only_public
Robin Sonnabend's avatar
Robin Sonnabend committed
59
        self.printer = printer
Robin Sonnabend's avatar
Robin Sonnabend committed
60
        self.calendar = calendar
61
62

    def __repr__(self):
Robin Sonnabend's avatar
Robin Sonnabend committed
63
64
65
        return ("<ProtocolType(id={}, short_name={}, name={}, "
                "organization={}, is_public={}, private_group={}, "
                "public_group={}, use_wiki={}, wiki_category='{}', "
Robin Sonnabend's avatar
Robin Sonnabend committed
66
67
                "wiki_only_public={}, printer={}, usual_time={}, "
                "calendar='{}')>".format(
Robin Sonnabend's avatar
Robin Sonnabend committed
68
69
70
            self.id, self.short_name, self.name,
            self.organization, self.is_public, self.private_group,
            self.public_group, self.use_wiki, self.wiki_category,
Robin Sonnabend's avatar
Robin Sonnabend committed
71
72
            self.wiki_only_public, self.printer, self.usual_time,
            self.calendar))
Robin Sonnabend's avatar
Robin Sonnabend committed
73
74

    def get_latest_protocol(self):
Robin Sonnabend's avatar
Robin Sonnabend committed
75
        candidates = sorted([protocol for protocol in self.protocols if protocol.is_done()], key=lambda p: p.date, reverse=True)
Robin Sonnabend's avatar
Robin Sonnabend committed
76
77
78
79
        if len(candidates) == 0:
            return None
        return candidates[0]

Robin Sonnabend's avatar
Robin Sonnabend committed
80
    @hybrid_method
Robin Sonnabend's avatar
Robin Sonnabend committed
81
82
83
84
85
86
87
    def has_public_view_right(self, user):
        return (self.is_public
            or (user is not None and 
                ((self.public_group != "" and self.public_group in user.groups)
                or (self.private_group != "" and self.private_group in user.groups))))

    def has_private_view_right(self, user):
Robin Sonnabend's avatar
Robin Sonnabend committed
88
        return (user is not None and self.private_group != "" and self.private_group in user.groups)
Robin Sonnabend's avatar
Robin Sonnabend committed
89
90
91
92

    def has_modify_right(self, user):
        return self.has_private_view_right(user)

Robin Sonnabend's avatar
Robin Sonnabend committed
93
    @staticmethod
Robin Sonnabend's avatar
Robin Sonnabend committed
94
    def get_modifiable_protocoltypes(user):
Robin Sonnabend's avatar
Robin Sonnabend committed
95
96
97
98
99
        return [
            protocoltype for protocoltype in ProtocolType.query.all()
            if protocoltype.has_modify_right(user)
        ]

Robin Sonnabend's avatar
Robin Sonnabend committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113
    @staticmethod
    def get_public_protocoltypes(user):
        return [
            protocoltype for protocoltype in ProtocolType.query.all()
            if protocoltype.has_public_view_right(user)
        ]

    @staticmethod
    def get_private_protocoltypes(user):
        return [
            protocoltype for protocoltype in ProtocolType.query.all()
            if protocoltype.has_private_view_right(user)
        ]

114
115
116
117
class Protocol(db.Model):
    __tablename__ = "protocols"
    id = db.Column(db.Integer, primary_key=True)
    protocoltype_id = db.Column(db.Integer, db.ForeignKey("protocoltypes.id"))
118
119
120
    source = db.Column(db.String)
    content_public = db.Column(db.String)
    content_private = db.Column(db.String)
121
122
123
124
125
126
    date = db.Column(db.Date)
    start_time = db.Column(db.Time)
    end_time = db.Column(db.Time)
    author = db.Column(db.String)
    participants = db.Column(db.String)
    location = db.Column(db.String)
Robin Sonnabend's avatar
Robin Sonnabend committed
127
    done = db.Column(db.Boolean)
128
    public = db.Column(db.Boolean)
129
130
131
132
133
134

    tops = relationship("TOP", backref=backref("protocol"), cascade="all, delete-orphan", order_by="TOP.number")
    decisions = relationship("Decision", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Decision.id")
    documents = relationship("Document", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Document.is_compiled")
    errors = relationship("Error", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Error.id")

135
    def __init__(self, protocoltype_id, date, source=None, content_public=None, content_private=None, start_time=None, end_time=None, author=None, participants=None, location=None, done=False, public=False):
136
137
138
        self.protocoltype_id = protocoltype_id
        self.date = date
        self.source = source
139
140
        self.content_private = content_private
        self.content_public = content_public
141
142
143
144
145
        self.start_time = start_time
        self.end_time = end_time
        self.author = author
        self.participants = participants
        self.location = location
Robin Sonnabend's avatar
Robin Sonnabend committed
146
        self.done = done
147
        self.public = public
148
149
150
151
152
153
154
155
156
157

    def __repr__(self):
        return "<Protocol(id={}, protocoltype_id={})>".format(
            self.id, self.protocoltype_id)

    def create_error(self, action, name, description):
        now = datetime.now()
        return Error(self.id, action, name, now, description)

    def fill_from_remarks(self, remarks):
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
        def _date_or_lazy(key, get_date=False, get_time=False):
            formats = []
            if get_date:
                formats.append("%d.%m.%Y")
            if get_time:
                formats.append("%H:%M")
            format = " ".join(formats)
            try:
                date = datetime.strptime(remarks[key].value.strip(), format)
                if (get_time and get_date) or (not get_time and not get_date):
                    return date
                elif get_time:
                    return date.time()
                elif get_date:
                    return date.date()
            except ValueError as exc:
                if config.PARSER_LAZY:
                    return None
                raise exc
        if DATE_KEY in remarks:
            new_date = _date_or_lazy(DATE_KEY, get_date=True)
            if self.date is not None:
                if new_date != self.date:
                    raise DateNotMatchingException(original_date=self.date, protocol_date=new_date)
            else:
                self.date = new_date
        if START_TIME_KEY in remarks:
            self.start_time = _date_or_lazy(START_TIME_KEY, get_time=True)
        if END_TIME_KEY in remarks:
            self.end_time = _date_or_lazy(END_TIME_KEY, get_time=True)
        if AUTHOR_KEY in remarks:
            self.author = remarks[AUTHOR_KEY].value.strip()
        if PARTICIPANTS_KEY in remarks:
            self.participants = remarks[PARTICIPANTS_KEY].value.strip()
        if LOCATION_KEY in remarks:
            self.location = remarks[LOCATION_KEY].value.strip()
194

195
196
197
198
199
200
201
202
203
204
205
206
    def has_public_view_right(self, user):
        return (
            (self.public and self.protocoltype.has_public_view_right(user))
            or self.protocoltype.has_private_view_right(user)
        )

    def has_private_view_right(self, user):
        return self.protocoltype.has_private_view_right(user)

    def has_modify_right(self, user):
        return self.protocoltype.has_modify_right(user)

Robin Sonnabend's avatar
Robin Sonnabend committed
207
208
209
    def is_done(self):
        return self.done

Robin Sonnabend's avatar
Robin Sonnabend committed
210
    def get_identifier(self):
Robin Sonnabend's avatar
Robin Sonnabend committed
211
212
        if self.date is None:
            return None
Robin Sonnabend's avatar
Robin Sonnabend committed
213
214
215
216
        return "{}-{}".format(
            self.protocoltype.short_name.lower(),
            self.date.strftime("%y-%m-%d"))

Robin Sonnabend's avatar
Robin Sonnabend committed
217
218
219
    def get_wiki_title(self):
        return "Protokoll:{}-{:%Y-%m-%d}".format(self.protocoltype.short_name, self.date)

Robin Sonnabend's avatar
Robin Sonnabend committed
220
    def get_etherpad_link(self):
Robin Sonnabend's avatar
Robin Sonnabend committed
221
222
223
        identifier = self.get_identifier()
        if identifier is None:
            return ""
Robin Sonnabend's avatar
Robin Sonnabend committed
224
        return get_etherpad_url(self.get_identifier())
Robin Sonnabend's avatar
Robin Sonnabend committed
225

Robin Sonnabend's avatar
Robin Sonnabend committed
226
227
228
    def get_datetime(self):
        return datetime(self.date.year, self.date.month, self.date.day, self.protocoltype.usual_time.hour, self.protocoltype.usual_time.minute)

Robin Sonnabend's avatar
Robin Sonnabend committed
229
230
231
232
233
    def has_nonplanned_tops(self):
        return len([top for top in self.tops if not top.planned]) > 0

    def get_originating_todos(self):
        return [todo for todo in self.todos if self == todo.get_first_protocol()]
234

235
236
237
238
239
240
    def get_open_todos(self):
        return [
            todo for todo in self.protocoltype.todos
            if not todo.is_done()
        ]

Robin Sonnabend's avatar
Robin Sonnabend committed
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
    def has_compiled_document(self):
        candidates = [
            document for document in self.documents
            if document.is_compiled
        ]
        return len(candidates) > 0

    def get_compiled_document(self, private=None):
        candidates = [
            document for document in self.documents
            if document.is_compiled
               and (private is None or document.is_private == private) 
        ]
        private_candidates = [document for document in candidates if document.is_private]
        public_candidates = [document for document in candidates if not document.is_private]
        if len(private_candidates) > 0:
            return private_candidates[0]
        elif len(public_candidates) > 0:
            return public_candidates[0]
        return None

Robin Sonnabend's avatar
Robin Sonnabend committed
262
263
264
    def get_template(self):
        return render_template("protocol-template.txt", protocol=self)

Robin Sonnabend's avatar
Robin Sonnabend committed
265
266
267
    def delete_orphan_todos(self):
        orphan_todos = [
            todo for todo in self.todos
Robin Sonnabend's avatar
Robin Sonnabend committed
268
            if len(todo.protocols) <= 1
Robin Sonnabend's avatar
Robin Sonnabend committed
269
270
271
272
273
274
275
276
277
278
        ]
        for todo in orphan_todos:
            self.todos.remove(todo)
            db.session.delete(todo)

@event.listens_for(Protocol, "before_delete")
def on_protocol_delete(mapper, connection, protocol):
    protocol.delete_orphan_todos()


279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
class DefaultTOP(db.Model):
    __tablename__ = "defaulttops"
    id = db.Column(db.Integer, primary_key=True)
    protocoltype_id = db.Column(db.Integer, db.ForeignKey("protocoltypes.id"))
    name = db.Column(db.String)
    number = db.Column(db.Integer)

    def __init__(self, protocoltype_id, name, number):
        self.protocoltype_id = protocoltype_id
        self.name = name
        self.number = number

    def __repr__(self):
        return "<DefaultTOP(id={}, protocoltype_id={}, name={}, number={})>".format(
            self.id, self.protocoltype_id, self.name, self.number)

    def is_at_end(self):
Robin Sonnabend's avatar
Robin Sonnabend committed
296
        return self.number > 0
297
298
299
300
301
302

class TOP(db.Model):
    __tablename__ = "tops"
    id = db.Column(db.Integer, primary_key=True)
    protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id"))
    name = db.Column(db.String)
Robin Sonnabend's avatar
Robin Sonnabend committed
303
    number = db.Column(db.Integer)
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
    planned = db.Column(db.Boolean)

    def __init__(self, protocol_id, name, number, planned):
        self.protocol_id = protocol_id
        self.name = name
        self.number = number
        self.planned = planned

    def __repr__(self):
        return "<TOP(id={}, protocol_id={}, name={}, number={}, planned={})>".format(
            self.id, self.protocol_id, self.name, self.number, self.planned)

class Document(db.Model):
    __tablename__ = "documents"
    id = db.Column(db.Integer, primary_key=True)
    protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id"))
    name = db.Column(db.String)
Robin Sonnabend's avatar
Robin Sonnabend committed
321
    filename = db.Column(db.String)
322
    is_compiled = db.Column(db.Boolean)
323
    is_private = db.Column(db.Boolean)
324

325
    def __init__(self, protocol_id, name, filename, is_compiled, is_private):
326
327
328
329
        self.protocol_id = protocol_id
        self.name = name
        self.filename = filename
        self.is_compiled = is_compiled
330
        self.is_private = is_private
331
332

    def __repr__(self):
333
334
335
336
337
338
        return "<Document(id={}, protocol_id={}, name={}, filename={}, is_compiled={}, is_private={})>".format(
            self.id, self.protocol_id, self.name, self.filename, self.is_compiled, self.is_private)

    def get_filename(self):
        return os.path.join(config.DOCUMENTS_PATH, self.filename)

339
340
341
342
    def as_file_like(self):
        with open(self.get_filename(), "rb") as file:
            return BytesIO(file.read())

343
@event.listens_for(Document, "before_delete")
Robin Sonnabend's avatar
Robin Sonnabend committed
344
def on_document_delete(mapper, connection, document):
345
    if document.filename is not None:
Robin Sonnabend's avatar
Robin Sonnabend committed
346
        document_path = document.get_filename()
347
348
        if os.path.isfile(document_path):
            os.remove(document_path)
349

Robin Sonnabend's avatar
Robin Sonnabend committed
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
class DecisionDocument(db.Model):
    __tablename__ = "decisiondocuments"
    id = db.Column(db.Integer, primary_key=True)
    decision_id = db.Column(db.Integer, db.ForeignKey("decisions.id"))
    name = db.Column(db.String)
    filename = db.Column(db.String)

    def __init__(self, decision_id, name, filename):
        self.decision_id = decision_id
        self.name = name
        self.filename = filename

    def __repr__(self):
        return "<DecisionDocument(id={}, decision_id={}, name={}, filename={})>".format(
            self.id, self.decision_id, self.name, self.filename)

    def get_filename(self):
        return os.path.join(config.DOCUMENTS_PATH, self.filename)

    def as_file_like(self):
        with open(self.get_filename(), "rb") as file:
            return BytesIO(file.read())

@event.listens_for(DecisionDocument, "before_delete")
def on_decisions_document_delete(mapper, connection, document):
    if document.filename is not None:
        document_path = document.get_filename()
        if os.path.isfile(document_path):
            os.remove(document_path)

380
381
382
383
384
385
386
387
388
389
class TodoState(Enum):
    open = 0
    waiting = 1
    in_progress = 2
    after = 3
    before = 4
    orphan = 5
    done = 6
    rejected = 7
    obsolete = 8
Robin Sonnabend's avatar
Robin Sonnabend committed
390

391
392
393
    def get_name(self):
        STATE_TO_NAME, NAME_TO_STATE = make_states(TodoState)
        return STATE_TO_NAME[self]
Robin Sonnabend's avatar
Robin Sonnabend committed
394
395
396
397
398
399
400
401
402
403
    
    @staticmethod
    def get_name_to_state():
        STATE_TO_NAME, NAME_TO_STATE = make_states(TodoState)
        return NAME_TO_STATE

    @staticmethod
    def get_state_to_name():
        STATE_TO_NAME, NAME_TO_STATE = make_states(TodoState)
        return STATE_TO_NAME
404
405
406
407
408
409
410
411
412
413
414
415
416
417

    def needs_date(self):
        return self in [TodoState.after, TodoState.before]

    def is_done(self):
        return self in [TodoState.done, TodoState.rejected, TodoState.obsolete]

    @staticmethod
    def from_name(name):
        name = name.strip().lower()
        STATE_TO_NAME, NAME_TO_STATE = make_states(TodoState)
        if name not in NAME_TO_STATE:
            raise ValueError("Unknown state: '{}'".format(name))
        return NAME_TO_STATE[name]
Robin Sonnabend's avatar
Robin Sonnabend committed
418

419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
    @staticmethod
    def from_name_lazy(name):
        name = name.strip().lower()
        STATE_TO_NAME, NAME_TO_STATE = make_states(TodoState)
        for key in NAME_TO_STATE:
            if name.startswith(key):
                return NAME_TO_STATE[key]
        raise ValueError("{} does not start with a state.".format(name))

    @staticmethod
    def from_name_with_date(name, protocol=None):
        name = name.strip().lower()
        if not " " in name:
            raise ValueError("{} does definitely not contain a state and a date".format(name))
        name_part, date_part = name.split(" ", 1)
        state = TodoState.from_name(name_part)
        date = None
        last_exc = None
        formats = [("%d.%m.%Y", False)]
        if config.PARSER_LAZY:
            formats.extend([("%d.%m.", True), ("%d.%m", True)])
        for format, year_missing in formats:
            try:
                date = datetime.strptime(date_part.strip(), format).date()
                if year_missing:
                    year = datetime.now().year
                    if protocol is not None:
                        year = protocol.date.year
                    date = datetime(year=year, month=date.month, day=date.day).date()
                break
            except ValueError as exc:
                last_exc = exc
                continue
        if date is None:
            raise last_exc
        return state, date


457
458
459
class Todo(db.Model):
    __tablename__ = "todos"
    id = db.Column(db.Integer, primary_key=True)
Robin Sonnabend's avatar
Robin Sonnabend committed
460
    protocoltype_id = db.Column(db.Integer, db.ForeignKey("protocoltypes.id"))
Robin Sonnabend's avatar
Robin Sonnabend committed
461
    number = db.Column(db.Integer)
462
463
    who = db.Column(db.String)
    description = db.Column(db.String)
464
465
    state = db.Column(db.Enum(TodoState), nullable=False)
    date = db.Column(db.Date, nullable=True)
466
467
468

    protocols = relationship("Protocol", secondary="todoprotocolassociations", backref="todos")

469
    def __init__(self, type_id, who, description, state, date, number=None):
Robin Sonnabend's avatar
Robin Sonnabend committed
470
        self.protocoltype_id = type_id
471
        self.number = number
472
473
        self.who = who
        self.description = description
474
475
        self.state = state
        self.date = date
476
477

    def __repr__(self):
478
479
480
481
        return "<Todo(id={}, number={}, who={}, description={}, state={}, date={})>".format(
            self.id, self.number, self.who, self.description, self.state, self.date)

    def is_done(self):
482
483
484
        if self.state.needs_date():
            if self.state == TodoState.after:
                return datetime.now().date() <= self.date
485
486
            elif self.state == TodoState.before:
                return datetime.now().date() >= self.date
487
        return self.state.is_done()
Robin Sonnabend's avatar
Robin Sonnabend committed
488
489
490

    def get_id(self):
        return self.number if self.number is not None else self.id
491

Robin Sonnabend's avatar
Robin Sonnabend committed
492
493
494
495
496
497
    def get_first_protocol(self):
        candidates = sorted(self.protocols, key=lambda p: p.date)
        if len(candidates) == 0:
            return None
        return candidates[0]

Robin Sonnabend's avatar
Robin Sonnabend committed
498
499
500
501
502
503
    def get_users(self):
        return [
            user.lower().strip()
            for user in split_terms(self.who, separators=" ,\t")
        ]

Robin Sonnabend's avatar
Robin Sonnabend committed
504
    def get_state(self):
505
        return "[{}]".format(self.get_state_plain())
Robin Sonnabend's avatar
Robin Sonnabend committed
506
    def get_state_plain(self):
507
508
        result = self.state.get_name()
        if self.state.needs_date():
509
            result = "{} {}".format(result, date_filter_short(self.date))
510
        return result
Robin Sonnabend's avatar
Robin Sonnabend committed
511
512
513
514
515
516
517
    def get_state_tex(self):
        return self.get_state_plain()

    def is_new(self, current_protocol=None):
        if current_protocol is not None:
            return self.get_first_protocol() == current_protocol
        return len(self.protocols) == 1
Robin Sonnabend's avatar
Robin Sonnabend committed
518
519
520
521
522
523
524
525
526

    def render_html(self):
        parts = [
            self.get_state(),
            "<strong>{}:</strong>".format(self.who),
            self.description
        ]
        return " ".join(parts)

Robin Sonnabend's avatar
Robin Sonnabend committed
527
528
    def render_latex(self, current_protocol=None):
        return r"\textbf{{{}}}: {}: {} -- {}".format(
Robin Sonnabend's avatar
Robin Sonnabend committed
529
            "Neuer Todo" if self.is_new(current_protocol) else "Todo",
530
531
532
            escape_tex(self.who),
            escape_tex(self.description),
            escape_tex(self.get_state_tex())
Robin Sonnabend's avatar
Robin Sonnabend committed
533
534
        )

Robin Sonnabend's avatar
Robin Sonnabend committed
535
536
537
538
539
540
541
542
    def render_wikitext(self, current_protocol=None):
        return "'''{}:''' {}: {} - {}".format(
            "Neuer Todo" if self.is_new(current_protocol) else "Todo",
            self.who,
            self.description,
            self.get_state_plain()
        )

543
544
545
546
547
548
549
    def render_template(self):
        parts = ["todo", self.who, self.description, self.state.get_name()]
        if self.state.needs_date():
            parts.append(date_filter(self.state))
        parts.append("id {}".format(self.get_id()))
        return "[{}]".format(";".join(parts))

Robin Sonnabend's avatar
Robin Sonnabend committed
550

551
552
553
554
555
556
557
558
559
560
561
class TodoProtocolAssociation(db.Model):
    __tablename__ = "todoprotocolassociations"
    todo_id = db.Column(db.Integer, db.ForeignKey("todos.id"), primary_key=True)
    protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id"), primary_key=True)

class Decision(db.Model):
    __tablename__ = "decisions"
    id = db.Column(db.Integer, primary_key=True)
    protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id"))
    content = db.Column(db.String)

Robin Sonnabend's avatar
Robin Sonnabend committed
562
563
    document = relationship("DecisionDocument", backref=backref("decision"), cascade="all, delete-orphan", uselist=False)

564
565
566
567
568
569
570
571
572
573
574
575
    def __init__(self, protocol_id, content):
        self.protocol_id = protocol_id
        self.content = content

    def __repr__(self):
        return "<Decision(id={}, protocol_id={}, content='{}')>".format(
            self.id, self.protocol_id, self.content)

class MeetingReminder(db.Model):
    __tablename__ = "meetingreminders"
    id = db.Column(db.Integer, primary_key=True)
    protocoltype_id = db.Column(db.Integer, db.ForeignKey("protocoltypes.id"))
576
    days_before = db.Column(db.Integer)
577
578
    send_public = db.Column(db.Boolean)
    send_private = db.Column(db.Boolean)
Robin Sonnabend's avatar
Robin Sonnabend committed
579
    additional_text = db.Column(db.String)
580

Robin Sonnabend's avatar
Robin Sonnabend committed
581
    def __init__(self, protocoltype_id, days_before, send_public, send_private, additional_text):
582
        self.protocoltype_id = protocoltype_id
583
        self.days_before = days_before
584
585
        self.send_public = send_public
        self.send_private = send_private
Robin Sonnabend's avatar
Robin Sonnabend committed
586
        self.additional_text = additional_text
587
588

    def __repr__(self):
589
590
        return "<MeetingReminder(id={}, protocoltype_id={}, days_before={}, send_public={}, send_private={})>".format(
            self.id, self.protocoltype_id, self.days_before, self.send_public, self.send_private)
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610

class Error(db.Model):
    __tablename__ = "errors"
    id = db.Column(db.Integer, primary_key=True)
    protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id"))
    action = db.Column(db.String)
    name = db.Column(db.String)
    datetime = db.Column(db.DateTime)
    description = db.Column(db.String)

    def __init__(self, protocol_id, action, name, datetime, description):
        self.protocol_id = protocol_id
        self.action = action
        self.name = name
        self.datetime = datetime
        self.description = description

    def __repr__(self):
        return "<Error(id={}, protocol_id={}, action={}, name={}, datetime={})>".format(
            self.id, self.protocol_id, self.action, self.name, self.datetime)
Robin Sonnabend's avatar
Robin Sonnabend committed
611
612
613
614
615

    def get_short_description(self):
        lines = self.description.splitlines()
        if len(lines) <= 4:
            return "\n".join(lines)
Robin Sonnabend's avatar
Robin Sonnabend committed
616
        return "\n".join([*lines[:2], "…", *lines[-2:]])
Robin Sonnabend's avatar
Robin Sonnabend committed
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633

class TodoMail(db.Model):
    __tablename__ = "todomails"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, unique=True)
    mail = db.Column(db.String)

    def __init__(self, name, mail):
        self.name = name
        self.mail = mail
    
    def __repr__(self):
        return "<TodoMail(name='{}', mail='{}')>".format(
            self.name, self.mail)

    def get_formatted_mail(self):
        return "{} <{}>".format(self.name, self.mail)
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652

class OldTodo(db.Model):
    __tablename__ = "oldtodos"
    id = db.Column(db.Integer, primary_key=True)
    old_id = db.Column(db.Integer)
    who = db.Column(db.String)
    description = db.Column(db.String)
    protocol_key = db.Column(db.String)

    def __init__(self, old_id, who, description, protocol_key):
        self.old_id = old_id
        self.who = who
        self.description = description
        self.protocol_key = protocol_key

    def __repr__(self):
        return ("<OldTodo(id={}, old_id={}, who='{}', description='{}', "
            "protocol={}".format(self.id, self.old_id, self.who,
            self.description, self.protocol_key))