diff --git a/pkgs/python-packages.nix b/pkgs/python-packages.nix --- a/pkgs/python-packages.nix +++ b/pkgs/python-packages.nix @@ -831,6 +831,19 @@ license = [ pkgs.lib.licenses.bsdOriginal ]; }; }; + marshmallow = super.buildPythonPackage { + name = "marshmallow-2.8.0"; + buildInputs = with self; []; + doCheck = false; + propagatedBuildInputs = with self; []; + src = fetchurl { + url = "https://pypi.python.org/packages/4f/64/9393d77847d86981c84b88bbea627d30ff71b5ab1402636b366f73737817/marshmallow-2.8.0.tar.gz"; + md5 = "204513fc123a3d9bdd7b63b9747f02e6"; + }; + meta = { + license = [ pkgs.lib.licenses.mit ]; + }; + }; mccabe = super.buildPythonPackage { name = "mccabe-0.3"; buildInputs = with self; []; @@ -1355,7 +1368,7 @@ name = "rhodecode-enterprise-ce-4.3.0"; buildInputs = with self; [WebTest configobj cssselect flake8 lxml mock pytest pytest-cov pytest-runner]; doCheck = true; - propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery colander decorator docutils gunicorn infrae.cache ipython iso8601 kombu msgpack-python packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt]; + propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery colander decorator docutils gunicorn infrae.cache ipython iso8601 kombu marshmallow msgpack-python packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt]; src = ./.; meta = { license = [ { fullName = "AGPLv3, and Commercial License"; } ]; @@ -1637,5 +1650,5 @@ ### Test requirements - + } diff --git a/requirements.txt b/requirements.txt --- a/requirements.txt +++ b/requirements.txt @@ -84,6 +84,7 @@ iso8601==0.1.11 itsdangerous==0.24 kombu==1.5.1 lxml==3.4.4 +marshmallow==2.8.0 mccabe==0.3 meld3==1.0.2 mock==1.0.1 diff --git a/rhodecode/api/tests/test_update_repo.py b/rhodecode/api/tests/test_update_repo.py --- a/rhodecode/api/tests/test_update_repo.py +++ b/rhodecode/api/tests/test_update_repo.py @@ -47,9 +47,14 @@ class TestApiUpdateRepo(object): ({'enable_statistics': True}, SAME_AS_UPDATES), ({'enable_locking': True}, SAME_AS_UPDATES), ({'enable_downloads': True}, SAME_AS_UPDATES), - ({'name': 'new_repo_name'}, {'repo_name': 'new_repo_name'}), - ({'group': 'test_group_for_update'}, - {'repo_name': 'test_group_for_update/%s' % UPDATE_REPO_NAME}), + ({'name': 'new_repo_name'}, { + 'repo_name': 'new_repo_name', + 'url': 'http://test.example.com:80/new_repo_name', + }), + ({'group': 'test_group_for_update'}, { + 'repo_name': 'test_group_for_update/%s' % UPDATE_REPO_NAME, + 'url': 'http://test.example.com:80/test_group_for_update/%s' % UPDATE_REPO_NAME + }), ]) def test_api_update_repo(self, updates, expected, backend): repo_name = UPDATE_REPO_NAME diff --git a/rhodecode/controllers/pullrequests.py b/rhodecode/controllers/pullrequests.py --- a/rhodecode/controllers/pullrequests.py +++ b/rhodecode/controllers/pullrequests.py @@ -454,6 +454,7 @@ class PullrequestsController(BaseRepoCon h.flash(_('Successfully opened new pull request'), category='success') except Exception as e: + raise msg = _('Error occurred during sending pull request') log.exception(msg) h.flash(msg, category='error') diff --git a/rhodecode/events/__init__.py b/rhodecode/events/__init__.py --- a/rhodecode/events/__init__.py +++ b/rhodecode/events/__init__.py @@ -19,12 +19,6 @@ from pyramid.threadlocal import get_current_registry -class RhodecodeEvent(object): - """ - Base event class for all Rhodecode events - """ - - def trigger(event): """ Helper method to send an event. This wraps the pyramid logic to send an @@ -37,6 +31,8 @@ def trigger(event): registry.notify(event) +from rhodecode.events.base import RhodecodeEvent + from rhodecode.events.user import ( UserPreCreate, UserPreUpdate, @@ -44,6 +40,7 @@ from rhodecode.events.user import ( ) from rhodecode.events.repo import ( + RepoEvent, RepoPreCreateEvent, RepoCreatedEvent, RepoPreDeleteEvent, RepoDeletedEvent, RepoPrePushEvent, RepoPushEvent, @@ -51,6 +48,7 @@ from rhodecode.events.repo import ( ) from rhodecode.events.pullrequest import ( + PullRequestEvent, PullRequestCreateEvent, PullRequestUpdateEvent, PullRequestReviewEvent, diff --git a/rhodecode/events/base.py b/rhodecode/events/base.py new file mode 100644 --- /dev/null +++ b/rhodecode/events/base.py @@ -0,0 +1,69 @@ +# Copyright (C) 2016-2016 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +from datetime import datetime +from marshmallow import Schema, fields +from pyramid.threadlocal import get_current_request +from rhodecode.lib.utils2 import AttributeDict + + +SYSTEM_USER = AttributeDict(dict( + username='__SYSTEM__' +)) + + +class UserSchema(Schema): + """ + Marshmallow schema for a user + """ + username = fields.Str() + + +class RhodecodeEventSchema(Schema): + """ + Marshmallow schema for a rhodecode event + """ + utc_timestamp = fields.DateTime() + acting_user = fields.Nested(UserSchema) + acting_ip = fields.Str() + + +class RhodecodeEvent(object): + """ + Base event class for all Rhodecode events + """ + MarshmallowSchema = RhodecodeEventSchema + + def __init__(self): + self.request = get_current_request() + self.utc_timestamp = datetime.utcnow() + + @property + def acting_user(self): + if self.request: + return self.request.user.get_instance() + return SYSTEM_USER + + @property + def acting_ip(self): + if self.request: + return self.request.user.ip_addr + return '' + + def as_dict(self): + return self.MarshmallowSchema().dump(self).data \ No newline at end of file diff --git a/rhodecode/events/pullrequest.py b/rhodecode/events/pullrequest.py --- a/rhodecode/events/pullrequest.py +++ b/rhodecode/events/pullrequest.py @@ -16,15 +16,40 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ +from marshmallow import Schema, fields + from rhodecode.events.repo import RepoEvent +def get_pull_request_url(pull_request): + from rhodecode.model.pull_request import PullRequestModel + return PullRequestModel().get_url(pull_request) + + +class PullRequestSchema(Schema): + """ + Marshmallow schema for a pull request + """ + pull_request_id = fields.Integer() + url = fields.Function(get_pull_request_url) + title = fields.Str() + + +class PullRequestEventSchema(RepoEvent.MarshmallowSchema): + """ + Marshmallow schema for a pull request event + """ + pullrequest = fields.Nested(PullRequestSchema) + + class PullRequestEvent(RepoEvent): """ - Base class for events acting on a repository. + Base class for pull request events. - :param repo: a :class:`Repository` instance + :param pullrequest: a :class:`PullRequest` instance """ + MarshmallowSchema = PullRequestEventSchema + def __init__(self, pullrequest): super(PullRequestEvent, self).__init__(pullrequest.target_repo) self.pullrequest = pullrequest diff --git a/rhodecode/events/repo.py b/rhodecode/events/repo.py --- a/rhodecode/events/repo.py +++ b/rhodecode/events/repo.py @@ -16,8 +16,31 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ +from marshmallow import Schema, fields + from rhodecode.model.db import Repository, Session -from rhodecode.events import RhodecodeEvent +from rhodecode.events.base import RhodecodeEvent + + +def get_pull_request_url(repo): + from rhodecode.model.repo import RepoModel + return RepoModel().get_url(repo) + + +class RepositorySchema(Schema): + """ + Marshmallow schema for a repository + """ + repo_id = fields.Integer() + repo_name = fields.Str() + url = fields.Function(get_pull_request_url) + + +class RepoEventSchema(RhodecodeEvent.MarshmallowSchema): + """ + Marshmallow schema for a repository event + """ + repository = fields.Nested(RepositorySchema) class RepoEvent(RhodecodeEvent): @@ -26,7 +49,10 @@ class RepoEvent(RhodecodeEvent): :param repo: a :class:`Repository` instance """ + MarshmallowSchema = RepoEventSchema + def __init__(self, repo): + super(RepoEvent, self).__init__() self.repo = repo @@ -73,6 +99,16 @@ class RepoVCSEvent(RepoEvent): self.extras = extras super(RepoVCSEvent, self).__init__(self.repo) + @property + def acting_user(self): + if self.extras.get('username'): + return User.get_by_username(extras['username']) + + @property + def acting_ip(self): + if self.extras.get('ip'): + return User.get_by_username(extras['ip']) + class RepoPrePullEvent(RepoVCSEvent): """ diff --git a/rhodecode/events/user.py b/rhodecode/events/user.py --- a/rhodecode/events/user.py +++ b/rhodecode/events/user.py @@ -17,7 +17,8 @@ # and proprietary license terms, please see https://rhodecode.com/licenses/ from zope.interface import implementer -from rhodecode.events import RhodecodeEvent + +from rhodecode.events.base import RhodecodeEvent from rhodecode.events.interfaces import ( IUserRegistered, IUserPreCreate, IUserPreUpdate) diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -1641,6 +1641,7 @@ class Repository(Base, BaseModel): 'repo_name': repo.repo_name, 'repo_type': repo.repo_type, 'clone_uri': repo.clone_uri or '', + 'url': url('summary_home', repo_name=self.repo_name, qualified=True), 'private': repo.private, 'created_on': repo.created_on, 'description': repo.description, @@ -3112,10 +3113,9 @@ class PullRequest(Base, _PullRequestBase merge_status = PullRequestModel().merge_status(pull_request) data = { 'pull_request_id': pull_request.pull_request_id, - 'url': url('pullrequest_show', - repo_name=pull_request.target_repo.repo_name, - pull_request_id=pull_request.pull_request_id, - qualified=True), + 'url': url('pullrequest_show', repo_name=self.target_repo.repo_name, + pull_request_id=self.pull_request_id, + qualified=True), 'title': pull_request.title, 'description': pull_request.description, 'status': pull_request.status, diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py --- a/rhodecode/model/pull_request.py +++ b/rhodecode/model/pull_request.py @@ -748,6 +748,11 @@ class PullRequestModel(BaseModel): return ids_to_add, ids_to_remove + def get_url(self, pull_request): + return url('pullrequest_show', repo_name=self.target_repo.repo_name, + pull_request_id=self.pull_request_id, + qualified=True) + def notify_reviewers(self, pull_request, reviewers_ids): # notification to reviewers if not reviewers_ids: diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -141,6 +141,9 @@ class RepoModel(BaseModel): return None + def get_url(self, repo): + return url('summary_home', repo_name=repo.repo_name, qualified=True) + def get_users(self, name_contains=None, limit=20, only_active=True): # TODO: mikhail: move this method to the UserModel. query = self.sa.query(User) diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ requirements = [ 'ipython', 'iso8601', 'kombu', + 'marshmallow', 'msgpack-python', 'packaging', 'psycopg2',