Show More
The requested changes are too big and content was truncated. Show full diff
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
@@ -0,0 +1,30 b'' | |||
|
1 | import logging | |
|
2 | ||
|
3 | from sqlalchemy import * | |
|
4 | ||
|
5 | from rhodecode.model import meta | |
|
6 | from rhodecode.lib.dbmigrate.versions import _reset_base, notify | |
|
7 | ||
|
8 | log = logging.getLogger(__name__) | |
|
9 | ||
|
10 | ||
|
11 | def upgrade(migrate_engine): | |
|
12 | """ | |
|
13 | Upgrade operations go here. | |
|
14 | Don't create your own engine; bind migrate_engine to your metadata | |
|
15 | """ | |
|
16 | _reset_base(migrate_engine) | |
|
17 | from rhodecode.lib.dbmigrate.schema import db_4_16_0_1 as db | |
|
18 | ||
|
19 | db.UserBookmark.__table__.create() | |
|
20 | ||
|
21 | fixups(db, meta.Session) | |
|
22 | ||
|
23 | ||
|
24 | def downgrade(migrate_engine): | |
|
25 | meta = MetaData() | |
|
26 | meta.bind = migrate_engine | |
|
27 | ||
|
28 | ||
|
29 | def fixups(models, _SESSION): | |
|
30 | pass |
@@ -0,0 +1,222 b'' | |||
|
1 | <%namespace name="dt" file="/data_table/_dt_elements.mako"/> | |
|
2 | ||
|
3 | <%def name="form_item(count, position=None, title=None, redirect_url=None, repo=None, repo_group=None)"> | |
|
4 | <tr> | |
|
5 | <td class="td-align-top" > | |
|
6 | <div class="label"> | |
|
7 | <label for="position">${_('Position')}:</label> | |
|
8 | </div> | |
|
9 | <div class="input"> | |
|
10 | <input type="text" name="position" value="${position or count}" style="width: 40px"/> | |
|
11 | </div> | |
|
12 | </td> | |
|
13 | ||
|
14 | <td> | |
|
15 | <div class="label"> | |
|
16 | <label for="title">${_('Bookmark title (max 30 characters, optional)')}:</label> | |
|
17 | </div> | |
|
18 | <div class="input"> | |
|
19 | <input type="text" name="title" value="${title}" style="width: 300px" maxlength="30"/> | |
|
20 | ||
|
21 | <div class="field pull-right"> | |
|
22 | <div> | |
|
23 | <label class="btn-link btn-danger">${_('Clear')}:</label> | |
|
24 | ${h.checkbox('remove', value=True)} | |
|
25 | </div> | |
|
26 | </div> | |
|
27 | </div> | |
|
28 | ||
|
29 | <div class="label"> | |
|
30 | <label for="redirect_url">${_('Redirect URL')}:</label> | |
|
31 | </div> | |
|
32 | <div class="input"> | |
|
33 | <input type="text" name="redirect_url" value="${redirect_url}" style="width: 600px"/> | |
|
34 | </div> | |
|
35 | ||
|
36 | ||
|
37 | <div class="select"> | |
|
38 | % if repo: | |
|
39 | <div class="label"> | |
|
40 | <label for="redirect_url">${_('Repository template')}:</label> | |
|
41 | </div> | |
|
42 | ${dt.repo_name(name=repo.repo_name, rtype=repo.repo_type,rstate=None,private=None,archived=False,fork_of=False)} | |
|
43 | ${h.hidden('bookmark_repo', repo.repo_id)} | |
|
44 | % elif repo_group: | |
|
45 | <div class="label"> | |
|
46 | <label for="redirect_url">${_('Repository group template')}:</label> | |
|
47 | </div> | |
|
48 | ${dt.repo_group_name(repo_group.group_name)} | |
|
49 | ${h.hidden('bookmark_repo_group', repo_group.group_id)} | |
|
50 | % else: | |
|
51 | <div class="label"> | |
|
52 | <label for="redirect_url">${_('Template Repository or Repository group')}:</label> | |
|
53 | </div> | |
|
54 | ${h.hidden('bookmark_repo', class_='bookmark_repo')} | |
|
55 | <span style="padding-right:15px">OR</span> | |
|
56 | ${h.hidden('bookmark_repo_group', class_='bookmark_repo_group')} | |
|
57 | % endif | |
|
58 | </div> | |
|
59 | ||
|
60 | <p class="help-block help-block-inline" > | |
|
61 | % if repo: | |
|
62 | ${_('Available as ${repo_url} e.g. Redirect url: ${repo_url}/changelog')} | |
|
63 | % elif repo_group: | |
|
64 | ${_('Available as ${repo_group_url} e.g. Redirect url: ${repo_group_url}')} | |
|
65 | % else: | |
|
66 | ${_('Available as full url variables in redirect url. i.e: ${repo_url}, ${repo_group_url}.')} | |
|
67 | % endif | |
|
68 | </p> | |
|
69 | </td> | |
|
70 | ||
|
71 | </tr> | |
|
72 | </%def> | |
|
73 | ||
|
74 | <div class="panel panel-default"> | |
|
75 | <div class="panel-heading"> | |
|
76 | <h3 class="panel-title">${_('Your Bookmarks')}</h3> | |
|
77 | </div> | |
|
78 | ||
|
79 | <div class="panel-body"> | |
|
80 | <p> | |
|
81 | ${_('Store upto 10 bookmark links to favorite repositories, external issue tracker or CI server. ')} | |
|
82 | <br/> | |
|
83 | ${_('Bookmarks are accessible from your username dropdown or by keyboard shortcut `g 0-9`')} | |
|
84 | </p> | |
|
85 | ||
|
86 | ${h.secure_form(h.route_path('my_account_bookmarks_update'), request=request)} | |
|
87 | <div class="form-vertical"> | |
|
88 | <table class="rctable"> | |
|
89 | ## generate always 10 entries | |
|
90 | <input type="hidden" name="__start__" value="bookmarks:sequence"/> | |
|
91 | % for cnt, item in enumerate((c.bookmark_items + [None for i in range(10)])[:10]): | |
|
92 | <input type="hidden" name="__start__" value="bookmark:mapping"/> | |
|
93 | % if item is None: | |
|
94 | ## empty placehodlder | |
|
95 | ${form_item(cnt)} | |
|
96 | % else: | |
|
97 | ## actual entry | |
|
98 | ${form_item(cnt, position=item.position, title=item.title, redirect_url=item.redirect_url, repo=item.repository, repo_group=item.repository_group)} | |
|
99 | % endif | |
|
100 | <input type="hidden" name="__end__" value="bookmark:mapping"/> | |
|
101 | % endfor | |
|
102 | <input type="hidden" name="__end__" value="bookmarks:sequence"/> | |
|
103 | </table> | |
|
104 | <div class="buttons"> | |
|
105 | ${h.submit('save',_('Save'),class_="btn")} | |
|
106 | </div> | |
|
107 | </div> | |
|
108 | ${h.end_form()} | |
|
109 | </div> | |
|
110 | </div> | |
|
111 | ||
|
112 | <script> | |
|
113 | $(document).ready(function(){ | |
|
114 | ||
|
115 | ||
|
116 | var repoFilter = function (data) { | |
|
117 | var results = []; | |
|
118 | ||
|
119 | if (!data.results[0]) { | |
|
120 | return data | |
|
121 | } | |
|
122 | ||
|
123 | $.each(data.results[0].children, function () { | |
|
124 | // replace name to ID for submision | |
|
125 | this.id = this.repo_id; | |
|
126 | results.push(this); | |
|
127 | }); | |
|
128 | ||
|
129 | data.results[0].children = results; | |
|
130 | return data; | |
|
131 | }; | |
|
132 | ||
|
133 | ||
|
134 | $(".bookmark_repo").select2({ | |
|
135 | cachedDataSource: {}, | |
|
136 | minimumInputLength: 2, | |
|
137 | placeholder: "${_('repository')}", | |
|
138 | dropdownAutoWidth: true, | |
|
139 | containerCssClass: "drop-menu", | |
|
140 | dropdownCssClass: "drop-menu-dropdown", | |
|
141 | formatResult: formatRepoResult, | |
|
142 | query: $.debounce(250, function (query) { | |
|
143 | self = this; | |
|
144 | var cacheKey = query.term; | |
|
145 | var cachedData = self.cachedDataSource[cacheKey]; | |
|
146 | ||
|
147 | if (cachedData) { | |
|
148 | query.callback({results: cachedData.results}); | |
|
149 | } else { | |
|
150 | $.ajax({ | |
|
151 | url: pyroutes.url('repo_list_data'), | |
|
152 | data: {'query': query.term}, | |
|
153 | dataType: 'json', | |
|
154 | type: 'GET', | |
|
155 | success: function (data) { | |
|
156 | data = repoFilter(data); | |
|
157 | self.cachedDataSource[cacheKey] = data; | |
|
158 | query.callback({results: data.results}); | |
|
159 | }, | |
|
160 | error: function (data, textStatus, errorThrown) { | |
|
161 | alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); | |
|
162 | } | |
|
163 | }) | |
|
164 | } | |
|
165 | }), | |
|
166 | }); | |
|
167 | ||
|
168 | var repoGroupFilter = function (data) { | |
|
169 | var results = []; | |
|
170 | ||
|
171 | if (!data.results[0]) { | |
|
172 | return data | |
|
173 | } | |
|
174 | ||
|
175 | $.each(data.results[0].children, function () { | |
|
176 | // replace name to ID for submision | |
|
177 | this.id = this.repo_group_id; | |
|
178 | results.push(this); | |
|
179 | }); | |
|
180 | ||
|
181 | data.results[0].children = results; | |
|
182 | return data; | |
|
183 | }; | |
|
184 | ||
|
185 | $(".bookmark_repo_group").select2({ | |
|
186 | cachedDataSource: {}, | |
|
187 | minimumInputLength: 2, | |
|
188 | placeholder: "${_('repository group')}", | |
|
189 | dropdownAutoWidth: true, | |
|
190 | containerCssClass: "drop-menu", | |
|
191 | dropdownCssClass: "drop-menu-dropdown", | |
|
192 | formatResult: formatRepoGroupResult, | |
|
193 | query: $.debounce(250, function (query) { | |
|
194 | self = this; | |
|
195 | var cacheKey = query.term; | |
|
196 | var cachedData = self.cachedDataSource[cacheKey]; | |
|
197 | ||
|
198 | if (cachedData) { | |
|
199 | query.callback({results: cachedData.results}); | |
|
200 | } else { | |
|
201 | $.ajax({ | |
|
202 | url: pyroutes.url('repo_group_list_data'), | |
|
203 | data: {'query': query.term}, | |
|
204 | dataType: 'json', | |
|
205 | type: 'GET', | |
|
206 | success: function (data) { | |
|
207 | data = repoGroupFilter(data); | |
|
208 | self.cachedDataSource[cacheKey] = data; | |
|
209 | query.callback({results: data.results}); | |
|
210 | }, | |
|
211 | error: function (data, textStatus, errorThrown) { | |
|
212 | alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); | |
|
213 | } | |
|
214 | }) | |
|
215 | } | |
|
216 | }) | |
|
217 | }); | |
|
218 | ||
|
219 | ||
|
220 | }); | |
|
221 | ||
|
222 | </script> |
@@ -45,7 +45,7 b' PYRAMID_SETTINGS = {}' | |||
|
45 | 45 | EXTENSIONS = {} |
|
46 | 46 | |
|
47 | 47 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
48 |
__dbversion__ = 9 |
|
|
48 | __dbversion__ = 94 # defines current db version for migrations | |
|
49 | 49 | __platform__ = platform.system() |
|
50 | 50 | __license__ = 'AGPLv3, and Commercial License' |
|
51 | 51 | __author__ = 'RhodeCode GmbH' |
@@ -56,6 +56,10 b' def includeme(config):' | |||
|
56 | 56 | pattern='/_repos') |
|
57 | 57 | |
|
58 | 58 | config.add_route( |
|
59 | name='repo_group_list_data', | |
|
60 | pattern='/_repo_groups') | |
|
61 | ||
|
62 | config.add_route( | |
|
59 | 63 | name='goto_switcher_data', |
|
60 | 64 | pattern='/_goto_data') |
|
61 | 65 |
@@ -172,7 +172,9 b' class HomeView(BaseAppView):' | |||
|
172 | 172 | 'id': obj.group_name, |
|
173 | 173 | 'value': org_query, |
|
174 | 174 | 'value_display': obj.group_name, |
|
175 | 'text': obj.group_name, | |
|
175 | 176 | 'type': 'repo_group', |
|
177 | 'repo_group_id': obj.group_id, | |
|
176 | 178 | 'url': h.route_path( |
|
177 | 179 | 'repo_group_home', repo_group_name=obj.group_name) |
|
178 | 180 | } |
@@ -302,6 +304,33 b' class HomeView(BaseAppView):' | |||
|
302 | 304 | } |
|
303 | 305 | return data |
|
304 | 306 | |
|
307 | @LoginRequired() | |
|
308 | @view_config( | |
|
309 | route_name='repo_group_list_data', request_method='GET', | |
|
310 | renderer='json_ext', xhr=True) | |
|
311 | def repo_group_list_data(self): | |
|
312 | _ = self.request.translate | |
|
313 | self.load_default_context() | |
|
314 | ||
|
315 | query = self.request.GET.get('query') | |
|
316 | ||
|
317 | log.debug('generating repo group list, query:%s', | |
|
318 | query) | |
|
319 | ||
|
320 | res = [] | |
|
321 | repo_groups = self._get_repo_group_list(query) | |
|
322 | if repo_groups: | |
|
323 | res.append({ | |
|
324 | 'text': _('Repository Groups'), | |
|
325 | 'children': repo_groups | |
|
326 | }) | |
|
327 | ||
|
328 | data = { | |
|
329 | 'more': False, | |
|
330 | 'results': res | |
|
331 | } | |
|
332 | return data | |
|
333 | ||
|
305 | 334 | def _get_default_search_queries(self, search_context, searcher, query): |
|
306 | 335 | if not searcher: |
|
307 | 336 | return [] |
@@ -95,6 +95,18 b' def includeme(config):' | |||
|
95 | 95 | pattern=ADMIN_PREFIX + '/my_account/watched') |
|
96 | 96 | |
|
97 | 97 | config.add_route( |
|
98 | name='my_account_bookmarks', | |
|
99 | pattern=ADMIN_PREFIX + '/my_account/bookmarks') | |
|
100 | ||
|
101 | config.add_route( | |
|
102 | name='my_account_bookmarks_update', | |
|
103 | pattern=ADMIN_PREFIX + '/my_account/bookmarks/update') | |
|
104 | ||
|
105 | config.add_route( | |
|
106 | name='my_account_goto_bookmark', | |
|
107 | pattern=ADMIN_PREFIX + '/my_account/bookmark/{bookmark_id}') | |
|
108 | ||
|
109 | config.add_route( | |
|
98 | 110 | name='my_account_perms', |
|
99 | 111 | pattern=ADMIN_PREFIX + '/my_account/perms') |
|
100 | 112 |
@@ -20,29 +20,30 b'' | |||
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | import datetime |
|
23 | import string | |
|
23 | 24 | |
|
24 | 25 | import formencode |
|
25 | 26 | import formencode.htmlfill |
|
27 | import peppercorn | |
|
26 | 28 | from pyramid.httpexceptions import HTTPFound |
|
27 | 29 | from pyramid.view import view_config |
|
28 | from pyramid.renderers import render | |
|
29 | from pyramid.response import Response | |
|
30 | 30 | |
|
31 | 31 | from rhodecode.apps._base import BaseAppView, DataGridAppView |
|
32 | 32 | from rhodecode import forms |
|
33 | 33 | from rhodecode.lib import helpers as h |
|
34 | 34 | from rhodecode.lib import audit_logger |
|
35 | 35 | from rhodecode.lib.ext_json import json |
|
36 | from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired | |
|
36 | from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, \ | |
|
37 | HasRepoPermissionAny, HasRepoGroupPermissionAny | |
|
37 | 38 | from rhodecode.lib.channelstream import ( |
|
38 | 39 | channelstream_request, ChannelstreamException) |
|
39 | 40 | from rhodecode.lib.utils2 import safe_int, md5, str2bool |
|
40 | 41 | from rhodecode.model.auth_token import AuthTokenModel |
|
41 | 42 | from rhodecode.model.comment import CommentsModel |
|
42 | 43 | from rhodecode.model.db import ( |
|
43 | Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload, | |
|
44 | PullRequest) | |
|
45 | from rhodecode.model.forms import UserForm, UserExtraEmailForm | |
|
44 | IntegrityError, joinedload, | |
|
45 | Repository, UserEmailMap, UserApiKeys, UserFollowing, | |
|
46 | PullRequest, UserBookmark, RepoGroup) | |
|
46 | 47 | from rhodecode.model.meta import Session |
|
47 | 48 | from rhodecode.model.pull_request import PullRequestModel |
|
48 | 49 | from rhodecode.model.scm import RepoList |
@@ -392,6 +393,140 b' class MyAccountView(BaseAppView, DataGri' | |||
|
392 | 393 | @LoginRequired() |
|
393 | 394 | @NotAnonymous() |
|
394 | 395 | @view_config( |
|
396 | route_name='my_account_bookmarks', request_method='GET', | |
|
397 | renderer='rhodecode:templates/admin/my_account/my_account.mako') | |
|
398 | def my_account_bookmarks(self): | |
|
399 | c = self.load_default_context() | |
|
400 | c.active = 'bookmarks' | |
|
401 | return self._get_template_context(c) | |
|
402 | ||
|
403 | def _process_entry(self, entry, user_id): | |
|
404 | position = safe_int(entry.get('position')) | |
|
405 | if position is None: | |
|
406 | return | |
|
407 | ||
|
408 | # check if this is an existing entry | |
|
409 | is_new = False | |
|
410 | db_entry = UserBookmark().get_by_position_for_user(position, user_id) | |
|
411 | ||
|
412 | if db_entry and str2bool(entry.get('remove')): | |
|
413 | log.debug('Marked bookmark %s for deletion', db_entry) | |
|
414 | Session().delete(db_entry) | |
|
415 | return | |
|
416 | ||
|
417 | if not db_entry: | |
|
418 | # new | |
|
419 | db_entry = UserBookmark() | |
|
420 | is_new = True | |
|
421 | ||
|
422 | should_save = False | |
|
423 | default_redirect_url = '' | |
|
424 | ||
|
425 | # save repo | |
|
426 | if entry.get('bookmark_repo'): | |
|
427 | repo = Repository.get(entry['bookmark_repo']) | |
|
428 | perm_check = HasRepoPermissionAny( | |
|
429 | 'repository.read', 'repository.write', 'repository.admin') | |
|
430 | if repo and perm_check(repo_name=repo.repo_name): | |
|
431 | db_entry.repository = repo | |
|
432 | should_save = True | |
|
433 | default_redirect_url = '${repo_url}' | |
|
434 | # save repo group | |
|
435 | elif entry.get('bookmark_repo_group'): | |
|
436 | repo_group = RepoGroup.get(entry['bookmark_repo_group']) | |
|
437 | perm_check = HasRepoGroupPermissionAny( | |
|
438 | 'group.read', 'group.write', 'group.admin') | |
|
439 | ||
|
440 | if repo_group and perm_check(group_name=repo_group.group_name): | |
|
441 | db_entry.repository_group = repo_group | |
|
442 | should_save = True | |
|
443 | default_redirect_url = '${repo_group_url}' | |
|
444 | # save generic info | |
|
445 | elif entry.get('title') and entry.get('redirect_url'): | |
|
446 | should_save = True | |
|
447 | ||
|
448 | if should_save: | |
|
449 | log.debug('Saving bookmark %s, new:%s', db_entry, is_new) | |
|
450 | # mark user and position | |
|
451 | db_entry.user_id = user_id | |
|
452 | db_entry.position = position | |
|
453 | db_entry.title = entry.get('title') | |
|
454 | db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url | |
|
455 | ||
|
456 | Session().add(db_entry) | |
|
457 | ||
|
458 | @LoginRequired() | |
|
459 | @NotAnonymous() | |
|
460 | @CSRFRequired() | |
|
461 | @view_config( | |
|
462 | route_name='my_account_bookmarks_update', request_method='POST') | |
|
463 | def my_account_bookmarks_update(self): | |
|
464 | _ = self.request.translate | |
|
465 | c = self.load_default_context() | |
|
466 | c.active = 'bookmarks' | |
|
467 | ||
|
468 | controls = peppercorn.parse(self.request.POST.items()) | |
|
469 | user_id = c.user.user_id | |
|
470 | ||
|
471 | try: | |
|
472 | for entry in controls.get('bookmarks', []): | |
|
473 | self._process_entry(entry, user_id) | |
|
474 | ||
|
475 | Session().commit() | |
|
476 | h.flash(_("Update Bookmarks"), category='success') | |
|
477 | except IntegrityError: | |
|
478 | h.flash(_("Failed to update bookmarks. " | |
|
479 | "Make sure an unique position is used"), category='error') | |
|
480 | ||
|
481 | return HTTPFound(h.route_path('my_account_bookmarks')) | |
|
482 | ||
|
483 | @LoginRequired() | |
|
484 | @NotAnonymous() | |
|
485 | @view_config( | |
|
486 | route_name='my_account_goto_bookmark', request_method='GET', | |
|
487 | renderer='rhodecode:templates/admin/my_account/my_account.mako') | |
|
488 | def my_account_goto_bookmark(self): | |
|
489 | ||
|
490 | bookmark_id = self.request.matchdict['bookmark_id'] | |
|
491 | user_bookmark = UserBookmark().query()\ | |
|
492 | .filter(UserBookmark.user_id == self.request.user.user_id) \ | |
|
493 | .filter(UserBookmark.position == bookmark_id).scalar() | |
|
494 | ||
|
495 | redirect_url = h.route_path('my_account_bookmarks') | |
|
496 | if not user_bookmark: | |
|
497 | raise HTTPFound(redirect_url) | |
|
498 | ||
|
499 | if user_bookmark.repository: | |
|
500 | repo_name = user_bookmark.repository.repo_name | |
|
501 | base_redirect_url = h.route_path( | |
|
502 | 'repo_summary', repo_name=repo_name) | |
|
503 | if user_bookmark.redirect_url and \ | |
|
504 | '${repo_url}' in user_bookmark.redirect_url: | |
|
505 | redirect_url = string.Template(user_bookmark.redirect_url)\ | |
|
506 | .safe_substitute({'repo_url': base_redirect_url}) | |
|
507 | else: | |
|
508 | redirect_url = base_redirect_url | |
|
509 | ||
|
510 | elif user_bookmark.repository_group: | |
|
511 | repo_group_name = user_bookmark.repository_group.group_name | |
|
512 | base_redirect_url = h.route_path( | |
|
513 | 'repo_group_home', repo_group_name=repo_group_name) | |
|
514 | if user_bookmark.redirect_url and \ | |
|
515 | '${repo_group_url}' in user_bookmark.redirect_url: | |
|
516 | redirect_url = string.Template(user_bookmark.redirect_url)\ | |
|
517 | .safe_substitute({'repo_group_url': base_redirect_url}) | |
|
518 | else: | |
|
519 | redirect_url = base_redirect_url | |
|
520 | ||
|
521 | elif user_bookmark.redirect_url: | |
|
522 | redirect_url = user_bookmark.redirect_url | |
|
523 | ||
|
524 | log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url) | |
|
525 | raise HTTPFound(redirect_url) | |
|
526 | ||
|
527 | @LoginRequired() | |
|
528 | @NotAnonymous() | |
|
529 | @view_config( | |
|
395 | 530 | route_name='my_account_perms', request_method='GET', |
|
396 | 531 | renderer='rhodecode:templates/admin/my_account/my_account.mako') |
|
397 | 532 | def my_account_perms(self): |
@@ -44,7 +44,7 b' from rhodecode.lib.exceptions import Use' | |||
|
44 | 44 | from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes) |
|
45 | 45 | from rhodecode.lib.utils2 import ( |
|
46 | 46 | str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str) |
|
47 | from rhodecode.model.db import Repository, User, ChangesetComment | |
|
47 | from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark | |
|
48 | 48 | from rhodecode.model.notification import NotificationModel |
|
49 | 49 | from rhodecode.model.settings import VcsSettingsModel, SettingsModel |
|
50 | 50 | |
@@ -282,7 +282,7 b' def get_current_lang(request):' | |||
|
282 | 282 | return getattr(request, '_LOCALE_', request.locale_name) |
|
283 | 283 | |
|
284 | 284 | |
|
285 | def attach_context_attributes(context, request, user_id): | |
|
285 | def attach_context_attributes(context, request, user_id=None): | |
|
286 | 286 | """ |
|
287 | 287 | Attach variables into template context called `c`. |
|
288 | 288 | """ |
@@ -422,7 +422,13 b' def attach_context_attributes(context, r' | |||
|
422 | 422 | context.csrf_token = auth.get_csrf_token(session=request.session) |
|
423 | 423 | context.backends = rhodecode.BACKENDS.keys() |
|
424 | 424 | context.backends.sort() |
|
425 | context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id) | |
|
425 | unread_count = 0 | |
|
426 | user_bookmark_list = [] | |
|
427 | if user_id: | |
|
428 | unread_count = NotificationModel().get_unread_cnt_for_user(user_id) | |
|
429 | user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id) | |
|
430 | context.unread_notifications = unread_count | |
|
431 | context.bookmark_items = user_bookmark_list | |
|
426 | 432 | |
|
427 | 433 | # web case |
|
428 | 434 | if hasattr(request, 'user'): |
@@ -4797,6 +4797,49 b' class UserGroupToRepoBranchPermission(Ba' | |||
|
4797 | 4797 | self.user_group_repo_to_perm, self.branch_pattern) |
|
4798 | 4798 | |
|
4799 | 4799 | |
|
4800 | class UserBookmark(Base, BaseModel): | |
|
4801 | __tablename__ = 'user_bookmarks' | |
|
4802 | __table_args__ = ( | |
|
4803 | UniqueConstraint('user_id', 'bookmark_repo_id'), | |
|
4804 | UniqueConstraint('user_id', 'bookmark_repo_group_id'), | |
|
4805 | UniqueConstraint('user_id', 'bookmark_position'), | |
|
4806 | base_table_args | |
|
4807 | ) | |
|
4808 | ||
|
4809 | user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
|
4810 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
|
4811 | position = Column("bookmark_position", Integer(), nullable=False) | |
|
4812 | title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None) | |
|
4813 | redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None) | |
|
4814 | created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
|
4815 | ||
|
4816 | bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None) | |
|
4817 | bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None) | |
|
4818 | ||
|
4819 | user = relationship("User") | |
|
4820 | ||
|
4821 | repository = relationship("Repository") | |
|
4822 | repository_group = relationship("RepoGroup") | |
|
4823 | ||
|
4824 | @classmethod | |
|
4825 | def get_by_position_for_user(cls, position, user_id): | |
|
4826 | return cls.query() \ | |
|
4827 | .filter(UserBookmark.user_id == user_id) \ | |
|
4828 | .filter(UserBookmark.position == position).scalar() | |
|
4829 | ||
|
4830 | @classmethod | |
|
4831 | def get_bookmarks_for_user(cls, user_id): | |
|
4832 | return cls.query() \ | |
|
4833 | .filter(UserBookmark.user_id == user_id) \ | |
|
4834 | .options(joinedload(UserBookmark.repository)) \ | |
|
4835 | .options(joinedload(UserBookmark.repository_group)) \ | |
|
4836 | .order_by(UserBookmark.position.asc()) \ | |
|
4837 | .all() | |
|
4838 | ||
|
4839 | def __unicode__(self): | |
|
4840 | return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url) | |
|
4841 | ||
|
4842 | ||
|
4800 | 4843 | class DbMigrateVersion(Base, BaseModel): |
|
4801 | 4844 | __tablename__ = 'db_migrate_version' |
|
4802 | 4845 | __table_args__ = ( |
@@ -200,7 +200,27 b'' | |||
|
200 | 200 | .user-menu.submenu { |
|
201 | 201 | right: 0; |
|
202 | 202 | left: auto; |
|
203 | min-width: 290px; | |
|
203 | 204 | } |
|
205 | ||
|
206 | ||
|
207 | .user-menu { | |
|
208 | .bookmark-items { | |
|
209 | padding: 4px 2px; | |
|
210 | color: @grey3; | |
|
211 | border-bottom: @grey3; | |
|
212 | ||
|
213 | a { | |
|
214 | padding: 0 !important; | |
|
215 | color: @rcblue !important; | |
|
216 | } | |
|
217 | } | |
|
218 | a.bookmark-item { | |
|
219 | color: @rcblue !important; | |
|
220 | } | |
|
221 | } | |
|
222 | ||
|
223 | ||
|
204 | 224 | #quick_login { |
|
205 | 225 | left: auto; |
|
206 | 226 | right: 0; |
@@ -172,7 +172,9 b' table.dataTable {' | |||
|
172 | 172 | &.td-buttons { |
|
173 | 173 | padding: .3em 0; |
|
174 | 174 | } |
|
175 | ||
|
175 | &.td-align-top { | |
|
176 | vertical-align: text-top | |
|
177 | } | |
|
176 | 178 | &.td-action { |
|
177 | 179 | // this is for the remove/delete/edit buttons |
|
178 | 180 | padding-right: 0; |
@@ -526,6 +526,10 b' address {' | |||
|
526 | 526 | font-family: @text-regular; |
|
527 | 527 | } |
|
528 | 528 | |
|
529 | .help-block-inline { | |
|
530 | margin: 0; | |
|
531 | } | |
|
532 | ||
|
529 | 533 | // help block text |
|
530 | 534 | .help-block { |
|
531 | 535 | display: block; |
@@ -51,6 +51,38 b' function setRCMouseBindings(repoName, re' | |||
|
51 | 51 | Mousetrap.bind(['g G'], function(e) { |
|
52 | 52 | window.location = pyroutes.url('gists_show', {'public': 1}); |
|
53 | 53 | }); |
|
54 | ||
|
55 | Mousetrap.bind(['g 0'], function(e) { | |
|
56 | window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 0}); | |
|
57 | }); | |
|
58 | Mousetrap.bind(['g 1'], function(e) { | |
|
59 | window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 1}); | |
|
60 | }); | |
|
61 | Mousetrap.bind(['g 2'], function(e) { | |
|
62 | window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 2}); | |
|
63 | }); | |
|
64 | Mousetrap.bind(['g 3'], function(e) { | |
|
65 | window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 3}); | |
|
66 | }); | |
|
67 | Mousetrap.bind(['g 4'], function(e) { | |
|
68 | window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 4}); | |
|
69 | }); | |
|
70 | Mousetrap.bind(['g 5'], function(e) { | |
|
71 | window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 5}); | |
|
72 | }); | |
|
73 | Mousetrap.bind(['g 6'], function(e) { | |
|
74 | window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 6}); | |
|
75 | }); | |
|
76 | Mousetrap.bind(['g 7'], function(e) { | |
|
77 | window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 7}); | |
|
78 | }); | |
|
79 | Mousetrap.bind(['g 8'], function(e) { | |
|
80 | window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 8}); | |
|
81 | }); | |
|
82 | Mousetrap.bind(['g 9'], function(e) { | |
|
83 | window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 9}); | |
|
84 | }); | |
|
85 | ||
|
54 | 86 | Mousetrap.bind(['n g'], function(e) { |
|
55 | 87 | window.location = pyroutes.url('gists_new'); |
|
56 | 88 | }); |
@@ -58,7 +90,7 b' function setRCMouseBindings(repoName, re' | |||
|
58 | 90 | window.location = pyroutes.url('repo_new'); |
|
59 | 91 | }); |
|
60 | 92 | |
|
61 | if (repoName && repoName != '') { | |
|
93 | if (repoName && repoName !== '') { | |
|
62 | 94 | // nav in repo context |
|
63 | 95 | Mousetrap.bind(['g s'], function(e) { |
|
64 | 96 | window.location = pyroutes.url( |
@@ -143,6 +143,7 b' function registerRCRoutes() {' | |||
|
143 | 143 | pyroutes.register('user_autocomplete_data', '/_users', []); |
|
144 | 144 | pyroutes.register('user_group_autocomplete_data', '/_user_groups', []); |
|
145 | 145 | pyroutes.register('repo_list_data', '/_repos', []); |
|
146 | pyroutes.register('repo_group_list_data', '/_repo_groups', []); | |
|
146 | 147 | pyroutes.register('goto_switcher_data', '/_goto_data', []); |
|
147 | 148 | pyroutes.register('markup_preview', '/_markup_preview', []); |
|
148 | 149 | pyroutes.register('store_user_session_value', '/_store_session_attr', []); |
@@ -297,6 +298,9 b' function registerRCRoutes() {' | |||
|
297 | 298 | pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []); |
|
298 | 299 | pyroutes.register('my_account_repos', '/_admin/my_account/repos', []); |
|
299 | 300 | pyroutes.register('my_account_watched', '/_admin/my_account/watched', []); |
|
301 | pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []); | |
|
302 | pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []); | |
|
303 | pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']); | |
|
300 | 304 | pyroutes.register('my_account_perms', '/_admin/my_account/perms', []); |
|
301 | 305 | pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []); |
|
302 | 306 | pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []); |
@@ -28,6 +28,7 b'' | |||
|
28 | 28 | <ul class="nav nav-pills nav-stacked"> |
|
29 | 29 | <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.route_path('my_account_profile')}">${_('Profile')}</a></li> |
|
30 | 30 | <li class="${'active' if c.active=='password' else ''}"><a href="${h.route_path('my_account_password')}">${_('Password')}</a></li> |
|
31 | <li class="${'active' if c.active=='bookmarks' else ''}"><a href="${h.route_path('my_account_bookmarks')}">${_('Bookmarks')}</a></li> | |
|
31 | 32 | <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li> |
|
32 | 33 | <li class="${'active' if c.active in ['ssh_keys', 'ssh_keys_generate'] else ''}"><a href="${h.route_path('my_account_ssh_keys')}">${_('SSH Keys')}</a></li> |
|
33 | 34 | <li class="${'active' if c.active=='user_group_membership' else ''}"><a href="${h.route_path('my_account_user_group_membership')}">${_('User Group Membership')}</a></li> |
@@ -52,7 +52,7 b'' | |||
|
52 | 52 | <p class="server-instance" style="display:${sid}"> |
|
53 | 53 | ## display hidden instance ID if specially defined |
|
54 | 54 | % if c.rhodecode_instanceid: |
|
55 |
${_('RhodeCode instance id: |
|
|
55 | ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)} | |
|
56 | 56 | % endif |
|
57 | 57 | </p> |
|
58 | 58 | </div> |
@@ -331,6 +331,50 b'' | |||
|
331 | 331 | <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li> |
|
332 | 332 | % endif |
|
333 | 333 | <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li> |
|
334 | ## bookmark-items | |
|
335 | <li class="bookmark-items"> | |
|
336 | ${_('Bookmarks')} | |
|
337 | <div class="pull-right"> | |
|
338 | <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a> | |
|
339 | </div> | |
|
340 | </li> | |
|
341 | % if not c.bookmark_items: | |
|
342 | <li> | |
|
343 | <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a> | |
|
344 | </li> | |
|
345 | % endif | |
|
346 | % for item in c.bookmark_items: | |
|
347 | <li> | |
|
348 | % if item.repository: | |
|
349 | <div> | |
|
350 | <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}"> | |
|
351 | ${item.position} | |
|
352 | % if item.repository.repo_type == 'hg': | |
|
353 | <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i> | |
|
354 | % elif item.repository.repo_type == 'git': | |
|
355 | <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i> | |
|
356 | % elif item.repository.repo_type == 'svn': | |
|
357 | <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i> | |
|
358 | % endif | |
|
359 | ${(item.title or h.shorter(item.repository.repo_name, 30))} | |
|
360 | </a> | |
|
361 | </div> | |
|
362 | % elif item.repository_group: | |
|
363 | <div> | |
|
364 | <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}"> | |
|
365 | ${item.position} | |
|
366 | <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i> | |
|
367 | ${(item.title or h.shorter(item.repository_group.group_name, 30))} | |
|
368 | </a> | |
|
369 | </div> | |
|
370 | % else: | |
|
371 | <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}"> | |
|
372 | ${item.position} | |
|
373 | ${item.title} | |
|
374 | </a> | |
|
375 | % endif | |
|
376 | </li> | |
|
377 | % endfor | |
|
334 | 378 | |
|
335 | 379 | <li class="logout"> |
|
336 | 380 | ${h.secure_form(h.route_path('logout'), request=request)} |
@@ -483,6 +527,26 b' commit:efced4, to search for commits' | |||
|
483 | 527 | }(result, escapeMarkup); |
|
484 | 528 | }; |
|
485 | 529 | |
|
530 | var formatRepoGroupResult = function(result, container, query, escapeMarkup) { | |
|
531 | return function(data, escapeMarkup) { | |
|
532 | if (!data.repo_group_id){ | |
|
533 | return data.text; // optgroup text Repositories | |
|
534 | } | |
|
535 | ||
|
536 | var tmpl = ''; | |
|
537 | var repoGroupName = data['text']; | |
|
538 | ||
|
539 | if(data){ | |
|
540 | ||
|
541 | tmpl += '<i class="icon-folder-close"></i> '; | |
|
542 | ||
|
543 | } | |
|
544 | tmpl += escapeMarkup(repoGroupName); | |
|
545 | return tmpl; | |
|
546 | ||
|
547 | }(result, escapeMarkup); | |
|
548 | }; | |
|
549 | ||
|
486 | 550 | |
|
487 | 551 | var autocompleteMainFilterFormatResult = function (data, value, org_formatter) { |
|
488 | 552 | |
@@ -610,6 +674,7 b' commit:efced4, to search for commits' | |||
|
610 | 674 | ('g h', 'Goto home page'), |
|
611 | 675 | ('g g', 'Goto my private gists page'), |
|
612 | 676 | ('g G', 'Goto my public gists page'), |
|
677 | ('g 0-9', 'Goto bookmarked items from 0-9'), | |
|
613 | 678 | ('n r', 'New repository page'), |
|
614 | 679 | ('n g', 'New gist page'), |
|
615 | 680 | ] |
@@ -88,9 +88,9 b'' | |||
|
88 | 88 | %endif |
|
89 | 89 | |
|
90 | 90 | ##PRIVATE/PUBLIC |
|
91 | %if private and c.visual.show_private_icon: | |
|
91 | %if private is True and c.visual.show_private_icon: | |
|
92 | 92 | <i class="icon-lock" title="${_('Private repository')}"></i> |
|
93 |
%elif |
|
|
93 | %elif private is False and c.visual.show_public_icon: | |
|
94 | 94 | <i class="icon-unlock-alt" title="${_('Public repository')}"></i> |
|
95 | 95 | %else: |
|
96 | 96 | <span></span> |
General Comments 0
You need to be logged in to leave comments.
Login now