Esempio n. 1
0
class Panorama(BaseMeta):
    id = Column(Integer, primary_key=True)
    type = Column(Choice(['article', 'comment', 'tag', 'group']),
                  nullable=False,
                  index=True)
    event = Column(Choice([
        'add', 'modify', 'move', 'remove', 'survey_start', 'survey_end',
        'article', 'group', 'user', 'file', 'add_rel', 'remove_rel',
        'add_menu', 'remove_menu', 'add_board', 'remove_board', 'new'
    ]),
                   nullable=False,
                   index=True)
    from_group_id = Column(Integer,
                           ForeignKey('Group.uid', ondelete='set null'),
                           nullable=True)
    from_board_id = Column(Integer,
                           ForeignKey('Board.uid', ondelete='set null'),
                           nullable=True)
    user_id = Column(Integer,
                     ForeignKey(
                         'User.uid',
                         ondelete='set null',
                     ),
                     nullable=True)
    target_id = Column(Integer,
                       ForeignKey('Base.uid', ondelete='set null'),
                       nullable=True)
    content = Column(UnicodeText, nullable=False, default=u'')
    created_at = Column(DateTime,
                        nullable=False,
                        default=func.now(),
                        index=True)

    from_group = relationship('Group',
                              primaryjoin='Panorama.from_group_id == \
                              Group.uid')
    from_board = relationship('Board',
                              primaryjoin='Panorama.from_board_id == \
                              Board.uid')
    user = relationship('User', primaryjoin='Panorama.user_id == User.uid')
    target = relationship('Base', primaryjoin='Panorama.target_id == Base.uid')
Esempio n. 2
0
class GroupMenu(BaseMeta):
    GROUP_MENU_TYPES = (u'board', u'link', u'text', u'indent', u'outdent', )
    __table_args__ = (
        UniqueConstraint('group_id', 'position'),
    )
    id = Column(Integer, primary_key=True, autoincrement=True)
    group_id = Column(Integer, ForeignKey('Group.uid', ondelete='cascade'),
                      nullable=True)
    menu_type = Column(Choice(GROUP_MENU_TYPES), nullable=False)
    board_uid = Column(Integer, ForeignKey('Board.uid', ondelete='cascade'),
                       nullable=True)
    url = Column(UnicodeText, nullable=False, default=u'')
    name = Column(Unicode(256), nullable=False, default=u'')
    position = Column(Integer, nullable=False, default=0, index=True)

    group = relationship('Group', backref=backref(
        'menus', order_by='GroupMenu.position'))
    board = relationship('Board')
Esempio n. 3
0
class Mention(BaseMeta):
    u'''
    멘션은 글 또는 댓글에서만 할 수 있다.
    [[uid]]로 할 수 있고, 당연히 uid가 달린 것들만 멘션할 수 있다.
    글 제목에서는 멘션할 수 없다.
    멘션하면 멘션 당한 유저에게 알림이 가야 한다.
    알림은 "A 유저가 B 유저를 멘션했습니다" 또는
    "A 유저가 B유저의 글/댓글 C를 멘션했습니다" 정도가 될 것이다.
    '''
    __table_args__ = (UniqueConstraint('mentioning_id', 'target_id'), )
    id = Column(Integer, primary_key=True, autoincrement=True)
    mentioning_user_id = Column(Integer,
                                ForeignKey('User.uid', ondelete='cascade'),
                                nullable=True,
                                index=True)
    mentioning_id = Column(Integer,
                           ForeignKey('Base.uid', ondelete='cascade'),
                           nullable=False,
                           index=True)
    target_id = Column(Integer,
                       ForeignKey('Base.uid', ondelete='cascade'),
                       nullable=False,
                       index=True)
    target_user_id = Column(Integer,
                            ForeignKey('User.uid', ondelete='cascade'),
                            nullable=True,
                            index=True)
    target_type = Column(Choice(['User', 'Article', 'Comment']),
                         nullable=False,
                         index=True)
    created_at = Column(DateTime, default=func.now(), nullable=False)

    mentioning_user = relationship('User',
                                   primaryjoin='User.uid == \
                                   Mention.mentioning_user_id')
    mentioning = relationship('Base',
                              primaryjoin='Base.uid == \
                              Mention.mentioning_id',
                              backref=backref('mentions',
                                              passive_deletes=True))
    target = relationship('Base', primaryjoin='Base.uid == Mention.target_id')
    target_user = relationship('User',
                               primaryjoin='User.uid == \
                               Mention.target_user_id')
Esempio n. 4
0
class Board(UIDMixin, Base, ArticleContainer):
    types = (
        'normal',
        'album',
        'forum',
    )
    types_str = (
        ('normal', u'일반게시판'),
        ('album', u'사진게시판'),
        ('forum', u'포럼게시판'),
    )
    name = Column(Unicode(256), nullable=False)
    board_type = Column('type',
                        Choice(types),
                        nullable=False,
                        default='normal')
    last_updated_at = Column(DateTime, nullable=False, default=func.now())
    admin_id = Column(Integer,
                      ForeignKey('User.uid', ondelete='set null'),
                      nullable=True,
                      default=None)

    _admin = relationship('User',
                          backref='managable_boards',
                          primaryjoin='Board.admin_id == User.uid')
    article_count = Column(Integer, nullable=False, default=0)
    articles = relationship('Article',
                            secondary='BoardAndArticle',
                            primaryjoin='Board.uid == \
                            BoardAndArticle.c.board_id',
                            order_by='Article.uid.desc()')

    group = relationship('Group',
                         secondary=GroupMenu.__table__,
                         primaryjoin='Board.uid == GroupMenu.board_uid',
                         uselist=False)

    @property
    def has_new_article(self):
        return datetime.now() - self.last_updated_at < timedelta(hours=24)

    @property
    def type_str(self):
        return dict(Board.types_str)[self.board_type]

    def get_group(self, session=None):
        from group import GroupMenu
        if session is None:
            session = db
        menu = session.query(GroupMenu).filter_by(board=self).first()
        if menu:
            return menu.group
        return None

    @hybrid_property
    def admin(self):
        if self._admin:
            return self._admin
        if self.group and self.group.admin:
            return self.group.admin
        return None

    @admin.setter
    def set_admin(self, value):
        self._admin = value

    def is_admin(self, user):
        return self.admin == user or self.group.admin == user

    @classmethod
    def from_group(cls, groups):
        if isinstance(groups, (list, tuple)) and not \
           any([not isinstance(e, Group) for e in groups]):
            return db.query(cls).filter(
                cls.group.has(Group.uid.in_([group.uid for group in groups])))
        elif isinstance(groups, Group):
            return groups.boards
        return []
Esempio n. 5
0
class NotificationLog(BaseMeta):
    u'''
    유저에게 알림을 날린 로그.
    타입은 다음과 같은 것들이 있으며, 추가될 수 있다.
    comment: 내가 쓴 글이나 댓글, 그리고 내 프로필 페이지에 댓글이 달림
    tag: 내가 쓴 글이나 업로드한 사진, 또는 내 프로필 페이지에 태그가 달림
    recommend: 내가 쓴 글이나 댓글에 추천이 달림
    article: 내가 즐겨찾기 추가한 게시판이나 모임에 새 글이 올라옴
    mention: 글이나 댓글로 나, 혹은 내가 쓴 글/댓글을 언급함
    '''
    id = Column(Integer, primary_key=True, autoincrement=True)
    type = Column(Choice([
        'comment',
        'tag',
        'recommend',
        'article',
        'mention',
    ]),
                  nullable=False,
                  index=True)
    sub_type = Column(Choice([
        'article',
        'comment',
        'commented_article',
        'favorite',
        'user',
    ]),
                      nullable=False,
                      index=True)
    receiver_id = Column(Integer,
                         ForeignKey('User.uid', ondelete='cascade'),
                         nullable=False)
    target_id = Column(Integer,
                       ForeignKey('Base.uid', ondelete='cascade'),
                       nullable=False)
    '''
    senders 구성:
        list: list of dict:
            sender_id: number
            sender_realname: string
            content: string
            content_id: number. optional.
    '''
    senders = Column(JSONEncodedDict, nullable=False, default={})
    created_at = Column(DateTime, nullable=False, default=func.now())
    last_updated_at = Column(DateTime,
                             nullable=False,
                             default=func.now(),
                             onupdate=func.now(),
                             index=True)

    receiver = relationship('User',
                            primaryjoin='NotificationLog.receiver_id \
                            == User.uid',
                            backref='noties')
    target = relationship('Base',
                          primaryjoin='NotificationLog.target_id == \
                          Base.uid')

    @property
    def url(self):
        try:
            return url_for('main.go_uid',
                           uid=self.senders['list'][0]['content_id'])
        except KeyError:
            return url_for('main.go_uid', uid=self.target_id)

    @property
    def target_name(self):
        target = db.query(Base).get(self.target_id)
        return target.title if isinstance(target, Article) else \
            target.content if isinstance(target, Comment) else \
            target.realname if isinstance(target, User) else \
            target.group.name + ' - ' + target.name if isinstance(target, Board) else \
            target.name

    @property
    def jsonify(self):
        u'''
        알림을 redis로 바로 뿌릴 수 있는 형태의 json으로 리턴
        '''
        result = {
            'target_id':
            self.target_id,
            'target_name':
            self.target_name,
            'receiver_id':
            self.receiver_id,
            'url':
            self.url,
            'senders': [{
                'uid': s['sender_id'],
                'realname': s['sender_realname'],
            } for s in self.senders['list']],
            'content':
            self.senders['list'][0]['content'],
            'type':
            self.type,
            'sub_type':
            self.sub_type,
        }
        return json.dumps(result)
Esempio n. 6
0
class User(UIDMixin, Base):
    CLASSES = OrderedDict([
        ('bachelor', u'학사 재학'),
        ('bechelor_degree', u'학사 졸업'),
        ('master', u'석사 재학'),
        ('master_degree', u'석사 졸업'),
        ('phd', u'박사 재학'),
        ('phd_degree', u'박사 졸업'),
        ('major', u'복수전공'),
        ('minor', u'부전공'),
        ('professor', u'교수'),
        ('staff', u'직원'),
        ('others', u'기타'),
        ('exchange', u'교환학생'),
    ])
    PHONES = OrderedDict([
        ('home', u'집'),
        ('cell', u'휴대전화'),
        ('office', u'사무실'),
        ('lab', u'연구실'),
        ('prof.room', u'교수실'),
    ])
    username = Column(Unicode(255), unique=True)
    _password = Column('password', Unicode(512), default=u'')
    salt = Column(Unicode(256), nullable=False, default=u'')
    realname = Column(Unicode(255), nullable=False, default=u'', index=True)
    email = Column(Unicode(254), nullable=False, default=u'')
    phone = Column(Unicode(20), nullable=False, default=u'')
    birthday = Column(Date, nullable=True, default=None, index=True)
    bs_year = Column(Integer, nullable=True, default=None, index=True)
    ms_year = Column(Integer, nullable=True, default=None, index=True)
    phd_year = Column(Integer, nullable=True, default=None, index=True)
    info = Column(JSONEncodedDict, nullable=False, default={})
    classes = Column(Unicode(255),
                     nullable=False,
                     default=u'bachelor',
                     index=True)
    state = Column(Choice([u'pending', u'normal', u'admin']),
                   nullable=False,
                   default=u'pending')
    photo_id = Column(Integer,
                      ForeignKey('File.uid',
                                 ondelete='set null',
                                 use_alter=True,
                                 name='User_photo_id_fkey'),
                      nullable=True)

    photo = relationship('File',
                         primaryjoin='User.photo_id == File.uid',
                         post_update=True)
    accessible_groups = relationship('Group',
                                     secondary=AccessibleGroup.__table__,
                                     primaryjoin='User.uid == \
                                     AccessibleGroup.user_id')

    def __init__(self, username, password, **kw):
        super(UIDMixin, self).__init__()
        super(Base, self).__init__()
        self.salt = reduce(
            lambda x, y: x + y,
            [random.choice(string.printable)
             for _ in range(256)]).decode('utf-8')
        self.username = self.sid = username
        self.password = password
        self.realname = kw['realname']
        self.email = kw['email']
        self.phone = kw['phone']
        self.birthday = kw['birthday']
        self.classes = kw['classes']
        self.state = kw['state']
        for degree in 'bs ms phd'.split(' '):
            if kw['%s_number' % degree]:
                try:
                    setattr(self, '%s_year' % degree,
                            int(kw['%s_number' % degree].split('-')[0]))
                except:
                    pass
        self.info = {}
        for info_field in ('bs_number', 'ms_number', 'phd_number', 'classes',
                           'graduate'):
            self.info[info_field] = kw[info_field]

    @hybrid_property
    def password(self):
        return self._password

    @password.setter
    def set_password(self, value):
        self._password = self._make_password(value)

    @property
    def recommended_uids(self):
        return [r.target_id for r in self.recommended]

    def _make_password(self, value):
        hash_value = self.__dict__['salt'] + value + self.__dict__['salt']
        return hashlib.sha3_512(hash_value.encode('utf-8')).hexdigest().\
            decode('utf-8')

    def correct_password(self, value):
        return self.password == self._make_password(value)

    @property
    def favorite_uids(self):
        return [b.target_id for b in self.favorites]

    @property
    def classes_str(self):
        try:
            return User.CLASSES[self.info['classes'][0]]
        except:
            return u''

    @hybrid_property
    def profile(self):
        return self.info.get('profile', u'')

    @profile.setter
    def set_profile(self, value):
        self.info['profile'] = value

    @hybrid_property
    def signature(self):
        return self.info.get('signature', u'')

    @signature.setter
    def set_signature(self, value):
        self.info['signature'] = value

    @property
    def unread_messages(self):
        return [m for m in self.received_messages if not m.is_read]
Esempio n. 7
0
class Article(UIDMixin, Base, Anonymity):
    RENDER_TYPES = ('html<br />', 'html', 'text')

    uid = Column(Integer, ForeignKey('Base.uid'), primary_key=True)
    is_notice = Column(Boolean, nullable=False, default=False, index=True)
    title = Column(UnicodeText, nullable=False, default=u'')
    _content = Column('content', UnicodeText, nullable=False, default=u'')
    render_type = Column(Choice(RENDER_TYPES),
                         nullable=False,
                         default='html<br />')
    author_id = Column(Integer,
                       ForeignKey('User.uid'),
                       nullable=True,
                       default=None)
    anonymous = Column(JSONEncodedDict, nullable=True, default=None)
    view_count = Column(Integer, nullable=False, default=0)
    created_at = Column(DateTime, nullable=False, default=func.now())
    parent_article_id = Column(Integer,
                               ForeignKey('Article.uid', ondelete='set null'),
                               nullable=True,
                               index=True)
    ancestor_article_id = Column(Integer,
                                 ForeignKey('Article.uid',
                                            ondelete='set null'),
                                 nullable=True,
                                 index=True)

    author = relationship('User',
                          primaryjoin=(author_id == User.uid),
                          backref='articles')

    boards = relationship(
        'Board',
        secondary='BoardAndArticle',
        primaryjoin='Article.uid == BoardAndArticle.c.article_id')
    parent_article = relationship('Article',
                                  primaryjoin='Article.\
                                  parent_article_id == Article.uid',
                                  remote_side=[uid],
                                  backref='child_articles',
                                  post_update=True)
    ancestor_article = relationship('Article',
                                    primaryjoin='Article.\
                                    ancestor_article_id == Article.uid',
                                    remote_side=[uid],
                                    backref='descendant_articles',
                                    post_update=True)

    @hybrid_property
    def content(self):
        if self.render_type == 'html<br />':
            return self._content.replace('\n', '<br />\n')
        elif self.render_type == 'html':
            return self._content
        elif self.render_type == 'text':
            return escape(self._content).replace('\n', '<br />\n')

    @content.setter
    def set_content(self, value):
        self._content = value

    @property
    def author_uid(self):
        return self.author.uid if self.author else None

    @property
    def author_name(self):
        return self.author.realname if self.author else \
            self.anonymous_name

    @property
    def is_new(self):
        return datetime.now() - self.created_at <= timedelta(hours=24)

    @property
    def content_for_read(self):
        # 글 읽기 페이지에선 멘션을 변환하고, 첨부파일 링크해 놓은 걸 변환해야
        # 하는 등 여러 작업이 필요하다. 그 작업을 여기서 처리한다.

        # TODO: 멘션 변환은 지금 글과 댓글에서 모두 쓰고 있고, jinja 필터로
        # 들어있다. 변환하는 함수를 따로 만들고, 필터에선 그 함수를 부르는
        # 식으로 변경해야 할 듯.
        file_list = {f.upload_filename: f.uid for f in self.files}

        def replace_filename_func(match):
            filename = match.group(2)
            if filename not in file_list:
                return match.group()
            return url_for('main.go_uid', uid=file_list[filename])

        content = _find_file_re.sub(replace_filename_func, self.content)

        return content

    @property
    def in_album_board(self):
        return any([b.board_type == 'album' for b in self.boards])

    @property
    def image_files(self):
        return [f for f in self.files if f.mime.startswith('image')]

    def thumbnail_image(self, scale=None, width=0, height=0):
        images = [f for f in self.files if f.mime.startswith('image')]
        if len(images) > 0:
            if scale is not None:
                return url_for('main.thumbnail',
                               uid=images[0].uid,
                               scale=scale)
            else:
                return url_for('main.thumbnail',
                               uid=images[0].uid,
                               width=width,
                               height=height)
        return None
Esempio n. 8
0
class Survey(BaseMeta):
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(Unicode(255), nullable=False, default=u'')
    due_date = Column(DateTime, nullable=False, default=None)
    parent_id = Column(Integer,
                       ForeignKey('Article.uid', ondelete='cascade'),
                       nullable=True)
    owner_id = Column(Integer,
                      ForeignKey('User.uid', ondelete='set null'),
                      nullable=True)
    is_anonymous = Column(Boolean, nullable=False, default=False)
    permission_type = Column(Choice(['all', 'select', 'except', 'firstcome']),
                             nullable=False,
                             default='all')
    permission_value = Column(Unicode(100), nullable=False, default=u'')
    expose_level = Column(Integer, nullable=False, default=0)
    min_vote_num = Column(Integer, nullable=True, default=None)
    _answered_user = Column('answered_user',
                            UnicodeText,
                            nullable=False,
                            default=u'[]')
    created_at = Column(DateTime, nullable=False, default=func.now())

    parent = relationship('Article', backref=backref('survey', uselist=False))
    owner = relationship('User')
    questions = relationship('SurveyQuestion', order_by='SurveyQuestion.id')

    @hybrid_property
    def answered_user(self):
        return json.loads(self._answered_user)

    @answered_user.setter
    def set_answered_user(self, value):
        assert isinstance(value, (list, tuple)) and \
            all(isinstance(n, (int, long)) for n in value)
        self._answered_user = json.dumps(value)

    @property
    def parsed_permission_value(self):
        if self.permission_type in ('select', 'except'):
            return [int(year) for year in self.permission_value.split(',')]
        elif self.permission_type == 'firstcome':
            return int(self.permission_value)
        return None

    @property
    def is_finished(self):
        return datetime.now() >= self.due_date or \
            (self.permission_type == 'firstcome' and
             len(self.answered_user) >= self.parsed_permission_value)

    def votable(self, user):
        return not self.is_finished and user.uid not in self.answered_user and\
            (self.permission_type == 'all' or
             (self.permission_type == 'select' and
              user.bs_year in self.parsed_permission_value) or
             (self.permission_type == 'except' and
              user.bs_year not in self.parsed_permission_value) or
             self.permission_type == 'firstcome'
             )

    def visible(self, user):
        return self.is_finished or \
            self.expose_level == 0 or \
            self.expose_level == 1 and user.uid in self.answered_user or \
            self.expose_level == 2 and datetime.now() >= self.due_date or \
            (self.expose_level == 3 and
             len(self.answered_user) >= self.min_vote_num)

    @property
    def answers(self):
        if getattr(self, '_answers', None) is not None:
            return self._answers

        result = []
        percentage = (1. / len(self.answered_user)) * 100 \
            if len(self.answered_user) > 0 else 0

        for i, question in enumerate(self.questions):
            result.append([])
            for _ in range(len(question.examples)):
                result[i].append({
                    'users': [],
                    'percentage': 0,
                })

        answers = db.query(SurveyAnswer).filter(
            SurveyAnswer.survey_question_id.in_([q.id
                                                 for q in self.questions]))
        for answer in answers:
            result[answer.survey_question.order][answer.answer]['users'].\
                append(answer.user)
            result[answer.survey_question.order][answer.answer]['percentage'] \
                += percentage

        self._answers = result
        return result