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>
<%def name="atom(name)">
%if c.rhodecode_user.username != h.DEFAULT_USER:
-
+
%else:
-
+
%endif
%def>
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()">
-
-
+
+
%def>
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 @@
%if c.rhodecode_user.username != h.DEFAULT_USER:
-
+
%else:
-
+
%endif
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