class DbNote(db.Model, NoteBase): _logger = logging_util.get_logger_by_name(__name__, 'DbNote') __tablename__ = 'note' id = db.Column(db.Integer, primary_key=True) content = db.Column(db.String(4000)) timestamp = db.Column(db.DateTime) task_id = db.Column(db.Integer, db.ForeignKey('task.id')) task = db.relationship('DbTask', backref=db.backref('notes', lazy='dynamic', order_by=timestamp)) def __init__(self, content, timestamp=None, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') db.Model.__init__(self) NoteBase.__init__(self, content, timestamp) @classmethod def from_dict(cls, d, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') return super(DbNote, cls).from_dict(d=d, lazy=None) def clear_relationships(self): self.task = None
class DbUser(db.Model, UserBase): _logger = logging_util.get_logger_by_name(__name__, 'DbUser') __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(100), nullable=False, unique=True) hashed_password = db.Column(db.String(100)) is_admin = db.Column(db.Boolean, nullable=False, default=False) tasks = db.relationship('DbTask', secondary=users_tasks_table, back_populates='users') def __init__(self, email, hashed_password=None, is_admin=False, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') db.Model.__init__(self) UserBase.__init__(self, email=email, hashed_password=hashed_password, is_admin=is_admin) @classmethod def from_dict(cls, d, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') return super(DbUser, cls).from_dict(d=d, lazy=None) def clear_relationships(self): self.tasks = []
class Option(Changeable, OptionBase): _logger = logging_util.get_logger_by_name(__name__, 'Option') _key = None _value = None @property def key(self): return self._key @key.setter def key(self, value): if value != self._key: self._on_attr_changing(self.FIELD_KEY, self._key) self._key = value self._on_attr_changed(self.FIELD_KEY, self.OP_SET, self._key) @property def value(self): return self._value @value.setter def value(self, value): if value != self._value: self._on_attr_changing(self.FIELD_VALUE, self._value) self._value = value self._on_attr_changed(self.FIELD_VALUE, self.OP_SET, self._value) def clear_relationships(self): pass
class DbTag(db.Model, TagBase): _logger = logging_util.get_logger_by_name(__name__, 'DbTag') __tablename__ = 'tag' id = db.Column(db.Integer, primary_key=True) value = db.Column(db.String(100), nullable=False, unique=True) description = db.Column(db.String(4000), nullable=True) tasks = db.relationship('DbTask', secondary=tags_tasks_table, back_populates='tags') def __init__(self, value, description=None, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') db.Model.__init__(self) TagBase.__init__(self, value, description) @classmethod def from_dict(cls, d, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') return super(DbTag, cls).from_dict(d=d, lazy=None) def clear_relationships(self): self.tasks = []
class DbOption(db.Model, OptionBase): _logger = logging_util.get_logger_by_name(__name__, 'DbOption') __tablename__ = 'option' key = db.Column(db.String(100), primary_key=True) value = db.Column(db.String(100), nullable=True) def __init__(self, key, value, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') db.Model.__init__(self) OptionBase.__init__(self, key, value) @classmethod def from_dict(cls, d, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') return super(DbOption, cls).from_dict(d=d, lazy=None) def clear_relationships(self): pass
class DbAttachment(db.Model, AttachmentBase): _logger = logging_util.get_logger_by_name(__name__, 'DbAttachment') __tablename__ = 'attachment' id = db.Column(db.Integer, primary_key=True) path = db.Column(db.String(1000), nullable=False) timestamp = db.Column(db.DateTime) filename = db.Column(db.String(100)) description = db.Column(db.String(100), default=None) task_id = db.Column(db.Integer, db.ForeignKey('task.id')) task = db.relationship('DbTask', backref=db.backref('attachments', lazy='dynamic', order_by=timestamp)) def __init__(self, path, description=None, timestamp=None, filename=None, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') db.Model.__init__(self) AttachmentBase.__init__(self, path, description, timestamp, filename) @classmethod def from_dict(cls, d, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') return super(DbAttachment, cls).from_dict(d=d, lazy=None) def clear_relationships(self): self.task = None
class OneToManySet(InterlinkedSet): _logger = logging_util.get_logger_by_name(__name__, 'OneToManySet') def add(self, item): self._logger.debug('add') self._logger.debug('%s: %s', self.c, item) if item not in self: self._logger.debug('adding the item') self.container._on_attr_changing(self.__change_field__, None) self._add(item) setattr(item, self.__attr_counterpart__, self.container) self.container._on_attr_changed(self.__change_field__, Changeable.OP_ADD, item) def discard(self, item): self._logger.debug('discard') self._logger.debug('%s: %s', self.c, item) if item in self: self._logger.debug('discarding the item') self.container._on_attr_changing(self.__change_field__, None) self._discard(item) setattr(item, self.__attr_counterpart__, None) self.container._on_attr_changed(self.__change_field__, Changeable.OP_REMOVE, item)
class DbTask(db.Model, TaskBase): _logger = logging_util.get_logger_by_name(__name__, 'DbTask') __tablename__ = 'task' id = db.Column(db.Integer, primary_key=True) summary = db.Column(db.String(100)) description = db.Column(db.String(4000)) is_done = db.Column(db.Boolean) is_deleted = db.Column(db.Boolean) order_num = db.Column(db.Integer, nullable=False, default=0) deadline = db.Column(db.DateTime) expected_duration_minutes = db.Column(db.Integer) expected_cost = db.Column(db.Numeric) is_public = db.Column(db.Boolean) tags = db.relationship('DbTag', secondary=tags_tasks_table, back_populates="tasks") users = db.relationship('DbUser', secondary=users_tasks_table, back_populates="tasks") parent_id = db.Column(db.Integer, db.ForeignKey('task.id'), nullable=True) parent = db.relationship('DbTask', remote_side=[id], backref=db.backref('children', lazy='dynamic')) # self depends on self.dependees # self.dependants depend on self dependees = db.relationship( 'DbTask', secondary=task_dependencies_table, primaryjoin=task_dependencies_table.c.dependant_id == id, secondaryjoin=task_dependencies_table.c.dependee_id == id, backref='dependants') # self is after self.prioritize_before's # self has lower priority than self.prioritize_before's # self is before self.prioritize_after's # self has higher priority than self.prioritize_after's prioritize_before = db.relationship( 'DbTask', secondary=task_prioritize_table, primaryjoin=task_prioritize_table.c.prioritize_after_id == id, secondaryjoin=task_prioritize_table.c.prioritize_before_id == id, backref='prioritize_after') def __init__(self, summary, description='', is_done=False, is_deleted=False, deadline=None, expected_duration_minutes=None, expected_cost=None, is_public=False, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') db.Model.__init__(self) TaskBase.__init__( self, summary=summary, description=description, is_done=is_done, is_deleted=is_deleted, deadline=deadline, expected_duration_minutes=expected_duration_minutes, expected_cost=expected_cost, is_public=is_public) @classmethod def from_dict(cls, d, lazy=None): if lazy: raise ValueError('parameter \'lazy\' must be None or empty') return super(DbTask, cls).from_dict(d=d, lazy=None) def clear_relationships(self): self._logger.debug('%s', self) self.parent = None self.children = [] self.tags = [] self.users = [] self.notes = [] self.attachments = [] self.dependees = [] self.dependants = [] self.prioritize_before = [] self.prioritize_after = []
class SqlAlchemyPersistenceLayer(object): _logger = logging_util.get_logger_by_name(__name__, 'SqlAlchemyPersistenceLayer') def __init__(self, db): self.db = db tags_tasks_table = db.Table( 'tags_tasks', db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True), db.Column('task_id', db.Integer, db.ForeignKey('task.id'), primary_key=True)) self.tags_tasks_table = tags_tasks_table self.DbTag = generate_tag_class(db, tags_tasks_table) users_tasks_table = db.Table( 'users_tasks', db.Column('user_id', db.Integer, db.ForeignKey('user.id'), index=True), db.Column('task_id', db.Integer, db.ForeignKey('task.id'), index=True)) self.users_tasks_table = users_tasks_table task_dependencies_table = db.Table( 'task_dependencies', db.Column('dependee_id', db.Integer, db.ForeignKey('task.id'), primary_key=True), db.Column('dependant_id', db.Integer, db.ForeignKey('task.id'), primary_key=True)) self.task_dependencies_table = task_dependencies_table task_prioritize_table = db.Table( 'task_prioritize', db.Column('prioritize_before_id', db.Integer, db.ForeignKey('task.id'), primary_key=True), db.Column('prioritize_after_id', db.Integer, db.ForeignKey('task.id'), primary_key=True)) self.task_prioritize_table = task_prioritize_table self.DbTask = generate_task_class(self, tags_tasks_table, users_tasks_table, task_dependencies_table, task_prioritize_table) self.DbNote = generate_note_class(db) self.DbAttachment = generate_attachment_class(db) self.DbUser = generate_user_class(db, users_tasks_table) self.DbOption = generate_option_class(db) self._db_by_domain = {} self._domain_by_db = {} def add(self, dbobj): self._logger.debug('begin, dbobj: %s', dbobj) if not self._is_db_object(dbobj): raise Exception( 'The object is not compatible with the PL: {}'.format(dbobj)) self.db.session.add(dbobj) self._logger.debug('end') def delete(self, dbobj): self._logger.debug('begin, dbobj: %s', dbobj) if not self._is_db_object(dbobj): raise Exception( 'The object is not compatible with the PL: {}'.format(dbobj)) dbobj.clear_relationships() self.db.session.delete(dbobj) self._logger.debug('end') def commit(self): self._logger.debug('begin') ############### self._logger.debug('committing the db session/transaction') self.db.session.commit() self._logger.debug('committed the db session/transaction') ############### self._logger.debug('end') def rollback(self): self._logger.debug('begin') self.db.session.rollback() self._logger.debug('end') def _is_db_object(self, obj): return isinstance(obj, self.db.Model) def create_all(self): self.db.create_all() UNSPECIFIED = object() ASCENDING = object() DESCENDING = object() TASK_ID = object() ORDER_NUM = object() DEADLINE = object() def get_db_field_by_order_field(self, f): if f is self.ORDER_NUM: return self.DbTask.order_num if f is self.TASK_ID: return self.DbTask.id if f is self.DEADLINE: return self.DbTask.deadline raise Exception('Unhandled order_by field: {}'.format(f)) @property def task_query(self): return self.DbTask.query def create_task(self, summary, description='', is_done=False, is_deleted=False, deadline=None, expected_duration_minutes=None, expected_cost=None, is_public=False, lazy=None): return self.DbTask(summary=summary, description=description, is_done=is_done, is_deleted=is_deleted, deadline=deadline, expected_duration_minutes=expected_duration_minutes, expected_cost=expected_cost, is_public=is_public, lazy=lazy) def get_task(self, task_id): return self._get_db_task(task_id) def _get_db_task(self, task_id): if task_id is None: return None return self.task_query.get(task_id) def _get_tasks_query(self, is_done=UNSPECIFIED, is_deleted=UNSPECIFIED, parent_id=UNSPECIFIED, parent_id_in=UNSPECIFIED, users_contains=UNSPECIFIED, task_id_in=UNSPECIFIED, task_id_not_in=UNSPECIFIED, deadline_is_not_none=False, tags_contains=UNSPECIFIED, is_public=UNSPECIFIED, is_public_or_users_contains=UNSPECIFIED, summary_description_search_term=UNSPECIFIED, order_num_greq_than=UNSPECIFIED, order_num_lesseq_than=UNSPECIFIED, order_by=UNSPECIFIED, limit=UNSPECIFIED): """order_by is a list of order directives. Each such directive is either a field (e.g. ORDER_NUM) or a sequence of field and direction (e.g. [ORDER_NUM, ASCENDING]). Default direction is ASCENDING if not specified.""" query = self.task_query if is_done is not self.UNSPECIFIED: query = query.filter_by(is_done=is_done) if is_deleted is not self.UNSPECIFIED: query = query.filter_by(is_deleted=is_deleted) if is_public is not self.UNSPECIFIED: query = query.filter_by(is_public=is_public) if parent_id is not self.UNSPECIFIED: if parent_id is None: query = query.filter(self.DbTask.parent_id.is_(None)) else: query = query.filter_by(parent_id=parent_id) if parent_id_in is not self.UNSPECIFIED: if parent_id_in: query = query.filter(self.DbTask.parent_id.in_(parent_id_in)) else: # avoid performance penalty query = query.filter(False) if users_contains is not self.UNSPECIFIED: query = query.filter(self.DbTask.users.contains(users_contains)) if is_public_or_users_contains is not self.UNSPECIFIED: db_user = is_public_or_users_contains query_m1 = query.outerjoin( self.users_tasks_table, self.users_tasks_table.c.task_id == self.DbTask.id) query_m2 = query_m1.filter( or_(self.users_tasks_table.c.user_id == db_user.id, self.DbTask.is_public)) query = query_m2 if task_id_in is not self.UNSPECIFIED: # Using in_ on an empty set works but is expensive for some db # engines. In the case of an empty collection, just use a query # that always returns an empty set, without the performance # penalty. if task_id_in: query = query.filter(self.DbTask.id.in_(task_id_in)) else: query = query.filter(False) if task_id_not_in is not self.UNSPECIFIED: # Using notin_ on an empty set works but is expensive for some db # engines. Moreover, it doesn't affect the actual set of selected # rows. In the case of an empty collection, just use the same query # object again, so we won't incur the performance penalty. if task_id_not_in: query = query.filter(self.DbTask.id.notin_(task_id_not_in)) else: query = query if deadline_is_not_none: query = query.filter(self.DbTask.deadline.isnot(None)) if tags_contains is not self.UNSPECIFIED: query = query.filter(self.DbTask.tags.contains(tags_contains)) if summary_description_search_term is not self.UNSPECIFIED: like_term = '%{}%'.format(summary_description_search_term) query = query.filter( self.DbTask.summary.ilike(like_term) | self.DbTask.description.ilike(like_term)) if order_num_greq_than is not self.UNSPECIFIED: query = query.filter(self.DbTask.order_num >= order_num_greq_than) if order_num_lesseq_than is not self.UNSPECIFIED: query = query.filter( self.DbTask.order_num <= order_num_lesseq_than) if order_by is not self.UNSPECIFIED: if not is_iterable(order_by): db_field = self.get_db_field_by_order_field(order_by) query = query.order_by(db_field) else: for ordering in order_by: direction = self.ASCENDING if is_iterable(ordering): order_field = ordering[0] if len(ordering) > 1: direction = ordering[1] else: order_field = ordering db_field = self.get_db_field_by_order_field(order_field) if direction is self.ASCENDING: query = query.order_by(db_field.asc()) elif direction is self.DESCENDING: query = query.order_by(db_field.desc()) else: raise Exception( 'Unknown order_by direction: {}'.format(direction)) if limit is not self.UNSPECIFIED: query = query.limit(limit) return query def get_tasks(self, is_done=UNSPECIFIED, is_deleted=UNSPECIFIED, parent_id=UNSPECIFIED, parent_id_in=UNSPECIFIED, users_contains=UNSPECIFIED, task_id_in=UNSPECIFIED, task_id_not_in=UNSPECIFIED, deadline_is_not_none=False, tags_contains=UNSPECIFIED, is_public=UNSPECIFIED, is_public_or_users_contains=UNSPECIFIED, summary_description_search_term=UNSPECIFIED, order_num_greq_than=UNSPECIFIED, order_num_lesseq_than=UNSPECIFIED, order_by=UNSPECIFIED, limit=UNSPECIFIED): query = self._get_tasks_query( is_done=is_done, is_deleted=is_deleted, parent_id=parent_id, parent_id_in=parent_id_in, users_contains=users_contains, task_id_in=task_id_in, task_id_not_in=task_id_not_in, deadline_is_not_none=deadline_is_not_none, tags_contains=tags_contains, is_public=is_public, is_public_or_users_contains=is_public_or_users_contains, summary_description_search_term=summary_description_search_term, order_num_greq_than=order_num_greq_than, order_num_lesseq_than=order_num_lesseq_than, order_by=order_by, limit=limit) return (_ for _ in query) def get_paginated_tasks(self, is_done=UNSPECIFIED, is_deleted=UNSPECIFIED, parent_id=UNSPECIFIED, parent_id_in=UNSPECIFIED, users_contains=UNSPECIFIED, task_id_in=UNSPECIFIED, task_id_not_in=UNSPECIFIED, deadline_is_not_none=False, tags_contains=UNSPECIFIED, is_public=UNSPECIFIED, is_public_or_users_contains=UNSPECIFIED, summary_description_search_term=UNSPECIFIED, order_num_greq_than=UNSPECIFIED, order_num_lesseq_than=UNSPECIFIED, order_by=UNSPECIFIED, limit=UNSPECIFIED, page_num=None, tasks_per_page=None): if page_num is not None and not isinstance(page_num, Number): raise TypeError('page_num must be a number') if page_num is not None and page_num < 1: raise ValueError('page_num must be greater than zero') if tasks_per_page is not None and not isinstance( tasks_per_page, Number): raise TypeError('tasks_per_page must be a number') if tasks_per_page is not None and tasks_per_page < 1: raise ValueError('tasks_per_page must be greater than zero') if page_num is None: page_num = 1 if tasks_per_page is None: tasks_per_page = 20 query = self._get_tasks_query( is_done=is_done, is_deleted=is_deleted, parent_id=parent_id, parent_id_in=parent_id_in, users_contains=users_contains, task_id_in=task_id_in, task_id_not_in=task_id_not_in, deadline_is_not_none=deadline_is_not_none, tags_contains=tags_contains, is_public=is_public, is_public_or_users_contains=is_public_or_users_contains, summary_description_search_term=summary_description_search_term, order_num_greq_than=order_num_greq_than, order_num_lesseq_than=order_num_lesseq_than, order_by=order_by, limit=limit) pager = query.paginate(page=page_num, per_page=tasks_per_page) items = list(pager.items) return Pager(page=pager.page, per_page=pager.per_page, items=items, total=pager.total, num_pages=pager.pages, _pager=pager) def count_tasks(self, is_done=UNSPECIFIED, is_deleted=UNSPECIFIED, parent_id=UNSPECIFIED, parent_id_in=UNSPECIFIED, users_contains=UNSPECIFIED, task_id_in=UNSPECIFIED, task_id_not_in=UNSPECIFIED, deadline_is_not_none=False, tags_contains=UNSPECIFIED, is_public=UNSPECIFIED, is_public_or_users_contains=UNSPECIFIED, summary_description_search_term=UNSPECIFIED, order_num_greq_than=UNSPECIFIED, order_num_lesseq_than=UNSPECIFIED, order_by=UNSPECIFIED, limit=UNSPECIFIED): return self._get_tasks_query( is_done=is_done, is_deleted=is_deleted, parent_id=parent_id, parent_id_in=parent_id_in, users_contains=users_contains, task_id_in=task_id_in, task_id_not_in=task_id_not_in, deadline_is_not_none=deadline_is_not_none, tags_contains=tags_contains, is_public=is_public, is_public_or_users_contains=is_public_or_users_contains, summary_description_search_term=summary_description_search_term, order_num_greq_than=order_num_greq_than, order_num_lesseq_than=order_num_lesseq_than, order_by=order_by, limit=limit).count() @property def tag_query(self): return self.DbTag.query def create_tag(self, value, description=None, lazy=None): return self.DbTag(value=value, description=description, lazy=lazy) def _get_db_tag(self, tag_id): if tag_id is None: raise ValueError('tag_id cannot be None') return self.tag_query.get(tag_id) def get_tag(self, tag_id): if tag_id is None: raise ValueError('tag_id cannot be None') return self._get_db_tag(tag_id) def _get_tags_query(self, value=UNSPECIFIED, limit=None): query = self.DbTag.query if value is not self.UNSPECIFIED: query = query.filter_by(value=value) if limit is not None: query = query.limit(limit) return query def get_tags(self, value=UNSPECIFIED, limit=None): query = self._get_tags_query(value=value, limit=limit) return (_ for _ in query) def count_tags(self, value=UNSPECIFIED, limit=None): return self._get_tags_query(value=value, limit=limit).count() def get_tag_by_value(self, value): return self._get_tags_query(value=value).first() @property def note_query(self): return self.DbNote.query def create_note(self, content, timestamp=None, lazy=None): return self.DbNote(content=content, timestamp=timestamp, lazy=lazy) def _get_db_note(self, note_id): if note_id is None: raise ValueError('note_id acannot be None') return self.note_query.get(note_id) def get_note(self, note_id): if note_id is None: raise ValueError('note_id acannot be None') return self._get_db_note(note_id) def _get_notes_query(self, note_id_in=UNSPECIFIED): query = self.note_query if note_id_in is not self.UNSPECIFIED: if note_id_in: query = query.filter(self.DbNote.id.in_(note_id_in)) else: # performance improvement query = query.filter(False) return query def get_notes(self, note_id_in=UNSPECIFIED): query = self._get_notes_query(note_id_in=note_id_in) return (_ for _ in query) def count_notes(self, note_id_in=UNSPECIFIED): return self._get_notes_query(note_id_in=note_id_in).count() @property def attachment_query(self): return self.DbAttachment.query def create_attachment(self, path, description=None, timestamp=None, filename=None, lazy=None): return self.DbAttachment(path=path, description=description, timestamp=timestamp, filename=filename, lazy=lazy) def _get_db_attachment(self, attachment_id): if attachment_id is None: raise ValueError('attachment_id acannot be None') return self.attachment_query.get(attachment_id) def get_attachment(self, attachment_id): if attachment_id is None: raise ValueError('attachment_id acannot be None') return self._get_db_attachment(attachment_id) def _get_attachments_query(self, attachment_id_in=UNSPECIFIED): query = self.attachment_query if attachment_id_in is not self.UNSPECIFIED: if attachment_id_in: query = query.filter( self.DbAttachment.id.in_(attachment_id_in)) else: query = query.filter(False) return query def get_attachments(self, attachment_id_in=UNSPECIFIED): query = self._get_attachments_query(attachment_id_in=attachment_id_in) return (_ for _ in query) def count_attachments(self, attachment_id_in=UNSPECIFIED): return self._get_attachments_query( attachment_id_in=attachment_id_in).count() @property def user_query(self): return self.DbUser.query def create_user(self, email, hashed_password=None, is_admin=False, lazy=None): return self.DbUser(email=email, hashed_password=hashed_password, is_admin=is_admin, lazy=lazy) _guest_user = None def get_guest_user(self): if self._guest_user is None: self._guest_user = self.create_user('guest@guest') self._guest_user._is_authenticated = False self._guest_user._is_anonymous = True self._guest_user.is_admin = False return self._guest_user def _get_db_user(self, user_id): if user_id is None: raise ValueError('user_id acannot be None') return self.user_query.get(user_id) def get_user(self, user_id): if user_id is None: raise ValueError('user_id acannot be None') return self._get_db_user(user_id) def get_user_by_email(self, email): return self.user_query.filter_by(email=email).first() def _get_users_query(self, email_in=UNSPECIFIED): query = self.user_query if email_in is not self.UNSPECIFIED: if email_in: query = query.filter(self.DbUser.email.in_(email_in)) else: # avoid performance penalty query = query.filter(False) return query def get_users(self, email_in=UNSPECIFIED): query = self._get_users_query(email_in=email_in) return (_ for _ in query) def count_users(self, email_in=UNSPECIFIED): return self._get_users_query(email_in=email_in).count() @property def option_query(self): return self.DbOption.query def create_option(self, key, value): return self.DbOption(key=key, value=value) def _get_db_option(self, key): if key is None: raise ValueError('key acannot be None') return self.option_query.get(key) def get_option(self, key): if key is None: raise ValueError('key acannot be None') return self._get_db_option(key) def _get_options_query(self, key_in=UNSPECIFIED): query = self.option_query if key_in is not self.UNSPECIFIED: if key_in: query = query.filter(self.DbOption.key.in_(key_in)) else: # avoid performance penalty query = query.filter(False) return query def get_options(self, key_in=UNSPECIFIED): query = self._get_options_query(key_in=key_in) return (_ for _ in query) def count_options(self, key_in=UNSPECIFIED): return self._get_options_query(key_in=key_in).count()
class InterlinkedChildren(OneToManySet): __change_field__ = TaskBase.FIELD_CHILDREN __attr_counterpart__ = 'parent' _logger = logging_util.get_logger_by_name(__name__, 'InterlinkedChildren')
class InterlinkedTasks(ManyToManySet): __change_field__ = UserBase.FIELD_TASKS __attr_counterpart__ = 'users' _logger = logging_util.get_logger_by_name(__name__, 'InterlinkedTasks')
class GenericTask(TaskBase): id = None summary = None description = None is_done = None is_deleted = None deadline = None expected_duration_minutes = None expected_cost = None order_num = None parent = None children = None dependees = None dependants = None prioritize_before = None prioritize_after = None tags = None users = None notes = None attachments = None is_public = None _logger = logging_util.get_logger_by_name(__name__, 'Task') def __init__(self, summary=None, description=None, is_done=None, is_deleted=None, deadline=None, expected_duration_minutes=None, expected_cost=None, is_public=None, id=None, parent=None, children=None, dependees=None, dependants=None, prioritize_before=None, prioritize_after=None, tags=None, users=None, notes=None, attachments=None): super(GenericTask, self).__init__( summary=summary, description=description, is_done=is_done, is_deleted=is_deleted, deadline=deadline, expected_duration_minutes=expected_duration_minutes, expected_cost=expected_cost, is_public=is_public) self.id = id self.parent = parent self.children = [] self.dependees = [] self.dependants = [] self.prioritize_before = [] self.prioritize_after = [] self.tags = [] self.users = [] self.notes = [] self.attachments = [] if children: self.children.extend(children) if dependees: self.dependees.extend(dependees) if dependants: self.dependants.extend(dependants) if prioritize_before: self.prioritize_before.extend(prioritize_before) if prioritize_after: self.prioritize_after.extend(prioritize_after) if tags: self.tags.extend(tags) if users: self.users.extend(users) if notes: self.notes.extend(notes) if attachments: self.attachments.extend(attachments)
class InterlinkedAttachments(OneToManySet): __change_field__ = TaskBase.FIELD_ATTACHMENTS __attr_counterpart__ = 'task' _logger = logging_util.get_logger_by_name(__name__, 'InterlinkedAttachments')
class User(Changeable, UserBase): _logger = logging_util.get_logger_by_name(__name__, 'User') _id = None _email = None _hashed_password = None _is_admin = None def __init__(self, email, hashed_password=None, is_admin=False, lazy=None): super(User, self).__init__(email=email, hashed_password=hashed_password, is_admin=is_admin) if lazy is None: lazy = {} self._tasks = InterlinkedTasks(self, lazy=lazy.get('tasks')) @property def id(self): return self._id @id.setter def id(self, value): if value != self._id: self._on_attr_changing(self.FIELD_ID, self._id) self._id = value self._on_attr_changed(self.FIELD_ID, self.OP_SET, self._id) @property def email(self): return self._email @email.setter def email(self, value): if value != self._email: self._on_attr_changing(self.FIELD_EMAIL, self._email) self._email = value self._on_attr_changed(self.FIELD_EMAIL, self.OP_SET, self._email) @property def hashed_password(self): return self._hashed_password @hashed_password.setter def hashed_password(self, value): if value != self._hashed_password: self._on_attr_changing(self.FIELD_HASHED_PASSWORD, self._hashed_password) self._hashed_password = value self._on_attr_changed(self.FIELD_HASHED_PASSWORD, self.OP_SET, self._hashed_password) @property def is_admin(self): return self._is_admin @is_admin.setter def is_admin(self, value): if value != self._is_admin: self._on_attr_changing(self.FIELD_IS_ADMIN, self._is_admin) self._is_admin = value self._on_attr_changed(self.FIELD_IS_ADMIN, self.OP_SET, self._is_admin) @property def tasks(self): self._logger.debug('%s', self) return self._tasks def clear_relationships(self): self.tasks.clear()
class InMemoryPersistenceLayer(object): _logger = logging_util.get_logger_by_name(__name__, 'InMemoryPersistenceLayer') def __init__(self): self._added_objects = set() self._deleted_objects = set() self._changed_objects = set() self._values_by_object = {} self._tasks = [] # TODO: change these to sets self._tasks_by_id = {} self._tags = [] self._tags_by_id = {} self._tags_by_value = {} self._users = [] self._users_by_id = {} self._users_by_email = {} self._options = [] self._options_by_key = {} self._notes = [] self._notes_by_id = {} self._attachments = [] self._attachments_by_id = {} UNSPECIFIED = object() ASCENDING = object() DESCENDING = object() TASK_ID = object() ORDER_NUM = object() DEADLINE = object() def create_all(self): pass def create_task(self, summary, description='', is_done=False, is_deleted=False, deadline=None, expected_duration_minutes=None, expected_cost=None, is_public=False, lazy=None): return Task(summary=summary, description=description, is_done=is_done, is_deleted=is_deleted, deadline=deadline, expected_duration_minutes=expected_duration_minutes, expected_cost=expected_cost, is_public=is_public, lazy=lazy) def get_task(self, task_id): return self._tasks_by_id.get(task_id) def get_tasks(self, is_done=UNSPECIFIED, is_deleted=UNSPECIFIED, parent_id=UNSPECIFIED, parent_id_in=UNSPECIFIED, users_contains=UNSPECIFIED, task_id_in=UNSPECIFIED, task_id_not_in=UNSPECIFIED, deadline_is_not_none=False, tags_contains=UNSPECIFIED, is_public=UNSPECIFIED, is_public_or_users_contains=UNSPECIFIED, summary_description_search_term=UNSPECIFIED, order_num_greq_than=UNSPECIFIED, order_num_lesseq_than=UNSPECIFIED, order_by=UNSPECIFIED, limit=UNSPECIFIED): query = self._tasks if is_done is not self.UNSPECIFIED: query = (_ for _ in query if _.is_done == is_done) if is_deleted is not self.UNSPECIFIED: query = (_ for _ in query if _.is_deleted == is_deleted) if is_public is not self.UNSPECIFIED: query = (_ for _ in query if _.is_public == is_public) if parent_id is not self.UNSPECIFIED: if parent_id is None: query = (_ for _ in query if _.parent_id is None) else: query = (_ for _ in query if _.parent_id == parent_id) if parent_id_in is not self.UNSPECIFIED: query = (_ for _ in query if _.parent_id in parent_id_in) if users_contains is not self.UNSPECIFIED: query = (_ for _ in query if users_contains in _.users) if is_public_or_users_contains is not self.UNSPECIFIED: query = (_ for _ in query if _.is_public or is_public_or_users_contains in _.users) if task_id_in is not self.UNSPECIFIED: query = (_ for _ in query if _.id in task_id_in) if task_id_not_in is not self.UNSPECIFIED: query = (_ for _ in query if _.id not in task_id_not_in) if deadline_is_not_none: query = (_ for _ in query if _.deadline is not None) if tags_contains is not self.UNSPECIFIED: query = (_ for _ in query if tags_contains in _.tags) if summary_description_search_term is not self.UNSPECIFIED: term = summary_description_search_term term_lower = term.casefold() query = (_ for _ in query if term_lower in _.summary.casefold() or term_lower in _.description.casefold()) if order_num_greq_than is not self.UNSPECIFIED: query = (_ for _ in query if _.order_num >= order_num_greq_than) if order_num_lesseq_than is not self.UNSPECIFIED: query = (_ for _ in query if _.order_num <= order_num_lesseq_than) if order_by is not self.UNSPECIFIED: if not is_iterable(order_by): sort_key = self._get_sort_key_by_order_field(order_by) query = sorted(query, key=sort_key) else: for ordering in order_by: direction = self.ASCENDING if is_iterable(ordering): order_field = ordering[0] if len(ordering) > 1: direction = ordering[1] else: order_field = ordering sort_key = self._get_sort_key_by_order_field(order_field) if direction is self.ASCENDING: query = sorted(query, key=sort_key) elif direction is self.DESCENDING: query = sorted(query, key=sort_key, reverse=True) else: raise Exception( 'Unknown order_by direction: {}'.format(direction)) if limit is not self.UNSPECIFIED and limit >= 0: query = islice(query, limit) return query def _get_sort_key_by_order_field(self, order_by): if order_by is self.ORDER_NUM: return lambda task: task.order_num if order_by is self.TASK_ID: return lambda task: task.id if order_by is self.DEADLINE: return lambda task: task.deadline raise Exception('Unhandled order_by field: {}'.format(order_by)) def get_paginated_tasks(self, is_done=UNSPECIFIED, is_deleted=UNSPECIFIED, parent_id=UNSPECIFIED, parent_id_in=UNSPECIFIED, users_contains=UNSPECIFIED, task_id_in=UNSPECIFIED, task_id_not_in=UNSPECIFIED, deadline_is_not_none=False, tags_contains=UNSPECIFIED, is_public=UNSPECIFIED, is_public_or_users_contains=UNSPECIFIED, summary_description_search_term=UNSPECIFIED, order_num_greq_than=UNSPECIFIED, order_num_lesseq_than=UNSPECIFIED, order_by=UNSPECIFIED, limit=UNSPECIFIED, page_num=None, tasks_per_page=None): if page_num is not None and not isinstance(page_num, Number): raise TypeError('page_num must be a number') if page_num is not None and page_num < 1: raise ValueError('page_num must be greater than zero') if tasks_per_page is not None and not isinstance( tasks_per_page, Number): raise TypeError('tasks_per_page must be a number') if tasks_per_page is not None and tasks_per_page < 1: raise ValueError('tasks_per_page must be greater than zero') if page_num is None: page_num = 1 if tasks_per_page is None: tasks_per_page = 20 query = self.get_tasks( is_done=is_done, is_deleted=is_deleted, parent_id=parent_id, parent_id_in=parent_id_in, users_contains=users_contains, task_id_in=task_id_in, task_id_not_in=task_id_not_in, deadline_is_not_none=deadline_is_not_none, tags_contains=tags_contains, is_public=is_public, is_public_or_users_contains=is_public_or_users_contains, summary_description_search_term=summary_description_search_term, order_num_greq_than=order_num_greq_than, order_num_lesseq_than=order_num_lesseq_than, order_by=order_by, limit=limit) tasks = list(query) start_task = (page_num - 1) * tasks_per_page items = list(islice(tasks, start_task, start_task + tasks_per_page)) total_tasks = len(tasks) num_pages = total_tasks // tasks_per_page if total_tasks % tasks_per_page > 0: num_pages += 1 return Pager(page=page_num, per_page=tasks_per_page, items=items, total=total_tasks, num_pages=num_pages, _pager=None) def count_tasks(self, is_done=UNSPECIFIED, is_deleted=UNSPECIFIED, parent_id=UNSPECIFIED, parent_id_in=UNSPECIFIED, users_contains=UNSPECIFIED, task_id_in=UNSPECIFIED, task_id_not_in=UNSPECIFIED, deadline_is_not_none=False, tags_contains=UNSPECIFIED, is_public=UNSPECIFIED, is_public_or_users_contains=UNSPECIFIED, summary_description_search_term=UNSPECIFIED, order_num_greq_than=UNSPECIFIED, order_num_lesseq_than=UNSPECIFIED, order_by=UNSPECIFIED, limit=UNSPECIFIED): return len( list( self.get_tasks( is_done=is_done, is_deleted=is_deleted, parent_id=parent_id, parent_id_in=parent_id_in, users_contains=users_contains, task_id_in=task_id_in, task_id_not_in=task_id_not_in, deadline_is_not_none=deadline_is_not_none, tags_contains=tags_contains, is_public=is_public, is_public_or_users_contains=is_public_or_users_contains, summary_description_search_term= summary_description_search_term, order_num_greq_than=order_num_greq_than, order_num_lesseq_than=order_num_lesseq_than, order_by=order_by, limit=limit))) def create_tag(self, value, description=None, lazy=None): return Tag(value=value, description=description, lazy=lazy) def get_tag(self, tag_id): if tag_id is None: raise ValueError('No tag_id provided.') return self._tags_by_id.get(tag_id) def get_tag_by_value(self, value): return self._tags_by_value.get(value) def get_tags(self, value=UNSPECIFIED, limit=None): query = self._tags if value is not self.UNSPECIFIED: query = (_ for _ in query if _.value == value) if limit is not None: query = islice(query, limit) return query def count_tags(self, value=UNSPECIFIED, limit=None): return len(list(self.get_tags(value=value, limit=limit))) def create_attachment(self, path, description=None, timestamp=None, filename=None, lazy=None): return Attachment(path=path, description=description, timestamp=timestamp, filename=filename, lazy=lazy) def get_attachment(self, attachment_id): if attachment_id is None: raise ValueError('No attachment_id provided.') return self._attachments_by_id.get(attachment_id) def get_attachments(self, attachment_id_in=UNSPECIFIED): query = (_ for _ in self._attachments) if attachment_id_in is not self.UNSPECIFIED: query = (_ for _ in query if _.id in attachment_id_in) return query def count_attachments(self, attachment_id_in=UNSPECIFIED): return len( list(self.get_attachments(attachment_id_in=attachment_id_in))) def create_note(self, content, timestamp=None, lazy=None): return Note(content=content, timestamp=timestamp, lazy=lazy) def get_note(self, note_id): if note_id is None: raise ValueError('No note_id provided.') return self._notes_by_id.get(note_id) def get_notes(self, note_id_in=UNSPECIFIED): query = self._notes if note_id_in is not self.UNSPECIFIED: query = (_ for _ in query if _.id in note_id_in) return query def count_notes(self, note_id_in=UNSPECIFIED): return len(list(self.get_notes(note_id_in=note_id_in))) def create_option(self, key, value): return Option(key=key, value=value) def get_option(self, key): if key is None: raise ValueError('No option_id provided.') return self._options_by_key.get(key) def get_options(self, key_in=UNSPECIFIED): query = self._options if key_in is not self.UNSPECIFIED: query = (_ for _ in query if _.id in key_in) return query def count_options(self, key_in=UNSPECIFIED): return len(list(self.get_options(key_in=key_in))) def create_user(self, email, hashed_password=None, is_admin=False, lazy=None): return User(email=email, hashed_password=hashed_password, is_admin=is_admin, lazy=lazy) _guest_user = None def get_guest_user(self): if self._guest_user is None: self._guest_user = self.create_user('guest@guest') self._guest_user._is_authenticated = False self._guest_user._is_anonymous = True self._guest_user.is_admin = False return self._guest_user def get_user(self, user_id): if user_id is None: raise ValueError('No user_id provided.') return self._users_by_id.get(user_id) def get_user_by_email(self, email): return self._users_by_email.get(email) def get_users(self, email_in=UNSPECIFIED): query = self._users if email_in is not self.UNSPECIFIED: query = (_ for _ in query if _.email in email_in) return query def count_users(self, email_in=UNSPECIFIED): return len(list(self.get_users(email_in=email_in))) def add(self, obj): if obj in self._added_objects: return if obj in self._deleted_objects: raise Exception( 'The object (id={}) has already been deleted.'.format(obj.id)) if (obj in self._tasks or obj in self._tags or obj in self._notes or obj in self._attachments or obj in self._users or obj in self._options): return d = obj.to_dict() self._values_by_object[obj] = d self._added_objects.add(obj) def delete(self, obj): if obj in self._deleted_objects: return if obj in self._added_objects: raise Exception( 'The object (id={}) has already been added.'.format(obj.id)) self._deleted_objects.add(obj) def commit(self): for domobj in list(self._added_objects): tt = self._get_object_type(domobj) if tt != ObjectTypes.Option and domobj.id is None: domobj.id = self._get_next_id(tt) if tt == ObjectTypes.Task and domobj.order_num is None: domobj.order_num = 0 if tt == ObjectTypes.Attachment: if domobj.id in self._attachments_by_id: raise Exception( 'There already exists an attachment with id ' '{}'.format(domobj.id)) self._attachments.append(domobj) self._attachments_by_id[domobj.id] = domobj elif tt == ObjectTypes.Note: if domobj.id in self._notes_by_id: raise Exception( 'There already exists a note with id {}'.format( domobj.id)) self._notes.append(domobj) self._notes_by_id[domobj.id] = domobj elif tt == ObjectTypes.Task: if domobj.id in self._tasks_by_id: raise Exception( 'There already exists a task with id {}'.format( domobj.id)) self._tasks.append(domobj) self._tasks_by_id[domobj.id] = domobj elif tt == ObjectTypes.Tag: if domobj.id in self._tags_by_id: raise Exception( 'There already exists a tag with id {}'.format( domobj.id)) if domobj.value in self._tags_by_value: raise Exception( 'There already exists a tag with value "{}"'.format( domobj.value)) self._tags.append(domobj) self._tags_by_id[domobj.id] = domobj self._tags_by_value[domobj.value] = domobj elif tt == ObjectTypes.Option: if domobj.key in self._options_by_key: raise Exception( 'There already exists an option with key {}'.format( domobj.id)) self._options.append(domobj) self._options_by_key[domobj.id] = domobj else: # tt == ObjectTypes.User if domobj.id in self._users_by_id: raise Exception( 'There already exists a user with id {}'.format( domobj.id)) if domobj.email in self._users_by_email: raise Exception( 'There already exists a user with email "{}"'.format( domobj.email)) self._users.append(domobj) self._users_by_id[domobj.id] = domobj self._users_by_email[domobj.email] = domobj self._values_by_object[domobj] = domobj.to_dict() self._added_objects.remove(domobj) self._added_objects.clear() for domobj in list(self._deleted_objects): tt = self._get_object_type(domobj) domobj.clear_relationships() if tt == ObjectTypes.Attachment: self._attachments.remove(domobj) del self._attachments_by_id[domobj.id] elif tt == ObjectTypes.Note: self._notes.remove(domobj) del self._notes_by_id[domobj.id] elif tt == ObjectTypes.Task: self._tasks.remove(domobj) del self._tasks_by_id[domobj.id] elif tt == ObjectTypes.Tag: self._tags.remove(domobj) del self._tags_by_id[domobj.id] elif tt == ObjectTypes.Option: self._options.remove(domobj) del self._options_by_key[domobj.key] else: # tt == ObjectTypes.User self._users.remove(domobj) del self._users_by_id[domobj.id] del self._users_by_email[domobj.email] self._deleted_objects.remove(domobj) self._deleted_objects.clear() def _process_changed_attr(domobj, new_values, type_name, attr_name, collection): old_value = self._values_by_object[domobj][attr_name] new_value = new_values[attr_name] if old_value != new_value: if new_value in collection: raise Exception( 'There already exists a {} with {} "{}"'.format( type_name, attr_name, new_value)) del collection[old_value] collection[new_value] = domobj for domobj in self._tasks: new_values = domobj.to_dict() _process_changed_attr(domobj, new_values, 'task', 'id', self._tasks_by_id) if 'order_num' in new_values and new_values['order_num'] is None: raise ValueError( 'order_num cannot be None, Task "{}" ({})'.format( domobj.summary, domobj.id)) self._values_by_object[domobj] = new_values for domobj in self._tags: new_values = domobj.to_dict() _process_changed_attr(domobj, new_values, 'tag', 'id', self._tags_by_id) _process_changed_attr(domobj, new_values, 'tag', 'value', self._tags_by_value) self._values_by_object[domobj] = new_values for domobj in self._notes: new_values = domobj.to_dict() _process_changed_attr(domobj, new_values, 'note', 'id', self._notes_by_id) self._values_by_object[domobj] = new_values for domobj in self._attachments: new_values = domobj.to_dict() _process_changed_attr(domobj, new_values, 'attachment', 'id', self._attachments_by_id) self._values_by_object[domobj] = new_values for domobj in self._users: new_values = domobj.to_dict() _process_changed_attr(domobj, new_values, 'user', 'id', self._users_by_id) _process_changed_attr(domobj, new_values, 'user', 'email', self._users_by_email) self._values_by_object[domobj] = new_values for domobj in self._options: new_values = domobj.to_dict() _process_changed_attr(domobj, new_values, 'option', 'key', self._options_by_key) self._values_by_object[domobj] = new_values self._clear_affected_objects() def _get_next_task_id(self): if not self._tasks_by_id: return 1 return max(self._tasks_by_id.keys()) + 1 def _get_next_tag_id(self): if not self._tags_by_id: return 1 return max(self._tags_by_id.keys()) + 1 def _get_next_note_id(self): if not self._notes_by_id: return 1 return max(self._notes_by_id.keys()) + 1 def _get_next_attachment_id(self): if not self._attachments_by_id: return 1 return max(self._attachments_by_id.keys()) + 1 def _get_next_user_id(self): if not self._users_by_id: return 1 return max(self._users_by_id.keys()) + 1 def _get_next_id(self, objtype): if objtype == ObjectTypes.Task: return self._get_next_task_id() if objtype == ObjectTypes.Tag: return self._get_next_tag_id() if objtype == ObjectTypes.Attachment: return self._get_next_attachment_id() if objtype == ObjectTypes.Note: return self._get_next_note_id() if objtype == ObjectTypes.User: return self._get_next_user_id() raise Exception('Unknown object type: {}'.format(objtype)) def rollback(self): for t, d in self._values_by_object.items(): t.update_from_dict(d) for t in self._added_objects: del self._values_by_object[t] self._clear_affected_objects() def _clear_affected_objects(self): self._changed_objects.clear() self._added_objects.clear() self._deleted_objects.clear() def _get_object_type(self, domobj): try: tt = domobj.object_type except AttributeError: raise Exception('Not a domain object: {}, {}'.format( domobj, type(domobj).__name__)) if tt not in ObjectTypes.all: raise Exception('Unknown object type: {}, {}, "{}"'.format( domobj, type(domobj).__name__, tt)) return tt
class InterlinkedDependants(ManyToManySet): __change_field__ = TaskBase.FIELD_DEPENDANTS __attr_counterpart__ = 'dependees' _logger = logging_util.get_logger_by_name(__name__, 'InterlinkedDependants')
class InterlinkedPrioritizeAfter(ManyToManySet): __change_field__ = TaskBase.FIELD_PRIORITIZE_AFTER __attr_counterpart__ = 'prioritize_before' _logger = logging_util.get_logger_by_name(__name__, 'InterlinkedPrioritizeAfter')
class Note(Changeable, NoteBase): _logger = logging_util.get_logger_by_name(__name__, 'Note') _id = None _content = '' _timestamp = None _task = None def __init__(self, content, timestamp=None, lazy=None): super(Note, self).__init__(content, timestamp) self._logger.debug('Note.__init__ %s', self) if lazy is None: lazy = {} self._task_lazy = lazy.get('task') @property def id(self): return self._id @id.setter def id(self, value): if value != self._id: self._on_attr_changing(self.FIELD_ID, self._id) self._id = value self._on_attr_changed(self.FIELD_ID, self.OP_SET, self._id) @property def content(self): return self._content @content.setter def content(self, value): if value != self._content: self._on_attr_changing(self.FIELD_CONTENT, self._content) self._content = value self._on_attr_changed(self.FIELD_CONTENT, self.OP_SET, self._content) @property def timestamp(self): return self._timestamp @timestamp.setter def timestamp(self, value): if value != self._timestamp: self._on_attr_changing(self.FIELD_TIMESTAMP, self._timestamp) self._timestamp = value self._on_attr_changed(self.FIELD_TIMESTAMP, self.OP_SET, self._timestamp) @property def task_id(self): if self.task: return self.task.id return None def _populate_task(self): if self._task_lazy: self._logger.debug('populating task from lazy %s', self) value = self._task_lazy() self._task_lazy = None self.task = value @property def task(self): self._populate_task() return self._task @task.setter def task(self, value): self._populate_task() if value != self._task: self._on_attr_changing(self.FIELD_TASK, self._task) if self._task is not None: self._task.notes.discard(self) self._task = value if self._task is not None: self._task.notes.add(self) self._on_attr_changed(self.FIELD_TASK, self.OP_SET, self._task) def clear_relationships(self): self.task = None
class ViewLayer(object): _logger = logging_util.get_logger_by_name(__name__, 'ViewLayer') def __init__(self, ll, bcrypt, renderer=None, login_src=None): self.ll = ll if renderer is None: renderer = DefaultRenderer() self.renderer = renderer if login_src is None: login_src = DefaultLoginSource(bcrypt) self.login_src = login_src def render_template(self, template_name, **kwargs): return self.renderer.render_template(template_name, **kwargs) def make_response(self, *args): return self.renderer.make_response(*args) def redirect(self, *args, **kwargs): return self.renderer.redirect(*args, **kwargs) def url_for(self, *args, **kwargs): return self.renderer.url_for(*args, **kwargs) def send_from_directory(self, *args, **kwargs): return self.renderer.send_from_directory(*args, **kwargs) def flash(self, *args, **kwargs): return self.renderer.flash(*args, **kwargs) def login_user(self, *args, **kwargs): return self.login_src.login_user(*args, **kwargs) def logout_user(self, *args, **kwargs): return self.login_src.logout_user(*args, **kwargs) def check_password_hash(self, *args, **kwargs): return self.login_src.check_password_hash(*args, **kwargs) def get_form_or_arg(self, request, name): if name in request.form: return request.form[name] return request.args.get(name) def index(self, request, current_user): show_deleted = request.cookies.get('show_deleted') show_done = request.cookies.get('show_done') page_num = None try: page_num = int(self.get_form_or_arg(request, 'page')) except: pass tasks_per_page = None try: tasks_per_page = int(self.get_form_or_arg(request, 'per_page')) except: pass data = self.ll.get_index_data(show_deleted, show_done, current_user, page_num=page_num, tasks_per_page=tasks_per_page) resp = self.make_response( self.render_template('index.t.html', show_deleted=data['show_deleted'], show_done=data['show_done'], cycle=itertools.cycle, user=current_user, tasks=data['tasks'], tags=data['all_tags'], pager=data['pager'], pager_link_page='index', pager_link_args={})) return resp def hierarchy(self, request, current_user): show_deleted = request.cookies.get('show_deleted') show_done = request.cookies.get('show_done') data = self.ll.get_index_hierarchy_data(show_deleted, show_done, current_user) resp = self.make_response( self.render_template('hierarchy.t.html', show_deleted=data['show_deleted'], show_done=data['show_done'], cycle=itertools.cycle, user=current_user, tasks_h=data['tasks_h'], tags=data['all_tags'])) return resp def deadlines(self, request, current_user): data = self.ll.get_deadlines_data(current_user) return self.make_response( self.render_template('deadlines.t.html', cycle=itertools.cycle, deadline_tasks=data['deadline_tasks'])) def task_new_get(self, request, current_user): summary = self.get_form_or_arg(request, 'summary') description = self.get_form_or_arg(request, 'description') deadline = self.get_form_or_arg(request, 'deadline') is_done = self.get_form_or_arg(request, 'is_done') is_deleted = self.get_form_or_arg(request, 'is_deleted') order_num = self.get_form_or_arg(request, 'order_num') expected_duration_minutes = self.get_form_or_arg( request, 'expected_duration_minutes') expected_cost = self.get_form_or_arg(request, 'expected_cost') parent_id = self.get_form_or_arg(request, 'parent_id') tags = self.get_form_or_arg(request, 'tags') prev_url = self.get_form_or_arg(request, 'prev_url') return self.render_template( 'new_task.t.html', prev_url=prev_url, summary=summary, description=description, deadline=deadline, is_done=is_done, is_deleted=is_deleted, order_num=order_num, expected_duration_minutes=expected_duration_minutes, expected_cost=expected_cost, parent_id=parent_id, tags=tags) def task_new_post(self, request, current_user): self._logger.debug('begin') summary = self.get_form_or_arg(request, 'summary') description = self.get_form_or_arg(request, 'description') deadline = self.get_form_or_arg(request, 'deadline') or None is_done = self.get_form_or_arg(request, 'is_done') or None is_deleted = self.get_form_or_arg(request, 'is_deleted') or None order_type = self.get_form_or_arg(request, 'order_type') or 'bottom' expected_duration_minutes = self.get_form_or_arg( request, 'expected_duration_minutes') or None expected_cost = self.get_form_or_arg(request, 'expected_cost') or None parent_id = self.get_form_or_arg(request, 'parent_id') or None is_public = self.get_form_or_arg(request, 'is_public') or None tags = self.get_form_or_arg(request, 'tags') if tags: tags = [s.strip() for s in tags.split(',')] self._logger.debug('calculating order_num') if order_type == 'top': order_num = self.ll.get_highest_order_num() if order_num is not None: order_num += 2 else: order_num = 0 elif order_type == 'order_num': order_num = self.get_form_or_arg(request, 'order_num') or None else: # bottom order_num = self.ll.get_lowest_order_num() if order_num is not None: order_num -= 2 else: order_num = 0 self._logger.debug('calculated order_num: %d', order_num) self._logger.debug('creating the new task object via LL') task = self.ll.create_new_task( summary=summary, description=description, is_done=is_done, is_deleted=is_deleted, deadline=deadline, order_num=order_num, expected_duration_minutes=expected_duration_minutes, expected_cost=expected_cost, parent_id=parent_id, is_public=is_public, current_user=current_user) if tags: for tag_name in tags: self._logger.debug('adding tag "%s"', tag_name) self.ll.do_add_tag_to_task(task, tag_name, current_user) self._logger.debug('getting next_url') next_url = self.get_form_or_arg(request, 'next_url') if not next_url: self._logger.debug('next_url not defined') next_url = self.url_for('view_task', id=task.id) self._logger.debug('end') return self.redirect(next_url) def task_mark_done(self, request, current_user, task_id): self.ll.task_set_done(task_id, current_user) return self.redirect(request.args.get('next') or self.url_for('index')) def task_mark_undone(self, request, current_user, task_id): self.ll.task_unset_done(task_id, current_user) return self.redirect(request.args.get('next') or self.url_for('index')) def task_delete(self, request, current_user, task_id): self.ll.task_set_deleted(task_id, current_user) return self.redirect(request.args.get('next') or self.url_for('index')) def task_undelete(self, request, current_user, task_id): self.ll.task_unset_deleted(task_id, current_user) return self.redirect(request.args.get('next') or self.url_for('index')) def task_purge(self, request, current_user, task_id): task = self.ll.pl_get_task(task_id) if not task: raise NotFound("No task found for the id '{}'".format(task_id)) if not task.is_deleted: raise BadRequest( "Indicated task (id {}) has not been deleted.".format(task_id)) self.ll.purge_task(task, current_user) return self.redirect(request.args.get('next') or self.url_for('index')) def purge_all(self, request, current_user): are_you_sure = request.args.get('are_you_sure') if are_you_sure: self.ll.purge_all_deleted_tasks(current_user) return self.redirect( request.args.get('next') or self.url_for('index')) return self.render_template('purge.t.html') def task(self, request, current_user, task_id): show_deleted = request.cookies.get('show_deleted') show_done = request.cookies.get('show_done') try: page_num = int(request.args.get('page', 1)) except Exception: page_num = 1 try: tasks_per_page = int(request.args.get('per_page', 20)) except Exception: tasks_per_page = 20 data = self.ll.get_task_data(task_id, current_user, include_deleted=show_deleted, include_done=show_done, page_num=page_num, tasks_per_page=tasks_per_page) return self.render_template('task.t.html', task=data['task'], descendants=data['descendants'], cycle=itertools.cycle, show_deleted=show_deleted, show_done=show_done, pager=data['pager'], pager_link_page='view_task', pager_link_args={'id': task_id}, current_user=current_user, ops=TaskUserOps, show_hierarchy=False) def task_hierarchy(self, request, current_user, task_id): show_deleted = request.cookies.get('show_deleted') show_done = request.cookies.get('show_done') data = self.ll.get_task_hierarchy_data(task_id, current_user, include_deleted=show_deleted, include_done=show_done) return self.render_template('task.t.html', task=data['task'], descendants=data['descendants'], cycle=itertools.cycle, show_deleted=show_deleted, show_done=show_done, ops=TaskUserOps, show_hierarchy=True) def note_new_post(self, request, current_user): if 'task_id' not in request.form: return ('No task_id specified', 400) task_id = request.form['task_id'] content = request.form['content'] self.ll.create_new_note(task_id, content, current_user) return self.redirect(self.url_for('view_task', id=task_id)) def task_edit(self, request, current_user, task_id): def render_get_response(): data = self.ll.get_edit_task_data(task_id, current_user) return self.render_template("edit_task.t.html", task=data['task'], tag_list=data['tag_list']) if request.method == 'GET': return render_get_response() if 'summary' not in request.form or 'description' not in request.form: return render_get_response() summary = request.form['summary'] description = request.form['description'] deadline = request.form['deadline'] is_done = ('is_done' in request.form and not not request.form['is_done']) is_deleted = ('is_deleted' in request.form and not not request.form['is_deleted']) order_num = None if 'order_num' in request.form: order_num = request.form['order_num'] parent_id = None if 'parent_id' in request.form: parent_id = request.form['parent_id'] is_public = ('is_public' in request.form and not not request.form['is_public']) duration = int_from_str(request.form['expected_duration_minutes']) expected_cost = money_from_str(request.form['expected_cost']) task = self.ll.set_task(task_id, current_user, summary, description, deadline, is_done, is_deleted, order_num, duration, expected_cost, parent_id, is_public) return self.redirect(self.url_for('view_task', id=task.id)) def attachment_new(self, request, current_user, timestamp=None): if 'task_id' not in request.form: raise BadRequest('No task_id specified') task_id = request.form['task_id'] f = request.files['filename'] if f is None or not f.filename or not self.ll.allowed_file(f.filename): raise BadRequest('Invalid file') if 'description' in request.form: description = request.form['description'] else: description = '' self.ll.create_new_attachment(task_id, f, description, current_user, timestamp=timestamp) return self.redirect(self.url_for('view_task', id=task_id)) def attachment(self, request, current_user, attachment_id, name): att = self.ll.pl_get_attachment(attachment_id) if att is None: raise NotFound( "No attachment found for the id '{}'".format(attachment_id)) return self.send_from_directory(self.ll.upload_folder, att.path) def task_up(self, request, current_user, task_id): show_deleted = request.cookies.get('show_deleted') self.ll.do_move_task_up(task_id, show_deleted, current_user) return self.redirect(request.args.get('next') or self.url_for('index')) def task_top(self, request, current_user, task_id): self.ll.do_move_task_to_top(task_id, current_user) return self.redirect(request.args.get('next') or self.url_for('index')) def task_down(self, request, current_user, task_id): show_deleted = request.cookies.get('show_deleted') self.ll.do_move_task_down(task_id, show_deleted, current_user) return self.redirect(request.args.get('next') or self.url_for('index')) def task_bottom(self, request, current_user, task_id): self.ll.do_move_task_to_bottom(task_id, current_user) return self.redirect(request.args.get('next') or self.url_for('index')) def long_order_change(self, request, current_user): task_to_move_id = self.get_form_or_arg(request, 'long_order_task_to_move') if task_to_move_id is None: return self.redirect( request.args.get('next') or self.url_for('index')) target_id = self.get_form_or_arg(request, 'long_order_target') if target_id is None: return self.redirect( request.args.get('next') or self.url_for('index')) self.ll.do_long_order_change(task_to_move_id, target_id, current_user) return self.redirect(request.args.get('next') or self.url_for('index')) def task_add_tag(self, request, current_user, task_id): value = self.get_form_or_arg(request, 'value') if value is None or value == '': return (self.redirect( request.args.get('next') or self.url_for('view_task', id=task_id))) self.ll.do_add_tag_to_task_by_id(task_id, value, current_user) return (self.redirect( request.args.get('next') or self.url_for('view_task', id=task_id))) def task_delete_tag(self, request, current_user, task_id, tag_id): if tag_id is None: tag_id = self.get_form_or_arg(request, 'tag_id') self.ll.do_delete_tag_from_task(task_id, tag_id, current_user) return (self.redirect( request.args.get('next') or self.url_for('view_task', id=task_id))) def task_authorize_user(self, request, current_user, task_id): email = self.get_form_or_arg(request, 'email') if email is None or email == '': return (self.redirect( request.args.get('next') or self.url_for('view_task', id=task_id))) self.ll.do_authorize_user_for_task_by_email(task_id, email, current_user) return (self.redirect( request.args.get('next') or self.url_for('view_task', id=task_id))) def task_pick_user(self, request, current_user, task_id): task = self.ll.get_task(task_id, current_user) users = self.ll.get_users() return self.render_template('pick_user.t.html', task=task, users=users, cycle=itertools.cycle) def task_authorize_user_user(self, request, current_user, task_id, user_id): if user_id is None or user_id == '': return (self.redirect( request.args.get('next') or self.url_for('view_task', id=task_id))) self.ll.do_authorize_user_for_task_by_id(task_id, user_id, current_user) return (self.redirect( request.args.get('next') or self.url_for('view_task', id=task_id))) def task_deauthorize_user(self, request, current_user, task_id, user_id): if user_id is None: user_id = self.get_form_or_arg(request, 'user_id') self.ll.do_deauthorize_user_for_task(task_id, user_id, current_user) return (self.redirect( request.args.get('next') or self.url_for('view_task', id=task_id))) def login(self, request, current_user): if request.method == 'GET': return self.render_template('login.t.html') email = request.form['email'] password = request.form['password'] user = self.ll.pl_get_user_by_email(email) if user is None: self.flash('Username or Password is invalid', 'error') return self.redirect(self.url_for('login')) if user.hashed_password is None or user.hashed_password == '': self.flash('Username or Password is invalid', 'error') return self.redirect(self.url_for('login')) if not self.check_password_hash(user.hashed_password, password): self.flash('Username or Password is invalid', 'error') return self.redirect(self.url_for('login')) self.login_user(user) self.flash('Logged in successfully') return self.redirect(request.args.get('next') or self.url_for('index')) def logout(self, request, current_user): self.logout_user() return self.redirect(self.url_for('index')) def users(self, request, current_user): if request.method == 'GET': users = self.ll.get_users() return self.render_template('list_users.t.html', users=users, cycle=itertools.cycle) email = request.form['email'] is_admin = False if 'is_admin' in request.form: is_admin = bool_from_str(request.form['is_admin']) self.ll.do_add_new_user(email, is_admin) return self.redirect(self.url_for('list_users')) def users_user_get(self, request, current_user, user_id): user = self.ll.do_get_user_data(user_id, current_user) return self.render_template('view_user.t.html', user=user) def show_hide_deleted(self, request, current_user): show_deleted = request.args.get('show_deleted') resp = self.make_response( self.redirect(request.args.get('next') or self.url_for('index'))) if show_deleted and show_deleted != '0': resp.set_cookie('show_deleted', '1') else: resp.set_cookie('show_deleted', '') return resp def show_hide_done(self, request, current_user): show_done = request.args.get('show_done') resp = self.make_response( self.redirect(request.args.get('next') or self.url_for('index'))) if show_done and show_done != '0': resp.set_cookie('show_done', '1') else: resp.set_cookie('show_done', '') return resp def options(self, request, current_user): if request.method == 'GET' or 'key' not in request.form: data = self.ll.get_view_options_data() return self.render_template('options.t.html', options=data) key = request.form['key'] value = '' if 'value' in request.form: value = request.form['value'] self.ll.do_set_option(key, value) return self.redirect( request.args.get('next') or self.url_for('view_options')) def option_delete(self, request, current_user, key): self.ll.do_delete_option(key) return self.redirect( request.args.get('next') or self.url_for('view_options')) def reset_order_nums(self, request, current_user): self.ll.do_reset_order_nums(current_user) return self.redirect(request.args.get('next') or self.url_for('index')) def export(self, request, current_user): if request.method == 'GET': return self.render_template('export.t.html', results=None) types_to_export = set( k for k in request.form.keys() if k in request.form and request.form[k] == 'all') results = self.ll.do_export_data(types_to_export) return jsonify(results) def import_(self, request, current_user): if request.method == 'GET': return self.render_template('import.t.html') f = request.files.get('file') if f is None or not f: r = request.form['raw'] src = json.loads(r) else: src = json.load(f) self.ll.do_import_data(src) return self.redirect(self.url_for('index')) def task_crud(self, request, current_user): if request.method == 'GET': tasks = self.ll.get_task_crud_data(current_user) return self.render_template('task_crud.t.html', tasks=tasks, cycle=itertools.cycle) crud_data = {} for key in request.form.keys(): if re.match( r'task_\d+_(summary|deadline|is_done|is_deleted|' r'order_num|duration|cost|parent_id)', key): crud_data[key] = request.form[key] self.ll.do_submit_task_crud(crud_data, current_user) return self.redirect(self.url_for('task_crud')) def tags(self, request, current_user): tags = self.ll.get_tags() return self.render_template('list_tags.t.html', tags=tags, cycle=itertools.cycle) def tags_id_get(self, request, current_user, tag_id): data = self.ll.get_tag_data(tag_id, current_user) return self.render_template('tag.t.html', tag=data['tag'], tasks=data['tasks'], cycle=itertools.cycle) def tags_id_edit(self, request, current_user, tag_id): def render_get_response(): tag = self.ll.get_tag(tag_id) return self.render_template("edit_tag.t.html", tag=tag) if request.method == 'GET': return render_get_response() if 'value' not in request.form or 'description' not in request.form: return render_get_response() value = request.form['value'] description = request.form['description'] self.ll.do_edit_tag(tag_id, value, description) return self.redirect(self.url_for('view_tag', id=tag_id)) def task_id_convert_to_tag(self, request, current_user, task_id): are_you_sure = request.args.get('are_you_sure') if are_you_sure: tag = self.ll.convert_task_to_tag(task_id, current_user) return self.redirect( request.args.get('next') or self.url_for('view_tag', id=tag.id)) task = self.ll.get_task(task_id, current_user) return self.render_template('convert_task_to_tag.t.html', task_id=task.id, tag_value=task.summary, tag_description=task.description, cycle=itertools.cycle, tasks=task.children) def search(self, request, current_user, search_query): if search_query is None and request.method == 'POST': search_query = request.form['query'] results = self.ll.search(search_query, current_user) return self.render_template('search.t.html', query=search_query, results=results) def task_id_add_dependee(self, request, current_user, task_id, dependee_id): if dependee_id is None or dependee_id == '': dependee_id = self.get_form_or_arg(request, 'dependee_id') if dependee_id is None or dependee_id == '': return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id))) self.ll.do_add_dependee_to_task(task_id, dependee_id, current_user) return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id))) def task_id_remove_dependee(self, request, current_user, task_id, dependee_id): if dependee_id is None: dependee_id = self.get_form_or_arg(request, 'dependee_id') self.ll.do_remove_dependee_from_task(task_id, dependee_id, current_user) return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id))) def task_id_add_dependant(self, request, current_user, task_id, dependant_id): if dependant_id is None or dependant_id == '': dependant_id = self.get_form_or_arg(request, 'dependant_id') if dependant_id is None or dependant_id == '': return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id))) self.ll.do_add_dependant_to_task(task_id, dependant_id, current_user) return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id))) def task_id_remove_dependant(self, request, current_user, task_id, dependant_id): if dependant_id is None: dependant_id = self.get_form_or_arg(request, 'dependant_id') self.ll.do_remove_dependant_from_task(task_id, dependant_id, current_user) return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id))) def task_id_add_prioritize_before(self, request, current_user, task_id, prioritize_before_id): if prioritize_before_id is None or prioritize_before_id == '': prioritize_before_id = self.get_form_or_arg( request, 'prioritize_before_id') if prioritize_before_id is None or prioritize_before_id == '': return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id))) self.ll.do_add_prioritize_before_to_task(task_id, prioritize_before_id, current_user) return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id))) def task_id_remove_prioritize_before(self, request, current_user, task_id, prioritize_before_id): if prioritize_before_id is None: prioritize_before_id = self.get_form_or_arg( request, 'prioritize_before_id') self.ll.do_remove_prioritize_before_from_task(task_id, prioritize_before_id, current_user) return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id))) def task_id_add_prioritize_after(self, request, current_user, task_id, prioritize_after_id): if prioritize_after_id is None or prioritize_after_id == '': prioritize_after_id = self.get_form_or_arg(request, 'prioritize_after_id') if prioritize_after_id is None or prioritize_after_id == '': return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id))) self.ll.do_add_prioritize_after_to_task(task_id, prioritize_after_id, current_user) return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id))) def task_id_remove_prioritize_after(self, request, current_user, task_id, prioritize_after_id): if prioritize_after_id is None: prioritize_after_id = self.get_form_or_arg(request, 'prioritize_after_id') self.ll.do_remove_prioritize_after_from_task(task_id, prioritize_after_id, current_user) return (self.redirect( request.args.get('next') or request.args.get('next_url') or self.url_for('view_task', id=task_id)))
class Attachment(Changeable, AttachmentBase): _logger = logging_util.get_logger_by_name(__name__, 'Attachment') _id = None _timestamp = None _path = None _filename = None _description = '' _task = None def __init__(self, path, description=None, timestamp=None, filename=None, lazy=None): super(Attachment, self).__init__(path, description, timestamp, filename) self._logger.debug('Attachment.__init__ %s', self) if lazy is None: lazy = {} self._task_lazy = lazy.get('task') @property def id(self): return self._id @id.setter def id(self, value): if value != self._id: self._on_attr_changing(self.FIELD_ID, self._id) self._id = value self._on_attr_changed(self.FIELD_ID, self.OP_SET, self._id) @property def timestamp(self): return self._timestamp @timestamp.setter def timestamp(self, value): if value != self._timestamp: self._on_attr_changing(self.FIELD_TIMESTAMP, self._timestamp) self._timestamp = value self._on_attr_changed(self.FIELD_TIMESTAMP, self.OP_SET, self._timestamp) @property def path(self): return self._path @path.setter def path(self, value): if value != self._path: self._on_attr_changing(self.FIELD_PATH, self._path) self._path = value self._on_attr_changed(self.FIELD_PATH, self.OP_SET, self._path) @property def filename(self): return self._filename @filename.setter def filename(self, value): if value != self._filename: self._on_attr_changing(self.FIELD_FILENAME, self._filename) self._filename = value self._on_attr_changed(self.FIELD_FILENAME, self.OP_SET, self._filename) @property def description(self): return self._description @description.setter def description(self, value): if value != self._description: self._on_attr_changing(self.FIELD_DESCRIPTION, self._description) self._description = value self._on_attr_changed(self.FIELD_DESCRIPTION, self.OP_SET, self._description) @property def task_id(self): if self.task: return self.task.id return None def _populate_task(self): if self._task_lazy: self._logger.debug('populating task from lazy %s', self) value = self._task_lazy() self._task_lazy = None self.task = value @property def task(self): self._populate_task() return self._task @task.setter def task(self, value): self._populate_task() if value != self._task: self._on_attr_changing(self.FIELD_TASK, self._task) if self._task is not None: self._task.attachments.discard(self) self._task = value if self._task is not None: self._task.attachments.add(self) self._on_attr_changed(self.FIELD_TASK, self.OP_SET, self._task) def clear_relationships(self): self.task = None
class Task(Changeable, TaskBase): _logger = logging_util.get_logger_by_name(__name__, 'Task') _id = None _summary = None _description = None _is_done = None _is_deleted = None _order_num = None _deadline = None _expected_duration_minutes = None _expected_cost = None _parent = None _is_public = None def __init__(self, summary, description='', is_done=False, is_deleted=False, deadline=None, expected_duration_minutes=None, expected_cost=None, is_public=False, lazy=None): super(Task, self).__init__(summary, description, is_done, is_deleted, deadline, expected_duration_minutes, expected_cost, is_public) self._logger.debug('Task.__init__ %s', self) if lazy is None: lazy = {} self._parent_lazy = lazy.get('parent') # self depends on self.dependees self._dependees = InterlinkedDependees(self, lazy=lazy.get('dependees')) # self.dependants depend on self self._dependants = InterlinkedDependants(self, lazy=lazy.get('dependants')) # self is after self.prioritize_before's # self has lower priority than self.prioritize_before's self._prioritize_before = InterlinkedPrioritizeBefore( self, lazy=lazy.get('prioritize_before')) # self is before self.prioritize_after's # self has higher priority than self.prioritize_after's self._prioritize_after = InterlinkedPrioritizeAfter( self, lazy=lazy.get('prioritize_after')) self._tags = InterlinkedTags(self, lazy=lazy.get('tags')) self._users = InterlinkedUsers(self, lazy=lazy.get('users')) self._children = InterlinkedChildren(self, lazy=lazy.get('children')) self._notes = InterlinkedNotes(self, lazy=lazy.get('notes')) self._attachments = InterlinkedAttachments( self, lazy=lazy.get('attachments')) @property def id(self): return self._id @id.setter def id(self, value): if value != self._id: self._logger.debug('%s: %s -> %s', self, self.id, value) self._on_attr_changing(self.FIELD_ID, self._id) self._id = value self._on_attr_changed(self.FIELD_ID, self.OP_SET, self._id) @property def summary(self): return self._summary @summary.setter def summary(self, value): if value != self._summary: self._logger.debug('%s: %s -> %s', self, repr(self.summary), repr(value)) self._on_attr_changing(self.FIELD_SUMMARY, self._summary) self._summary = value self._on_attr_changed(self.FIELD_SUMMARY, self.OP_SET, self._summary) @property def description(self): return self._description @description.setter def description(self, value): if value != self._description: self._logger.debug('%s: %s -> %s', self, self.description, value) self._on_attr_changing(self.FIELD_DESCRIPTION, self._description) self._description = value self._on_attr_changed(self.FIELD_DESCRIPTION, self.OP_SET, self._description) @property def is_done(self): return self._is_done @is_done.setter def is_done(self, value): if value != self._is_done: self._logger.debug('%s: %s -> %s', self, self.is_done, value) self._on_attr_changing(self.FIELD_IS_DONE, self._is_done) self._is_done = value self._on_attr_changed(self.FIELD_IS_DONE, self.OP_SET, self._is_done) @property def is_deleted(self): return self._is_deleted @is_deleted.setter def is_deleted(self, value): if value != self._is_deleted: self._logger.debug('%s: %s -> %s', self, self.is_deleted, value) self._on_attr_changing(self.FIELD_IS_DELETED, self._is_deleted) self._is_deleted = value self._on_attr_changed(self.FIELD_IS_DELETED, self.OP_SET, self._is_deleted) @property def order_num(self): return self._order_num @order_num.setter def order_num(self, value): if value != self._order_num: self._logger.debug('%s: %s -> %s', self, self.order_num, value) self._on_attr_changing(self.FIELD_ORDER_NUM, self._order_num) self._order_num = value self._on_attr_changed(self.FIELD_ORDER_NUM, self.OP_SET, self._order_num) @property def deadline(self): return self._deadline @deadline.setter def deadline(self, value): if value != self._deadline: self._logger.debug('%s: %s -> %s', self, self.deadline, value) self._on_attr_changing(self.FIELD_DEADLINE, self._deadline) self._deadline = value self._on_attr_changed(self.FIELD_DEADLINE, self.OP_SET, self._deadline) @property def expected_duration_minutes(self): return self._expected_duration_minutes @expected_duration_minutes.setter def expected_duration_minutes(self, value): if value != self._expected_duration_minutes: self._logger.debug('%s: %s -> %s', self, self.expected_duration_minutes, value) self._on_attr_changing(self.FIELD_EXPECTED_DURATION_MINUTES, self._expected_duration_minutes) self._expected_duration_minutes = value self._on_attr_changed(self.FIELD_EXPECTED_DURATION_MINUTES, self.OP_SET, self._expected_duration_minutes) @property def expected_cost(self): return self._expected_cost @expected_cost.setter def expected_cost(self, value): if value != self._expected_cost: self._logger.debug('%s: %s -> %s', self, self.expected_cost, value) self._on_attr_changing(self.FIELD_EXPECTED_COST, self._expected_cost) self._expected_cost = value self._on_attr_changed(self.FIELD_EXPECTED_COST, self.OP_SET, self._expected_cost) @property def parent_id(self): if self.parent: return self.parent.id return None def _populate_parent(self): if self._parent_lazy: self._logger.debug('populating parent from lazy %s', self) value = self._parent_lazy() self._parent_lazy = None self.parent = value @property def parent(self): self._populate_parent() return self._parent @parent.setter def parent(self, value): self._logger.debug('%s', self) self._populate_parent() if value != self._parent: self._logger.debug('%s: %s -> %s', self, self._parent, value) self._on_attr_changing(self.FIELD_PARENT, self._parent) if self._parent is not None: self._parent.children.discard(self) self._parent = value if self._parent is not None: self._parent.children.append(self) self._on_attr_changed(self.FIELD_PARENT, self.OP_SET, self._parent) @property def children(self): return self._children @property def tags(self): return self._tags @property def users(self): return self._users @property def dependees(self): return self._dependees @property def dependants(self): return self._dependants @property def prioritize_before(self): return self._prioritize_before @property def prioritize_after(self): return self._prioritize_after @property def notes(self): return self._notes @property def attachments(self): return self._attachments @property def is_public(self): return self._is_public @is_public.setter def is_public(self, value): if value != self._is_public: self._logger.debug('%s: %s -> %s', self, self.is_public, value) self._on_attr_changing(self.FIELD_IS_PUBLIC, self._is_public) self._is_public = value self._on_attr_changed(self.FIELD_IS_PUBLIC, self.OP_SET, self._is_public) def contains_dependency_cycle(self, visited=None): self._logger.debug('%s', self) if visited is None: visited = set() if self in visited: return True visited = set(visited) visited.add(self) for dependee in self.dependees: if dependee.contains_dependency_cycle(visited): return True return False def contains_priority_cycle(self, visited=None): self._logger.debug('%s', self) if visited is None: visited = set() if self in visited: return True visited = set(visited) visited.add(self) for before in self.prioritize_before: if before.contains_priority_cycle(visited): return True return False def clear_relationships(self): self._logger.debug('%s', self) self.parent = None self.children.clear() self.tags.clear() self.users.clear() self.notes.clear() self.attachments.clear() self.dependees.clear() self.dependants.clear() self.prioritize_before.clear() self.prioritize_after.clear()
class InterlinkedSet(collections.MutableSet): _logger = logging_util.get_logger_by_name(__name__, 'InterlinkedSet') __change_field__ = None __attr_counterpart__ = None def __init__(self, container, lazy=None): self._logger.debug('__init__') if container is None: raise ValueError('container cannot be None') self.container = container self.set = set() self._lazy = lazy def __repr__(self): self._logger.debug('__repr__') cls = type(self).__name__ if self._lazy: return '{}(<lazy>)'.format(cls) return '{}({})'.format(cls, self.set) def _populate(self): self._logger.debug('_populate') if self._lazy: self._logger.debug('populating the collection') self.set.update(self._lazy) self._lazy = None @property def c(self): return self.container def __len__(self): self._logger.debug('__len__') self._populate() return len(self.set) def __contains__(self, item): self._logger.debug('__contains__') self._populate() return self.set.__contains__(item) def __iter__(self): self._logger.debug('__iter__') self._populate() return self.set.__iter__() def append(self, item): self._logger.debug('append') self._logger.debug('%s: %s', self.c, item) self._populate() self.add(item) def __str__(self): self._logger.debug('__str__') if self._lazy: return 'set(<lazy>)' return str(self.set) def _add(self, item): self._logger.debug('_add') self._populate() self.set.add(item) def _discard(self, item): self._logger.debug('_discard') self._populate() self.set.discard(item) def count(self): return len(self)
class InterlinkedUsers(ManyToManySet): __change_field__ = TaskBase.FIELD_USERS __attr_counterpart__ = 'tasks' _logger = logging_util.get_logger_by_name(__name__, 'InterlinkedUsers')
class Tag(Changeable, TagBase): _logger = logging_util.get_logger_by_name(__name__, 'Tag') _id = None _value = None _description = None _tasks = None def __init__(self, value, description=None, lazy=None): super(Tag, self).__init__(value=value, description=description) self._logger.debug('Tag.__init__ %s', self) if lazy is None: lazy = {} self._tasks = InterlinkedTasks(self, lazy=lazy.get('tasks')) @property def id(self): return self._id @id.setter def id(self, value): if value != self._id: self._logger.debug('%s: %s -> %s', self, self._id, value) self._on_attr_changing(self.FIELD_ID, self._id) self._id = value self._on_attr_changed(self.FIELD_ID, self.OP_SET, self._id) @property def value(self): return self._value @value.setter def value(self, value): if value != self._value: self._logger.debug('%s: %s -> %s', self, self._value, value) self._on_attr_changing(self.FIELD_VALUE, self._value) self._value = value self._on_attr_changed(self.FIELD_VALUE, self.OP_SET, self._value) @property def description(self): return self._description @description.setter def description(self, value): if value != self._description: self._logger.debug('%s: %s -> %s', self, self._description, value) self._on_attr_changing(self.FIELD_DESCRIPTION, self._description) self._description = value self._on_attr_changed(self.FIELD_DESCRIPTION, self.OP_SET, self._description) @property def tasks(self): return self._tasks def clear_relationships(self): self.tasks.clear()
class InterlinkedNotes(OneToManySet): __change_field__ = TaskBase.FIELD_NOTES __attr_counterpart__ = 'task' _logger = logging_util.get_logger_by_name(__name__, 'InterlinkedNotes')