Ejemplo n.º 1
0
class ProjectGroupNotice(db.EmbeddedDocument):
    # project_group = db.ReferenceField('ProjectGroup')
    seq = db.IntField(default=0)
    created_at = db.DateTimeField(default=datetime.now, required=True)
    created_by = db.ReferenceField('User', required=True)
    publish_begin_at = db.DateTimeField(default=datetime.now, required=True)
    publish_end_at = db.DateTimeField(default=datetime.now, required=True)
    content = db.StringField(required=True)
Ejemplo n.º 2
0
class ProjectGroupUser(JsonifyPatchMixin, db.Document):

    meta = {
        'indexes': [
            {
                'fields': ['project_group', 'user'],
                'unique': True
            },
        ],
    }

    ####################################################################
    # fields
    ####################################################################
    project_group = db.ReferenceField('ProjectGroup',
                                      required=True,
                                      reverse_delete_rule=mongoengine.CASCADE)
    user = db.ReferenceField('User',
                             required=True,
                             reverse_delete_rule=mongoengine.CASCADE)

    is_owner = db.BooleanField(default=False)
    is_moderator = db.BooleanField(default=False)
    is_termer = db.BooleanField(default=False)

    last_visited_at = db.DateTimeField(default=datetime.now)
    created_at = db.DateTimeField(default=datetime.now)

    # ui
    project_layout = db.StringField(default='boxed')

    render_template_path = "project_group/_members_tbl_row.html"

    ####################################################################
    # cached properties
    ####################################################################
    user_email = db.EmailField(max_length=255)

    ####################################################################
    # methods
    ####################################################################
    def __unicode__(self):
        return '{0}:{1}'.format(self.project_group.slug, self.user.email)

    def clean(self):
        self.user_email = self.user.email

    @classmethod
    def post_save(cls, sender, document, **kwargs):
        logger.debug('post save : %s' % document)
        on_joined_to_project_group.send(
            (document.user, document.project_group))
Ejemplo n.º 3
0
class AuditableMixin(object):
    created_by = db.ReferenceField(
        'User',
        default=current_user_or_none,
        reverse_delete_rule=mongoengine.NULLIFY)
    created_at = db.DateTimeField(default=datetime.now, required=True)
    modified_by = db.ReferenceField(
        'User',
        default=current_user_or_none,
        reverse_delete_rule=mongoengine.NULLIFY)
    modified_at = db.DateTimeField(default=datetime.now, required=True)

    def clean_by_auditable_mixin(self):
        self.modified_at = datetime.now()
        self.modified_by = current_user_or_none()
Ejemplo n.º 4
0
class ProjectUserBase(JsonifyPatchMixin, db.Document):
    """프로젝트-사용자를 표현하는 기본클래스.
    정확히는 프로젝트와 프로젝트그룹사용자의 관계를 표현한다.
    사용자객체와 email은 자주 꺼내쓰기때문에 cache해둔다.
    """

    meta = {
        'allow_inheritance': True,
        'index_cls': False,
        'indexes': [
            {
                'fields': ['project', 'user'],
                'unique': True
            },
        ],
    }

    ####################################################################
    # fields
    ####################################################################
    project = db.ReferenceField('Project',
                                required=True,
                                reverse_delete_rule=mongoengine.CASCADE)
    created_at = db.DateTimeField(default=datetime.now)

    ####################################################################
    # cached property
    user_email = db.EmailField(max_length=255)
    user = db.ReferenceField('User',
                             required=True,
                             reverse_delete_rule=mongoengine.CASCADE)
Ejemplo n.º 5
0
class EmailToken(db.Document):
    meta = {
        'allow_inheritance': True,
    }

    email = db.EmailField(max_length=255, required=True)
    token = db.StringField(
        default=lambda: str(uuid4()).replace('-', ''), required=True)
    created_at = db.DateTimeField(default=datetime.datetime.now, required=True)

    def __unicode__(self):
        return self.email
Ejemplo n.º 6
0
class ProjectWaitingUserInbound(ProjectUserBase):

    project_group_user = db.ReferenceField(
        'ProjectGroupUser',
        required=True,
        reverse_delete_rule=mongoengine.CASCADE)
    """사용자가 가입요청을 했을 경우"""
    done = db.BooleanField(default=False)
    done_by = db.ReferenceField('User',
                                reverse_delete_rule=mongoengine.NULLIFY)
    done_at = db.DateTimeField(default=datetime.now)
    done_message = db.StringField()
    rejected = db.BooleanField(default=False)

    asked_at = db.DateTimeField(default=datetime.now)
    asked_message = db.StringField()

    render_template_path = 'project/_members_waiting_tbl_row.html'

    def clean(self):
        pgu = ProjectGroupUser.objects(
            project_group=self.project.project_group, user=self.user).first()
        if pgu is None:
            raise ValidationError('Invalid group-project-usermapping')
        else:
            self.project_group_user = pgu

        # cached property
        self.user_email = self.user.email

    def approve_and_get_new_project_user(self):
        if not self.project.is_new_member_available():
            return None

        project = self.project
        user = self.user

        self.delete()

        return ProjectUser(project=project, user=user).save()
Ejemplo n.º 7
0
class User(JsonifyPatchMixin,
           db.Document,
           AdminMixin,
           FlaskLoginMixin,
           UserProjectMixin,
           UserProfileImageMixin,
           UserProjectGroupMixin,
           UserProjectCreateBillingCheckMixin,
           UserVerifyMixin,
           UserLocaleMixin):

    meta = {
        'ordering': ['-created_at']
    }

    # email_without_domain = db.StringField(max_length=255)
    email = db.EmailField(max_length=255, required=True, unique=True)
    user_id = db.StringField(max_length=255, unique=True, sparse=True)
    user_no = db.StringField(max_length=255)
    name = db.StringField(max_length=255)
    password = db.StringField(
        max_length=255,
        required=True,
        exclude_to_json=True)
    profile = db.StringField()

    created_at = db.DateTimeField(default=datetime.datetime.now, required=True)

    render_template_path = "project/_user_tbl_row.html"

    # def to_json(self, *args, **kwargs):
    #     """기본적인 to_json동작은 mongodb의 bson에 대한 변환이기 때문에
    #     결과물에 encrypted-email이 노출된다. email필드만 별도로 decrypt해서 내보내는
    #     patch-logic을 구현"""
    #     ret = super(User, self).to_json(*args, **kwargs)

    #     # patch for email-encryption (slow...)
    #     from erks.utils.crypt import decrypt
    #     from json import dumps, loads
    #     d = loads(ret)
    #     d['email'] = decrypt(d['email'])
    #     return dumps(d)

    def resend_verifying_mail(self):
        # TODO: 기존 Token은 삭제하니까 이력추적이 어렵다.
        token = UserToken(email=self.email)
        token.save()
        token.sendmail()

    def clean(self):
        if not self.name:
            try:
                self.name = self.email.split('@')[0]
            except (ValueError, IndexError):
                pass

        # TODO: project_group_id validation

    @property
    def url(self):
        return url_for('.profile')

    def __unicode__(self):
        return self.name or self.email

    def save(self, *args, **kwargs):
        is_new = not self.id
        new_user_obj = super(User, self).save(*args, **kwargs)
        if is_new:
            on_created_user.send(new_user_obj)
            # 신규생성시 verififed를 True로 바로 설정한다면,
            if self.verified:
                on_verified_user.send(new_user_obj)

        return new_user_obj
Ejemplo n.º 8
0
class ProjectWaitingUserOutbound(ProjectUserBase):
    """가입되지 않은 사용자를 초대받을때"""
    user = db.EmailField(required=True)
    outbound_email_sent_at = db.DateTimeField()
    outbound_email_sent_count = db.IntField(default=0)
    invited_by = db.ReferenceField('User',
                                   reverse_delete_rule=mongoengine.NULLIFY)
    invited_at = db.DateTimeField(default=datetime.now)
    # dropped = db.BooleanField(default=False)

    render_template_path = 'project/_members_invited_tbl_row.html'

    @property
    def is_expired(self):
        '''초대요청이 오래지나면 만료된다. 만료기간은 14일.'''
        return self.outbound_email_sent_at < datetime.now() - \
            timedelta(days=14)

    def check_integrity(self):
        '''외부사용자객체의 무결성을 검사합니다.
        외부사용자로 되어있는데 실제 user에도 존재하는 경우는 강제보정
        만료된 객체의 경우 삭제처리합니다.'''

        if self.is_expired:
            logger.info(
                'Deleting expired old-waiting-user-project-relation'
                '[%s].', self)
            self.delete()
        else:
            user = User.objects(email=self.user).first()
            if user:
                '''이미 가입된 사용자로 존재하는군요.
                프로젝트와의 관계까지검사합니다.'''
                relation = ProjectUserBase.objects(project=self.project,
                                                   user=user).first()
                if relation:
                    logger.info(
                        'Deleting useless old-waiting-user-project-relation'
                        '[%s].', self)
                    self.delete()  # useless.
                else:
                    '''미처리된 사용자이므로 가입처리'''
                    new_project_user = ProjectUser(project=self.project,
                                                   user=user).save()
                    logger.info('Creating new-user-project-relation'
                                '[%s].', new_project_user)
                    logger.info(
                        'Deleting old-waiting-user-project-relation'
                        '[%s].', self)
                    self.delete()
            else:
                '''정상적인 waiting이므로 done nothing.'''
                pass

    def sendmail(self):
        if self.outbound_email_sent_count > 4:
            logger.warning('outbound-email[%s]-surge-protection', self.user)
            return

        token = InviteToken(email=self.user, project=self.project).save()
        token.sendmail()

        self.outbound_email_sent_at = datetime.now()
        self.outbound_email_sent_count += 1
        self.save()

    def clean(self):
        if self.project.project_group.is_not_default:
            raise ValidationError(
                'outbound-user cannot be created at non-default-project-group')

        # 초대그룹의 email이 encrypt되어있지 않아서 다른쪽과 interface를 맞추기 위해 암호화
        self.user_email = self.user

    def to_json(self, *args, **kwargs):
        """일부 계산된 properties를 json으로 내보내기 위한 patch"""
        ret = super(ProjectWaitingUserOutbound, self).to_json(*args, **kwargs)

        # patch for email-encryption (slow...)
        from json import dumps, loads
        d = loads(ret)
        d['is_expired'] = self.is_expired
        return dumps(d)
Ejemplo n.º 9
0
class ProjectUser(ProjectUserBase, ProjectUserReportSubscriptionMixin):
    """project에 속한 user관리."""

    project_group_user = db.ReferenceField(
        'ProjectGroupUser',
        required=True,
        reverse_delete_rule=mongoengine.CASCADE)

    is_owner = db.BooleanField(default=False)
    last_visited_at = db.DateTimeField(default=datetime.now)

    # # for modeling
    # is_modeler = db.BooleanField(default=False)
    # can_manage_all_models = db.BooleanField(default=True)
    # manageable_models = db.ListField(db.ReferenceField(
    #     'Model', reverse_delete_rule=mongoengine.PULL))

    # # for terming
    # is_termer = db.BooleanField(default=False)
    # can_manage_all_glossaries = db.BooleanField(default=True)
    # manageable_glossaries = db.ListField(db.ReferenceField(
    #     'GlossaryBase', reverse_delete_rule=mongoengine.PULL))

    # ui
    project_layout = db.StringField(default='boxed')

    render_template_path = 'project/_members_tbl_row.html'

    def clean(self):
        pgu = ProjectGroupUser.objects(
            project_group=self.project.project_group, user=self.user).first()
        if pgu is None:
            raise ValidationError('Invalid project-group-user-mapping')
        else:
            self.project_group_user = pgu

        # cached property
        self.user_email = self.user.email

    def visit(self):
        if self.project.demo:
            return
        # self.last_visited_at = datetime.now()
        # self.save()
        self.update(last_visited_at=datetime.now())

    @property
    def grades(self):
        ret = []
        if self.is_owner:
            ret.append('owner')
        if self.is_modeler:
            ret.append('modeler')
        if self.is_termer:
            ret.append('termer')
        if len(ret) == 0:
            ret.append('member')
        return ret

    def __unicode__(self):
        return u'%s:%s:%s' % (self.project, self.user, ','.join(self.grades))
Ejemplo n.º 10
0
class ProjectGroup(
        db.Document,
        ProjectGroupNotices,
        ProjectGroupAppereance,
        ProjectGroupLimitFactors,
        ProjectGroupMy,
        ProjectGroupMemberJoin,
        ProjectGroupGlossaryMasterMixin,
        # BillingMixin
):
    """프로젝트를 그룹하여 관리하는 객체.
    project는 한개의 group에 종속된다.
    default라는 이름으로 기본 project_group이 항상 존재해야 함.
    """

    meta = {
        'indexes': [
            {
                'fields': ['slug'],
                'unique': True
            },
        ],
    }

    ####################################################################
    # fields
    ####################################################################
    title = db.StringField(required=True, max_length=255, min_length=2)
    slug = db.StringField(required=True, max_length=255, min_length=2)
    description = db.StringField(required=True, max_length=4000)
    external_homepage_url = db.URLField(max_length=1000)

    private = db.BooleanField(default=False)
    visible = db.BooleanField(default=True)

    created_at = db.DateTimeField(default=datetime.now, required=True)
    created_by = db.ReferenceField('User')

    allow_subscribe_all_project_changes = db.BooleanField(default=False)

    ####################################################################
    # properties
    ####################################################################
    @property
    def queryset_project(self):
        from erks.erks_bps.project.models import Project
        return Project.objects(project_group=self)

    @property
    def queryset_project_group_user(self):
        return ProjectGroupUser.objects(project_group=self)

    @property
    def url(self):
        return url_for('project_group.list_projects', slug=self.slug)

    @property
    def is_not_default(self):
        return self.slug != 'default'

    @property
    def is_default(self):
        return self.slug == 'default'

    @classmethod
    def default(cls):
        slug = current_app.config['DEFAULT_PROJECT_GROUP_SLUG']
        default_project_group = ProjectGroup.objects(slug=slug).first()

        # if not default_project_group.theme_key and current_app.config['DEFAULT_PROJECT_GROUP_THEME_KEY']:
        #     default_project_group.theme_key = current_app.config['DEFAULT_PROJECT_GROUP_THEME_KEY']

        return default_project_group

    ####################################################################
    # methods
    ####################################################################
    def __unicode__(self):
        return self.slug

    def clean(self):
        # project_group에 속한 여러개의 mixins의
        # clean method를 차례로 호출
        for base in ProjectGroup.__bases__:
            try:
                base.clean(self)
            except AttributeError:
                pass

    def save_as_random_slug(self):
        logger.debug("save_as_random_slug called")
        try:
            # import pdb; pdb.set_trace()
            self.slug = self.slug + str(random.randrange(1, 1000))
            self.save()
            logger.debug("slug {0}, "
                         "save_as_random_slug saved".format(self.slug))
        except mongoengine.errors.NotUniqueError:
            self.save_as_random_slug()

    def save(self, *args, **kwargs):
        is_new = not self.id
        self.is_free = False  # project_group always shouldn't be free.
        ret = super(ProjectGroup, self).save(*args, **kwargs)
        # if kwargs.get('emit_signals', True) and self.is_not_default:
        if is_new:
            on_created_project_group.send(self)
        else:
            on_changed_project_group.send(self)
        return ret