From 81fa8573185efa25812dfdfabb9b0e059530232f Mon Sep 17 00:00:00 2001 From: Daniel Dourvaris Date: Sun, 9 Jul 2017 10:25:14 +0200 Subject: [PATCH] repo-feed: moved from pylons controller to pyramid views. --- rhodecode/apps/repository/__init__.py | 24 ++++ .../apps/repository/tests/test_repo_feed.py | 94 +++++++++++++++ .../repository/views/repo_feed.py} | 108 +++++++++++------- rhodecode/config/routing.py | 11 -- rhodecode/public/js/rhodecode/routes.js | 5 + .../templates/data_table/_dt_elements.mako | 8 +- rhodecode/templates/summary/base.mako | 4 +- rhodecode/templates/summary/summary.mako | 4 +- rhodecode/tests/functional/test_feed.py | 75 ------------ 9 files changed, 197 insertions(+), 136 deletions(-) create mode 100644 rhodecode/apps/repository/tests/test_repo_feed.py rename rhodecode/{controllers/feed.py => apps/repository/views/repo_feed.py} (63%) delete mode 100644 rhodecode/tests/functional/test_feed.py diff --git a/rhodecode/apps/repository/__init__.py b/rhodecode/apps/repository/__init__.py index 36317b2b..06c1251a 100644 --- a/rhodecode/apps/repository/__init__.py +++ b/rhodecode/apps/repository/__init__.py @@ -80,6 +80,21 @@ def includeme(config): pattern='/{repo_name:.*?[^/]}/pull-request-data', repo_route=True, repo_accepted_types=['hg', 'git']) + # commits aka changesets + # TODO(dan): handle default landing revision ? + config.add_route( + name='changeset_home', + pattern='/{repo_name:.*?[^/]}/changeset/{revision}', + repo_route=True) + config.add_route( + name='changeset_children', + pattern='/{repo_name:.*?[^/]}/changeset_children/{revision}', + repo_route=True) + config.add_route( + name='changeset_parents', + pattern='/{repo_name:.*?[^/]}/changeset_parents/{revision}', + repo_route=True) + # Settings config.add_route( name='edit_repo', @@ -143,6 +158,15 @@ def includeme(config): name='strip_execute', pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True) + # ATOM/RSS Feed + config.add_route( + name='rss_feed_home', + pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True) + + config.add_route( + name='atom_feed_home', + pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True) + # NOTE(marcink): needs to be at the end for catch-all add_route_with_slash( config, diff --git a/rhodecode/apps/repository/tests/test_repo_feed.py b/rhodecode/apps/repository/tests/test_repo_feed.py new file mode 100644 index 00000000..a419ad84 --- /dev/null +++ b/rhodecode/apps/repository/tests/test_repo_feed.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010-2017 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/ +import pytest +from rhodecode.model.auth_token import AuthTokenModel +from rhodecode.tests import TestController + + +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'rss_feed_home': '/{repo_name}/feed/rss', + 'atom_feed_home': '/{repo_name}/feed/atom', + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + +class TestFeedView(TestController): + + @pytest.mark.parametrize("feed_type,response_types,content_type",[ + ('rss', [''], + "application/rss+xml"), + ('atom', [''], + "application/atom+xml"), + ]) + def test_feed(self, backend, feed_type, response_types, content_type): + self.log_user() + response = self.app.get( + route_path('{}_feed_home'.format(feed_type), repo_name=backend.repo_name)) + + for content in response_types: + assert content in response + + assert response.content_type == content_type + + @pytest.mark.parametrize("feed_type, content_type", [ + ('rss', "application/rss+xml"), + ('atom', "application/atom+xml") + ]) + def test_feed_with_auth_token( + self, backend, user_admin, feed_type, content_type): + auth_token = user_admin.feed_token + assert auth_token != '' + + response = self.app.get( + route_path( + '{}_feed_home'.format(feed_type), repo_name=backend.repo_name, + params=dict(auth_token=auth_token)), + status=200) + + assert response.content_type == content_type + + @pytest.mark.parametrize("feed_type", ['rss', 'atom']) + def test_feed_with_auth_token_of_wrong_type( + self, backend, user_util, feed_type): + user = user_util.create_user() + auth_token = AuthTokenModel().create( + user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API) + auth_token = auth_token.api_key + + self.app.get( + route_path( + '{}_feed_home'.format(feed_type), repo_name=backend.repo_name, + params=dict(auth_token=auth_token)), + status=302) + + auth_token = AuthTokenModel().create( + user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_FEED) + auth_token = auth_token.api_key + self.app.get( + route_path( + '{}_feed_home'.format(feed_type), repo_name=backend.repo_name, + params=dict(auth_token=auth_token)), + status=200) diff --git a/rhodecode/controllers/feed.py b/rhodecode/apps/repository/views/repo_feed.py similarity index 63% rename from rhodecode/controllers/feed.py rename to rhodecode/apps/repository/views/repo_feed.py index 2cf56426..3af42a27 100644 --- a/rhodecode/controllers/feed.py +++ b/rhodecode/apps/repository/views/repo_feed.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2010-2017 RhodeCode GmbH +# Copyright (C) 2017-2017 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 @@ -18,31 +18,37 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ -""" -Feed controller for RhodeCode -""" - +import pytz import logging -import pytz -from pylons import url, response, tmpl_context as c -from pylons.i18n.translation import _ - from beaker.cache import cache_region -from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed +from pyramid.view import view_config +from pyramid.response import Response +from webhelpers.feedgenerator import Rss201rev2Feed, Atom1Feed -from rhodecode.model.db import CacheKey, UserApiKeys +from rhodecode.apps._base import RepoAppView +from rhodecode.lib import audit_logger from rhodecode.lib import helpers as h -from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator -from rhodecode.lib.base import BaseRepoController +from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator, + NotAnonymous, CSRFRequired) from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer -from rhodecode.lib.utils2 import safe_int, str2bool -from rhodecode.lib.utils import PartialRenderer +from rhodecode.lib.ext_json import json +from rhodecode.lib.utils2 import str2bool, safe_int +from rhodecode.model.db import UserApiKeys, CacheKey log = logging.getLogger(__name__) -class FeedController(BaseRepoController): +class RepoFeedView(RepoAppView): + def load_default_context(self): + c = self._get_local_tmpl_context() + + # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead + c.repo_info = self.db_repo + + self._register_global_c(c) + self._load_defaults() + return c def _get_config(self): import rhodecode @@ -61,20 +67,19 @@ class FeedController(BaseRepoController): safe_int(config.get('rss_cut_off_limit', 32 * 1024)), } - @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) - def __before__(self): - super(FeedController, self).__before__() + def _load_defaults(self): + _ = self.request.translate config = self._get_config() # common values for feeds self.description = _('Changes on %s repository') - self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s') + self.title = self.title = _('%s %s feed') % (self.db_repo_name, '%s') self.language = config["language"] self.ttl = config["feed_ttl"] self.feed_include_diff = config['feed_include_diff'] self.feed_diff_limit = config['feed_diff_limit'] self.feed_items_per_page = config['feed_items_per_page'] - def __changes(self, commit): + def _changes(self, commit): diff_processor = DiffProcessor( commit.diff(), diff_limit=self.feed_diff_limit) _parsed = diff_processor.prepare(inline_diff=False) @@ -86,8 +91,9 @@ class FeedController(BaseRepoController): return h.shorter(commit.message, 160) def _get_description(self, commit): - _renderer = PartialRenderer('feed/atom_feed_entry.mako') - parsed_diff, limited_diff = self.__changes(commit) + _renderer = self.request.get_partial_renderer( + 'feed/atom_feed_entry.mako') + parsed_diff, limited_diff = self._changes(commit) return _renderer( 'body', commit=commit, @@ -102,19 +108,26 @@ class FeedController(BaseRepoController): return date def _get_commits(self): - return list(c.rhodecode_repo[-self.feed_items_per_page:]) + return list(self.rhodecode_vcs_repo[-self.feed_items_per_page:]) + @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) @HasRepoPermissionAnyDecorator( 'repository.read', 'repository.write', 'repository.admin') - def atom(self, repo_name): - """Produce an atom-1.0 feed via feedgenerator module""" + @view_config( + route_name='atom_feed_home', request_method='GET', + renderer=None) + def atom(self): + """ + Produce an atom-1.0 feed via feedgenerator module + """ + self.load_default_context() @cache_region('long_term') def _generate_feed(cache_key): feed = Atom1Feed( - title=self.title % repo_name, - link=h.route_url('repo_summary', repo_name=repo_name), - description=self.description % repo_name, + title=self.title % self.db_repo_name, + link=h.route_url('repo_summary', repo_name=self.db_repo_name), + description=self.description % self.db_repo_name, language=self.language, ttl=self.ttl ) @@ -125,33 +138,42 @@ class FeedController(BaseRepoController): title=self._get_title(commit), author_name=commit.author, description=self._get_description(commit), - link=url('changeset_home', repo_name=repo_name, - revision=commit.raw_id, qualified=True), + link=h.route_url( + 'changeset_home', repo_name=self.db_repo_name, + revision=commit.raw_id), pubdate=date,) return feed.mime_type, feed.writeString('utf-8') invalidator_context = CacheKey.repo_context_cache( - _generate_feed, repo_name, CacheKey.CACHE_TYPE_ATOM) + _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM) with invalidator_context as context: context.invalidate() mime_type, feed = context.compute() + response = Response(feed) response.content_type = mime_type - return feed + return response + @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) @HasRepoPermissionAnyDecorator( 'repository.read', 'repository.write', 'repository.admin') - def rss(self, repo_name): - """Produce an rss2 feed via feedgenerator module""" + @view_config( + route_name='rss_feed_home', request_method='GET', + renderer=None) + def rss(self): + """ + Produce an rss2 feed via feedgenerator module + """ + self.load_default_context() @cache_region('long_term') def _generate_feed(cache_key): feed = Rss201rev2Feed( - title=self.title % repo_name, - link=h.route_url('repo_summary', repo_name=repo_name), - description=self.description % repo_name, + title=self.title % self.db_repo_name, + link=h.route_url('repo_summary', repo_name=self.db_repo_name), + description=self.description % self.db_repo_name, language=self.language, ttl=self.ttl ) @@ -162,18 +184,20 @@ class FeedController(BaseRepoController): title=self._get_title(commit), author_name=commit.author, description=self._get_description(commit), - link=url('changeset_home', repo_name=repo_name, - revision=commit.raw_id, qualified=True), + link=h.route_url( + 'changeset_home', repo_name=self.db_repo_name, + revision=commit.raw_id), pubdate=date,) return feed.mime_type, feed.writeString('utf-8') invalidator_context = CacheKey.repo_context_cache( - _generate_feed, repo_name, CacheKey.CACHE_TYPE_RSS) + _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_RSS) with invalidator_context as context: context.invalidate() mime_type, feed = context.compute() + response = Response(feed) response.content_type = mime_type - return feed + return response diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py index 2a1556e0..3bb129cd 100644 --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -510,17 +510,6 @@ def make_map(config): controller='journal', action='toggle_following', jsroute=True, conditions={'method': ['POST']}) - # FEEDS - rmap.connect('rss_feed_home', '/{repo_name}/feed/rss', - controller='feed', action='rss', - conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - - rmap.connect('atom_feed_home', '/{repo_name}/feed/atom', - controller='feed', action='atom', - conditions={'function': check_repo}, - requirements=URL_NAME_REQUIREMENTS) - #========================================================================== # REPOSITORY ROUTES #========================================================================== diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js index 824098ce..acd4c279 100644 --- a/rhodecode/public/js/rhodecode/routes.js +++ b/rhodecode/public/js/rhodecode/routes.js @@ -113,6 +113,9 @@ function registerRCRoutes() { pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']); pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']); + pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); + pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']); + pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']); pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']); @@ -128,6 +131,8 @@ function registerRCRoutes() { pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']); pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']); pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']); + pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']); + pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']); pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']); pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']); pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']); diff --git a/rhodecode/templates/data_table/_dt_elements.mako b/rhodecode/templates/data_table/_dt_elements.mako index b12dc3e7..ce031075 100644 --- a/rhodecode/templates/data_table/_dt_elements.mako +++ b/rhodecode/templates/data_table/_dt_elements.mako @@ -92,17 +92,17 @@ <%def name="rss(name)"> %if c.rhodecode_user.username != h.DEFAULT_USER: - + %else: - + %endif <%def name="atom(name)"> %if c.rhodecode_user.username != h.DEFAULT_USER: - + %else: - + %endif diff --git a/rhodecode/templates/summary/base.mako b/rhodecode/templates/summary/base.mako index 1ab74481..6ee3b860 100644 --- a/rhodecode/templates/summary/base.mako +++ b/rhodecode/templates/summary/base.mako @@ -10,8 +10,8 @@ <%def name="head_extra()"> - - + + diff --git a/rhodecode/templates/summary/summary.mako b/rhodecode/templates/summary/summary.mako index 7ec018f4..b048673e 100644 --- a/rhodecode/templates/summary/summary.mako +++ b/rhodecode/templates/summary/summary.mako @@ -14,9 +14,9 @@ diff --git a/rhodecode/tests/functional/test_feed.py b/rhodecode/tests/functional/test_feed.py deleted file mode 100644 index e6e37afd..00000000 --- a/rhodecode/tests/functional/test_feed.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2010-2017 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 rhodecode.model.auth_token import AuthTokenModel -from rhodecode.model.db import User -from rhodecode.tests import * - - -class TestFeedController(TestController): - - def test_rss(self, backend): - self.log_user() - response = self.app.get(url(controller='feed', action='rss', - repo_name=backend.repo_name)) - - assert response.content_type == "application/rss+xml" - assert """""" in response - - def test_rss_with_auth_token(self, backend, user_admin): - auth_token = user_admin.feed_token - assert auth_token != '' - response = self.app.get( - url(controller='feed', action='rss', - repo_name=backend.repo_name, auth_token=auth_token, - status=200)) - - assert response.content_type == "application/rss+xml" - assert """""" in response - - def test_rss_with_auth_token_of_wrong_type(self, backend, user_util): - user = user_util.create_user() - auth_token = AuthTokenModel().create( - user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API) - auth_token = auth_token.api_key - - self.app.get( - url(controller='feed', action='rss', - repo_name=backend.repo_name, auth_token=auth_token), - status=302) - - auth_token = AuthTokenModel().create( - user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_FEED) - auth_token = auth_token.api_key - self.app.get( - url(controller='feed', action='rss', - repo_name=backend.repo_name, auth_token=auth_token), - status=200) - - def test_atom(self, backend): - self.log_user() - response = self.app.get(url(controller='feed', action='atom', - repo_name=backend.repo_name)) - - assert response.content_type == """application/atom+xml""" - assert """""" in response - - tag1 = '' - tag2 = '' - assert tag1 in response or tag2 in response