Show More
@@ -0,0 +1,203 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2017-2017 RhodeCode GmbH | |||
|
4 | # | |||
|
5 | # This program is free software: you can redistribute it and/or modify | |||
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
7 | # (only), as published by the Free Software Foundation. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU Affero General Public License | |||
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
16 | # | |||
|
17 | # This program is dual-licensed. If you wish to learn more about the | |||
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
20 | ||||
|
21 | import pytz | |||
|
22 | import logging | |||
|
23 | ||||
|
24 | from beaker.cache import cache_region | |||
|
25 | from pyramid.view import view_config | |||
|
26 | from pyramid.response import Response | |||
|
27 | from webhelpers.feedgenerator import Rss201rev2Feed, Atom1Feed | |||
|
28 | ||||
|
29 | from rhodecode.apps._base import RepoAppView | |||
|
30 | from rhodecode.lib import audit_logger | |||
|
31 | from rhodecode.lib import helpers as h | |||
|
32 | from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator, | |||
|
33 | NotAnonymous, CSRFRequired) | |||
|
34 | from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer | |||
|
35 | from rhodecode.lib.ext_json import json | |||
|
36 | from rhodecode.lib.utils2 import str2bool, safe_int | |||
|
37 | from rhodecode.model.db import UserApiKeys, CacheKey | |||
|
38 | ||||
|
39 | log = logging.getLogger(__name__) | |||
|
40 | ||||
|
41 | ||||
|
42 | class RepoFeedView(RepoAppView): | |||
|
43 | def load_default_context(self): | |||
|
44 | c = self._get_local_tmpl_context() | |||
|
45 | ||||
|
46 | # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead | |||
|
47 | c.repo_info = self.db_repo | |||
|
48 | ||||
|
49 | self._register_global_c(c) | |||
|
50 | self._load_defaults() | |||
|
51 | return c | |||
|
52 | ||||
|
53 | def _get_config(self): | |||
|
54 | import rhodecode | |||
|
55 | config = rhodecode.CONFIG | |||
|
56 | ||||
|
57 | return { | |||
|
58 | 'language': 'en-us', | |||
|
59 | 'feed_ttl': '5', # TTL of feed, | |||
|
60 | 'feed_include_diff': | |||
|
61 | str2bool(config.get('rss_include_diff', False)), | |||
|
62 | 'feed_items_per_page': | |||
|
63 | safe_int(config.get('rss_items_per_page', 20)), | |||
|
64 | 'feed_diff_limit': | |||
|
65 | # we need to protect from parsing huge diffs here other way | |||
|
66 | # we can kill the server | |||
|
67 | safe_int(config.get('rss_cut_off_limit', 32 * 1024)), | |||
|
68 | } | |||
|
69 | ||||
|
70 | def _load_defaults(self): | |||
|
71 | _ = self.request.translate | |||
|
72 | config = self._get_config() | |||
|
73 | # common values for feeds | |||
|
74 | self.description = _('Changes on %s repository') | |||
|
75 | self.title = self.title = _('%s %s feed') % (self.db_repo_name, '%s') | |||
|
76 | self.language = config["language"] | |||
|
77 | self.ttl = config["feed_ttl"] | |||
|
78 | self.feed_include_diff = config['feed_include_diff'] | |||
|
79 | self.feed_diff_limit = config['feed_diff_limit'] | |||
|
80 | self.feed_items_per_page = config['feed_items_per_page'] | |||
|
81 | ||||
|
82 | def _changes(self, commit): | |||
|
83 | diff_processor = DiffProcessor( | |||
|
84 | commit.diff(), diff_limit=self.feed_diff_limit) | |||
|
85 | _parsed = diff_processor.prepare(inline_diff=False) | |||
|
86 | limited_diff = isinstance(_parsed, LimitedDiffContainer) | |||
|
87 | ||||
|
88 | return _parsed, limited_diff | |||
|
89 | ||||
|
90 | def _get_title(self, commit): | |||
|
91 | return h.shorter(commit.message, 160) | |||
|
92 | ||||
|
93 | def _get_description(self, commit): | |||
|
94 | _renderer = self.request.get_partial_renderer( | |||
|
95 | 'feed/atom_feed_entry.mako') | |||
|
96 | parsed_diff, limited_diff = self._changes(commit) | |||
|
97 | return _renderer( | |||
|
98 | 'body', | |||
|
99 | commit=commit, | |||
|
100 | parsed_diff=parsed_diff, | |||
|
101 | limited_diff=limited_diff, | |||
|
102 | feed_include_diff=self.feed_include_diff, | |||
|
103 | ) | |||
|
104 | ||||
|
105 | def _set_timezone(self, date, tzinfo=pytz.utc): | |||
|
106 | if not getattr(date, "tzinfo", None): | |||
|
107 | date.replace(tzinfo=tzinfo) | |||
|
108 | return date | |||
|
109 | ||||
|
110 | def _get_commits(self): | |||
|
111 | return list(self.rhodecode_vcs_repo[-self.feed_items_per_page:]) | |||
|
112 | ||||
|
113 | @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) | |||
|
114 | @HasRepoPermissionAnyDecorator( | |||
|
115 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
116 | @view_config( | |||
|
117 | route_name='atom_feed_home', request_method='GET', | |||
|
118 | renderer=None) | |||
|
119 | def atom(self): | |||
|
120 | """ | |||
|
121 | Produce an atom-1.0 feed via feedgenerator module | |||
|
122 | """ | |||
|
123 | self.load_default_context() | |||
|
124 | ||||
|
125 | @cache_region('long_term') | |||
|
126 | def _generate_feed(cache_key): | |||
|
127 | feed = Atom1Feed( | |||
|
128 | title=self.title % self.db_repo_name, | |||
|
129 | link=h.route_url('repo_summary', repo_name=self.db_repo_name), | |||
|
130 | description=self.description % self.db_repo_name, | |||
|
131 | language=self.language, | |||
|
132 | ttl=self.ttl | |||
|
133 | ) | |||
|
134 | ||||
|
135 | for commit in reversed(self._get_commits()): | |||
|
136 | date = self._set_timezone(commit.date) | |||
|
137 | feed.add_item( | |||
|
138 | title=self._get_title(commit), | |||
|
139 | author_name=commit.author, | |||
|
140 | description=self._get_description(commit), | |||
|
141 | link=h.route_url( | |||
|
142 | 'changeset_home', repo_name=self.db_repo_name, | |||
|
143 | revision=commit.raw_id), | |||
|
144 | pubdate=date,) | |||
|
145 | ||||
|
146 | return feed.mime_type, feed.writeString('utf-8') | |||
|
147 | ||||
|
148 | invalidator_context = CacheKey.repo_context_cache( | |||
|
149 | _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM) | |||
|
150 | ||||
|
151 | with invalidator_context as context: | |||
|
152 | context.invalidate() | |||
|
153 | mime_type, feed = context.compute() | |||
|
154 | ||||
|
155 | response = Response(feed) | |||
|
156 | response.content_type = mime_type | |||
|
157 | return response | |||
|
158 | ||||
|
159 | @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED]) | |||
|
160 | @HasRepoPermissionAnyDecorator( | |||
|
161 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
162 | @view_config( | |||
|
163 | route_name='rss_feed_home', request_method='GET', | |||
|
164 | renderer=None) | |||
|
165 | def rss(self): | |||
|
166 | """ | |||
|
167 | Produce an rss2 feed via feedgenerator module | |||
|
168 | """ | |||
|
169 | self.load_default_context() | |||
|
170 | ||||
|
171 | @cache_region('long_term') | |||
|
172 | def _generate_feed(cache_key): | |||
|
173 | feed = Rss201rev2Feed( | |||
|
174 | title=self.title % self.db_repo_name, | |||
|
175 | link=h.route_url('repo_summary', repo_name=self.db_repo_name), | |||
|
176 | description=self.description % self.db_repo_name, | |||
|
177 | language=self.language, | |||
|
178 | ttl=self.ttl | |||
|
179 | ) | |||
|
180 | ||||
|
181 | for commit in reversed(self._get_commits()): | |||
|
182 | date = self._set_timezone(commit.date) | |||
|
183 | feed.add_item( | |||
|
184 | title=self._get_title(commit), | |||
|
185 | author_name=commit.author, | |||
|
186 | description=self._get_description(commit), | |||
|
187 | link=h.route_url( | |||
|
188 | 'changeset_home', repo_name=self.db_repo_name, | |||
|
189 | revision=commit.raw_id), | |||
|
190 | pubdate=date,) | |||
|
191 | ||||
|
192 | return feed.mime_type, feed.writeString('utf-8') | |||
|
193 | ||||
|
194 | invalidator_context = CacheKey.repo_context_cache( | |||
|
195 | _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_RSS) | |||
|
196 | ||||
|
197 | with invalidator_context as context: | |||
|
198 | context.invalidate() | |||
|
199 | mime_type, feed = context.compute() | |||
|
200 | ||||
|
201 | response = Response(feed) | |||
|
202 | response.content_type = mime_type | |||
|
203 | return response |
@@ -80,6 +80,21 b' def includeme(config):' | |||||
80 | pattern='/{repo_name:.*?[^/]}/pull-request-data', |
|
80 | pattern='/{repo_name:.*?[^/]}/pull-request-data', | |
81 | repo_route=True, repo_accepted_types=['hg', 'git']) |
|
81 | repo_route=True, repo_accepted_types=['hg', 'git']) | |
82 |
|
82 | |||
|
83 | # commits aka changesets | |||
|
84 | # TODO(dan): handle default landing revision ? | |||
|
85 | config.add_route( | |||
|
86 | name='changeset_home', | |||
|
87 | pattern='/{repo_name:.*?[^/]}/changeset/{revision}', | |||
|
88 | repo_route=True) | |||
|
89 | config.add_route( | |||
|
90 | name='changeset_children', | |||
|
91 | pattern='/{repo_name:.*?[^/]}/changeset_children/{revision}', | |||
|
92 | repo_route=True) | |||
|
93 | config.add_route( | |||
|
94 | name='changeset_parents', | |||
|
95 | pattern='/{repo_name:.*?[^/]}/changeset_parents/{revision}', | |||
|
96 | repo_route=True) | |||
|
97 | ||||
83 | # Settings |
|
98 | # Settings | |
84 | config.add_route( |
|
99 | config.add_route( | |
85 | name='edit_repo', |
|
100 | name='edit_repo', | |
@@ -143,6 +158,15 b' def includeme(config):' | |||||
143 | name='strip_execute', |
|
158 | name='strip_execute', | |
144 | pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True) |
|
159 | pattern='/{repo_name:.*?[^/]}/settings/strip_execute', repo_route=True) | |
145 |
|
160 | |||
|
161 | # ATOM/RSS Feed | |||
|
162 | config.add_route( | |||
|
163 | name='rss_feed_home', | |||
|
164 | pattern='/{repo_name:.*?[^/]}/feed/rss', repo_route=True) | |||
|
165 | ||||
|
166 | config.add_route( | |||
|
167 | name='atom_feed_home', | |||
|
168 | pattern='/{repo_name:.*?[^/]}/feed/atom', repo_route=True) | |||
|
169 | ||||
146 | # NOTE(marcink): needs to be at the end for catch-all |
|
170 | # NOTE(marcink): needs to be at the end for catch-all | |
147 | add_route_with_slash( |
|
171 | add_route_with_slash( | |
148 | config, |
|
172 | config, |
@@ -17,59 +17,78 b'' | |||||
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | import pytest | |||
20 | from rhodecode.model.auth_token import AuthTokenModel |
|
21 | from rhodecode.model.auth_token import AuthTokenModel | |
21 |
from rhodecode. |
|
22 | from rhodecode.tests import TestController | |
22 | from rhodecode.tests import * |
|
23 | ||
|
24 | ||||
|
25 | def route_path(name, params=None, **kwargs): | |||
|
26 | import urllib | |||
|
27 | ||||
|
28 | base_url = { | |||
|
29 | 'rss_feed_home': '/{repo_name}/feed/rss', | |||
|
30 | 'atom_feed_home': '/{repo_name}/feed/atom', | |||
|
31 | }[name].format(**kwargs) | |||
|
32 | ||||
|
33 | if params: | |||
|
34 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) | |||
|
35 | return base_url | |||
23 |
|
36 | |||
24 |
|
37 | |||
25 |
class TestFeed |
|
38 | class TestFeedView(TestController): | |
26 |
|
39 | |||
27 | def test_rss(self, backend): |
|
40 | @pytest.mark.parametrize("feed_type,response_types,content_type",[ | |
|
41 | ('rss', ['<rss version="2.0">'], | |||
|
42 | "application/rss+xml"), | |||
|
43 | ('atom', ['<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us">'], | |||
|
44 | "application/atom+xml"), | |||
|
45 | ]) | |||
|
46 | def test_feed(self, backend, feed_type, response_types, content_type): | |||
28 | self.log_user() |
|
47 | self.log_user() | |
29 |
response = self.app.get( |
|
48 | response = self.app.get( | |
30 | repo_name=backend.repo_name)) |
|
49 | route_path('{}_feed_home'.format(feed_type), repo_name=backend.repo_name)) | |
|
50 | ||||
|
51 | for content in response_types: | |||
|
52 | assert content in response | |||
31 |
|
53 | |||
32 |
assert response.content_type == |
|
54 | assert response.content_type == content_type | |
33 | assert """<rss version="2.0">""" in response |
|
|||
34 |
|
55 | |||
35 | def test_rss_with_auth_token(self, backend, user_admin): |
|
56 | @pytest.mark.parametrize("feed_type, content_type", [ | |
|
57 | ('rss', "application/rss+xml"), | |||
|
58 | ('atom', "application/atom+xml") | |||
|
59 | ]) | |||
|
60 | def test_feed_with_auth_token( | |||
|
61 | self, backend, user_admin, feed_type, content_type): | |||
36 | auth_token = user_admin.feed_token |
|
62 | auth_token = user_admin.feed_token | |
37 | assert auth_token != '' |
|
63 | assert auth_token != '' | |
38 | response = self.app.get( |
|
|||
39 | url(controller='feed', action='rss', |
|
|||
40 | repo_name=backend.repo_name, auth_token=auth_token, |
|
|||
41 | status=200)) |
|
|||
42 |
|
64 | |||
43 | assert response.content_type == "application/rss+xml" |
|
65 | response = self.app.get( | |
44 | assert """<rss version="2.0">""" in response |
|
66 | route_path( | |
|
67 | '{}_feed_home'.format(feed_type), repo_name=backend.repo_name, | |||
|
68 | params=dict(auth_token=auth_token)), | |||
|
69 | status=200) | |||
45 |
|
70 | |||
46 | def test_rss_with_auth_token_of_wrong_type(self, backend, user_util): |
|
71 | assert response.content_type == content_type | |
|
72 | ||||
|
73 | @pytest.mark.parametrize("feed_type", ['rss', 'atom']) | |||
|
74 | def test_feed_with_auth_token_of_wrong_type( | |||
|
75 | self, backend, user_util, feed_type): | |||
47 | user = user_util.create_user() |
|
76 | user = user_util.create_user() | |
48 | auth_token = AuthTokenModel().create( |
|
77 | auth_token = AuthTokenModel().create( | |
49 | user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API) |
|
78 | user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API) | |
50 | auth_token = auth_token.api_key |
|
79 | auth_token = auth_token.api_key | |
51 |
|
80 | |||
52 | self.app.get( |
|
81 | self.app.get( | |
53 | url(controller='feed', action='rss', |
|
82 | route_path( | |
54 |
repo_name=backend.repo_name, |
|
83 | '{}_feed_home'.format(feed_type), repo_name=backend.repo_name, | |
|
84 | params=dict(auth_token=auth_token)), | |||
55 | status=302) |
|
85 | status=302) | |
56 |
|
86 | |||
57 | auth_token = AuthTokenModel().create( |
|
87 | auth_token = AuthTokenModel().create( | |
58 | user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_FEED) |
|
88 | user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_FEED) | |
59 | auth_token = auth_token.api_key |
|
89 | auth_token = auth_token.api_key | |
60 | self.app.get( |
|
90 | self.app.get( | |
61 | url(controller='feed', action='rss', |
|
91 | route_path( | |
62 |
repo_name=backend.repo_name, |
|
92 | '{}_feed_home'.format(feed_type), repo_name=backend.repo_name, | |
|
93 | params=dict(auth_token=auth_token)), | |||
63 | status=200) |
|
94 | status=200) | |
64 |
|
||||
65 | def test_atom(self, backend): |
|
|||
66 | self.log_user() |
|
|||
67 | response = self.app.get(url(controller='feed', action='atom', |
|
|||
68 | repo_name=backend.repo_name)) |
|
|||
69 |
|
||||
70 | assert response.content_type == """application/atom+xml""" |
|
|||
71 | assert """<?xml version="1.0" encoding="utf-8"?>""" in response |
|
|||
72 |
|
||||
73 | tag1 = '<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us">' |
|
|||
74 | tag2 = '<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom">' |
|
|||
75 | assert tag1 in response or tag2 in response |
|
@@ -510,17 +510,6 b' def make_map(config):' | |||||
510 | controller='journal', action='toggle_following', jsroute=True, |
|
510 | controller='journal', action='toggle_following', jsroute=True, | |
511 | conditions={'method': ['POST']}) |
|
511 | conditions={'method': ['POST']}) | |
512 |
|
512 | |||
513 | # FEEDS |
|
|||
514 | rmap.connect('rss_feed_home', '/{repo_name}/feed/rss', |
|
|||
515 | controller='feed', action='rss', |
|
|||
516 | conditions={'function': check_repo}, |
|
|||
517 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
518 |
|
||||
519 | rmap.connect('atom_feed_home', '/{repo_name}/feed/atom', |
|
|||
520 | controller='feed', action='atom', |
|
|||
521 | conditions={'function': check_repo}, |
|
|||
522 | requirements=URL_NAME_REQUIREMENTS) |
|
|||
523 |
|
||||
524 | #========================================================================== |
|
513 | #========================================================================== | |
525 | # REPOSITORY ROUTES |
|
514 | # REPOSITORY ROUTES | |
526 | #========================================================================== |
|
515 | #========================================================================== |
@@ -113,6 +113,9 b' function registerRCRoutes() {' | |||||
113 | pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); |
|
113 | pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']); | |
114 | pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']); |
|
114 | pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']); | |
115 | pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']); |
|
115 | pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']); | |
|
116 | pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); | |||
|
117 | pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']); | |||
|
118 | pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']); | |||
116 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); |
|
119 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); | |
117 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); |
|
120 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); | |
118 | pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']); |
|
121 | pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']); | |
@@ -128,6 +131,8 b' function registerRCRoutes() {' | |||||
128 | pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']); |
|
131 | pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']); | |
129 | pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']); |
|
132 | pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']); | |
130 | pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']); |
|
133 | pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']); | |
|
134 | pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']); | |||
|
135 | pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']); | |||
131 | pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']); |
|
136 | pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']); | |
132 | pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']); |
|
137 | pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']); | |
133 | pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']); |
|
138 | pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']); |
@@ -92,17 +92,17 b'' | |||||
92 |
|
92 | |||
93 | <%def name="rss(name)"> |
|
93 | <%def name="rss(name)"> | |
94 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
94 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
95 | <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.url('rss_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a> |
|
95 | <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a> | |
96 | %else: |
|
96 | %else: | |
97 |
<a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h. |
|
97 | <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a> | |
98 | %endif |
|
98 | %endif | |
99 | </%def> |
|
99 | </%def> | |
100 |
|
100 | |||
101 | <%def name="atom(name)"> |
|
101 | <%def name="atom(name)"> | |
102 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
102 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
103 | <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.url('atom_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a> |
|
103 | <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a> | |
104 | %else: |
|
104 | %else: | |
105 |
<a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h. |
|
105 | <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a> | |
106 | %endif |
|
106 | %endif | |
107 | </%def> |
|
107 | </%def> | |
108 |
|
108 |
@@ -10,8 +10,8 b'' | |||||
10 |
|
10 | |||
11 |
|
11 | |||
12 | <%def name="head_extra()"> |
|
12 | <%def name="head_extra()"> | |
13 |
<link href="${h. |
|
13 | <link href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" rel="alternate" title="${h.tooltip(_('%s ATOM feed') % c.repo_name)}" type="application/atom+xml" /> | |
14 |
<link href="${h. |
|
14 | <link href="${h.route_path('rss_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" rel="alternate" title="${h.tooltip(_('%s RSS feed') % c.repo_name)}" type="application/rss+xml" /> | |
15 | </%def> |
|
15 | </%def> | |
16 |
|
16 | |||
17 |
|
17 |
@@ -14,9 +14,9 b'' | |||||
14 | <ul class="links icon-only-links block-right"> |
|
14 | <ul class="links icon-only-links block-right"> | |
15 | <li> |
|
15 | <li> | |
16 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
16 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
17 |
<a href="${h. |
|
17 | <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}"><i class="icon-rss-sign"></i></a> | |
18 | %else: |
|
18 | %else: | |
19 |
<a href="${h. |
|
19 | <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name)}" title="${_('RSS Feed')}"><i class="icon-rss-sign"></i></a> | |
20 | %endif |
|
20 | %endif | |
21 | </li> |
|
21 | </li> | |
22 | </ul> |
|
22 | </ul> |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now