class ProjectWatcher(BaseModel): __orz_table__ = "codedouban_watches" user_id = ModelField(as_key=ModelField.KeyType.DESC) project_id = ModelField(as_key=ModelField.KeyType.DESC) class OrzMeta: id2str = True
class Activity(BaseModel): __orz_table__ = "center_activities" title = ModelField(as_key=ModelField.KeyType.DESC) description = ModelField() type = ModelField(as_key=ModelField.KeyType.ONLY_INDEX) creator_id = ModelField(as_key=ModelField.KeyType.ONLY_INDEX) created_at = ModelField(auto_now_create=True) def to_dict(self): return { 'id': self.id, 'title': self.title, 'description': self.description, 'created_at': self.created_at.strftime('%Y-%m-%dT%H:%M:%S+0800') } @property def rendered_description(self): from vilya.libs.text import render_markdown description = self.description if description: description = render_desc(self.type, description) return render_markdown(description) return '' @property def short_description(self): if self.description: return trunc_utf8(self.description, 80) return '' @property def url(self): return 'activities/%s' % self.id
class ProjectHook(BaseModel): __orz_table__ = "codedouban_hooks" hook_id = ModelField(as_key=ModelField.KeyType.DESC) url = ModelField() project_id = ModelField(as_key=ModelField.KeyType.DESC) class OrzMeta: id2str = True
class TeamProjectRelationship(BaseModel): __orz_table__ = "team_project_relationship" team_id = ModelField(as_key=ModelField.KeyType.DESC) project_id = ModelField(as_key=ModelField.KeyType.DESC) created_at = ModelField(auto_now_create=True) class OrzMeta: id2str = True def __repr__(self): return '<TeamProjectRelationship %s, %s, %s>' % (self.id, self.team_id, self.project_id)
class GroupUser(BaseModel): __orz_table__ = "group_users" group_id = ModelField(as_key=ModelField.KeyType.DESC) user_id = ModelField(as_key=ModelField.KeyType.DESC) created_at = ModelField(auto_now_create=True) @property def group(self): return TeamGroup.get(id=self.group_id) @property def user(self): from vilya.models.user import User return User(self.user_id) def to_dict(self): return dict(group_name=self.group.name, user_name=self.user_id)
class ProjectGroup(BaseModel): __orz_table__ = "projects_groups" project_id = ModelField(as_key=ModelField.KeyType.DESC) group_id = ModelField(as_key=ModelField.KeyType.DESC) created_at = ModelField(auto_now_create=True) @property def group(self): return TeamGroup.get(id=self.group_id) @property def project(self): from vilya.models.project import CodeDoubanProject return CodeDoubanProject.get(self.project_id) def to_dict(self): return dict(group_name=self.group.name, project_name=self.project.name)
class TeamUserRelationship(BaseModel): __orz_table__ = "team_user_relationship" team_id = ModelField(as_key=ModelField.KeyType.DESC) user_id = ModelField(as_key=ModelField.KeyType.DESC) identity = ModelField(as_key=ModelField.KeyType.DESC) created_at = ModelField(auto_now_create=True) class OrzMeta: id2str = True def __repr__(self): return '<TeamUserRelationship %s, %s, %s>' % (self.id, self.team_id, self.user_id) @property def is_owner(self): return self.identity == TEAM_OWNER @property def is_member(self): return self.identity == TEAM_MEMBER
class User2(BaseModel): __orz_table__ = "users" name = ModelField(as_key=ModelField.KeyType.DESC) password = ModelField() role = ModelField() created_at = ModelField(auto_now_create=True) class OrzMeta: id2str = True def __repr__(self): return '<u %s, %s>' % (self.id, self.name) @property def is_intern(self): return int(self.role) == USER_ROLE_INTERN def validate_password(self, password): return check_password(password, self.password) @classmethod def is_exists(cls, name): return cls.get(name=name) @classmethod def add(cls, name, password): encoded_password = make_password(password, hasher=PASSWORD_METHOD) return cls.create(name=name, password=encoded_password, role=USER_ROLE_DEFAULT) def set_role(self, intern=False): self.role = USER_ROLE_INTERN if intern else USER_ROLE_STAFF self.save() def has_role(self): if self.role is not None and \ int(self.role) != USER_ROLE_DEFAULT: return True
class CodeDoubanMirror(BaseModel): __orz_table__ = "codedouban_mirror" url = ModelField() state = ModelField() project_id = ModelField(ModelField.KeyType.ASC) with_proxy = ModelField(default=MIRROR_NOT_PROXY) frequency = ModelField(default=0) class OrzMeta: order_combs = (("-id", ), ) def __str__(self): return "Mirror: %s " % self.url @classmethod def validate(cls, repo_url): return check_git_url(repo_url) @classmethod def add(cls, url, state, project_id, with_proxy=MIRROR_NOT_PROXY): return cls.create(project_id=project_id, url=url, state=state, with_proxy=with_proxy) @classmethod def get_by_project_id(cls, project_id): return cls.get(project_id=project_id) def update_state(self, state): self.state = state self.save() @property def is_clone_completed(self): return self.state == MIRROR_STATE_CLONED
class TeamGroup(BaseModel): __orz_table__ = "team_groups" name = ModelField(as_key=ModelField.KeyType.DESC) team_id = ModelField(as_key=ModelField.KeyType.DESC) description = ModelField() permission = ModelField(as_key=ModelField.KeyType.DESC, default=PERM_PULL) creator_id = ModelField() created_at = ModelField(auto_now_create=True) @property def permission_text(self): return PERM_TEXT.get(self.permission, 'pull') def add_user(self, **kw): kw['group_id'] = self.id return GroupUser.create(**kw) def add_project(self, **kw): kw['group_id'] = self.id return ProjectGroup.create(**kw) def remove_user(self, **kw): kw['group_id'] = self.id u = GroupUser.get(**kw) if u: u.delete() def remove_project(self, **kw): kw['group_id'] = self.id u = ProjectGroup.get(**kw) if u: u.delete() @property def url(self): return self.team.url + 'groups/%s/' % self.name @property def team(self): from vilya.models.team import Team return Team.get(self.team_id) @property def full_name(self): return "%s/%s" % (self.team.uid, self.name) @property def members(self): from vilya.models.user import User rs = GroupUser.gets(group_id=self.id) return [User(r.user_id) for r in rs] @property def projects(self): from vilya.models.project import CodeDoubanProject rs = ProjectGroup.gets(group_id=self.id) return filter(None, [CodeDoubanProject.get(r.project_id) for r in rs]) def is_member(self, user_id): r = GroupUser.get(group_id=self.id, user_id=user_id) return True if r else False def to_dict(self): return dict(name=self.name, team_id=self.team_id) @classmethod def translate_perm(cls, perm): r = PERM_PULL if perm == 'push': r = PERM_PUSH elif perm == 'merge': r = PERM_MERGE elif perm == 'admin': r = PERM_ADMIN return r
class Pull2(BaseModel, PropsMixin, PullMixin): __orz_table__ = "pullreq" from_project = ModelField(as_key=ModelField.KeyType.DESC) from_branch = ModelField(as_key=ModelField.KeyType.DESC) to_project = ModelField(as_key=ModelField.KeyType.DESC) to_branch = ModelField(as_key=ModelField.KeyType.DESC) merged = ModelField(as_key=ModelField.KeyType.DESC) ticket_id = ModelField(as_key=ModelField.KeyType.DESC) class OrzMeta: id2str = True cache_ver = "v1" def get_uuid(self): return '/pull/%s' % self.id def after_create(self): self.init_pull() self.sync() @classmethod def get(cls, **kwargs): r = super(Pull2, cls).get(**kwargs) if r: r.init_pull() r.sync() return r @property @cache(MC_KEY_PULL_MERGE_BASE % ('{self.id}', '{self.from_sha}', '{self.to_sha}')) def merge_base(self): return self._get_merge_base() @property @cache(MC_KEY_PULL_COMMITS % ('{self.id}', '{self.from_sha}', '{self.to_sha}')) def commits(self): return self._get_commits() @property def ticket(self): if not self.ticket_id: return None ticket = Ticket.get_by_projectid_and_ticketnumber( self.to_proj.id, self.ticket_id) return ticket @property def full_url(self): return "%s/%s/pull/%s/" % (DOMAIN, self.to_proj.name, self.ticket_id) def get_commits_by_status(self, status): if status == 'closed': return self.merged_commits return self.commits def get_commits(self): return self.commits def get_commits_shas(self): return [c.sha for c in self.commits] def get_format_patch(self): text = self.to_proj.repo.get_patch_file(self.from_sha, self.merge_base) return text def get_diff_tree(self): text = self.to_proj.repo.get_diff_file(self.from_sha, self.merge_base) return text # TODO: cache def get_diff_length(self, ref=None, from_ref=None, paths=None): ref = ref or self.from_sha from_ref = from_ref or self.merge_base return self.repo.get_diff_length(ref=ref, from_ref=from_ref, paths=paths) def get_contexts(self, ref, path, line_start, line_end): return self.repo.get_contexts(ref, path, line_start, line_end) @classmethod def get_by_proj_and_ticket(cls, proj_id, tid): return cls.get(to_project=proj_id, ticket_id=tid) @classmethod def get_by_ticket(cls, ticket): proj_id = ticket.project_id tid = ticket.ticket_id return cls.get(to_project=proj_id, ticket_id=tid) @classmethod def get_by_from_and_to(cls, from_project, from_branch, to_project, to_branch): rs = cls.gets(from_project=from_project, from_branch=from_branch, to_project=to_project, to_branch=to_branch) return rs @classmethod # backward compatibility def get_max_ticket_id(cls, proj_id): rs = store.execute( "select max(ticket_id) " "from pullreq where to_project=%s", (proj_id, )) r = rs and rs[0] if r and r[0]: return int(r[0]) @cache(MC_KEY_PULL_IS_MERGABLE % ('{self.id}', '{self.from_sha}', '{self.to_sha}')) def is_auto_mergable(self): return self.repo.can_merge() def can_fastforward(self): return self.repo.can_fastforward() # TODO: remove this def remove_branch_if_temp(self): if self.from_ref.startswith('patch_tmp'): try: self.to_proj.repo.delete_branch(self.from_ref) except AssertionError as err: logging.debug("Unable to remove_temp_branch(%s): %s" % (self.from_ref, err)) def is_temp_pull(self): if self.from_ref.startswith('patch_tmp'): return True def _save_merged(self, operator, from_sha, to_sha, merge_commit_sha): self.merged = "%s..%s" % (from_sha, to_sha) self.save() merged_time = datetime.now() self.merge_by = operator self.merge_time = merged_time.strftime('%Y-%m-%d %H:%M:%S') # FIXME: pullreq without ticket_id? if self.ticket_id: ticket = Ticket.get_by_projectid_and_ticketnumber( self.to_proj.id, self.ticket_id) if ticket: # add commits to ticket ticket.add_commits(merge_commit_sha, operator) TicketNode.add_merge(ticket.id, operator, merged_time) def _get_merge_by(self): return self.props.get('merge_by') def _set_merge_by(self, merge_by): self.set_props_item('merge_by', merge_by) merge_by = property(_get_merge_by, _set_merge_by) def _get_merge_time(self): return self.props.get('merge_time') def _set_merge_time(self, merge_time): self.set_props_item('merge_time', merge_time) def _base_repo_as_dict(self): # TODO: wrap to project repo d = self._repo_as_dict(self.to_proj) commit = self.to_commit ts = commit.author_timestamp if commit else 0 d['repo']['pushed_at'] = datetime.fromtimestamp( float(ts)).strftime('%Y-%m-%dT%H:%M:%S') d['label'] = "%s:%s" % (self.to_proj.name, self.to_ref) d['sha'] = commit.sha if commit else None d['ref'] = self.to_ref return d def _head_repo_as_dict(self): d = self._repo_as_dict(self.from_proj) if not d['repo']: return d commit = self.from_commit if commit: ts = commit.author_timestamp if commit else 0 d['repo']['pushed_at'] = datetime.fromtimestamp( float(ts)).strftime('%Y-%m-%dT%H:%M:%S') d['label'] = "%s:%s" % (self.from_proj.name, self.from_ref) d['sha'] = commit.sha if commit else None d['ref'] = self.from_ref return d def _repo_as_dict(self, proj): if not proj: return {'repo': {}} d = {} d['repo'] = {} d['repo']['id'] = proj.id d['repo']['name'] = proj.name d['repo']['clone_url'] = "%s/%s.git" % (DOMAIN, proj.name) d['repo']['html_url'] = "%s/%s" % (DOMAIN, proj.name) d['repo']['name'] = proj.name d['repo']['summary'] = proj.summary d['repo'][ 'fork'] = True if proj.fork_from and proj.fork_from > 0 else False d['repo']['created_at'] = proj.time.strftime('%Y-%m-%dT%H:%M:%S') d['repo']['author'] = {'login': proj.owner_id} return d def as_dict(self): if not (self.ticket_id and self.to_proj): return None ticket = Ticket.get_by_projectid_and_ticketnumber( self.to_proj.id, self.ticket_id) if not ticket: return None d = {} d['url'] = "%s/api/%s/pull/%s" % (DOMAIN, self.to_proj.name, self.ticket_id) d['html_url'] = "%s/%s/pull/%s/" % (DOMAIN, self.to_proj.name, self.ticket_id) d['number'] = self.ticket_id # or ticket_id as name is better? d['title'] = ticket.title d['description'] = ticket.description d['merged'] = self.merged and True or False d['created_at'] = ticket.time.strftime('%Y-%m-%dT%H:%M:%S') if ticket.closed: d['closed_at'] = ticket.closed.strftime('%Y-%m-%dT%H:%M:%S') d['state'] = 'closed' else: d['closed_at'] = None d['state'] = 'open' if d['merged']: # No merged time recored, use closed_at instead d['merged_at'] = d['closed_at'] else: d['merged_at'] = None d['author'] = {} d['author']['login'] = ticket.author d['ticket_id'] = self.ticket_id d['from_proj'] = self.from_proj and self.from_proj.name or '' d['to_proj'] = self.to_proj and self.to_proj.name or '' d['to_proj_id'] = self.to_proj and self.to_proj.id or -1 d['creator'] = ticket.author d['base'] = self._base_repo_as_dict() d['head'] = self._head_repo_as_dict() commits = self.get_commits_by_status(d['state']) d['commit_id'] = [c.sha for c in commits] return d merge_time = property(_get_merge_time, _set_merge_time) def get_merged_commit_shas(self): commits_shas = [] for ticket_commits in self.ticket.get_commits(): commits_shas += [str(c) for c in ticket_commits.commits.split(',')] commits_shas = [c for c in commits_shas if c] return commits_shas def get_merged_commits(self): commits = [] repo = self.to_proj.repo commits = filter(None, (repo.get_commit(str(c)) for c in self.get_merged_commit_shas())) commits = sorted(commits, key=lambda x: x.committer_time, reverse=True) if self.merged: # exclude last merge commit return commits[1:] else: return commits def get_delta_commits(self): """ 补充pull request创建之后再提交的commits """ saved = self.get_merged_commit_shas() nowcommits = self.get_commits_shas() delta_commits = [c for c in nowcommits if c not in saved] if delta_commits: delta_commits = [ self.to_proj.repo.get_commit(str(d)) for d in delta_commits ] delta_commits = filter(None, delta_commits) return delta_commits return [] @classmethod def open(cls, from_project, from_branch, to_project, to_branch): return PullMixin(from_project.id, from_branch, to_project.id, to_branch)
class TicketComment(BaseModel): __orz_table__ = "codedouban_ticket_comment" author = ModelField() # content = ModelField() ticket_id = ModelField(as_key=ModelField.KeyType.DESC) time = ModelField(as_key=ModelField.KeyType.DESC, auto_now_create=True) @property def content(self): return bdb.get(BDB_TICKET_COMMENT_CONTENT_KEY % self.id) @property def uid(self): return PULL_COMMENT_UID_PATTERN % self.id def as_dict(self): return { 'content': self.content, 'author': self.author, 'date': self.time.strftime('%Y-%m-%dT%H:%M:%S'), 'id': self.id, 'ticket_id': self.ticket_id } @classmethod def add(cls, ticket_id, content, author): comment = cls.create(ticket_id=ticket_id, extra_args=content, author=author) return comment def after_create(self, extra_args): from vilya.models.ticket import Ticket comment = self TicketNode.add_comment(comment) content = extra_args bdb.set(BDB_TICKET_COMMENT_CONTENT_KEY % self.id, content) ticket = Ticket.get(self.ticket_id) # TODO: 将Feed全部迁移到新的系统后,取消signal发送 codereview_signal.send(comment, content=content, ticket=ticket, author=self.author, comment=comment) dispatch('codereview', data={ 'comment': comment, 'ticket': ticket, 'sender': self.author, }) def update(self, content): bdb.set(BDB_TICKET_COMMENT_CONTENT_KEY % self.id, content) self.time = datetime.now() self.save() def after_delete(self): bdb.delete(BDB_TICKET_COMMENT_CONTENT_KEY % self.id) node = TicketNode.get_by_comment(self) if node: node.delete() @classmethod def gets_by_ticketid(cls, ticket_id): return cls.gets(ticket_id=ticket_id)
class TicketNode(BaseModel): __orz_table__ = "ticket_nodes" author = ModelField() type = ModelField(as_key=ModelField.KeyType.DESC) type_id = ModelField(as_key=ModelField.KeyType.DESC) ticket_id = ModelField(as_key=ModelField.KeyType.DESC) created_at = ModelField(as_key=ModelField.KeyType.ASC, auto_now_create=True) class OrzMeta: id2str = False order_combs = (("created_at", ), ) @property def commit(self): if self.type == TICKET_NODE_TYPE_COMMIT: return TicketCommits.get(id=self.type_id) @property def comment(self): if self.type == TICKET_NODE_TYPE_COMMENT: return TicketComment.get(id=self.type_id) @property def codereview(self): if self.type == TICKET_NODE_TYPE_LINECOMMENT: return PullLineComment.get(self.type_id) @classmethod def gets_by_ticket_id(cls, id): return cls.gets(ticket_id=id, order_by='created_at') @classmethod def get_by_type_id(cls, type, id): return cls.get(type=type, type_id=id) @classmethod def add(cls, type, type_id, author, ticket_id, created_at): return cls.create(type=type, type_id=type_id, author=author, ticket_id=ticket_id, created_at=created_at) @classmethod def add_commit(cls, commit): id = commit.id ticket_id = commit.ticket_id author = commit.author created_at = commit.time type = TICKET_NODE_TYPE_COMMIT return cls.add(type, id, author, ticket_id, created_at) @classmethod def add_comment(cls, comment): id = comment.id ticket_id = comment.ticket_id author = comment.author created_at = comment.time type = TICKET_NODE_TYPE_COMMENT return cls.add(type, id, author, ticket_id, created_at) @classmethod def add_codereview(cls, codereview): id = codereview.id ticket_id = codereview.target_id author = codereview.author created_at = codereview.created_at type = TICKET_NODE_TYPE_LINECOMMENT return cls.add(type, id, author, ticket_id, created_at) @classmethod def add_merge(cls, ticket_id, author, created_at): type = TICKET_NODE_TYPE_MERGE id = 0 return cls.add(type, id, author, ticket_id, created_at) @classmethod def add_open(cls, ticket_id, author, created_at): type = TICKET_NODE_TYPE_OPEN id = 0 return cls.add(type, id, author, ticket_id, created_at) @classmethod def add_close(cls, ticket_id, author, created_at): type = TICKET_NODE_TYPE_CLOSE id = 0 return cls.add(type, id, author, ticket_id, created_at) @classmethod def get_by_comment(cls, comment): type = TICKET_NODE_TYPE_COMMENT return cls.get_by_type_id(type, comment.id) @classmethod def get_by_codereview(cls, codereview): type = TICKET_NODE_TYPE_LINECOMMENT return cls.get_by_type_id(type, codereview.id) # 用于migrate @classmethod def get_by_codereview_id(cls, codereview_id): type = TICKET_NODE_TYPE_LINECOMMENT return cls.get_by_type_id(type, codereview_id) @classmethod def get_by_commit(cls, commit): type = TICKET_NODE_TYPE_COMMIT return cls.get_by_type_id(type, commit.id) def delete_type(self): if self.type == TICKET_NODE_TYPE_COMMENT: comment = self.comment comment.delete() elif self.type == TICKET_NODE_TYPE_COMMIT: commit = self.commit commit.delete() elif self.type == TICKET_NODE_TYPE_LINECOMMENT: codereview = self.codereview codereview.delete()
class TicketCommits(BaseModel): __orz_table__ = "codedouban_ticket_commits" author = ModelField() commits = ModelField() ticket_id = ModelField(as_key=ModelField.KeyType.DESC) time = ModelField(as_key=ModelField.KeyType.DESC, auto_now_create=True) @property def ticket(self): from vilya.models.ticket import Ticket return Ticket.get(self.ticket_id) @property def project(self): return CodeDoubanProject.get(self.ticket.project_id) @classmethod def commit_as_dict(cls, proj, sha): d = {} d['sha'] = sha commit = proj.repo.get_commit(sha) d['commit'] = {} d['commit']['html_url'] = "%s/%s/commits/%s" % (DOMAIN, proj.name, sha) d['commit']['sha'] = sha d['commit']['message'] = commit.message d['commit']['date'] = commit.committer_time.strftime( '%Y-%m-%dT%H:%M:%S') d['commit']['author'] = dict(name=commit.author.name, email=commit.author.email) d['commit']['committer'] = dict(name=commit.committer.name, email=commit.committer.email) d['commit']['tree'] = dict(html_url="%s/%s/tree/%s" % (DOMAIN, proj.name, sha), sha=sha) parent = [ dict(sha=commit.parent, html_url="%s/%s/commits/%s" % (DOMAIN, proj.name, commit.parent)) ] if commit.parent else [] d['parent'] = parent files = [] for delta in commit.diff.deltas: files.append( dict(type=delta.status_text, filepath=delta.new_file_path)) d['commit']['files'] = files return d def as_dict(self): l = [] for sha in self.commits.split(','): d = TicketCommits.commit_as_dict(self.project, sha) l.append(d) return l @classmethod def add(cls, ticket_id, commits, author): return cls.create(ticket_id=ticket_id, commits=commits, author=author) @classmethod def gets_by_ticketid(cls, ticket_id): return cls.gets(ticket_id=ticket_id) def after_create(self): commit = self TicketNode.add_commit(commit)