class DataColumn(Entity): """Column mapper """ using_options(tablename='column') title = Field(Unicode(200)) index = Field(Integer) nb_max_cards = Field(Integer) archive = Field(Boolean, default=False) cards = OneToMany('DataCard', order_by='index', # cascade='delete', collection_class=ordering_list('index')) board = ManyToOne('DataBoard', colname='board_id') def update(self, other): self.title = other.title self.index = other.index self.nb_max_cards = other.nb_max_cards session.flush() @classmethod def create_column(cls, board, index, title, nb_cards=None, archive=False): """Create new column In: - ``board`` -- DataBoard, father of the column - ``index`` -- position in the board - ``title`` -- title of the column Return: - created DataColumn instance """ q = cls.query q = q.filter(cls.index >= index) q = q.filter(cls.board == board) q.update({'index': cls.index + 1}) col = cls(title=title, index=index, board=board, nb_max_cards=nb_cards, archive=archive) # session.add(col) session.flush() return col def create_card(self, title, user): card = DataCard(title=title, creation_date=datetime.now()) self.cards.append(card) session.flush() return card def remove_card(self, card): if card in self.cards: self.cards.remove(card) def insert_card(self, index, card): self.cards.insert(index, card) def delete_card(self, card): self.remove_card(card) card.delete() def purge_cards(self): for card in self.cards: card.delete() def append_card(self, card): card.index = None self.cards.append(card) @classmethod def delete_column(cls, column): """Delete column Delete a given column, re-index other columns and delete all cards of the column In: - ``column`` -- DataColumn instance to delete """ index = column.index board = column.board column.delete() session.flush() q = cls.query q = q.filter(cls.index >= index) q = q.filter(cls.board == board) q.update({'index': cls.index - 1}) def get_cards_count(self): q = DataCard.query.filter(DataCard.column_id == self.id) return q.count()
class DataUser(Entity): """Label mapper """ using_options(tablename='user') # VARCHAR(binary=True) here is a hack to make MySQL case sensitive # like the other DBMS. # No consequences on regular databases. username = Field(VARCHAR(255, binary=True), unique=True, primary_key=True, nullable=False) source = Field(Unicode(255), nullable=False, primary_key=True) fullname = Field(Unicode(255), nullable=False) email = Field(Unicode(255), nullable=True) picture = Field(Unicode(255), nullable=True) language = Field(Unicode(255), default=u"en", nullable=True) email_to_confirm = Field(Unicode(255)) _salt = Field(Unicode(255), colname='salt', nullable=False) _password = Field(Unicode(255), colname='password', nullable=True) registration_date = Field(DateTime, nullable=False) last_login = Field(DateTime, nullable=True) display_week_numbers = Field(Boolean, default=False) board_members = OneToMany('DataBoardMember') boards = AssociationProxy( 'board_members', 'board', creator=lambda board: DataBoardMember(board=board)) board_managers = OneToMany('DataBoardManager') managed_boards = AssociationProxy( 'board_managers', 'board', creator=lambda board: DataBoardManager(board=board)) last_board = OneToOne('DataBoard', inverse='last_users') cards = ManyToMany('DataCard', inverse='members', lazy='dynamic') history = OneToMany('DataHistory') def __init__(self, username, password, fullname, email, source=u'application', picture=None, **kw): """Create a new user with an unconfirmed email""" super(DataUser, self).__init__(username=username, fullname=fullname, email=None, email_to_confirm=email, source=source, picture=picture, registration_date=datetime.datetime.utcnow(), **kw) # Create password if source is local if source == "application": self.change_password(password) else: # External authentication self.change_password('passwd') self.email_to_confirm = None @property def id(self): return self.username def update(self, fullname, email, picture=None): self.fullname = fullname if email: self.email = email self.picture = picture def check_password(self, clear_password): """Check the user password. Return True if the password is valid for this user""" encrypted_password = self._encrypt_password(self._salt, clear_password) return encrypted_password == self._password def change_password(self, clear_password): """Change the user password""" self._salt = self._create_random_salt() self._password = self._encrypt_password(self._salt, clear_password) def set_email_to_confirm(self, email_to_confirm): if email_to_confirm: self.email_to_confirm = email_to_confirm def is_validated(self): return self.email_to_confirm is None def confirm_email(self): """Called when a user confirms his email address""" # already confirmed if self.email_to_confirm is None: return self.email = self.email_to_confirm self.email_to_confirm = None def add_board(self, board, role="member"): """Add board to user's board lists In: - ``board`` -- DataBoard instance to add - ``role`` -- user is member or manager """ boards = set(dbm.board for dbm in self.board_members) if board not in boards: self.board_members.append(DataBoardMember(board=board)) if role == "manager" and board not in self.managed_boards: self.managed_boards.append(board) def get_picture(self): return self.picture @classmethod def get_confirmed_users(cls): return cls.query.filter(cls.email is not None) @staticmethod def _create_random_salt(length=32): allowed_chars = string.ascii_letters + string.digits return u''.join(random.choice(allowed_chars) for _ in range(length)) @staticmethod def _encrypt_password(salt, password): secret = "NzlSszmvDNY2e2lVMwiKJwgWjNGFCP1a" secret_salt = hashlib.sha512(secret + salt).hexdigest() utf8_password = password.encode('utf-8') return unicode(hashlib.sha512(secret_salt + utf8_password).hexdigest()) @classmethod def get_unconfirmed_users(cls, before_date=None): q = cls.query.filter(cls.email is None) if before_date: q = q.filter(cls.registration_date < before_date) return q @classmethod def get_by_username(cls, username): return cls.get_by(username=username) @classmethod def get_by_email(cls, email): return cls.get_by(email=email) @classmethod def search(cls, value): return cls.query.filter( cls.fullname.ilike('%' + value + '%') | cls.email.ilike('%' + value + '%')) def best_friends(self, exclude_list=(), size=None): from kansha.board.models import DataBoard cls = self.__class__ bm2 = aliased(DataBoardMember) cnt = func.count(DataBoardMember.board_id) query = database.session.query(cls, cnt) query = query.join( (DataBoardMember, and_(DataBoardMember.user_source == cls.source, DataBoardMember.user_username == cls.username))) query = query.join( (DataBoard, DataBoard.id == DataBoardMember.board_id)) query = query.join((bm2, bm2.board_id == DataBoard.id)) query = query.filter(bm2.member == self) if exclude_list: query = query.filter(~cls.email.in_(exclude_list)) query = query.group_by(cls) query = query.order_by(cnt.desc(), cls.fullname) if size: query = query.limit(size) return [res[0] for res in query]
class DataBoard(Entity): """Board mapper - ``title`` -- board title - ``is_template`` -- is this a real board or a template? - ``columns`` -- list of board columns - ``labels`` -- list of labels for cards - ``comments_allowed`` -- who can comment ? (0 nobody, 1 board members only , 2 all application users) - ``votes_allowed`` -- who can vote ? (0 nobody, 1 board members only , 2 all application users) - ``description`` -- board description - ``visibility`` -- board visibility [0 Private, 1 Public (anyone with the URL can view), 2 Shared (anyone can view it from her home page)] - ``uri`` -- board URI (Universally Unique IDentifier) - ``last_users`` -- list of last users - ``pending`` -- invitations pending for new members (use token) - ``archive`` -- display archive column ? (0 false, 1 true) - ``archived`` -- is board archived ? """ using_options(tablename='board') title = Field(Unicode(255)) is_template = Field(Boolean, default=False) columns = OneToMany('DataColumn', order_by="index", cascade='delete', lazy='subquery') # provisional labels = OneToMany('DataLabel', order_by='index') comments_allowed = Field(Integer, default=1) votes_allowed = Field(Integer, default=1) description = Field(UnicodeText, default=u'') visibility = Field(Integer, default=0) version = Field(Integer, default=0, server_default='0') # provisional board_members = OneToMany('DataMembership', lazy='subquery', order_by=('manager')) uri = Field(Unicode(255), index=True, unique=True) last_users = ManyToOne('DataUser', order_by=('fullname', 'email')) pending = OneToMany('DataToken', order_by='username') history = OneToMany('DataHistory') background_image = Field(Unicode(255)) background_position = Field(Unicode(255)) title_color = Field(Unicode(255)) show_archive = Field(Integer, default=0) archived = Field(Boolean, default=False) # provisional weight_config = OneToOne('DataBoardWeightConfig') def __init__(self, *args, **kwargs): """Initialization. Create board and uri of the board """ super(DataBoard, self).__init__(*args, **kwargs) self.uri = unicode(uuid.uuid4()) @property def template_title(self): manager = self.get_first_manager() if not manager or self.visibility == 0: return self.title return u'{0} ({1})'.format(self.title, manager.fullname) def get_first_manager(self): if not self.board_members: return None potential_manager = self.board_members[-1] return potential_manager.user if potential_manager.manager else None def copy(self): new_data = DataBoard(title=self.title, description=self.description, background_position=self.background_position, title_color=self.title_color, comments_allowed=self.comments_allowed, votes_allowed=self.votes_allowed) # TODO: move to board extension new_data.weight_config = DataBoardWeightConfig( weighting_cards=self.weighting_cards, weights=self.weights) session.add(new_data) session.flush() # TODO: move to board extension for label in self.labels: new_data.labels.append(label.copy()) session.flush() return new_data def get_label_by_title(self, title): return (l for l in self.labels if l.title == title).next() def delete_history(self): for event in self.history: session.delete(event) session.flush() def increase_version(self): self.version += 1 if self.version > 2147483600: self.version = 1 @property def url(self): return "%s/%s" % (urllib.quote_plus( self.title.encode('ascii', 'ignore').replace('/', '_')), self.uri) @classmethod def get_by_id(cls, id): return cls.get(id) @classmethod def get_by_uri(cls, uri): return cls.get_by(uri=uri) def set_background_image(self, image): self.background_image = image or u'' @classmethod def get_all_boards(cls, user): """Return all boards the user is member of.""" query = session.query(cls).join(DataMembership) query = query.filter(cls.is_template == False, DataMembership.user == user) return query.order_by(cls.title) @classmethod def get_shared_boards(cls): query = session.query(cls).filter(cls.visibility == BOARD_SHARED) return query.order_by(cls.title) @classmethod def get_templates_for(cls, user, public_value): q = cls.query q = q.filter(cls.archived == False) q = q.filter(cls.is_template == True) q = q.order_by(cls.title) q1 = q.filter(cls.visibility == public_value) q2 = q.join(DataMembership) q2 = q2.filter(DataMembership.user == user) q2 = q2.filter(cls.visibility != public_value) return q1, q2 def create_column(self, index, title, nb_cards=None, archive=False): return DataColumn.create_column(self, index, title, nb_cards, archive) def delete_column(self, column): if column in self.columns: self.columns.remove(column) def create_label(self, title, color): label = DataLabel(title=title, color=color) self.labels.append(label) session.flush() return label ############# Membership management; those functions belong to a board extension def delete_members(self): DataMembership.delete_members(self) def has_member(self, user): """Return True if user is member of the board In: - ``user`` -- user to test (DataUser instance) Return: - True if user is member of the board """ return DataMembership.has_member(self, user) def has_manager(self, user): """Return True if user is manager of the board In: - ``user`` -- user to test (DataUser instance) Return: - True if user is manager of the board """ return DataMembership.has_member(self, user, manager=True) def remove_member(self, user): DataMembership.remove_member(board=self, user=user) def change_role(self, user, new_role): DataMembership.change_role(self, user, new_role == 'manager') def add_member(self, user, role='member'): """ Add new member to the board In: - ``new_member`` -- user to add (DataUser instance) - ``role`` -- role's member (manager or member) """ DataMembership.add_member(self, user, role == 'manager') ############# Weight configuration, those functions belong to an extension @property def weights(self): return self.weight_config.weights @weights.setter def weights(self, value): self.weight_config.weights = value @property def weighting_cards(self): return self.weight_config.weighting_cards @weighting_cards.setter def weighting_cards(self, value): self.weight_config.weighting_cards = value def reset_card_weights(self): self.weight_config.reset_card_weights() def total_weight(self): return self.weight_config.total_weight()
class DataCard(Entity): """Card mapper """ using_options(tablename='card') title = Field(UnicodeText) description = Field(UnicodeText, default=u'') votes = OneToMany('DataVote') index = Field(Integer) column = ManyToOne('DataColumn') labels = ManyToMany('DataLabel') comments = OneToMany('DataComment', order_by="-creation_date") assets = OneToMany('DataAsset', order_by="-creation_date") checklists = OneToMany('DataChecklist', order_by="index") members = ManyToMany('DataUser') cover = OneToOne('DataAsset', inverse="cover") author = ManyToOne('DataUser', inverse="my_cards") creation_date = Field(DateTime) due_date = Field(Date) history = OneToMany('DataHistory') weight = Field(Unicode(255), default=u'') def delete_history(self): for event in self.history: session.delete(event) session.flush() @classmethod def create_card(cls, column, title, user): """Create new column In: - ``column`` -- DataColumn, father of the column - ``title`` -- title of the card - ``user`` -- DataUser, author of the card Return: - created DataCard instance """ new_card = cls(title=title, author=user, creation_date=datetime.datetime.utcnow()) column.cards.append(new_card) return new_card @classmethod def delete_card(cls, card): """Delete card Delete a given card and re-index other cards In: - ``card`` -- DataCard instance to delete """ index = card.index column = card.column card.delete() session.flush() # legacy databases may be broken… if index is None: return q = cls.query q = q.filter(cls.index >= index) q = q.filter(cls.column == column) q.update({'index': cls.index - 1}) @classmethod def get_all(cls): query = cls.query.options(subqueryload('labels'), subqueryload('comments')) return query def make_cover(self, asset): """ """ DataCard.get(self.id).cover = asset.data def remove_cover(self): """ """ DataCard.get(self.id).cover = None def remove_board_member(self, member): """Remove member from board""" if member.get_user_data() in self.members: self.members.remove(member.get_user_data()) session.flush()
class DataColumn(Entity): """Column mapper """ using_options(tablename='column') title = Field(Unicode(200)) index = Field(Integer) nb_max_cards = Field(Integer) archive = Field(Boolean, default=False) cards = OneToMany('DataCard', order_by='index', cascade='delete') board = ManyToOne('DataBoard', colname='board_id') @classmethod def create_column(cls, board, index, title, nb_cards=None, archive=False): """Create new column In: - ``board`` -- DataBoard, father of the column - ``index`` -- position in the board - ``title`` -- title of the column Return: - created DataColumn instance """ q = cls.query q = q.filter(cls.index >= index) q = q.filter(cls.board == board) q.update({'index': cls.index + 1}) col = cls(title=title, index=index, board=board, nb_max_cards=nb_cards, archive=archive) session.add(col) session.flush() return col @classmethod def delete_column(cls, column): """Delete column Delete a given column, re-index other columns and delete all cards of the column In: - ``column`` -- DataColumn instance to delete """ index = column.index board = column.board column.delete() session.flush() q = cls.query q = q.filter(cls.index >= index) q = q.filter(cls.board == board) q.update({'index': cls.index - 1}) def reorder(self): for i, card in enumerate(self.cards): card.index = i def get_cards_count(self): q = DataCard.query.filter(DataCard.column_id == self.id) return q.count()
class DataBoard(Entity): """Board mapper - ``title`` -- board title - ``is_template`` -- is this a real board or a template? - ``columns`` -- list of board columns - ``labels`` -- list of labels for cards - ``comments_allowed`` -- who can comment ? (0 nobody, 1 board members only , 2 all application users) - ``votes_allowed`` -- who can vote ? (0 nobody, 1 board members only , 2 all application users) - ``description`` -- board description - ``visibility`` -- board visibility (0 Private, 1 Public) - ``members`` -- list of members (simple members and manager) - ``managers`` -- list of managers - ``uri`` -- board URI (Universally Unique IDentifier) - ``last_users`` -- list of last users - ``pending`` -- invitations pending for new members (use token) - ``archive`` -- display archive column ? (0 false, 1 true) - ``archived`` -- is board archived ? """ using_options(tablename='board') title = Field(Unicode(255)) is_template = Field(Boolean, default=False) columns = OneToMany('DataColumn', order_by="index", cascade='delete') labels = OneToMany('DataLabel', order_by='index') comments_allowed = Field(Integer, default=1) votes_allowed = Field(Integer, default=1) description = Field(UnicodeText, default=u'') visibility = Field(Integer, default=0) version = Field(Integer, default=0, server_default='0') board_members = OneToMany('DataBoardMember', cascade='delete') board_managers = OneToMany('DataBoardManager', cascade='delete') members = AssociationProxy('board_members', 'member', creator=lambda member: DataBoardMember(member=member)) managers = AssociationProxy('board_managers', 'member', creator=lambda member: DataBoardManager(member=member)) uri = Field(Unicode(255), index=True, unique=True) last_users = ManyToOne('DataUser', order_by=('fullname', 'email')) pending = OneToMany('DataToken', order_by='username') history = OneToMany('DataHistory') background_image = Field(Unicode(255)) background_position = Field(Unicode(255)) title_color = Field(Unicode(255)) show_archive = Field(Integer, default=0) archived = Field(Boolean, default=False) weighting_cards = Field(Integer, default=0) weights = Field(Unicode(255), default=u'') @property def template_title(self): if not self.managers or self.visibility == 0: return self.title return u'{0} ({1})'.format(self.title, self.managers[0].fullname) def copy(self): new_data = DataBoard(title=self.title, description=self.description, background_position=self.background_position, title_color=self.title_color, comments_allowed=self.comments_allowed, votes_allowed=self.votes_allowed, weighting_cards=self.weighting_cards, weights=self.weights) session.flush() # TODO: move to board extension for label in self.labels: new_data.labels.append(label.copy()) session.flush() return new_data def get_label_by_title(self, title): return (l for l in self.labels if l.title == title).next() def delete_members(self): for member in self.board_members: session.delete(member) session.flush() def delete_history(self): for event in self.history: session.delete(event) session.flush() def increase_version(self): self.version += 1 if self.version > 2147483600: self.version = 1 @property def url(self): return urllib.quote_plus( "%s/%s" % (self.title.encode('ascii', 'ignore'), self.uri), '/' ) def __init__(self, *args, **kwargs): """Initialization. Create board and uri of the board """ super(DataBoard, self).__init__(*args, **kwargs) self.uri = unicode(uuid.uuid4()) @classmethod def get_by_id(cls, id): return cls.get(id) @classmethod def get_by_uri(cls, uri): return cls.get_by(uri=uri) def has_member(self, user): """Return True if user is member of the board In: - ``user`` -- user to test (User instance) Return: - True if user is member of the board """ return user.data in self.members def remove_member(self, board_member): board_member.delete() def has_manager(self, user): """Return True if user is manager of the board In: - ``user`` -- user to test (User instance) Return: - True if user is manager of the board """ return user.data in self.managers def remove_manager(self, board_member): obj = DataBoardManager.get_by(board=self, member=board_member.get_user_data()) if obj is not None: obj.delete() self.remove_member(board_member) def change_role(self, board_member, new_role): obj = DataBoardManager.get_by(board=self, member=board_member.get_user_data()) if new_role == 'manager': if obj is None: obj = DataBoardManager(board=self, member=board_member.get_user_data()) session.add(obj) else: if obj is not None: obj.delete() def last_manager(self, member): """Return True if member is the last manager of the board""" return member.role == 'manager' and len(self.managers) == 1 def add_member(self, new_member, role='member'): """ Add new member to the board In: - ``new_member`` -- user to add (DataUser instance) - ``role`` -- role's member (manager or member) """ self.board_members.append(DataBoardMember(member=new_member.data)) if role == 'manager': self.managers.append(new_member.data) session.flush() def get_pending_users(self): emails = [token.username for token in self.pending] return DataUser.query.filter(DataUser.email.in_(emails)) def set_background_image(self, image): self.background_image = image or u'' @classmethod def get_all_board_ids(cls): return session.query(cls.id).filter_by(is_template=False).order_by(cls.title) @classmethod def get_templates_for(cls, user_username, user_source, public_value): q = cls.query q = q.filter(cls.archived == False) q = q.filter(cls.is_template == True) q = q.order_by(cls.title) q1 = q.filter(cls.visibility == public_value) q2 = q.join(DataBoardManager) q2 = q2.filter(DataBoardManager.user_username == user_username) q2 = q2.filter(DataBoardManager.user_source == user_source) q2 = q2.filter(cls.visibility != public_value) return q1, q2 def create_column(self, index, title, nb_cards=None, archive=False): return DataColumn.create_column(self, index, title, nb_cards, archive) def create_label(self, title, color): label = DataLabel(title=title, color=color) self.labels.append(label) session.flush() return label
class Artigo(Entity): using_options(tablename='artigo_lei') lei = ManyToOne('Lei') numero = Field(Unicode(32)) paragrafos = OneToMany('Paragrafo') incisos = OneToMany('Inciso')
class User(Entity, ShallowCopyMixin): first_name = Field(Unicode(64), index=True) last_name = Field(Unicode(64), index=True) username = Field(Unicode(64), unique=True, index=True) _password = Field(Unicode(64), colname='password', synonym='password') email = Field(Unicode(64), index=True) bio = Field(Unicode(512)) signup_date = Field(DateTime, default=datetime.utcnow) location = Field(Unicode(256)) recipes = OneToMany('Recipe', inverse='author', order_by='-last_updated') user_settings = OneToMany( 'UserSetting', cascade='all, delete-orphan', collection_class=attribute_mapped_collection('name')) settings = AssociationProxy( 'user_settings', 'value', creator=lambda name, value: UserSetting(name=name, value=value)) def __init__(self, *args, **kwargs): super(User, self).__init__(*args, **kwargs) UserSetting.init_defaults(self) @property def full_name(self): return "%s %s" % (self.first_name or '', self.last_name or '') @property def printed_name(self): if self.full_name.strip(): return self.full_name.strip() return self.username @property def printed_first_name(self): if self.first_name and self.first_name.strip(): return self.first_name.strip() return self.username @property def abbreviated_name(self): if self.first_name and self.last_name: return "%s %s." % (self.first_name, self.last_name[0]) return self.username @property def password(self): return self._password @password.setter # noqa def password(self, v): self._password = self.__hash_password__(v) @property def published_recipes(self): return filter(lambda r: r.state == "PUBLISHED", self.recipes) @property def drafts(self): return filter( lambda r: r.state == "DRAFT" and r.published_version is None, self.recipes) @property def gravatar(self): return 'https://www.gravatar.com/avatar/%s?d=https://draughtcraft.com/images/glass-square.png' % ( md5(self.email.strip().lower()).hexdigest()) @classmethod def __hash_password__(cls, plain, salt=None): if salt is None: salt = b64encode(urandom(6)) if ':' not in salt: salt = '%s:' % salt salt = salt.split(':')[0] return '%s:%s' % ( salt, b64encode(PBKDF2(plain, salt, iterations=16000).read(32))) @classmethod def validate(cls, username, password): # Lookup the user user = cls.get_by(username=username) if user: salt = user.password.split(':')[0] pbk = cls.__hash_password__(password, salt) # If PBKDF2 matches... match = cls.query.filter( and_(cls.username == username, cls.password == pbk)).first() if match is not None: return match # Otherwise the user might have a sha256 password salt = getattr(getattr(conf, 'session', None), 'password_salt', 'example') sha = sha256(password + salt).hexdigest() # If sha256 matches... match = cls.query.filter( and_(cls.username == username, cls.password == sha)).first() if match is not None: # Overwrite to use PBKDF2 in the future user.password = password return match
class Ticket(Entity): using_options(tablename=tname) using_table_options(mysql_engine='InnoDB', mysql_charset='utf8') # Athena username requestor = Field(Unicode(255), index=True) # Locker name locker = Field(Unicode(255), index=True) # Hostname involved hostname = Field(Unicode(255), index=True) # path path = Field(Unicode(255)) # "open" or "moira" or "dns" or "resolved" state = Field(Unicode(32)) rtid = Field(Integer) # Purpose purpose = Field(UnicodeText()) events = OneToMany('Event', order_by='timestamp') @staticmethod def create(locker, hostname, path, requestor=None, purpose=""): if requestor is None: requestor = auth.current_user() t = Ticket(requestor=requestor, hostname=hostname, locker=locker, path=path, state="open", purpose=purpose) session.flush() t.addEvent(type='request', state="open", target='us') return t def addEvent(self, type, state, by=None, target=None, subject=None, body=None): if by is None: by = auth.current_user() Event(ticket=self, type=type, target=target, subject=subject, body=body, by=by) if state != self.state: self.state = state pat = "%s's %s changed the ticket re: %s to %s" else: pat = "%s's %s left the ticket re: %s as %s" try: url = "%s%s" % (tg.request.host_url, tg.url('/queue')) except: # Default to something sane if we're not in the context of a request url = "https://pony.scripts.mit.edu:444/queue" log.zwrite(pat % (by, type, self.hostname, state), instance=self.id, zsig=url) @staticmethod def all(): return Ticket.query.all()
class DataChecklist(Entity): using_options(tablename='checklists') title = Field(Unicode(255)) items = OneToMany('DataChecklistItem', order_by='index', inverse='checklist', collection_class=ordering_list('index')) card = ManyToOne('DataCard') author = ManyToOne('DataUser') index = Field(Integer) @classmethod def get_by_card(cls, card): q = cls.query q = q.filter_by(card=card) q = q.order_by(cls.index) return q.all() def update(self, other): self.title = other.title self.index = other.index for item in other.items: self.items.append( DataChecklistItem(title=item.title, index=item.index, done=False)) database.session.flush() def __unicode__(self): titles = [item.title for item in self.items if item.title] if self.title: titles.insert(0, self.title) return u'\n'.join(titles) def to_indexable(self): return unicode(self) def add_item_from_str(self, text): item = DataChecklistItem.new_from_str(text) return self.add_item(item) def add_item(self, item): self.items.append(item) return item def insert_item(self, index, item): self.items.insert(index, item) def remove_item(self, item): self.items.remove(item) def delete_item(self, item): self.remove_item(item) item.delete() def purge(self): for item in self.items: item.delete() @staticmethod def total_items(card): return DataChecklistItem.total_items(card) @staticmethod def total_items_done(card): return DataChecklistItem.total_items_done(card)
class Recipe(Entity, DeepCopyMixin, ShallowCopyMixin): TYPES = ('MASH', 'EXTRACT', 'EXTRACTSTEEP', 'MINIMASH') MASH_METHODS = ('SINGLESTEP', 'TEMPERATURE', 'DECOCTION', 'MULTISTEP') STATES = ('DRAFT', 'PUBLISHED') type = Field(Enum(*TYPES, native_enum=False), default='MASH', index=True) name = Field(Unicode(256), index=True) gallons = Field(Float, default=5) boil_minutes = Field(Integer, default=60) notes = Field(UnicodeText) creation_date = Field(DateTime, default=datetime.utcnow) last_updated = Field(DateTime, default=datetime.utcnow, index=True) # Cached statistics _og = Field(Float, colname='og') _fg = Field(Float, colname='fg') _abv = Field(Float, colname='abv') _srm = Field(Integer, colname='srm') _ibu = Field(Integer, colname='ibu') mash_method = Field(Enum(*MASH_METHODS, native_enum=False), default='SINGLESTEP') mash_instructions = Field(UnicodeText) state = Field(Enum(*STATES, native_enum=False), default='DRAFT') current_draft = ManyToOne('Recipe', inverse='published_version') published_version = OneToOne('Recipe', inverse='current_draft', order_by='creation_date') copied_from = ManyToOne('Recipe', inverse='copies') copies = OneToMany('Recipe', inverse='copied_from', order_by='creation_date') views = OneToMany('RecipeView', inverse='recipe', cascade='all, delete-orphan') additions = OneToMany('RecipeAddition', inverse='recipe', cascade='all, delete-orphan') fermentation_steps = OneToMany('FermentationStep', inverse='recipe', order_by='step', cascade='all, delete-orphan') slugs = OneToMany('RecipeSlug', inverse='recipe', order_by='id', cascade='all, delete-orphan') style = ManyToOne('Style', inverse='recipes') author = ManyToOne('User', inverse='recipes') __ignored_properties__ = ('current_draft', 'published_version', 'copies', 'views', 'creation_date', 'state', '_og', '_fg', '_abv', '_srm', '_ibu') def __init__(self, **kwargs): super(Recipe, self).__init__(**kwargs) if kwargs.get('name') and not kwargs.get('slugs'): self.slugs.append(entities.RecipeSlug(name=kwargs['name'])) def duplicate(self, overrides={}): """ Used to duplicate a recipe. An optional hash of `overrides` can be specified to override the default copied values, e.g., dupe = user.recipes[0].duplicate({'author': otheruser}) assert dupe.author == otheruser """ # Make a deep copy of the instance copy = deepcopy(self) # For each override... for k, v in overrides.items(): # If the key is already defined, and is a list (i.e., a ManyToOne) if isinstance(getattr(copy, k, None), list): # # Delete each existing entity, because we're about to # override the value. # for i in getattr(copy, k): i.delete() # Set the new (overridden) value setattr(copy, k, v) return copy def draft(self): """ Used to create a new, unpublished draft of a recipe. """ if self.current_draft: self.current_draft.delete() self.current_draft = None return self.duplicate({'published_version': self}) def publish(self): """ Used to publish an orphan draft as a new recipe. """ assert self.state == 'DRAFT', "Only drafts can be published." # If this recipe is a draft of another, merge the changes back in if self.published_version: return self.merge() # Otherwise, just set the state to PUBLISHED self.state = 'PUBLISHED' # Store cached values self._og = self.calculations.og self._fg = self.calculations.fg self._abv = self.calculations.abv self._srm = self.calculations.srm self._ibu = self.calculations.ibu # Generate a new slug if the existing one hasn't changed. existing = [slug.slug for slug in self.slugs] if entities.RecipeSlug.to_slug(self.name) not in existing: self.slugs.append(entities.RecipeSlug(name=self.name)) def merge(self): """ Used to merge a drafted recipe's changes back into its source. """ # Make sure this is a draft with a source recipe assert self.state == 'DRAFT', "Only drafts can be merged." source = self.published_version assert source is not None, \ "This recipe doesn't have a `published_version`." # Clone the draft onto the published version self.__copy_target__ = self.published_version deepcopy(self) # Delete the draft self.delete() @property def calculations(self): return Calculations(self) @property def efficiency(self): if self.author: return self.author.settings['brewhouse_efficiency'] return .75 @property def unit_system(self): if request.context['metric'] is True: return 'METRIC' return 'US' @property def metric(self): return self.unit_system == 'METRIC' @property def liters(self): liters = to_metric(*(self.gallons, "GALLON"))[0] return round(liters, 3) @liters.setter # noqa def liters(self, v): gallons = to_us(*(v, "LITER"))[0] self.gallons = gallons def _partition(self, additions): """ Partition a set of recipe additions by ingredient type, e.g.,: _partition([grain, grain2, hop]) {'Fermentable': [grain, grain2], 'Hop': [hop]} """ p = {} for a in additions: p.setdefault(a.ingredient.__class__, []).append(a) return p def _percent(self, partitions): """ Calculate percentage of additions by amount within a set of recipe partitions. e.g., _percent({'Fermentable': [grain, grain2], 'Hop': [hop]}) {grain : .75, grain2 : .25, hop : 1} """ percentages = {} for type, additions in partitions.items(): total = sum([addition.amount for addition in additions]) for addition in additions: if total: percentages[addition] = float( addition.amount) / float(total) else: percentages[addition] = 0 return percentages @property def mash(self): return self._partition([a for a in self.additions if a.step == 'mash']) @property def boil(self): return self._partition([a for a in self.additions if a.step == 'boil']) @property def fermentation(self): return self._partition( [a for a in self.additions if a.step == 'fermentation']) def contains(self, ingredient, step): if step not in ('mash', 'boil', 'fermentation'): return False additions = getattr(self, step) for a in sum(additions.values(), []): if a.ingredient == ingredient: return True return False @property def next_fermentation_step(self): """ The next available fermentation step for a recipe. e.g., if temperature/length is already defined for "PRIMARY" a fermentation period, returns "SECONDARY". If "SECONDARY" is already defined, returns "TERTIARY". Always returns one of `model.FermentationStep.STEPS`. """ total = len(self.fermentation_steps) return {1: 'SECONDARY', 2: 'TERTIARY'}.get(total, None) def url(self, public=True): """ The URL for a recipe. """ return '/recipes/%s/%s/%s' % ( ('%x' % self.id).lower(), self.slugs[-1].slug, '' if public else 'builder') @property def printable_type(self): return { 'MASH': 'All Grain', 'EXTRACT': 'Extract', 'EXTRACTSTEEP': 'Extract w/ Steeped Grains', 'MINIMASH': 'Mini-Mash' }[self.type] def touch(self): self.last_updated = datetime.utcnow() @property def og(self): return self._og @property def fg(self): return self._fg @property def abv(self): return self._abv @property def srm(self): return self._srm @property def ibu(self): return self._ibu def to_xml(self): from draughtcraft.lib.beerxml import export kw = { 'name': self.name, 'type': { 'MASH': 'All Grain', 'MINIMASH': 'Partial Mash' }.get(self.type, 'Extract'), 'brewer': self.author.printed_name if self.author else 'Unknown', 'batch_size': self.liters, 'boil_size': self.liters * 1.25, 'boil_time': self.boil_minutes, 'notes': self.notes, 'fermentation_stages': len(self.fermentation_steps), } hops = [a.to_xml() for a in self.additions if a.hop] fermentables = [a.to_xml() for a in self.additions if a.fermentable] yeast = [a.to_xml() for a in self.additions if a.yeast] extras = [a.to_xml() for a in self.additions if a.extra] kw['hops'] = hops kw['fermentables'] = fermentables kw['yeasts'] = yeast kw['miscs'] = extras kw['mash'] = [] kw['waters'] = [] if self.style is None: kw['style'] = export.Style(name='', category='No Style Chosen', type='None', category_number=0, style_letter='', og_min=0, og_max=0, ibu_min=0, ibu_max=0, color_min=0, color_max=0, fg_min=0, fg_max=0) else: kw['style'] = self.style.to_xml() if self.type != 'EXTRACT': kw['efficiency'] = self.efficiency * 100.00 for stage in self.fermentation_steps: if stage.step == 'PRIMARY': kw['primary_age'] = stage.days kw['primary_temp'] = stage.celsius if stage.step == 'SECONDARY': kw['secondary_age'] = stage.days kw['secondary_temp'] = stage.celsius if stage.step == 'TERTIARY': kw['tertiary_age'] = stage.days kw['tertiary_temp'] = stage.celsius return export.Recipe(**kw).render() def __json__(self): from draughtcraft.templates.helpers import alphanum_key def inventory(cls, types=[]): return sorted([ f.__json__() for f in cls.query.all() if not types or (types and f.type in types) ], key=lambda f: alphanum_key(f['name'])) # # Attempt to look up the preferred calculation method for the # recipe's author. # ibu_method = 'tinseth' user = self.author if user: ibu_method = user.settings.get('default_ibu_formula', 'tinseth') return { # Basic attributes 'name': self.name, 'author': self.author.username if self.author else '', 'style': self.style.id if self.style else None, 'gallons': self.gallons, # Ingredients 'mash': filter(lambda a: a.step == 'mash', self.additions), 'boil': filter(lambda a: a.step == 'boil', self.additions), 'fermentation': filter(lambda a: a.step == 'fermentation', self.additions), 'ibu_method': ibu_method, 'efficiency': self.efficiency, # Inventory 'inventory': { 'malts': inventory(entities.Fermentable, ('MALT', 'GRAIN', 'ADJUNCT', 'SUGAR')), 'extracts': inventory(entities.Fermentable, ('EXTRACT', )), 'hops': inventory(entities.Hop), 'yeast': inventory(entities.Yeast), 'extras': inventory(entities.Extra) }, # Extras 'mash_method': self.mash_method, 'mash_instructions': self.mash_instructions, 'boil_minutes': self.boil_minutes, 'fermentation_steps': self.fermentation_steps, 'notes': self.notes, 'metric': self.metric }
class Movie(Entity): using_options(tablename='movies') title = Field(Unicode(60), required=True) short_description = Field(Unicode(512)) releasedate = Field(Date) # # All relation types are covered with their own editor # director = ManyToOne('Person') cast = OneToMany('Cast') visitor_reports = OneToMany('VisitorReport') tags = ManyToMany('Tag') genre = Field(Unicode(15)) rating = Field(camelot.types.Rating()) # end short movie definition # # Camelot includes custom sqlalchemy types, like Image, which stores an # image on disk and keeps the reference to it in the database. # cover = Field(camelot.types.Image(upload_to='covers')) # # Or File, which stores a file in the upload_to directory and stores a # reference to it in the database # script = Field(camelot.types.File(upload_to='script')) description = Field(camelot.types.RichText) # # Using a ColumnProperty, an sql query can be assigned to a field # @ColumnProperty def total_visitors(self): return sql.select([sql.func.sum(VisitorReport.visitors)], VisitorReport.movie_id==self.id) # # Normal python properties can be used as well, but then the # delegate needs be specified # @property def visitors_chart(self): # # Container classes are used to transport chunks of data between # the model thread and the gui thread, in this case a chart # from camelot.container.chartcontainer import BarContainer return BarContainer(range(len(self.visitor_reports)), [vr.visitors for vr in self.visitor_reports]) # # Each Entity subclass can have a subclass of EntityAdmin as # its inner class. The EntityAdmin class defines how the Entity # class will be displayed in the GUI. Its behavior can be steered # by specifying some class attributes # # To fully customize the way the entity is visualized, the EntityAdmin # subclass should overrule some of the EntityAdmin's methods # class Admin(EntityAdmin): # the list_display attribute specifies which entity attributes should # be visible in the table view list_display = ['cover', 'title', 'releasedate', 'rating',] lines_per_row = 5 # define filters to be available in the table view list_filter = ['genre', ComboBoxFilter('director.full_name')] # if the search function needs to look in related object attributes, # those should be specified within list_search list_search = ['director.full_name'] # the form_display attribute specifies which entity attributes should be # visible in the form view form_display = TabForm([ ('Movie', Form([ HBoxForm([WidgetOnlyForm('cover'), ['title', 'rating']]), 'short_description', 'releasedate', 'director', 'script', 'genre', 'description',], columns = 2)), ('Cast', WidgetOnlyForm('cast')), ('Visitors', WidgetOnlyForm('visitors_chart')), ('Tags', WidgetOnlyForm('tags')) ]) # create a list of actions available for the user on the form view # those actions will be executed within the model thread # form_actions = [('Burn DVD', burn_to_disk)] # # additional attributes for a field can be specified in the # field_attributes dictionary # field_attributes = dict(cast=dict(create_inline=True), genre=dict(choices=genre_choices, editable=lambda o:bool(o.title and len(o.title))), releasedate=dict(background_color=lambda o:ColorScheme.orange_1 if o.releasedate and o.releasedate < datetime.date(1920,1,1) else None), visitors_chart=dict(delegate=delegates.ChartDelegate), rating=dict(tooltip='''<table> <tr><td>1 star</td><td>Not that good</td></tr> <tr><td>2 stars</td><td>Almost good</td></tr> <tr><td>3 stars</td><td>Good</td></tr> <tr><td>4 stars</td><td>Very good</td></tr> <tr><td>5 stars</td><td>Awesome !</td></tr> </table>'''), smiley=dict(delegate=delegates.SmileyDelegate), script=dict(remove_original=True)) def __unicode__(self): return self.title or ''
class Style(Entity, ShallowCopyMixin): TYPES = [ 'LAGER', 'ALE', 'MEAD', 'CIDER' ] uid = Field(Unicode(32), unique=True) name = Field(Unicode(256), index=True) url = Field(Unicode(256)) # Gravities min_og = Field(Float) max_og = Field(Float) min_fg = Field(Float) max_fg = Field(Float) # IBU min_ibu = Field(Integer) max_ibu = Field(Integer) # SRM min_srm = Field(Integer) max_srm = Field(Integer) # ABV min_abv = Field(Float) max_abv = Field(Float) category = Field(Unicode(64)) category_number = Field(Integer) style_letter = Field(Unicode(1)) type = Field(Enum(*TYPES, native_enum=False)) recipes = OneToMany('Recipe', inverse='style') def defined(self, statistic): if statistic not in ( 'og', 'fg', 'abv', 'srm', 'ibu' ): raise InvalidStatistic('Invalid statistic, %s' % statistic) minimum = getattr(self, 'min_%s' % statistic) maximum = getattr(self, 'max_%s' % statistic) return minimum is not None and maximum is not None def matches(self, recipe, statistic): if statistic not in ( 'og', 'fg', 'abv', 'srm', 'ibu' ): raise InvalidStatistic('Invalid statistic, %s' % statistic) minimum = getattr(self, 'min_%s' % statistic) maximum = getattr(self, 'max_%s' % statistic) if minimum is None or maximum is None: return False actual = getattr(recipe.calculations, statistic) if actual <= maximum and actual >= minimum: return True return False def to_xml(self): from draughtcraft.lib.beerxml import export kw = { 'name': self.name, 'category': self.category, 'category_number': self.category_number, 'style_letter': self.style_letter, 'style_guide': 'BJCP', 'type': self.type.capitalize(), 'og_min': self.min_og, 'og_max': self.max_og, 'fg_min': self.min_fg, 'fg_max': self.max_fg, 'ibu_min': self.min_ibu, 'ibu_max': self.max_ibu, 'color_min': self.min_srm, 'color_max': self.max_srm, 'abv_min': self.min_abv, 'abv_max': self.max_abv } return export.Style(**kw)
class User(Entity): """Mapper for the `user' entity that is the same table used by wordpress""" using_options(tablename='wp_users') id = Field(Integer, primary_key=True) name = Field(Unicode(64), colname='user_nicename') nickname = Field(Unicode(64), colname='display_name') username = Field(Unicode(64), colname='user_login') password = Field(Unicode(256), colname='user_pass') email = Field(Unicode(64), colname='user_email') user_activation_key = Field(Unicode(60)) receive_sms = Field(Boolean) receive_email = Field(Boolean) buzzes = OneToMany('Buzz') contribs = OneToMany('Contrib') follows = OneToMany('UserFollow') creation_date = Field( DateTime, colname='user_registered', default=datetime.now) status = Field( Boolean, colname='user_status', default=True) @before_insert def set_defaults(self): """Sets default values to fields that depends on other field values to be set before inserting""" if not self.name: self.name = unicode(self.username) if not self.nickname: self.nickname = unicode(self.name) @before_insert def hash_password(self): """Converts the password field into a phpass hashed string""" print "\n\nGRAVANDO SENHA NOVA 1:",self.password hasher = phpass.PasswordHash(8, False) self.password = hasher.hash_password(self.password) print "GRAVANDO SENHA NOVA 2:",self.password,"\n\n" def set_password(self, passwd): """Updates the user's password This method does _not_ update the database itself, please call the '''session.commit()''' method. """ self.password = passwd self.hash_password() def has_roles(self, roles): """Returns True if the current user has one of the given roles. This method uses the `OR' logic. If the user has _AT LEAST ONE_ of the specified roles, it'll return True. """ # Making sure we're handling lists or tuples if not isinstance(roles, (list, tuple)): raise TypeError('has_roles() does not handle any thing but ' 'lists and tuples') # So, let's query for the usermeta object. Once we don't handle # wordpress registered rows, let's make sure that it will never # raise an unexpected exception. query = UserMeta.query.filter_by( user_id=self.id, meta_key='wp_capabilities') try: meta = query.one() except NoResultFound: return False # Now it is tome to make sure that the user has _AT LEAST ONE_ # of the roles specified in the `roles' param. return True in [i.lower() in meta.meta_value for i in roles] def public_dict(self): """Returns all public items about the user in a dict format""" # It's easier to create a new dict than selecting each public # field from the `.to_dict()' return. return dict( id=self.id, name=self.name, nickname=self.nickname, email=self.email, display_name=self.display_name, avatar_url=self.avatar_url, creation_date=self.creation_date, facebook=self.get_meta('facebook'), twitter=self.get_meta('twitter') ) def public_json(self): """Returns the same content as `public_dict()', but in JSON format""" return dumps(self.public_dict()) @property def avatar_url(self): """Returns the avatar image of this user or the default one""" fname = self.get_meta('avatar') return fname and Upload.imageset.url(fname) or \ url_for('static', filename='img/avatar.png') @property def display_name(self): """Just a shortcut to decide which value should be exposed to identify a user""" return self.nickname or self.name def get_meta(self, key): """Returns a value of a meta var of a user""" try: return UserMeta.query \ .filter_by(user_id=self.id, meta_key=key) \ .one() \ .meta_value except NoResultFound: return None def set_meta(self, key, value): """Set a meta value for the user instance. This method does not flush to the database! So you have to call the `session.commit()' method after calling it.""" meta = get_or_create(UserMeta, user_id=self.id, meta_key=key)[0] meta.meta_value = value def metadata(self): """Returns _all_ metadata set for an user instance. This method is here to clean a mess created by using the wordpress scheme. Because of this choice, we have user metadata split between the `wp_users' and `wp_user_meta' tables (and, sometimes, coming from a social network). So, to fix this situation, I've at least wrote this method to provide an encapsulated method to get all metadata associated with an user instance. Be careful exposing this method to the end users. It will return all the information about an user, including his/her (hashed) password string, mail address, etc. """ data = self.to_dict() data.update(dict((i.meta_key, i.meta_value) for i in UserMeta.query.filter_by(user_id=self.id).all())) return data
class Inciso(Entity): using_options(tablename='inciso_artigo') artigo = ManyToOne('Artigo') numero = Field(Unicode(32)) letras = OneToMany('Letra')
class DataMembership(Entity): using_options(tablename='membership') board = ManyToOne('DataBoard', ondelete='cascade', required=True) user = ManyToOne(DataUser, ondelete='cascade', required=True) card_memberships = OneToMany(DataCardMembership, cascade='delete, delete-orphan') # provisional, for notifications until they are refactored cards = association_proxy('card_memberships', 'card') manager = Field(Boolean, default=False, nullable=False) notify = Field(Integer, default=NOTIFY_MINE) using_table_options( sa.UniqueConstraint('board_id', 'user_username', 'user_source', name='membership_ix')) @classmethod def get_for_card(cls, card): return cls.query.join(DataCardMembership).filter( DataCardMembership.card == card) @classmethod def search(cls, board, user): return cls.get_by(board=board, user=user) # at most one @classmethod def subscribers(cls): return cls.query.filter( sa.or_(DataMembership.notify == NOTIFY_ALL, DataMembership.notify == NOTIFY_MINE)) @staticmethod def favorites_for(card): query = database.session.query(DataUser) query = query.join(DataMembership) # In the future, cards will be linked to boards directly, so demeter won't be hurt anymore query = query.filter(DataMembership.board == card.column.board) query = query.outerjoin(DataCardMembership) query = query.group_by(DataUser) query = query.order_by(func.count(DataUser.username).desc()) return query @classmethod def add_card_members_from_emails(cls, card, emails): """Provisional: will take memberships instead of emails.""" query = cls.query.join(DataUser).filter( sa.or_(DataUser.email.in_(emails), DataUser.email_to_confirm.in_(emails)), DataMembership.board == card.column.board) memberships = query.all() for membership in memberships: membership.card_memberships.append(DataCardMembership(card=card)) database.session.flush() return memberships @classmethod def remove_card_member(cls, card, username): """Provisional: will take a membership instead of username.""" membership = cls.query.join(DataUser).filter( DataUser.username == username, cls.board == card.column.board).first() if membership: card_membership = DataCardMembership.get_by(card=card, membership=membership) if card_membership: card_membership.delete() @classmethod def add_member(cls, board, user, manager=False): membership = cls(board=board, user=user, manager=manager) database.session.add(membership) database.session.flush() return membership @classmethod def remove_member(cls, board, user): membership = cls.get_by(board=board, user=user) if membership: membership.delete() database.session.flush() @classmethod def has_member(cls, board, user, manager=False): membership = cls.get_by(board=board, user=user) return (bool(membership) and membership.manager) if manager else bool(membership) @classmethod def delete_members(cls, board): cls.query.filter_by(board=board).delete(synchronize_session=False) @classmethod def change_role(cls, board, user, manager): ms = cls.get_by(board=board, user=user) if ms: ms.manager = manager database.session.flush()
class Director(Entity): name = Field(Unicode(60)) movies = OneToMany('Movie', inverse='director') using_options(tablename='directors')