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)
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))
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()
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)
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
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()
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
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)
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))
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