class ProjectGroupLimitFactors(object): # project_group billing-type에 따라 count에 제한을 둠. da_cnt_limit = db.IntField(default=100) project_cnt_limit = db.IntField(default=100) member_cnt_limit = db.IntField(default=100) # project 생성통제 ban_create_project = db.BooleanField(default=False) # ip기반의 위치통제를 하기 위함 use_firewall = db.BooleanField(default=False) firewall_rules = db.ListField(db.EmbeddedDocumentField(FirewallRule)) def check_allowed_ip(self, ipaddr_str): ipaddr = ipaddress.ip_address(text(ipaddr_str)) rules = [rule for rule in self.firewall_rules if rule.allow] if len(rules) == 0: return True return any((rule.match(ipaddr) for rule in rules)) def is_firewall_working(self): return self.use_firewall and len(self.firewall_rules) > 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))
class UserVerifyMixin(object): verified = db.BooleanField(default=False) def verify(self): if not self.verified: self.verified = True self.save() logger.debug(u'%s is verified' % self) on_verified_user.send(self) else: logger.warning(u'%s is already verified' % self)
class ProjectRecommendationMixin(object): """프로젝트 추천하기.""" picked = db.BooleanField(default=False) display_priority = db.IntField(default=0) @classmethod def _random(cls, limit, project_group, picked=False): u"""추천알고리즘없이 임의로 프로젝트 골라오기.""" kw = dict(private=False, visible=True, demo=False, project_group=project_group) if picked: kw['picked'] = True # picked는 false일때는 true/false모두 챙긴다. projects = Project.objects(**kw).no_dereference().only('id') if len(projects): random.shuffle(list(projects)) projects = [ projects[i].reload() for i in range(min(limit, len(projects))) ] projects.sort(key=lambda x: -x.display_priority) return projects else: return [] @classmethod def random(cls, limit=4, project_group=None): """추천알고리즘없이 임의로 프로젝트 골라오기. PROJECT: 이 함수들은 성능이 좋지 않아서 PRODUCTION에서는 사용할 수 없습니다. batch로 project list를 준비하던가 해야겠음 """ if project_group is None: project_group = ProjectGroup.default() # picked priority selected = cls._random(limit=limit, picked=True, project_group=project_group) if len(selected) < limit: # 만약 limit만큼 채워지지 않았으면 조금 더 얻어오기 chunks = cls._random(limit=limit - len(selected), project_group=project_group) selected.extend(chunks) # shuffle! # random.shuffle(selected) return selected
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 ProjectGroupNotices(object): show_notices = db.BooleanField(default=True) notices = db.ListField(db.EmbeddedDocumentField(ProjectGroupNotice)) # notices = db.ListField(ProjectGroupNoticeField()) @property def queryset_notice(self): from erks.models import ProjectGroupNotice return ProjectGroupNotice.objects(project_group=self, use_yn=True) @property def queryset_qna(self): from erks.models import ProjectGroupQnA return ProjectGroupQnA.objects(project_group=self, use_yn=True)
class FirewallRule(db.EmbeddedDocument): seq = db.IntField(default=0) allow = db.BooleanField(default=True) source = db.StringField(max_length=50, required=True) cidr = db.IntField(max_value=32, required=True) def to_ipcidr(self): return u"{0}/{1}".format(self.source, self.cidr) def match(self, ipaddr): # if isinstance(ipaddr, str) or isinstance(ipaddr, unicode): if isinstance(ipaddr, six.text_type): ipaddr = ipaddress.ip_address(text(ipaddr)) return ipaddr in ipaddress.ip_network(self.to_ipcidr())
class ProjectGroupGlossaryMasterMixin(object): use_glossary_master = db.BooleanField(default=False) @property def glossary_master(self): from erks.models import GlossaryMaster return GlossaryMaster.objects(project_group=self).first() # glossary_master = db.ReferenceField('GlossaryMaster') @property def queryset_master_term(self): from erks.models import Term if self.use_glossary_master and self.glossary_master: return Term.objects(glossary=self.glossary_master) else: return Term.objects(id='0' * 24) # empty @property def queryset_master_term_request(self): from erks.models import TermMasterRequest if self.use_glossary_master and self.glossary_master: return TermMasterRequest.objects( glossary_master=self.glossary_master) else: return TermMasterRequest.objects(id='0' * 24) # dummy @property def queryset_glossary(self): from erks.models import Glossary return Glossary.head_objects(project_group=self) def create_glossary_master(self): from erks.models import GlossaryMaster if self.glossary_master: raise ProjectGroupIntegrityError( gettext('프로젝트그룹 마스터용어집은 이미 존재합니다')) # glossary = GlossaryMaster.objects(project_group=self).first() # if glossary is None: glossary = GlossaryMaster() glossary.project_group = self glossary.glossary_name = self.slug # temporary glossary.save() logger.debug('glossary_master is created.')
class ProjectGroupMemberJoin(object): # members = db.ListField(db.ReferenceField('User')) use_join_with_re = db.BooleanField(default=False) use_join_with_domain = db.BooleanField(default=False) # 신규사용자에 대해 자동등록검사 apply_for_newbie_always = db.BooleanField(default=False) join_domain_rules = db.ListField(db.StringField(max_length=1000)) join_re_rules = db.ListField(db.StringField(max_length=1000)) # 프로젝트 초대시 자동으로 그룹사용자 등록 join_with_project_invatation = db.BooleanField(default=False) # 프로젝트그룹내 사용자를 검색해서 프로젝트에 초대할 수 있는지 여부 can_search_member = db.BooleanField(default=True) # 프로젝트그룹내 사용자를 검색해서 프로젝트에 초대할 수 있는지 여부 can_invite_oubound_user = db.BooleanField(default=False) def join(self, user): return ProjectGroupUser(project_group=self, user=user).save() def leave(self, user): ProjectGroupUser.objects(project_group=self, user=user).delete() def test_joinrule(self, email): chunks = email.split('@') if len(chunks) < 2: return False if self.use_join_with_domain and \ any([ domain == chunks[1] for domain in self.join_domain_rules ]): return True return False def clean(self): self.join_domain_rules = list(set(self.join_domain_rules)) self.join_domain_rules = list( filter(lambda x: len(x), self.join_domain_rules)) self.join_re_rules = list(set(self.join_re_rules)) self.join_re_rules = list(filter(lambda x: len(x), self.join_re_rules))
class AdminMixin(object): @property def is_admin(self): return self.admin admin = db.BooleanField(default=False)
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 ProjectUserReportSubscriptionMixin(object): subscribed_report_model_glossary = db.BooleanField(default=False) subscribed_report_model_schema = db.BooleanField(default=False) subscribed_report_model_change = db.BooleanField(default=False)
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
class Project( JsonifyPatchMixin, AuditableMixin, db.Document, # ProjectBillingMixin, # ProjectGlossaryDerivedMixin, ProjectInvatationMixin, # ProjectRecommendationMixin, # ProjectReportMixin, # ProjectSchemaCollectorMixin, ProjectUserMixin, ): title = db.StringField(required=True, max_length=40, min_length=2) # created_at = db.DateTimeField(default=datetime.now, required=True) private = db.BooleanField(default=False) visible = db.BooleanField(default=True) demo = db.BooleanField(default=False) description = db.StringField() contact = db.StringField() profile_imgf = db.ImageField( collection_name='project_images', size=(600, 600, True), thumbnail_size=(140, 140, True), ) project_group = db.ReferenceField('ProjectGroup', required=True) project_group_managed = db.BooleanField(default=False) # modified_at = db.DateTimeField( # default=datetime.now, required=True) meta = {'ordering': ['-modified_at', '-created_at']} #################################################################### # PROJECT내에서 사용되는 GRADE #################################################################### ORGANIZER = u'owner' MEMBER = u'member' MODELER = u'modeler' TERM_MANAGER = u'termer' WAITING_MEMBER = u'waiting_member' GUEST = u'guest' @property def base_html(self): if self.project_group: return self.project_group.project_base_html else: return "project/base.html" @property def base4erc_html(self): if current_user.is_authenticated: if self.project_group: return self.project_group.base4erc_html else: return "base4erc.html" else: return ProjectGroup.default().base4erc_html #################################################################### # properties #################################################################### @property def queryset_project_user(self): from erks.models import ProjectUserBase return ProjectUserBase.objects(project=self) @property def queryset_post(self): from erks.models import Post return Post.objects(project=self) # @property # def queryset_glossary(self): # from erks.models import GlossaryBase # return GlossaryBase.head_objects(project=self) @property def queryset_user(self): '''todo: mongodb와 document 설계 한계로 인해서 사용자객체의 queryset 형태로 구현하기에는 현재로서는 어려워보임.''' # code = """ # function(field) { # var docs = []; # db['project_user_base'].find( # {project: ObjectId(options.project_id)} # ).forEach(function(doc) { # //docs.push(db[collection].findOne({_id: doc.user})); # docs.push(doc.user); # }); # return docs; # } # """ # return User.objects.exec_js(code, 'id', project_id=str(self.id)) raise NotImplementedError() # @property # def context_term_managers(self): # '''현재 project, glossary에 속해있는 용어관리자를 return''' # return [ # entry.user # for entry in self.queryset_project_user.filter( # is_termer=True).only('user', 'can_manage_all_glossaries', # 'manageable_glossaries') # if entry.can_manage_all_glossaries or ( # entry.manageable_glossaries and # g.glossary in entry.manageable_glossaries) # ] @property def owner(self): project_owner = self.queryset_project_user.filter( is_owner=True).only('user').first() return project_owner.user @property def members(self): # TODO: 속도개선을 위해 iterable하고 container type의 객체를 return하도록 return [ entry.user for entry in self.queryset_project_user.only('user') ] @property def term_managers(self): # TODO: 속도개선을 위해 iterable하고 container type의 객체를 return하도록 return [ entry.user for entry in self.queryset_project_user.filter( is_termer=True).only('user') ] @property def modelers(self): # TODO: 속도개선을 위해 iterable하고 container type의 객체를 return하도록 return [ entry.user for entry in self.queryset_project_user.filter( is_modeler=True).only('user') ] @property def url(self): return url_for('project.index', project_id=self.id) @property def external_url(self): return url_for('project.index', project_id=self.id, _external=True) #################################################################### # queryset #################################################################### @db.queryset_manager def demo_objects(doc_cls, queryset): return queryset.filter(demo=True) @db.queryset_manager def visible_objects(doc_cls, queryset): '''프로젝트포털에 노출가능한 것만 보여준다.''' # TODO: 내 것은 무조건 보여야 한다. return queryset.filter((Q(visible=True) | Q(visible=None)) & Q(demo=False)) #################################################################### # methods #################################################################### def save(self, *args, **kwargs): # NOTIFY_MAP = { # 'title': dict(message=lazy_gettext(u'프로젝트명이 \'%s\'로 변경되었습니다.' % self.title, typ='project'), # 'private': dict(message=lazy_gettext(u'이 프로젝트는 %s입니다.' % (lazy_gettext(u'공개', lazy_gettext(u'비공개')[self.private], typ='project'), # 'visible': dict(message=lazy_gettext(u'이 프로젝트는 %s.' % (lazy_gettext(u'검색되지 않습니다.', lazy_gettext(u'검색에 노출됩니다.')[self.visible], typ='project'), # 'description': dict(message=lazy_gettext(u'상세 설명 부분이 변경되었습니다.', typ='project'), # 'profile_imgf': dict(message=lazy_gettext(u'프로젝트 이미지가 변경되었습니다.', typ='project'), # } if self.id: # [self.notify(**NOTIFY_MAP[fieldname]) # for fieldname in self._changed_fields # if fieldname in NOTIFY_MAP] # OWNER변경에 대한 NOTIFICATION처리, OWNER가 종종 알수없이 _changed_fields에 # 들어가있다. # if 'owner' in self._changed_fields: # self.notify(message=lazy_gettext(u'{{user.email}} 사용자는 관리자입니다.', # user=self.owner, typ='user') # on_changed_project_owner.send(self) on_changed_project.send(self) ret = super(Project, self).save(*args, **kwargs) else: # You can only reference documents once they have been saved to the # database ret = super(Project, self).save(*args, **kwargs) on_created_project.send(self) # on_changed_project_owner.send(self) # self.notify( # message=lazy_gettext(u'프로젝트가 생성되었습니다 by {{user.email}}', user=self.owner, typ='project') # self.notify(message=lazy_gettext(u'{{user.email}} 사용자는 관리자입니다.', # user=self.owner, typ='user') return ret def __unicode__(self): return self.title def __lt__(self, other): return str(self) < str(other) def __gt__(self, other): return str(self) > str(other) def clean(self): # free-project-cannot-be-private # self.id가 없을때 product를 참고하면 오류발 if current_app.config['BILLING']: if self.id and \ (self.product is None or not self.product.support_private): self.private = False self.visible = True # project-group-check if self.project_group is None: self.project_group = ProjectGroup.default() def get_grade(self, user): from erks.models import ( ProjectUser, ProjectUserBase, ProjectWaitingUserInbound, ) # 미로그인사용자도 사용할 수 있는 함수 # if user.is_active: # if user == self.owner: # return self.ORGANIZER # elif user in self.modelers: # return self.MODELER # elif user in self.term_managers: # return self.TERM_MANAGER # elif user in self.members: # return self.MEMBER # elif user in self.waiting_members: # return self.WAITING_MEMBER # # elif user in self.invited_members: # # return self.WAITING_MEMBER # else: # logger.debug('user is not active.') # return self.GUEST if user.is_active: entry = ProjectUserBase.objects(project=self, user=user).first() if entry: if isinstance(entry, ProjectUser): if entry.is_owner: return self.ORGANIZER elif entry.is_modeler: return self.MODELER elif entry.is_termer: return self.TERM_MANAGER else: return self.MEMBER elif isinstance(entry, ProjectWaitingUserInbound): return self.WAITING_MEMBER else: logger.debug('user is not active.') return self.GUEST def _get_grades(self, user): from erks.models import ( ProjectUser, ProjectUserBase, ProjectWaitingUserInbound, ) grades = [] if user.is_active: entry = ProjectUserBase.objects(project=self, user=user).first() if isinstance(entry, ProjectUser): if entry.is_owner: grades.append(self.ORGANIZER) if entry.is_modeler: grades.append(self.MODELER) if entry.is_termer: grades.append(self.TERM_MANAGER) if not grades: if isinstance(entry, ProjectUser): grades.append(self.MEMBER) elif isinstance(entry, ProjectWaitingUserInbound): grades.append(self.WAITING_MEMBER) if grades: return grades return [self.GUEST] def get_grades(self, user): def _(grade): return { 'owner': { 'label': 'label-primary', 'grade': gettext(u'프로젝트 관리자') }, 'member': { 'label': 'label-warning', 'grade': gettext(u'일반회원') }, 'modeler': { 'label': 'label-success', 'grade': gettext(u'모델러') }, 'termer': { 'label': 'label-info', 'grade': gettext(u'용어 관리자') }, 'waiting_member': { 'label': 'label-default', 'grade': gettext(u'손님(등록 대기중)') }, 'guest': { 'label': 'label-default', 'grade': gettext(u'손님') } }[grade] return [_(grade) for grade in self._get_grades(user)] def check_to_enter(self, user=None): from erks.models import ( ProjectUser, ) '''project에 입장할 수 있는지를 검사''' if self.demo: return True # TODO: 이게 맞나요? if not current_user.is_active: return False if user is None: user = current_user._get_current_object() is_member = ProjectUser.objects( project=self, user=user).only('id').no_dereference().first() is not None return not self.private or is_member # def count_entities(self): # from erks.erks_bps.erc.models import Entity # return len(Entity.objects(prjId=str(self.id))) # def count_terms(self): # from erks.erks_bps.term.models import Term # return len(Term.objects(project=self)) # def is_custom_img(self): # return self.profile_imgf # def is_new(self): # """ # project가 새 것인지를 판단하기 # 주제영역이 하나라도 구성되어 있으면 알림을 주지 않기 위해. # """ # from erks.erks_bps.erc.models import SubjectArea # return SubjectArea.objects(prjId=str(self.id)).first() is None def get_profile_img_url(self, thumbnail=False): """binary값 기준으로 무작위로 profile image를 골라준다.""" # 샘플이미지파일 개수 SAMPLE_IMG_CNT = 5 # TODO: 매번계산하므로 성능상 불리. 개선필요하다. random_number = sum( map(ord, list(hashlib.md5( self.id.binary).hexdigest()))) % SAMPLE_IMG_CNT + 1 if self.profile_imgf: return url_for('project.profile_img', project_id=self.id) else: if self.project_group and self.project_group.theme_key in THEME_KEYS: filename = '%01s/img/Project_thumb_768x573_%02d.jpg' % ( self.project_group.theme_key, random_number) else: filename = 'img/r3/Project_thumb_768x573_%02d.jpg' % ( random_number) return url_for('static', filename=filename) def destroy(self): '''project 삭제처리. project에 속한 모든 resource를 삭제하므로 경우에 따라 heavy할 수 있기 때문에 가급적이면 web-request에서 처리하지 않는 것을 권장''' from erks.erks_bps.erc.models import (Model, Domain, SubjectArea, Entity, Relation, Lock) # delete models Model.objects(prjId=str(self.id)).delete() # domain Domain.objects(prjId=str(self.id)).delete() # delete subjectarea SubjectArea.objects(prjId=str(self.id)).delete() # delete entities Entity.objects(prjId=str(self.id)).delete() # delete relation Relation.objects(prjId=str(self.id)).delete() # delete lock Lock.objects(prjId=str(self.id)).delete() # delete project self.delete()
class ProjectInvatationMixin(object): # 사용자도 멤버초대를 할 수 있는지 allow_invitation_by_member = db.BooleanField(default=True) # TODO: 이 필드는 더 이상 사용하지 않습니다. 정리해주세요. # waiting_members = db.ListField(db.ReferenceField('User')) # invited_members = db.ListField(db.ReferenceField('User')) # auto_approve_requested_user = db.BooleanField(default=False) # auto_approve_invite_user = db.BooleanField(default=False) # 사용하지 않는 필드모음 끝. @property def waiting_invited_members(self): """프로젝트에 초대된 멤버목록을 보여줍니다.""" from erks.models import ProjectWaitingUserOutbound cls = ProjectWaitingUserOutbound return (entry.user for entry in cls.objects(project=self).only('user')) @property def waiting_requested_members(self): """프로젝트에 가입요청한 멤버목록을 보여줍니다.""" from erks.models import ProjectWaitingUserInbound cls = ProjectWaitingUserInbound return (entry.user for entry in cls.objects(project=self).only('user')) @property def members_cnt(self): '''project의 의미있는 member는 가입된 사용자 수 + 초대된 사용자 수. 프로젝트 구성원이 의지적으로 포함시키지 않은 request member는 제외한다.''' from erks.models import ProjectUserBase return ProjectUserBase.objects(project=self).count() - \ len(list(self.waiting_requested_members)) def _available_member_cnt(self): from erks.models import ProjectUser, ProjectWaitingUserOutbound members_cnt = ProjectUser.objects(project=self).count() waiting_invited_members_cnt = ProjectWaitingUserOutbound.objects( project=self).count() return (self.product.member_cnt_limit - members_cnt - waiting_invited_members_cnt) def is_new_member_available(self, new_member_cnt=1): """새로운 멤버를 받을 수 있는지 체크.""" if current_app.config['BILLING']: product = self.product if product is None: return False elif product.member_cnt_limit == 0: return True else: return self._available_member_cnt() - new_member_cnt >= 0 else: return True def invite(self, email, inviter): '''email로 초대하기 (외부 사용자를 포함해야 하므로 사용자객체 아님) 여러가지 방향이 있었지만 현재는 invite의 경우 별도의 승인없이 무조건 편입한다. invite는 의지적인 행동이기 때문에 별다른 검증을 거치지 않는 것으로 process 수립 todo: code is messy. please tide up. ''' from erks.models import ProjectUser, ProjectWaitingUserOutbound logger.debug('inviting %s', email) pg = self.project_group if (self.get_grade(inviter) == self.ORGANIZER or self.allow_invitation_by_member) and \ self.is_new_member_available(): try: user = User.objects.get(email=email) pgu = pg.queryset_member.filter(user=user).first() if pgu: try: ProjectUser(project=self, user=user, project_group_user=pgu).save() ret = True logger.debug('inviting process for inbound-user(%s)' % email) except NotUniqueError: logger.warning('already registered project-member(%s)', email) ret = False else: logger.warning('user(%s)-does-not-in-project-group(%s).', email, pg) ret = False except User.DoesNotExist: if pg.can_invite_oubound_user: try: invitee = ProjectWaitingUserOutbound( project=self, user=email).save() invitee.sendmail() ret = True logger.debug('inviting process for outbound-user(%s)' % email) except NotUniqueError: logger.warning('already registered outbound-user') ret = False else: logger.warning( 'outbound user(%s) cannot' ' join project-group(%s).' ' check the pref.', email, pg) ret = False else: ret = False return ret def leave(self, user): '''Project탈퇴는 MEMBER는 가능, OWNER는 불가능하다. 탈퇴 성공여부를 True/False로 리턴''' from erks.models import ProjectUser entry = ProjectUser.objects(project=self, user=user, is_owner=False).first() if entry: entry.delete() return True else: return False def request_to_join(self, user, message=u''): '''외부 사용자의 의지적인 합류 요청. ''' from erks.models import ProjectWaitingUserInbound try: ProjectWaitingUserInbound(project=self, user=user, asked_message=message).save() logger.debug('inviting process for inbound-user(%s)' % user) ret = True except NotUniqueError: logger.warning('already registered project-waiting-member') ret = False return ret