##// END OF EJS Templates
- refactoring to overcome poor usage of global pylons config...
marcink -
r1723:64e91067 beta
parent child Browse files
Show More
@@ -1,56 +1,63 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.__init__
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode, a web based repository management based on pylons
7 7 versioning implementation: http://semver.org/
8 8
9 9 :created_on: Apr 9, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import platform
27 27
28 28 VERSION = (1, 3, 0, 'beta')
29 29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
30 30 __dbversion__ = 4 #defines current db version for migrations
31 31 __platform__ = platform.system()
32 32 __license__ = 'GPLv3'
33 33
34 34 PLATFORM_WIN = ('Windows')
35 35 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS')
36 36
37 37 try:
38 38 from rhodecode.lib import get_current_revision
39 39 _rev = get_current_revision()
40 40 except ImportError:
41 41 #this is needed when doing some setup.py operations
42 42 _rev = False
43 43
44 44 if len(VERSION) > 3 and _rev:
45 45 __version__ += ' [rev:%s]' % _rev[0]
46 46
47 47
48 48 def get_version():
49 49 """Returns shorter version (digit parts only) as string."""
50 50
51 51 return '.'.join((str(each) for each in VERSION[:3]))
52 52
53 53 BACKENDS = {
54 54 'hg': 'Mercurial repository',
55 55 #'git': 'Git repository',
56 56 }
57
58 CELERY_ON = False
59
60 # link to config for pylons
61 CONFIG = None
62
63
@@ -1,80 +1,85 b''
1 1 """Pylons environment configuration"""
2 2
3 3 import os
4 4 import logging
5 5
6 6 from mako.lookup import TemplateLookup
7 7 from pylons.configuration import PylonsConfig
8 8 from pylons.error import handle_mako_error
9 9
10 import rhodecode
10 11 import rhodecode.lib.app_globals as app_globals
11 12 import rhodecode.lib.helpers
12 13
13 14 from rhodecode.config.routing import make_map
14 from rhodecode.lib import celerypylons
15 # don't remove this import it does magic for celery
16 from rhodecode.lib import celerypylons, str2bool
15 17 from rhodecode.lib import engine_from_config
16 from rhodecode.lib.timerproxy import TimerProxy
17 18 from rhodecode.lib.auth import set_available_permissions
18 19 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
19 20 from rhodecode.model import init_model
20 21 from rhodecode.model.scm import ScmModel
21 22
22 23 log = logging.getLogger(__name__)
23 24
24 25
25 26 def load_environment(global_conf, app_conf, initial=False):
26 27 """Configure the Pylons environment via the ``pylons.config``
27 28 object
28 29 """
29 30 config = PylonsConfig()
30 31
31 32 # Pylons paths
32 33 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
33 34 paths = dict(root=root,
34 35 controllers=os.path.join(root, 'controllers'),
35 36 static_files=os.path.join(root, 'public'),
36 37 templates=[os.path.join(root, 'templates')])
37 38
38 39 # Initialize config with the basic options
39 40 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
40 41
42 # store some globals into our main isntance
43 rhodecode.CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
44 rhodecode.CONFIG = config
45
41 46 config['routes.map'] = make_map(config)
42 47 config['pylons.app_globals'] = app_globals.Globals(config)
43 48 config['pylons.h'] = rhodecode.lib.helpers
44 49
45 50 # Setup cache object as early as possible
46 51 import pylons
47 52 pylons.cache._push_object(config['pylons.app_globals'].cache)
48 53
49 54 # Create the Mako TemplateLookup, with the default auto-escaping
50 55 config['pylons.app_globals'].mako_lookup = TemplateLookup(
51 56 directories=paths['templates'],
52 57 error_handler=handle_mako_error,
53 58 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
54 59 input_encoding='utf-8', default_filters=['escape'],
55 60 imports=['from webhelpers.html import escape'])
56 61
57 62 #sets the c attribute access when don't existing attribute are accessed
58 63 config['pylons.strict_tmpl_context'] = True
59 64 test = os.path.split(config['__file__'])[-1] == 'test.ini'
60 65 if test:
61 66 from rhodecode.lib.utils import create_test_env, create_test_index
62 67 from rhodecode.tests import TESTS_TMP_PATH
63 68 create_test_env(TESTS_TMP_PATH, config)
64 create_test_index(TESTS_TMP_PATH, config, True)
69 #create_test_index(TESTS_TMP_PATH, config, True)
65 70
66 71 #MULTIPLE DB configs
67 72 # Setup the SQLAlchemy database engine
68 73 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
69 74
70 75 init_model(sa_engine_db1)
71 76
72 77 repos_path = make_ui('db').configitems('paths')[0][1]
73 78 repo2db_mapper(ScmModel().repo_scan(repos_path))
74 79 set_available_permissions(config)
75 80 config['base_path'] = repos_path
76 81 set_rhodecode_config(config)
77 82 # CONFIGURATION OPTIONS HERE (note: all config options will override
78 83 # any Pylons config options)
79 84
80 85 return config
@@ -1,102 +1,103 b''
1 1 import logging
2 2 import traceback
3 3
4 4 from pylons import tmpl_context as c, url
5 from pylons.controllers.util import redirect
5 6
6 7 from rhodecode.lib.base import BaseController, render
7 8 from rhodecode.model.db import Notification
8 9
9 10 from rhodecode.model.notification import NotificationModel
10 11 from rhodecode.lib.auth import LoginRequired
11 12 from rhodecode.lib import helpers as h
12 13 from rhodecode.model.meta import Session
13 from pylons.controllers.util import redirect
14
14 15
15 16 log = logging.getLogger(__name__)
16 17
17 18 class NotificationsController(BaseController):
18 19 """REST Controller styled on the Atom Publishing Protocol"""
19 20 # To properly map this controller, ensure your config/routing.py
20 21 # file has a resource setup:
21 22 # map.resource('notification', 'notifications', controller='_admin/notifications',
22 23 # path_prefix='/_admin', name_prefix='_admin_')
23 24
24 25 @LoginRequired()
25 26 def __before__(self):
26 27 super(NotificationsController, self).__before__()
27 28
28 29
29 30 def index(self, format='html'):
30 31 """GET /_admin/notifications: All items in the collection"""
31 32 # url('notifications')
32 33 c.user = self.rhodecode_user
33 34 c.notifications = NotificationModel()\
34 35 .get_for_user(self.rhodecode_user.user_id)
35 36 return render('admin/notifications/notifications.html')
36 37
37 38 def create(self):
38 39 """POST /_admin/notifications: Create a new item"""
39 40 # url('notifications')
40 41
41 42 def new(self, format='html'):
42 43 """GET /_admin/notifications/new: Form to create a new item"""
43 44 # url('new_notification')
44 45
45 46 def update(self, notification_id):
46 47 """PUT /_admin/notifications/id: Update an existing item"""
47 48 # Forms posted to this method should contain a hidden field:
48 49 # <input type="hidden" name="_method" value="PUT" />
49 50 # Or using helpers:
50 51 # h.form(url('notification', notification_id=ID),
51 52 # method='put')
52 53 # url('notification', notification_id=ID)
53 54
54 55 def delete(self, notification_id):
55 56 """DELETE /_admin/notifications/id: Delete an existing item"""
56 57 # Forms posted to this method should contain a hidden field:
57 58 # <input type="hidden" name="_method" value="DELETE" />
58 59 # Or using helpers:
59 60 # h.form(url('notification', notification_id=ID),
60 61 # method='delete')
61 62 # url('notification', notification_id=ID)
62 63
63 64 try:
64 65 no = Notification.get(notification_id)
65 66 owner = lambda: (no.notifications_to_users.user.user_id
66 67 == c.rhodecode_user.user_id)
67 68 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
68 69 NotificationModel().delete(c.rhodecode_user.user_id, no)
69 70 Session.commit()
70 71 return 'ok'
71 72 except Exception:
72 73 Session.rollback()
73 74 log.error(traceback.format_exc())
74 75 return 'fail'
75 76
76 77 def show(self, notification_id, format='html'):
77 78 """GET /_admin/notifications/id: Show a specific item"""
78 79 # url('notification', notification_id=ID)
79 80 c.user = self.rhodecode_user
80 81 no = Notification.get(notification_id)
81 82
82 83 owner = lambda: (no.notifications_to_users.user.user_id
83 84 == c.user.user_id)
84 85 if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
85 86 unotification = NotificationModel()\
86 87 .get_user_notification(c.user.user_id, no)
87 88
88 89 # if this association to user is not valid, we don't want to show
89 90 # this message
90 91 if unotification:
91 92 if unotification.read is False:
92 93 unotification.mark_as_read()
93 94 Session.commit()
94 95 c.notification = no
95 96
96 97 return render('admin/notifications/show_notification.html')
97 98
98 99 return redirect(url('notifications'))
99 100
100 101 def edit(self, notification_id, format='html'):
101 102 """GET /_admin/notifications/id/edit: Form to edit an existing item"""
102 103 # url('edit_notification', notification_id=ID)
@@ -1,109 +1,105 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 celery libs for RhodeCode
7 7
8 8 :created_on: Nov 27, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import sys
28 28 import socket
29 29 import traceback
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from hashlib import md5
34 34 from decorator import decorator
35 from pylons import config
36 35
37 36 from vcs.utils.lazy import LazyProperty
38
37 from rhodecode import CELERY_ON
39 38 from rhodecode.lib import str2bool, safe_str
40 39 from rhodecode.lib.pidlock import DaemonLock, LockHeld
41 40
42 41 from celery.messaging import establish_connection
43 42
44 43
45 44 log = logging.getLogger(__name__)
46 45
47 try:
48 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
49 except KeyError:
50 CELERY_ON = False
46
51 47
52 48
53 49 class ResultWrapper(object):
54 50 def __init__(self, task):
55 51 self.task = task
56 52
57 53 @LazyProperty
58 54 def result(self):
59 55 return self.task
60 56
61 57
62 58 def run_task(task, *args, **kwargs):
63 59 if CELERY_ON:
64 60 try:
65 61 t = task.apply_async(args=args, kwargs=kwargs)
66 62 log.info('running task %s:%s', t.task_id, task)
67 63 return t
68 64
69 65 except socket.error, e:
70 66 if isinstance(e, IOError) and e.errno == 111:
71 67 log.debug('Unable to connect to celeryd. Sync execution')
72 68 else:
73 69 log.error(traceback.format_exc())
74 70 except KeyError, e:
75 71 log.debug('Unable to connect to celeryd. Sync execution')
76 72 except Exception, e:
77 73 log.error(traceback.format_exc())
78 74
79 75 log.debug('executing task %s in sync mode', task)
80 76 return ResultWrapper(task(*args, **kwargs))
81 77
82 78
83 79 def __get_lockkey(func, *fargs, **fkwargs):
84 80 params = list(fargs)
85 81 params.extend(['%s-%s' % ar for ar in fkwargs.items()])
86 82
87 83 func_name = str(func.__name__) if hasattr(func, '__name__') else str(func)
88 84
89 85 lockkey = 'task_%s.lock' % \
90 86 md5(func_name + '-' + '-'.join(map(safe_str, params))).hexdigest()
91 87 return lockkey
92 88
93 89
94 90 def locked_task(func):
95 91 def __wrapper(func, *fargs, **fkwargs):
96 92 lockkey = __get_lockkey(func, *fargs, **fkwargs)
97 93 lockkey_path = config['here']
98 94
99 95 log.info('running task with lockkey %s', lockkey)
100 96 try:
101 97 l = DaemonLock(file_=jn(lockkey_path, lockkey))
102 98 ret = func(*fargs, **fkwargs)
103 99 l.release()
104 100 return ret
105 101 except LockHeld:
106 102 log.info('LockHeld')
107 103 return 'Task with key %s already running' % lockkey
108 104
109 105 return decorator(__wrapper, func)
@@ -1,406 +1,404 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.tasks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode task modules, containing all task that suppose to be run
7 7 by celery daemon
8 8
9 9 :created_on: Oct 6, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 from celery.decorators import task
27 27
28 28 import os
29 29 import traceback
30 30 import logging
31 31 from os.path import join as jn
32 32
33 33 from time import mktime
34 34 from operator import itemgetter
35 35 from string import lower
36 36
37 37 from pylons import config, url
38 38 from pylons.i18n.translation import _
39 39
40 40 from vcs import get_backend
41 41
42 from rhodecode import CELERY_ON
42 43 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
43 44 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
44 45 __get_lockkey, LockHeld, DaemonLock
45 46 from rhodecode.lib.helpers import person
46 47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
47 48 from rhodecode.lib.utils import add_cache, action_logger
48 49 from rhodecode.lib.compat import json, OrderedDict
49 50
50 51 from rhodecode.model import init_model
51 52 from rhodecode.model import meta
52 53 from rhodecode.model.db import Statistics, Repository, User
53 54
54 55 from sqlalchemy import engine_from_config
55 56
56 57 add_cache(config)
57 58
58 59 __all__ = ['whoosh_index', 'get_commits_stats',
59 60 'reset_user_password', 'send_email']
60 61
61 62
62 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
63
64
65 63 def get_session():
66 64 if CELERY_ON:
67 65 engine = engine_from_config(config, 'sqlalchemy.db1.')
68 66 init_model(engine)
69 67 sa = meta.Session()
70 68 return sa
71 69
72 70 def get_logger(cls):
73 71 if CELERY_ON:
74 72 try:
75 73 log = cls.get_logger()
76 74 except:
77 75 log = logging.getLogger(__name__)
78 76 else:
79 77 log = logging.getLogger(__name__)
80 78
81 79 return log
82 80
83 81 @task(ignore_result=True)
84 82 @locked_task
85 83 def whoosh_index(repo_location, full_index):
86 84 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
87 85
88 86 #log = whoosh_index.get_logger()
89 87
90 88 index_location = config['index_dir']
91 89 WhooshIndexingDaemon(index_location=index_location,
92 90 repo_location=repo_location, sa=get_session())\
93 91 .run(full_index=full_index)
94 92
95 93
96 94 @task(ignore_result=True)
97 95 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
98 96 log = get_logger(get_commits_stats)
99 97
100 98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
101 99 ts_max_y)
102 100 lockkey_path = config['here']
103 101
104 102 log.info('running task with lockkey %s', lockkey)
105 103 try:
106 104 sa = get_session()
107 105 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
108 106
109 107 # for js data compatibilty cleans the key for person from '
110 108 akc = lambda k: person(k).replace('"', "")
111 109
112 110 co_day_auth_aggr = {}
113 111 commits_by_day_aggregate = {}
114 112 repo = Repository.get_by_repo_name(repo_name).scm_instance
115 113 repo_size = len(repo.revisions)
116 114 #return if repo have no revisions
117 115 if repo_size < 1:
118 116 lock.release()
119 117 return True
120 118
121 119 skip_date_limit = True
122 120 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
123 121 last_rev = 0
124 122 last_cs = None
125 123 timegetter = itemgetter('time')
126 124
127 125 dbrepo = sa.query(Repository)\
128 126 .filter(Repository.repo_name == repo_name).scalar()
129 127 cur_stats = sa.query(Statistics)\
130 128 .filter(Statistics.repository == dbrepo).scalar()
131 129
132 130 if cur_stats is not None:
133 131 last_rev = cur_stats.stat_on_revision
134 132
135 133 if last_rev == repo.get_changeset().revision and repo_size > 1:
136 134 # pass silently without any work if we're not on first revision or
137 135 # current state of parsing revision(from db marker) is the
138 136 # last revision
139 137 lock.release()
140 138 return True
141 139
142 140 if cur_stats:
143 141 commits_by_day_aggregate = OrderedDict(json.loads(
144 142 cur_stats.commit_activity_combined))
145 143 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
146 144
147 145 log.debug('starting parsing %s', parse_limit)
148 146 lmktime = mktime
149 147
150 148 last_rev = last_rev + 1 if last_rev > 0 else last_rev
151 149
152 150 for cs in repo[last_rev:last_rev + parse_limit]:
153 151 last_cs = cs # remember last parsed changeset
154 152 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
155 153 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
156 154
157 155 if akc(cs.author) in co_day_auth_aggr:
158 156 try:
159 157 l = [timegetter(x) for x in
160 158 co_day_auth_aggr[akc(cs.author)]['data']]
161 159 time_pos = l.index(k)
162 160 except ValueError:
163 161 time_pos = False
164 162
165 163 if time_pos >= 0 and time_pos is not False:
166 164
167 165 datadict = \
168 166 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
169 167
170 168 datadict["commits"] += 1
171 169 datadict["added"] += len(cs.added)
172 170 datadict["changed"] += len(cs.changed)
173 171 datadict["removed"] += len(cs.removed)
174 172
175 173 else:
176 174 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
177 175
178 176 datadict = {"time": k,
179 177 "commits": 1,
180 178 "added": len(cs.added),
181 179 "changed": len(cs.changed),
182 180 "removed": len(cs.removed),
183 181 }
184 182 co_day_auth_aggr[akc(cs.author)]['data']\
185 183 .append(datadict)
186 184
187 185 else:
188 186 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
189 187 co_day_auth_aggr[akc(cs.author)] = {
190 188 "label": akc(cs.author),
191 189 "data": [{"time":k,
192 190 "commits":1,
193 191 "added":len(cs.added),
194 192 "changed":len(cs.changed),
195 193 "removed":len(cs.removed),
196 194 }],
197 195 "schema": ["commits"],
198 196 }
199 197
200 198 #gather all data by day
201 199 if k in commits_by_day_aggregate:
202 200 commits_by_day_aggregate[k] += 1
203 201 else:
204 202 commits_by_day_aggregate[k] = 1
205 203
206 204 overview_data = sorted(commits_by_day_aggregate.items(),
207 205 key=itemgetter(0))
208 206
209 207 if not co_day_auth_aggr:
210 208 co_day_auth_aggr[akc(repo.contact)] = {
211 209 "label": akc(repo.contact),
212 210 "data": [0, 1],
213 211 "schema": ["commits"],
214 212 }
215 213
216 214 stats = cur_stats if cur_stats else Statistics()
217 215 stats.commit_activity = json.dumps(co_day_auth_aggr)
218 216 stats.commit_activity_combined = json.dumps(overview_data)
219 217
220 218 log.debug('last revison %s', last_rev)
221 219 leftovers = len(repo.revisions[last_rev:])
222 220 log.debug('revisions to parse %s', leftovers)
223 221
224 222 if last_rev == 0 or leftovers < parse_limit:
225 223 log.debug('getting code trending stats')
226 224 stats.languages = json.dumps(__get_codes_stats(repo_name))
227 225
228 226 try:
229 227 stats.repository = dbrepo
230 228 stats.stat_on_revision = last_cs.revision if last_cs else 0
231 229 sa.add(stats)
232 230 sa.commit()
233 231 except:
234 232 log.error(traceback.format_exc())
235 233 sa.rollback()
236 234 lock.release()
237 235 return False
238 236
239 237 #final release
240 238 lock.release()
241 239
242 240 #execute another task if celery is enabled
243 241 if len(repo.revisions) > 1 and CELERY_ON:
244 242 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
245 243 return True
246 244 except LockHeld:
247 245 log.info('LockHeld')
248 246 return 'Task with key %s already running' % lockkey
249 247
250 248 @task(ignore_result=True)
251 249 def send_password_link(user_email):
252 250 from rhodecode.model.notification import EmailNotificationModel
253 251
254 252 log = get_logger(send_password_link)
255 253
256 254 try:
257 255 sa = get_session()
258 256 user = User.get_by_email(user_email)
259 257 if user:
260 258 log.debug('password reset user found %s' % user)
261 259 link = url('reset_password_confirmation', key=user.api_key,
262 260 qualified=True)
263 261 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
264 262 body = EmailNotificationModel().get_email_tmpl(reg_type,
265 263 **{'user':user.short_contact,
266 264 'reset_url':link})
267 265 log.debug('sending email')
268 266 run_task(send_email, user_email,
269 267 _("password reset link"), body)
270 268 log.info('send new password mail to %s', user_email)
271 269 else:
272 270 log.debug("password reset email %s not found" % user_email)
273 271 except:
274 272 log.error(traceback.format_exc())
275 273 return False
276 274
277 275 return True
278 276
279 277 @task(ignore_result=True)
280 278 def reset_user_password(user_email):
281 279 from rhodecode.lib import auth
282 280
283 281 log = get_logger(reset_user_password)
284 282
285 283 try:
286 284 try:
287 285 sa = get_session()
288 286 user = User.get_by_email(user_email)
289 287 new_passwd = auth.PasswordGenerator().gen_password(8,
290 288 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
291 289 if user:
292 290 user.password = auth.get_crypt_password(new_passwd)
293 291 user.api_key = auth.generate_api_key(user.username)
294 292 sa.add(user)
295 293 sa.commit()
296 294 log.info('change password for %s', user_email)
297 295 if new_passwd is None:
298 296 raise Exception('unable to generate new password')
299 297 except:
300 298 log.error(traceback.format_exc())
301 299 sa.rollback()
302 300
303 301 run_task(send_email, user_email,
304 302 'Your new password',
305 303 'Your new RhodeCode password:%s' % (new_passwd))
306 304 log.info('send new password mail to %s', user_email)
307 305
308 306 except:
309 307 log.error('Failed to update user password')
310 308 log.error(traceback.format_exc())
311 309
312 310 return True
313 311
314 312
315 313 @task(ignore_result=True)
316 314 def send_email(recipients, subject, body, html_body=''):
317 315 """
318 316 Sends an email with defined parameters from the .ini files.
319 317
320 318 :param recipients: list of recipients, it this is empty the defined email
321 319 address from field 'email_to' is used instead
322 320 :param subject: subject of the mail
323 321 :param body: body of the mail
324 322 :param html_body: html version of body
325 323 """
326 324 log = get_logger(send_email)
327 325 email_config = config
328 326
329 327 subject = "%s %s" % (email_config.get('email_prefix'), subject)
330 328 if not recipients:
331 329 # if recipients are not defined we send to email_config + all admins
332 330 admins = [u.email for u in User.query()
333 331 .filter(User.admin == True).all()]
334 332 recipients = [email_config.get('email_to')] + admins
335 333
336 334 mail_from = email_config.get('app_email_from', 'RhodeCode')
337 335 user = email_config.get('smtp_username')
338 336 passwd = email_config.get('smtp_password')
339 337 mail_server = email_config.get('smtp_server')
340 338 mail_port = email_config.get('smtp_port')
341 339 tls = str2bool(email_config.get('smtp_use_tls'))
342 340 ssl = str2bool(email_config.get('smtp_use_ssl'))
343 341 debug = str2bool(config.get('debug'))
344 342 smtp_auth = email_config.get('smtp_auth')
345 343
346 344 try:
347 345 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
348 346 mail_port, ssl, tls, debug=debug)
349 347 m.send(recipients, subject, body, html_body)
350 348 except:
351 349 log.error('Mail sending failed')
352 350 log.error(traceback.format_exc())
353 351 return False
354 352 return True
355 353
356 354
357 355 @task(ignore_result=True)
358 356 def create_repo_fork(form_data, cur_user):
359 357 """
360 358 Creates a fork of repository using interval VCS methods
361 359
362 360 :param form_data:
363 361 :param cur_user:
364 362 """
365 363 from rhodecode.model.repo import RepoModel
366 364
367 365 log = get_logger(create_repo_fork)
368 366
369 367 Session = get_session()
370 368 base_path = Repository.base_path()
371 369
372 370 RepoModel(Session).create(form_data, cur_user, just_db=True, fork=True)
373 371
374 372 alias = form_data['repo_type']
375 373 org_repo_name = form_data['org_path']
376 374 source_repo_path = os.path.join(base_path, org_repo_name)
377 375 destination_fork_path = os.path.join(base_path, form_data['repo_name_full'])
378 376
379 377 log.info('creating fork of %s as %s', source_repo_path,
380 378 destination_fork_path)
381 379 backend = get_backend(alias)
382 380 backend(safe_str(destination_fork_path), create=True,
383 381 src_url=safe_str(source_repo_path))
384 382 action_logger(cur_user, 'user_forked_repo:%s' % org_repo_name,
385 383 org_repo_name, '', Session)
386 384 # finally commit at latest possible stage
387 385 Session.commit()
388 386
389 387 def __get_codes_stats(repo_name):
390 388 repo = Repository.get_by_repo_name(repo_name).scm_instance
391 389
392 390 tip = repo.get_changeset()
393 391 code_stats = {}
394 392
395 393 def aggregate(cs):
396 394 for f in cs[2]:
397 395 ext = lower(f.extension)
398 396 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
399 397 if ext in code_stats:
400 398 code_stats[ext] += 1
401 399 else:
402 400 code_stats[ext] = 1
403 401
404 402 map(aggregate, tip.walk('/'))
405 403
406 404 return code_stats or {}
@@ -1,600 +1,600 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import paste
31 31 import beaker
32 32 import tarfile
33 33 import shutil
34 34 from os.path import abspath
35 35 from os.path import dirname as dn, join as jn
36 36
37 37 from paste.script.command import Command, BadCommand
38 38
39 39 from mercurial import ui, config
40 40
41 41 from webhelpers.text import collapse, remove_formatting, strip_tags
42 42
43 43 from vcs import get_backend
44 44 from vcs.backends.base import BaseChangeset
45 45 from vcs.utils.lazy import LazyProperty
46 46 from vcs.utils.helpers import get_scm
47 47 from vcs.exceptions import VCSError
48 48
49 49 from rhodecode.lib.caching_query import FromCache
50 50
51 51 from rhodecode.model import meta
52 52 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 53 UserLog, RepoGroup, RhodeCodeSetting
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 def recursive_replace(str_, replace=' '):
59 59 """Recursive replace of given sign to just one instance
60 60
61 61 :param str_: given string
62 62 :param replace: char to find and replace multiple instances
63 63
64 64 Examples::
65 65 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
66 66 'Mighty-Mighty-Bo-sstones'
67 67 """
68 68
69 69 if str_.find(replace * 2) == -1:
70 70 return str_
71 71 else:
72 72 str_ = str_.replace(replace * 2, replace)
73 73 return recursive_replace(str_, replace)
74 74
75 75
76 76 def repo_name_slug(value):
77 77 """Return slug of name of repository
78 78 This function is called on each creation/modification
79 79 of repository to prevent bad names in repo
80 80 """
81 81
82 82 slug = remove_formatting(value)
83 83 slug = strip_tags(slug)
84 84
85 85 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
86 86 slug = slug.replace(c, '-')
87 87 slug = recursive_replace(slug, '-')
88 88 slug = collapse(slug, '-')
89 89 return slug
90 90
91 91
92 92 def get_repo_slug(request):
93 93 return request.environ['pylons.routes_dict'].get('repo_name')
94 94
95 95
96 96 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
97 97 """
98 98 Action logger for various actions made by users
99 99
100 100 :param user: user that made this action, can be a unique username string or
101 101 object containing user_id attribute
102 102 :param action: action to log, should be on of predefined unique actions for
103 103 easy translations
104 104 :param repo: string name of repository or object containing repo_id,
105 105 that action was made on
106 106 :param ipaddr: optional ip address from what the action was made
107 107 :param sa: optional sqlalchemy session
108 108
109 109 """
110 110
111 111 if not sa:
112 112 sa = meta.Session()
113 113
114 114 try:
115 115 if hasattr(user, 'user_id'):
116 116 user_obj = user
117 117 elif isinstance(user, basestring):
118 118 user_obj = User.get_by_username(user)
119 119 else:
120 120 raise Exception('You have to provide user object or username')
121 121
122 122 if hasattr(repo, 'repo_id'):
123 123 repo_obj = Repository.get(repo.repo_id)
124 124 repo_name = repo_obj.repo_name
125 125 elif isinstance(repo, basestring):
126 126 repo_name = repo.lstrip('/')
127 127 repo_obj = Repository.get_by_repo_name(repo_name)
128 128 else:
129 129 raise Exception('You have to provide repository to action logger')
130 130
131 131 user_log = UserLog()
132 132 user_log.user_id = user_obj.user_id
133 133 user_log.action = action
134 134
135 135 user_log.repository_id = repo_obj.repo_id
136 136 user_log.repository_name = repo_name
137 137
138 138 user_log.action_date = datetime.datetime.now()
139 139 user_log.user_ip = ipaddr
140 140 sa.add(user_log)
141 141
142 142 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
143 143 if commit:
144 144 sa.commit()
145 145 except:
146 146 log.error(traceback.format_exc())
147 147 raise
148 148
149 149
150 150 def get_repos(path, recursive=False):
151 151 """
152 152 Scans given path for repos and return (name,(type,path)) tuple
153 153
154 154 :param path: path to scann for repositories
155 155 :param recursive: recursive search and return names with subdirs in front
156 156 """
157 157
158 158 if path.endswith(os.sep):
159 159 #remove ending slash for better results
160 160 path = path[:-1]
161 161
162 162 def _get_repos(p):
163 163 if not os.access(p, os.W_OK):
164 164 return
165 165 for dirpath in os.listdir(p):
166 166 if os.path.isfile(os.path.join(p, dirpath)):
167 167 continue
168 168 cur_path = os.path.join(p, dirpath)
169 169 try:
170 170 scm_info = get_scm(cur_path)
171 171 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
172 172 except VCSError:
173 173 if not recursive:
174 174 continue
175 175 #check if this dir containts other repos for recursive scan
176 176 rec_path = os.path.join(p, dirpath)
177 177 if os.path.isdir(rec_path):
178 178 for inner_scm in _get_repos(rec_path):
179 179 yield inner_scm
180 180
181 181 return _get_repos(path)
182 182
183 183
184 184 def is_valid_repo(repo_name, base_path):
185 185 """
186 186 Returns True if given path is a valid repository False otherwise
187 187 :param repo_name:
188 188 :param base_path:
189 189
190 190 :return True: if given path is a valid repository
191 191 """
192 192 full_path = os.path.join(base_path, repo_name)
193 193
194 194 try:
195 195 get_scm(full_path)
196 196 return True
197 197 except VCSError:
198 198 return False
199 199
200 200 def is_valid_repos_group(repos_group_name, base_path):
201 201 """
202 202 Returns True if given path is a repos group False otherwise
203 203
204 204 :param repo_name:
205 205 :param base_path:
206 206 """
207 207 full_path = os.path.join(base_path, repos_group_name)
208 208
209 209 # check if it's not a repo
210 210 if is_valid_repo(repos_group_name, base_path):
211 211 return False
212 212
213 213 # check if it's a valid path
214 214 if os.path.isdir(full_path):
215 215 return True
216 216
217 217 return False
218 218
219 219 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
220 220 while True:
221 221 ok = raw_input(prompt)
222 222 if ok in ('y', 'ye', 'yes'):
223 223 return True
224 224 if ok in ('n', 'no', 'nop', 'nope'):
225 225 return False
226 226 retries = retries - 1
227 227 if retries < 0:
228 228 raise IOError
229 229 print complaint
230 230
231 231 #propagated from mercurial documentation
232 232 ui_sections = ['alias', 'auth',
233 233 'decode/encode', 'defaults',
234 234 'diff', 'email',
235 235 'extensions', 'format',
236 236 'merge-patterns', 'merge-tools',
237 237 'hooks', 'http_proxy',
238 238 'smtp', 'patch',
239 239 'paths', 'profiling',
240 240 'server', 'trusted',
241 241 'ui', 'web', ]
242 242
243 243
244 244 def make_ui(read_from='file', path=None, checkpaths=True):
245 245 """A function that will read python rc files or database
246 246 and make an mercurial ui object from read options
247 247
248 248 :param path: path to mercurial config file
249 249 :param checkpaths: check the path
250 250 :param read_from: read from 'file' or 'db'
251 251 """
252 252
253 253 baseui = ui.ui()
254 254
255 255 #clean the baseui object
256 256 baseui._ocfg = config.config()
257 257 baseui._ucfg = config.config()
258 258 baseui._tcfg = config.config()
259 259
260 260 if read_from == 'file':
261 261 if not os.path.isfile(path):
262 262 log.warning('Unable to read config file %s' % path)
263 263 return False
264 264 log.debug('reading hgrc from %s', path)
265 265 cfg = config.config()
266 266 cfg.read(path)
267 267 for section in ui_sections:
268 268 for k, v in cfg.items(section):
269 269 log.debug('settings ui from file[%s]%s:%s', section, k, v)
270 270 baseui.setconfig(section, k, v)
271 271
272 272 elif read_from == 'db':
273 273 sa = meta.Session()
274 274 ret = sa.query(RhodeCodeUi)\
275 275 .options(FromCache("sql_cache_short",
276 276 "get_hg_ui_settings")).all()
277 277
278 278 hg_ui = ret
279 279 for ui_ in hg_ui:
280 280 if ui_.ui_active:
281 281 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
282 282 ui_.ui_key, ui_.ui_value)
283 283 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
284 284
285 285 meta.Session.remove()
286 286 return baseui
287 287
288 288
289 289 def set_rhodecode_config(config):
290 290 """
291 291 Updates pylons config with new settings from database
292 292
293 293 :param config:
294 294 """
295 295 hgsettings = RhodeCodeSetting.get_app_settings()
296 296
297 297 for k, v in hgsettings.items():
298 298 config[k] = v
299 299
300 300
301 301 def invalidate_cache(cache_key, *args):
302 302 """
303 303 Puts cache invalidation task into db for
304 304 further global cache invalidation
305 305 """
306 306
307 307 from rhodecode.model.scm import ScmModel
308 308
309 309 if cache_key.startswith('get_repo_cached_'):
310 310 name = cache_key.split('get_repo_cached_')[-1]
311 311 ScmModel().mark_for_invalidation(name)
312 312
313 313
314 314 class EmptyChangeset(BaseChangeset):
315 315 """
316 316 An dummy empty changeset. It's possible to pass hash when creating
317 317 an EmptyChangeset
318 318 """
319 319
320 320 def __init__(self, cs='0' * 40, repo=None, requested_revision=None, alias=None):
321 321 self._empty_cs = cs
322 322 self.revision = -1
323 323 self.message = ''
324 324 self.author = ''
325 325 self.date = ''
326 326 self.repository = repo
327 327 self.requested_revision = requested_revision
328 328 self.alias = alias
329 329
330 330 @LazyProperty
331 331 def raw_id(self):
332 332 """
333 333 Returns raw string identifying this changeset, useful for web
334 334 representation.
335 335 """
336 336
337 337 return self._empty_cs
338 338
339 339 @LazyProperty
340 340 def branch(self):
341 341 return get_backend(self.alias).DEFAULT_BRANCH_NAME
342 342
343 343 @LazyProperty
344 344 def short_id(self):
345 345 return self.raw_id[:12]
346 346
347 347 def get_file_changeset(self, path):
348 348 return self
349 349
350 350 def get_file_content(self, path):
351 351 return u''
352 352
353 353 def get_file_size(self, path):
354 354 return 0
355 355
356 356
357 357 def map_groups(groups):
358 358 """
359 359 Checks for groups existence, and creates groups structures.
360 360 It returns last group in structure
361 361
362 362 :param groups: list of groups structure
363 363 """
364 364 sa = meta.Session()
365 365
366 366 parent = None
367 367 group = None
368 368
369 369 # last element is repo in nested groups structure
370 370 groups = groups[:-1]
371 371
372 372 for lvl, group_name in enumerate(groups):
373 373 group_name = '/'.join(groups[:lvl] + [group_name])
374 374 group = sa.query(RepoGroup).filter(RepoGroup.group_name == group_name).scalar()
375 375
376 376 if group is None:
377 377 group = RepoGroup(group_name, parent)
378 378 sa.add(group)
379 379 sa.commit()
380 380 parent = group
381 381 return group
382 382
383 383
384 384 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
385 385 """
386 386 maps all repos given in initial_repo_list, non existing repositories
387 387 are created, if remove_obsolete is True it also check for db entries
388 388 that are not in initial_repo_list and removes them.
389 389
390 390 :param initial_repo_list: list of repositories found by scanning methods
391 391 :param remove_obsolete: check for obsolete entries in database
392 392 """
393 393 from rhodecode.model.repo import RepoModel
394 394 sa = meta.Session()
395 395 rm = RepoModel()
396 396 user = sa.query(User).filter(User.admin == True).first()
397 397 if user is None:
398 398 raise Exception('Missing administrative account !')
399 399 added = []
400 400
401 401 for name, repo in initial_repo_list.items():
402 402 group = map_groups(name.split(Repository.url_sep()))
403 403 if not rm.get_by_repo_name(name, cache=False):
404 404 log.info('repository %s not found creating default', name)
405 405 added.append(name)
406 406 form_data = {
407 407 'repo_name': name,
408 408 'repo_name_full': name,
409 409 'repo_type': repo.alias,
410 410 'description': repo.description \
411 411 if repo.description != 'unknown' else \
412 412 '%s repository' % name,
413 413 'private': False,
414 414 'group_id': getattr(group, 'group_id', None)
415 415 }
416 416 rm.create(form_data, user, just_db=True)
417
417 sa.commit()
418 418 removed = []
419 419 if remove_obsolete:
420 420 #remove from database those repositories that are not in the filesystem
421 421 for repo in sa.query(Repository).all():
422 422 if repo.repo_name not in initial_repo_list.keys():
423 423 removed.append(repo.repo_name)
424 424 sa.delete(repo)
425 425 sa.commit()
426 426
427 427 return added, removed
428 428
429 429 # set cache regions for beaker so celery can utilise it
430 430 def add_cache(settings):
431 431 cache_settings = {'regions': None}
432 432 for key in settings.keys():
433 433 for prefix in ['beaker.cache.', 'cache.']:
434 434 if key.startswith(prefix):
435 435 name = key.split(prefix)[1].strip()
436 436 cache_settings[name] = settings[key].strip()
437 437 if cache_settings['regions']:
438 438 for region in cache_settings['regions'].split(','):
439 439 region = region.strip()
440 440 region_settings = {}
441 441 for key, value in cache_settings.items():
442 442 if key.startswith(region):
443 443 region_settings[key.split('.')[1]] = value
444 444 region_settings['expire'] = int(region_settings.get('expire',
445 445 60))
446 446 region_settings.setdefault('lock_dir',
447 447 cache_settings.get('lock_dir'))
448 448 region_settings.setdefault('data_dir',
449 449 cache_settings.get('data_dir'))
450 450
451 451 if 'type' not in region_settings:
452 452 region_settings['type'] = cache_settings.get('type',
453 453 'memory')
454 454 beaker.cache.cache_regions[region] = region_settings
455 455
456 456
457 457 #==============================================================================
458 458 # TEST FUNCTIONS AND CREATORS
459 459 #==============================================================================
460 460 def create_test_index(repo_location, config, full_index):
461 461 """
462 462 Makes default test index
463 463
464 464 :param config: test config
465 465 :param full_index:
466 466 """
467 467
468 468 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
469 469 from rhodecode.lib.pidlock import DaemonLock, LockHeld
470 470
471 471 repo_location = repo_location
472 472
473 473 index_location = os.path.join(config['app_conf']['index_dir'])
474 474 if not os.path.exists(index_location):
475 475 os.makedirs(index_location)
476 476
477 477 try:
478 478 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
479 479 WhooshIndexingDaemon(index_location=index_location,
480 480 repo_location=repo_location)\
481 481 .run(full_index=full_index)
482 482 l.release()
483 483 except LockHeld:
484 484 pass
485 485
486 486
487 487 def create_test_env(repos_test_path, config):
488 488 """
489 489 Makes a fresh database and
490 490 install test repository into tmp dir
491 491 """
492 492 from rhodecode.lib.db_manage import DbManage
493 493 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
494 494
495 495 # PART ONE create db
496 496 dbconf = config['sqlalchemy.db1.url']
497 497 log.debug('making test db %s', dbconf)
498 498
499 499 # create test dir if it doesn't exist
500 500 if not os.path.isdir(repos_test_path):
501 501 log.debug('Creating testdir %s' % repos_test_path)
502 502 os.makedirs(repos_test_path)
503 503
504 504 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
505 505 tests=True)
506 506 dbmanage.create_tables(override=True)
507 507 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
508 508 dbmanage.create_default_user()
509 509 dbmanage.admin_prompt()
510 510 dbmanage.create_permissions()
511 511 dbmanage.populate_default_permissions()
512 512
513 513 # PART TWO make test repo
514 514 log.debug('making test vcs repositories')
515 515
516 516 idx_path = config['app_conf']['index_dir']
517 517 data_path = config['app_conf']['cache_dir']
518 518
519 519 #clean index and data
520 520 if idx_path and os.path.exists(idx_path):
521 521 log.debug('remove %s' % idx_path)
522 522 shutil.rmtree(idx_path)
523 523
524 524 if data_path and os.path.exists(data_path):
525 525 log.debug('remove %s' % data_path)
526 526 shutil.rmtree(data_path)
527 527
528 528 #CREATE DEFAULT HG REPOSITORY
529 529 cur_dir = dn(dn(abspath(__file__)))
530 530 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
531 531 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
532 532 tar.close()
533 533
534 534
535 535 #==============================================================================
536 536 # PASTER COMMANDS
537 537 #==============================================================================
538 538 class BasePasterCommand(Command):
539 539 """
540 540 Abstract Base Class for paster commands.
541 541
542 542 The celery commands are somewhat aggressive about loading
543 543 celery.conf, and since our module sets the `CELERY_LOADER`
544 544 environment variable to our loader, we have to bootstrap a bit and
545 545 make sure we've had a chance to load the pylons config off of the
546 546 command line, otherwise everything fails.
547 547 """
548 548 min_args = 1
549 549 min_args_error = "Please provide a paster config file as an argument."
550 550 takes_config_file = 1
551 551 requires_config_file = True
552 552
553 553 def notify_msg(self, msg, log=False):
554 554 """Make a notification to user, additionally if logger is passed
555 555 it logs this action using given logger
556 556
557 557 :param msg: message that will be printed to user
558 558 :param log: logging instance, to use to additionally log this message
559 559
560 560 """
561 561 if log and isinstance(log, logging):
562 562 log(msg)
563 563
564 564 def run(self, args):
565 565 """
566 566 Overrides Command.run
567 567
568 568 Checks for a config file argument and loads it.
569 569 """
570 570 if len(args) < self.min_args:
571 571 raise BadCommand(
572 572 self.min_args_error % {'min_args': self.min_args,
573 573 'actual_args': len(args)})
574 574
575 575 # Decrement because we're going to lob off the first argument.
576 576 # @@ This is hacky
577 577 self.min_args -= 1
578 578 self.bootstrap_config(args[0])
579 579 self.update_parser()
580 580 return super(BasePasterCommand, self).run(args[1:])
581 581
582 582 def update_parser(self):
583 583 """
584 584 Abstract method. Allows for the class's parser to be updated
585 585 before the superclass's `run` method is called. Necessary to
586 586 allow options/arguments to be passed through to the underlying
587 587 celery command.
588 588 """
589 589 raise NotImplementedError("Abstract Method.")
590 590
591 591 def bootstrap_config(self, conf):
592 592 """
593 593 Loads the pylons configuration.
594 594 """
595 595 from pylons import config as pylonsconfig
596 596
597 597 path_to_ini_file = os.path.realpath(conf)
598 598 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
599 599 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
600 600
@@ -1,1209 +1,1206 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 from datetime import date
31 30
32 31 from sqlalchemy import *
33 from sqlalchemy.exc import DatabaseError
34 32 from sqlalchemy.ext.hybrid import hybrid_property
35 33 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 34 from beaker.cache import cache_region, region_invalidate
37 35
38 36 from vcs import get_backend
39 37 from vcs.utils.helpers import get_scm
40 38 from vcs.exceptions import VCSError
41 39 from vcs.utils.lazy import LazyProperty
42 40
43 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
44 generate_api_key, safe_unicode
41 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
45 42 from rhodecode.lib.exceptions import UsersGroupsAssignedException
46 43 from rhodecode.lib.compat import json
47 44 from rhodecode.lib.caching_query import FromCache
48 45
49 46 from rhodecode.model.meta import Base, Session
50 47
51 48 log = logging.getLogger(__name__)
52 49
53 50 #==============================================================================
54 51 # BASE CLASSES
55 52 #==============================================================================
56 53
57 54 class ModelSerializer(json.JSONEncoder):
58 55 """
59 56 Simple Serializer for JSON,
60 57
61 58 usage::
62 59
63 60 to make object customized for serialization implement a __json__
64 61 method that will return a dict for serialization into json
65 62
66 63 example::
67 64
68 65 class Task(object):
69 66
70 67 def __init__(self, name, value):
71 68 self.name = name
72 69 self.value = value
73 70
74 71 def __json__(self):
75 72 return dict(name=self.name,
76 73 value=self.value)
77 74
78 75 """
79 76
80 77 def default(self, obj):
81 78
82 79 if hasattr(obj, '__json__'):
83 80 return obj.__json__()
84 81 else:
85 82 return json.JSONEncoder.default(self, obj)
86 83
87 84 class BaseModel(object):
88 85 """Base Model for all classess
89 86
90 87 """
91 88
92 89 @classmethod
93 90 def _get_keys(cls):
94 91 """return column names for this model """
95 92 return class_mapper(cls).c.keys()
96 93
97 94 def get_dict(self):
98 95 """return dict with keys and values corresponding
99 96 to this model data """
100 97
101 98 d = {}
102 99 for k in self._get_keys():
103 100 d[k] = getattr(self, k)
104 101 return d
105 102
106 103 def get_appstruct(self):
107 104 """return list with keys and values tupples corresponding
108 105 to this model data """
109 106
110 107 l = []
111 108 for k in self._get_keys():
112 109 l.append((k, getattr(self, k),))
113 110 return l
114 111
115 112 def populate_obj(self, populate_dict):
116 113 """populate model with data from given populate_dict"""
117 114
118 115 for k in self._get_keys():
119 116 if k in populate_dict:
120 117 setattr(self, k, populate_dict[k])
121 118
122 119 @classmethod
123 120 def query(cls):
124 121 return Session().query(cls)
125 122
126 123 @classmethod
127 124 def get(cls, id_):
128 125 if id_:
129 126 return cls.query().get(id_)
130 127
131 128 @classmethod
132 129 def getAll(cls):
133 130 return cls.query().all()
134 131
135 132 @classmethod
136 133 def delete(cls, id_):
137 134 obj = cls.query().get(id_)
138 135 Session().delete(obj)
139 136
140 137
141 138 class RhodeCodeSetting(Base, BaseModel):
142 139 __tablename__ = 'rhodecode_settings'
143 140 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
144 141 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
145 142 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
146 143 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
147 144
148 145 def __init__(self, k='', v=''):
149 146 self.app_settings_name = k
150 147 self.app_settings_value = v
151 148
152 149
153 150 @validates('_app_settings_value')
154 151 def validate_settings_value(self, key, val):
155 152 assert type(val) == unicode
156 153 return val
157 154
158 155 @hybrid_property
159 156 def app_settings_value(self):
160 157 v = self._app_settings_value
161 158 if v == 'ldap_active':
162 159 v = str2bool(v)
163 160 return v
164 161
165 162 @app_settings_value.setter
166 163 def app_settings_value(self, val):
167 164 """
168 165 Setter that will always make sure we use unicode in app_settings_value
169 166
170 167 :param val:
171 168 """
172 169 self._app_settings_value = safe_unicode(val)
173 170
174 171 def __repr__(self):
175 172 return "<%s('%s:%s')>" % (self.__class__.__name__,
176 173 self.app_settings_name, self.app_settings_value)
177 174
178 175
179 176 @classmethod
180 177 def get_by_name(cls, ldap_key):
181 178 return cls.query()\
182 179 .filter(cls.app_settings_name == ldap_key).scalar()
183 180
184 181 @classmethod
185 182 def get_app_settings(cls, cache=False):
186 183
187 184 ret = cls.query()
188 185
189 186 if cache:
190 187 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
191 188
192 189 if not ret:
193 190 raise Exception('Could not get application settings !')
194 191 settings = {}
195 192 for each in ret:
196 193 settings['rhodecode_' + each.app_settings_name] = \
197 194 each.app_settings_value
198 195
199 196 return settings
200 197
201 198 @classmethod
202 199 def get_ldap_settings(cls, cache=False):
203 200 ret = cls.query()\
204 201 .filter(cls.app_settings_name.startswith('ldap_')).all()
205 202 fd = {}
206 203 for row in ret:
207 204 fd.update({row.app_settings_name:row.app_settings_value})
208 205
209 206 return fd
210 207
211 208
212 209 class RhodeCodeUi(Base, BaseModel):
213 210 __tablename__ = 'rhodecode_ui'
214 211 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
215 212
216 213 HOOK_UPDATE = 'changegroup.update'
217 214 HOOK_REPO_SIZE = 'changegroup.repo_size'
218 215 HOOK_PUSH = 'pretxnchangegroup.push_logger'
219 216 HOOK_PULL = 'preoutgoing.pull_logger'
220 217
221 218 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
222 219 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
223 220 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
224 221 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
225 222 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
226 223
227 224
228 225 @classmethod
229 226 def get_by_key(cls, key):
230 227 return cls.query().filter(cls.ui_key == key)
231 228
232 229
233 230 @classmethod
234 231 def get_builtin_hooks(cls):
235 232 q = cls.query()
236 233 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
237 234 cls.HOOK_REPO_SIZE,
238 235 cls.HOOK_PUSH, cls.HOOK_PULL]))
239 236 return q.all()
240 237
241 238 @classmethod
242 239 def get_custom_hooks(cls):
243 240 q = cls.query()
244 241 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
245 242 cls.HOOK_REPO_SIZE,
246 243 cls.HOOK_PUSH, cls.HOOK_PULL]))
247 244 q = q.filter(cls.ui_section == 'hooks')
248 245 return q.all()
249 246
250 247 @classmethod
251 248 def create_or_update_hook(cls, key, val):
252 249 new_ui = cls.get_by_key(key).scalar() or cls()
253 250 new_ui.ui_section = 'hooks'
254 251 new_ui.ui_active = True
255 252 new_ui.ui_key = key
256 253 new_ui.ui_value = val
257 254
258 255 Session().add(new_ui)
259 256 Session().commit()
260 257
261 258
262 259 class User(Base, BaseModel):
263 260 __tablename__ = 'users'
264 261 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
265 262 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
266 263 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
267 264 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 265 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
269 266 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
270 267 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 268 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 269 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 270 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
274 271 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
275 272 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
276 273
277 274 user_log = relationship('UserLog', cascade='all')
278 275 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
279 276
280 277 repositories = relationship('Repository')
281 278 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
282 279 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
283 280
284 281 group_member = relationship('UsersGroupMember', cascade='all')
285 282
286 283 notifications = relationship('UserNotification',)
287 284
288 285 @property
289 286 def full_contact(self):
290 287 return '%s %s <%s>' % (self.name, self.lastname, self.email)
291 288
292 289 @property
293 290 def short_contact(self):
294 291 return '%s %s' % (self.name, self.lastname)
295 292
296 293 @property
297 294 def is_admin(self):
298 295 return self.admin
299 296
300 297 def __repr__(self):
301 298 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
302 299 self.user_id, self.username)
303 300
304 301
305 302 @classmethod
306 303 def get_by_username(cls, username, case_insensitive=False, cache=False):
307 304 if case_insensitive:
308 305 q = cls.query().filter(cls.username.ilike(username))
309 306 else:
310 307 q = cls.query().filter(cls.username == username)
311 308
312 309 if cache:
313 310 q = q.options(FromCache("sql_cache_short",
314 311 "get_user_%s" % username))
315 312 return q.scalar()
316 313
317 314 @classmethod
318 315 def get_by_api_key(cls, api_key, cache=False):
319 316 q = cls.query().filter(cls.api_key == api_key)
320 317
321 318 if cache:
322 319 q = q.options(FromCache("sql_cache_short",
323 320 "get_api_key_%s" % api_key))
324 321 return q.scalar()
325 322
326 323 @classmethod
327 324 def get_by_email(cls, email, cache=False):
328 325 q = cls.query().filter(cls.email == email)
329 326
330 327 if cache:
331 328 q = q.options(FromCache("sql_cache_short",
332 329 "get_api_key_%s" % email))
333 330 return q.scalar()
334 331
335 332 def update_lastlogin(self):
336 333 """Update user lastlogin"""
337 334
338 335 self.last_login = datetime.datetime.now()
339 336 Session().add(self)
340 337 Session().commit()
341 338 log.debug('updated user %s lastlogin', self.username)
342 339
343 340
344 341 class UserLog(Base, BaseModel):
345 342 __tablename__ = 'user_logs'
346 343 __table_args__ = {'extend_existing':True}
347 344 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
348 345 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
349 346 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
350 347 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
351 348 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 349 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
353 350 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
354 351
355 352 @property
356 353 def action_as_day(self):
357 return date(*self.action_date.timetuple()[:3])
354 return datetime.date(*self.action_date.timetuple()[:3])
358 355
359 356 user = relationship('User')
360 357 repository = relationship('Repository')
361 358
362 359
363 360 class UsersGroup(Base, BaseModel):
364 361 __tablename__ = 'users_groups'
365 362 __table_args__ = {'extend_existing':True}
366 363
367 364 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
368 365 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
369 366 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
370 367
371 368 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
372 369
373 370 def __repr__(self):
374 371 return '<userGroup(%s)>' % (self.users_group_name)
375 372
376 373 @classmethod
377 374 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
378 375 if case_insensitive:
379 376 gr = cls.query()\
380 377 .filter(cls.users_group_name.ilike(group_name))
381 378 else:
382 379 gr = cls.query()\
383 380 .filter(cls.users_group_name == group_name)
384 381 if cache:
385 382 gr = gr.options(FromCache("sql_cache_short",
386 383 "get_user_%s" % group_name))
387 384 return gr.scalar()
388 385
389 386
390 387 @classmethod
391 388 def get(cls, users_group_id, cache=False):
392 389 users_group = cls.query()
393 390 if cache:
394 391 users_group = users_group.options(FromCache("sql_cache_short",
395 392 "get_users_group_%s" % users_group_id))
396 393 return users_group.get(users_group_id)
397 394
398 395 @classmethod
399 396 def create(cls, form_data):
400 397 try:
401 398 new_users_group = cls()
402 399 for k, v in form_data.items():
403 400 setattr(new_users_group, k, v)
404 401
405 402 Session().add(new_users_group)
406 403 Session().commit()
407 404 return new_users_group
408 405 except:
409 406 log.error(traceback.format_exc())
410 407 Session().rollback()
411 408 raise
412 409
413 410 @classmethod
414 411 def update(cls, users_group_id, form_data):
415 412
416 413 try:
417 414 users_group = cls.get(users_group_id, cache=False)
418 415
419 416 for k, v in form_data.items():
420 417 if k == 'users_group_members':
421 418 users_group.members = []
422 419 Session().flush()
423 420 members_list = []
424 421 if v:
425 422 v = [v] if isinstance(v, basestring) else v
426 423 for u_id in set(v):
427 424 member = UsersGroupMember(users_group_id, u_id)
428 425 members_list.append(member)
429 426 setattr(users_group, 'members', members_list)
430 427 setattr(users_group, k, v)
431 428
432 429 Session().add(users_group)
433 430 Session().commit()
434 431 except:
435 432 log.error(traceback.format_exc())
436 433 Session().rollback()
437 434 raise
438 435
439 436 @classmethod
440 437 def delete(cls, users_group_id):
441 438 try:
442 439
443 440 # check if this group is not assigned to repo
444 441 assigned_groups = UsersGroupRepoToPerm.query()\
445 442 .filter(UsersGroupRepoToPerm.users_group_id ==
446 443 users_group_id).all()
447 444
448 445 if assigned_groups:
449 446 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
450 447 assigned_groups)
451 448
452 449 users_group = cls.get(users_group_id, cache=False)
453 450 Session().delete(users_group)
454 451 Session().commit()
455 452 except:
456 453 log.error(traceback.format_exc())
457 454 Session().rollback()
458 455 raise
459 456
460 457 class UsersGroupMember(Base, BaseModel):
461 458 __tablename__ = 'users_groups_members'
462 459 __table_args__ = {'extend_existing':True}
463 460
464 461 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
465 462 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
466 463 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
467 464
468 465 user = relationship('User', lazy='joined')
469 466 users_group = relationship('UsersGroup')
470 467
471 468 def __init__(self, gr_id='', u_id=''):
472 469 self.users_group_id = gr_id
473 470 self.user_id = u_id
474 471
475 472 @staticmethod
476 473 def add_user_to_group(group, user):
477 474 ugm = UsersGroupMember()
478 475 ugm.users_group = group
479 476 ugm.user = user
480 477 Session().add(ugm)
481 478 Session().commit()
482 479 return ugm
483 480
484 481 class Repository(Base, BaseModel):
485 482 __tablename__ = 'repositories'
486 483 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
487 484
488 485 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
489 486 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
490 487 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
491 488 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
492 489 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
493 490 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
494 491 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
495 492 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
496 493 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
497 494 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
498 495
499 496 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
500 497 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
501 498
502 499
503 500 user = relationship('User')
504 501 fork = relationship('Repository', remote_side=repo_id)
505 502 group = relationship('RepoGroup')
506 503 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
507 504 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
508 505 stats = relationship('Statistics', cascade='all', uselist=False)
509 506
510 507 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
511 508
512 509 logs = relationship('UserLog', cascade='all')
513 510
514 511 def __repr__(self):
515 512 return "<%s('%s:%s')>" % (self.__class__.__name__,
516 513 self.repo_id, self.repo_name)
517 514
518 515 @classmethod
519 516 def url_sep(cls):
520 517 return '/'
521 518
522 519 @classmethod
523 520 def get_by_repo_name(cls, repo_name):
524 521 q = Session().query(cls).filter(cls.repo_name == repo_name)
525 522 q = q.options(joinedload(Repository.fork))\
526 523 .options(joinedload(Repository.user))\
527 524 .options(joinedload(Repository.group))
528 525 return q.one()
529 526
530 527 @classmethod
531 528 def get_repo_forks(cls, repo_id):
532 529 return cls.query().filter(Repository.fork_id == repo_id)
533 530
534 531 @classmethod
535 532 def base_path(cls):
536 533 """
537 534 Returns base path when all repos are stored
538 535
539 536 :param cls:
540 537 """
541 538 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
542 539 cls.url_sep())
543 540 q.options(FromCache("sql_cache_short", "repository_repo_path"))
544 541 return q.one().ui_value
545 542
546 543 @property
547 544 def just_name(self):
548 545 return self.repo_name.split(Repository.url_sep())[-1]
549 546
550 547 @property
551 548 def groups_with_parents(self):
552 549 groups = []
553 550 if self.group is None:
554 551 return groups
555 552
556 553 cur_gr = self.group
557 554 groups.insert(0, cur_gr)
558 555 while 1:
559 556 gr = getattr(cur_gr, 'parent_group', None)
560 557 cur_gr = cur_gr.parent_group
561 558 if gr is None:
562 559 break
563 560 groups.insert(0, gr)
564 561
565 562 return groups
566 563
567 564 @property
568 565 def groups_and_repo(self):
569 566 return self.groups_with_parents, self.just_name
570 567
571 568 @LazyProperty
572 569 def repo_path(self):
573 570 """
574 571 Returns base full path for that repository means where it actually
575 572 exists on a filesystem
576 573 """
577 574 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
578 575 Repository.url_sep())
579 576 q.options(FromCache("sql_cache_short", "repository_repo_path"))
580 577 return q.one().ui_value
581 578
582 579 @property
583 580 def repo_full_path(self):
584 581 p = [self.repo_path]
585 582 # we need to split the name by / since this is how we store the
586 583 # names in the database, but that eventually needs to be converted
587 584 # into a valid system path
588 585 p += self.repo_name.split(Repository.url_sep())
589 586 return os.path.join(*p)
590 587
591 588 def get_new_name(self, repo_name):
592 589 """
593 590 returns new full repository name based on assigned group and new new
594 591
595 592 :param group_name:
596 593 """
597 594 path_prefix = self.group.full_path_splitted if self.group else []
598 595 return Repository.url_sep().join(path_prefix + [repo_name])
599 596
600 597 @property
601 598 def _ui(self):
602 599 """
603 600 Creates an db based ui object for this repository
604 601 """
605 602 from mercurial import ui
606 603 from mercurial import config
607 604 baseui = ui.ui()
608 605
609 606 #clean the baseui object
610 607 baseui._ocfg = config.config()
611 608 baseui._ucfg = config.config()
612 609 baseui._tcfg = config.config()
613 610
614 611
615 612 ret = RhodeCodeUi.query()\
616 613 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
617 614
618 615 hg_ui = ret
619 616 for ui_ in hg_ui:
620 617 if ui_.ui_active:
621 618 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
622 619 ui_.ui_key, ui_.ui_value)
623 620 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
624 621
625 622 return baseui
626 623
627 624 @classmethod
628 625 def is_valid(cls, repo_name):
629 626 """
630 627 returns True if given repo name is a valid filesystem repository
631 628
632 629 @param cls:
633 630 @param repo_name:
634 631 """
635 632 from rhodecode.lib.utils import is_valid_repo
636 633
637 634 return is_valid_repo(repo_name, cls.base_path())
638 635
639 636
640 637 #==========================================================================
641 638 # SCM PROPERTIES
642 639 #==========================================================================
643 640
644 641 def get_changeset(self, rev):
645 642 return get_changeset_safe(self.scm_instance, rev)
646 643
647 644 @property
648 645 def tip(self):
649 646 return self.get_changeset('tip')
650 647
651 648 @property
652 649 def author(self):
653 650 return self.tip.author
654 651
655 652 @property
656 653 def last_change(self):
657 654 return self.scm_instance.last_change
658 655
659 656 #==========================================================================
660 657 # SCM CACHE INSTANCE
661 658 #==========================================================================
662 659
663 660 @property
664 661 def invalidate(self):
665 662 return CacheInvalidation.invalidate(self.repo_name)
666 663
667 664 def set_invalidate(self):
668 665 """
669 666 set a cache for invalidation for this instance
670 667 """
671 668 CacheInvalidation.set_invalidate(self.repo_name)
672 669
673 670 @LazyProperty
674 671 def scm_instance(self):
675 672 return self.__get_instance()
676 673
677 674 @property
678 675 def scm_instance_cached(self):
679 676 @cache_region('long_term')
680 677 def _c(repo_name):
681 678 return self.__get_instance()
682 679 rn = self.repo_name
683 680
684 681 inv = self.invalidate
685 682 if inv is not None:
686 683 region_invalidate(_c, None, rn)
687 684 # update our cache
688 685 CacheInvalidation.set_valid(inv.cache_key)
689 686 return _c(rn)
690 687
691 688 def __get_instance(self):
692 689
693 690 repo_full_path = self.repo_full_path
694 691
695 692 try:
696 693 alias = get_scm(repo_full_path)[0]
697 694 log.debug('Creating instance of %s repository', alias)
698 695 backend = get_backend(alias)
699 696 except VCSError:
700 697 log.error(traceback.format_exc())
701 698 log.error('Perhaps this repository is in db and not in '
702 699 'filesystem run rescan repositories with '
703 700 '"destroy old data " option from admin panel')
704 701 return
705 702
706 703 if alias == 'hg':
707 704
708 705 repo = backend(safe_str(repo_full_path), create=False,
709 706 baseui=self._ui)
710 707 # skip hidden web repository
711 708 if repo._get_hidden():
712 709 return
713 710 else:
714 711 repo = backend(repo_full_path, create=False)
715 712
716 713 return repo
717 714
718 715
719 716 class RepoGroup(Base, BaseModel):
720 717 __tablename__ = 'groups'
721 718 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
722 719 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
723 720 __mapper_args__ = {'order_by':'group_name'}
724 721
725 722 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
726 723 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
727 724 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
728 725 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
729 726
730 727 parent_group = relationship('RepoGroup', remote_side=group_id)
731 728
732 729
733 730 def __init__(self, group_name='', parent_group=None):
734 731 self.group_name = group_name
735 732 self.parent_group = parent_group
736 733
737 734 def __repr__(self):
738 735 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
739 736 self.group_name)
740 737
741 738 @classmethod
742 739 def groups_choices(cls):
743 740 from webhelpers.html import literal as _literal
744 741 repo_groups = [('', '')]
745 742 sep = ' &raquo; '
746 743 _name = lambda k: _literal(sep.join(k))
747 744
748 745 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
749 746 for x in cls.query().all()])
750 747
751 748 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
752 749 return repo_groups
753 750
754 751 @classmethod
755 752 def url_sep(cls):
756 753 return '/'
757 754
758 755 @classmethod
759 756 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
760 757 if case_insensitive:
761 758 gr = cls.query()\
762 759 .filter(cls.group_name.ilike(group_name))
763 760 else:
764 761 gr = cls.query()\
765 762 .filter(cls.group_name == group_name)
766 763 if cache:
767 764 gr = gr.options(FromCache("sql_cache_short",
768 765 "get_group_%s" % group_name))
769 766 return gr.scalar()
770 767
771 768 @property
772 769 def parents(self):
773 770 parents_recursion_limit = 5
774 771 groups = []
775 772 if self.parent_group is None:
776 773 return groups
777 774 cur_gr = self.parent_group
778 775 groups.insert(0, cur_gr)
779 776 cnt = 0
780 777 while 1:
781 778 cnt += 1
782 779 gr = getattr(cur_gr, 'parent_group', None)
783 780 cur_gr = cur_gr.parent_group
784 781 if gr is None:
785 782 break
786 783 if cnt == parents_recursion_limit:
787 784 # this will prevent accidental infinit loops
788 785 log.error('group nested more than %s' %
789 786 parents_recursion_limit)
790 787 break
791 788
792 789 groups.insert(0, gr)
793 790 return groups
794 791
795 792 @property
796 793 def children(self):
797 794 return RepoGroup.query().filter(RepoGroup.parent_group == self)
798 795
799 796 @property
800 797 def name(self):
801 798 return self.group_name.split(RepoGroup.url_sep())[-1]
802 799
803 800 @property
804 801 def full_path(self):
805 802 return self.group_name
806 803
807 804 @property
808 805 def full_path_splitted(self):
809 806 return self.group_name.split(RepoGroup.url_sep())
810 807
811 808 @property
812 809 def repositories(self):
813 810 return Repository.query().filter(Repository.group == self)
814 811
815 812 @property
816 813 def repositories_recursive_count(self):
817 814 cnt = self.repositories.count()
818 815
819 816 def children_count(group):
820 817 cnt = 0
821 818 for child in group.children:
822 819 cnt += child.repositories.count()
823 820 cnt += children_count(child)
824 821 return cnt
825 822
826 823 return cnt + children_count(self)
827 824
828 825
829 826 def get_new_name(self, group_name):
830 827 """
831 828 returns new full group name based on parent and new name
832 829
833 830 :param group_name:
834 831 """
835 832 path_prefix = (self.parent_group.full_path_splitted if
836 833 self.parent_group else [])
837 834 return RepoGroup.url_sep().join(path_prefix + [group_name])
838 835
839 836
840 837 class Permission(Base, BaseModel):
841 838 __tablename__ = 'permissions'
842 839 __table_args__ = {'extend_existing':True}
843 840 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
844 841 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
845 842 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
846 843
847 844 def __repr__(self):
848 845 return "<%s('%s:%s')>" % (self.__class__.__name__,
849 846 self.permission_id, self.permission_name)
850 847
851 848 @classmethod
852 849 def get_by_key(cls, key):
853 850 return cls.query().filter(cls.permission_name == key).scalar()
854 851
855 852 class UserRepoToPerm(Base, BaseModel):
856 853 __tablename__ = 'repo_to_perm'
857 854 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
858 855 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
859 856 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
860 857 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
861 858 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
862 859
863 860 user = relationship('User')
864 861 permission = relationship('Permission')
865 862 repository = relationship('Repository')
866 863
867 864 class UserToPerm(Base, BaseModel):
868 865 __tablename__ = 'user_to_perm'
869 866 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
870 867 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
871 868 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
872 869 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
873 870
874 871 user = relationship('User')
875 872 permission = relationship('Permission')
876 873
877 874 @classmethod
878 875 def has_perm(cls, user_id, perm):
879 876 if not isinstance(perm, Permission):
880 877 raise Exception('perm needs to be an instance of Permission class')
881 878
882 879 return cls.query().filter(cls.user_id == user_id)\
883 880 .filter(cls.permission == perm).scalar() is not None
884 881
885 882 @classmethod
886 883 def grant_perm(cls, user_id, perm):
887 884 if not isinstance(perm, Permission):
888 885 raise Exception('perm needs to be an instance of Permission class')
889 886
890 887 new = cls()
891 888 new.user_id = user_id
892 889 new.permission = perm
893 890 try:
894 891 Session().add(new)
895 892 Session().commit()
896 893 except:
897 894 Session().rollback()
898 895
899 896
900 897 @classmethod
901 898 def revoke_perm(cls, user_id, perm):
902 899 if not isinstance(perm, Permission):
903 900 raise Exception('perm needs to be an instance of Permission class')
904 901
905 902 try:
906 903 obj = cls.query().filter(cls.user_id == user_id)\
907 904 .filter(cls.permission == perm).one()
908 905 Session().delete(obj)
909 906 Session().commit()
910 907 except:
911 908 Session().rollback()
912 909
913 910 class UsersGroupRepoToPerm(Base, BaseModel):
914 911 __tablename__ = 'users_group_repo_to_perm'
915 912 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
916 913 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
917 914 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
918 915 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
919 916 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
920 917
921 918 users_group = relationship('UsersGroup')
922 919 permission = relationship('Permission')
923 920 repository = relationship('Repository')
924 921
925 922 def __repr__(self):
926 923 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
927 924
928 925 class UsersGroupToPerm(Base, BaseModel):
929 926 __tablename__ = 'users_group_to_perm'
930 927 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
931 928 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
932 929 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
933 930
934 931 users_group = relationship('UsersGroup')
935 932 permission = relationship('Permission')
936 933
937 934
938 935 @classmethod
939 936 def has_perm(cls, users_group_id, perm):
940 937 if not isinstance(perm, Permission):
941 938 raise Exception('perm needs to be an instance of Permission class')
942 939
943 940 return cls.query().filter(cls.users_group_id ==
944 941 users_group_id)\
945 942 .filter(cls.permission == perm)\
946 943 .scalar() is not None
947 944
948 945 @classmethod
949 946 def grant_perm(cls, users_group_id, perm):
950 947 if not isinstance(perm, Permission):
951 948 raise Exception('perm needs to be an instance of Permission class')
952 949
953 950 new = cls()
954 951 new.users_group_id = users_group_id
955 952 new.permission = perm
956 953 try:
957 954 Session().add(new)
958 955 Session().commit()
959 956 except:
960 957 Session().rollback()
961 958
962 959
963 960 @classmethod
964 961 def revoke_perm(cls, users_group_id, perm):
965 962 if not isinstance(perm, Permission):
966 963 raise Exception('perm needs to be an instance of Permission class')
967 964
968 965 try:
969 966 obj = cls.query().filter(cls.users_group_id == users_group_id)\
970 967 .filter(cls.permission == perm).one()
971 968 Session().delete(obj)
972 969 Session().commit()
973 970 except:
974 971 Session().rollback()
975 972
976 973
977 974 class UserRepoGroupToPerm(Base, BaseModel):
978 975 __tablename__ = 'group_to_perm'
979 976 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
980 977
981 978 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
982 979 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
983 980 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
984 981 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
985 982
986 983 user = relationship('User')
987 984 permission = relationship('Permission')
988 985 group = relationship('RepoGroup')
989 986
990 987 class UsersGroupRepoGroupToPerm(Base, BaseModel):
991 988 __tablename__ = 'users_group_repo_group_to_perm'
992 989 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
993 990
994 991 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
995 992 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
996 993 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
997 994 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
998 995
999 996 users_group = relationship('UsersGroup')
1000 997 permission = relationship('Permission')
1001 998 group = relationship('RepoGroup')
1002 999
1003 1000 class Statistics(Base, BaseModel):
1004 1001 __tablename__ = 'statistics'
1005 1002 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
1006 1003 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1007 1004 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1008 1005 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1009 1006 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1010 1007 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1011 1008 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1012 1009
1013 1010 repository = relationship('Repository', single_parent=True)
1014 1011
1015 1012 class UserFollowing(Base, BaseModel):
1016 1013 __tablename__ = 'user_followings'
1017 1014 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1018 1015 UniqueConstraint('user_id', 'follows_user_id')
1019 1016 , {'extend_existing':True})
1020 1017
1021 1018 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1022 1019 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1023 1020 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1024 1021 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1025 1022 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1026 1023
1027 1024 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1028 1025
1029 1026 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1030 1027 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1031 1028
1032 1029
1033 1030 @classmethod
1034 1031 def get_repo_followers(cls, repo_id):
1035 1032 return cls.query().filter(cls.follows_repo_id == repo_id)
1036 1033
1037 1034 class CacheInvalidation(Base, BaseModel):
1038 1035 __tablename__ = 'cache_invalidation'
1039 1036 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1040 1037 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1041 1038 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1042 1039 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1043 1040 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1044 1041
1045 1042
1046 1043 def __init__(self, cache_key, cache_args=''):
1047 1044 self.cache_key = cache_key
1048 1045 self.cache_args = cache_args
1049 1046 self.cache_active = False
1050 1047
1051 1048 def __repr__(self):
1052 1049 return "<%s('%s:%s')>" % (self.__class__.__name__,
1053 1050 self.cache_id, self.cache_key)
1054 1051
1055 1052 @classmethod
1056 1053 def invalidate(cls, key):
1057 1054 """
1058 1055 Returns Invalidation object if this given key should be invalidated
1059 1056 None otherwise. `cache_active = False` means that this cache
1060 1057 state is not valid and needs to be invalidated
1061 1058
1062 1059 :param key:
1063 1060 """
1064 1061 return cls.query()\
1065 1062 .filter(CacheInvalidation.cache_key == key)\
1066 1063 .filter(CacheInvalidation.cache_active == False)\
1067 1064 .scalar()
1068 1065
1069 1066 @classmethod
1070 1067 def set_invalidate(cls, key):
1071 1068 """
1072 1069 Mark this Cache key for invalidation
1073 1070
1074 1071 :param key:
1075 1072 """
1076 1073
1077 1074 log.debug('marking %s for invalidation' % key)
1078 1075 inv_obj = Session().query(cls)\
1079 1076 .filter(cls.cache_key == key).scalar()
1080 1077 if inv_obj:
1081 1078 inv_obj.cache_active = False
1082 1079 else:
1083 1080 log.debug('cache key not found in invalidation db -> creating one')
1084 1081 inv_obj = CacheInvalidation(key)
1085 1082
1086 1083 try:
1087 1084 Session().add(inv_obj)
1088 1085 Session().commit()
1089 1086 except Exception:
1090 1087 log.error(traceback.format_exc())
1091 1088 Session().rollback()
1092 1089
1093 1090 @classmethod
1094 1091 def set_valid(cls, key):
1095 1092 """
1096 1093 Mark this cache key as active and currently cached
1097 1094
1098 1095 :param key:
1099 1096 """
1100 1097 inv_obj = CacheInvalidation.query()\
1101 1098 .filter(CacheInvalidation.cache_key == key).scalar()
1102 1099 inv_obj.cache_active = True
1103 1100 Session().add(inv_obj)
1104 1101 Session().commit()
1105 1102
1106 1103
1107 1104 class ChangesetComment(Base, BaseModel):
1108 1105 __tablename__ = 'changeset_comments'
1109 1106 __table_args__ = ({'extend_existing':True},)
1110 1107 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1111 1108 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1112 1109 revision = Column('revision', String(40), nullable=False)
1113 1110 line_no = Column('line_no', Unicode(10), nullable=True)
1114 1111 f_path = Column('f_path', Unicode(1000), nullable=True)
1115 1112 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1116 1113 text = Column('text', Unicode(25000), nullable=False)
1117 1114 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1118 1115
1119 1116 author = relationship('User', lazy='joined')
1120 1117 repo = relationship('Repository')
1121 1118
1122 1119
1123 1120 @classmethod
1124 1121 def get_users(cls, revision):
1125 1122 """
1126 1123 Returns user associated with this changesetComment. ie those
1127 1124 who actually commented
1128 1125
1129 1126 :param cls:
1130 1127 :param revision:
1131 1128 """
1132 1129 return Session().query(User)\
1133 1130 .filter(cls.revision == revision)\
1134 1131 .join(ChangesetComment.author).all()
1135 1132
1136 1133
1137 1134 class Notification(Base, BaseModel):
1138 1135 __tablename__ = 'notifications'
1139 1136 __table_args__ = ({'extend_existing':True})
1140 1137
1141 1138 TYPE_CHANGESET_COMMENT = u'cs_comment'
1142 1139 TYPE_MESSAGE = u'message'
1143 1140 TYPE_MENTION = u'mention'
1144 1141
1145 1142 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1146 1143 subject = Column('subject', Unicode(512), nullable=True)
1147 1144 body = Column('body', Unicode(50000), nullable=True)
1148 1145 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1149 1146 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1150 1147 type_ = Column('type', Unicode(256))
1151 1148
1152 1149 created_by_user = relationship('User')
1153 1150 notifications_to_users = relationship('UserNotification', lazy='joined',
1154 1151 cascade="all, delete, delete-orphan")
1155 1152
1156 1153 @property
1157 1154 def recipients(self):
1158 1155 return [x.user for x in UserNotification.query()\
1159 1156 .filter(UserNotification.notification == self).all()]
1160 1157
1161 1158 @classmethod
1162 1159 def create(cls, created_by, subject, body, recipients, type_=None):
1163 1160 if type_ is None:
1164 1161 type_ = Notification.TYPE_MESSAGE
1165 1162
1166 1163 notification = cls()
1167 1164 notification.created_by_user = created_by
1168 1165 notification.subject = subject
1169 1166 notification.body = body
1170 1167 notification.type_ = type_
1171 1168 notification.created_on = datetime.datetime.now()
1172 1169
1173 1170 for u in recipients:
1174 1171 assoc = UserNotification()
1175 1172 assoc.notification = notification
1176 1173 u.notifications.append(assoc)
1177 1174 Session().add(notification)
1178 1175 return notification
1179 1176
1180 1177 @property
1181 1178 def description(self):
1182 1179 from rhodecode.model.notification import NotificationModel
1183 1180 return NotificationModel().make_description(self)
1184 1181
1185 1182 class UserNotification(Base, BaseModel):
1186 1183 __tablename__ = 'user_to_notification'
1187 1184 __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
1188 1185 {'extend_existing':True})
1189 1186 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
1190 1187 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1191 1188 read = Column('read', Boolean, default=False)
1192 1189 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1193 1190
1194 1191 user = relationship('User', lazy="joined")
1195 1192 notification = relationship('Notification', lazy="joined",
1196 1193 order_by=lambda:Notification.created_on.desc(),
1197 1194 cascade='all')
1198 1195
1199 1196 def mark_as_read(self):
1200 1197 self.read = True
1201 1198 Session().add(self)
1202 1199
1203 1200 class DbMigrateVersion(Base, BaseModel):
1204 1201 __tablename__ = 'db_migrate_version'
1205 1202 __table_args__ = {'extend_existing':True}
1206 1203 repository_id = Column('repository_id', String(250), primary_key=True)
1207 1204 repository_path = Column('repository_path', Text)
1208 1205 version = Column('version', Integer)
1209 1206
@@ -1,204 +1,201 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.notification
4 4 ~~~~~~~~~~~~~~
5 5
6 6 Model for notifications
7 7
8 8
9 9 :created_on: Nov 20, 2011
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 30 import datetime
31 31
32 from pylons import config
33 32 from pylons.i18n.translation import _
34 33
34 import rhodecode
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.model import BaseModel
37 37 from rhodecode.model.db import Notification, User, UserNotification
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class NotificationModel(BaseModel):
43 43
44
45 44 def __get_user(self, user):
46 45 if isinstance(user, basestring):
47 46 return User.get_by_username(username=user)
48 47 else:
49 48 return self._get_instance(User, user)
50 49
51 50 def __get_notification(self, notification):
52 51 if isinstance(notification, Notification):
53 52 return notification
54 53 elif isinstance(notification, int):
55 54 return Notification.get(notification)
56 55 else:
57 56 if notification:
58 57 raise Exception('notification must be int or Instance'
59 58 ' of Notification got %s' % type(notification))
60 59
61
62 60 def create(self, created_by, subject, body, recipients,
63 61 type_=Notification.TYPE_MESSAGE):
64 62 """
65 63
66 64 Creates notification of given type
67 65
68 66 :param created_by: int, str or User instance. User who created this
69 67 notification
70 68 :param subject:
71 69 :param body:
72 70 :param recipients: list of int, str or User objects
73 71 :param type_: type of notification
74 72 """
75 73 from rhodecode.lib.celerylib import tasks, run_task
76 74
77 75 if not getattr(recipients, '__iter__', False):
78 76 raise Exception('recipients must be a list of iterable')
79 77
80 78 created_by_obj = self.__get_user(created_by)
81 79
82 80 recipients_objs = []
83 81 for u in recipients:
84 82 obj = self.__get_user(u)
85 83 if obj:
86 84 recipients_objs.append(obj)
87 85 recipients_objs = set(recipients_objs)
88 86
89 87 notif = Notification.create(created_by=created_by_obj, subject=subject,
90 88 body=body, recipients=recipients_objs,
91 89 type_=type_)
92 90
93
94 91 # send email with notification
95 92 for rec in recipients_objs:
96 93 email_subject = NotificationModel().make_description(notif, False)
97 94 type_ = EmailNotificationModel.TYPE_CHANGESET_COMMENT
98 95 email_body = body
99 96 email_body_html = EmailNotificationModel()\
100 97 .get_email_tmpl(type_, **{'subject':subject,
101 98 'body':h.rst(body)})
102 99 run_task(tasks.send_email, rec.email, email_subject, email_body,
103 100 email_body_html)
104 101
105 102 return notif
106 103
107 104 def delete(self, user, notification):
108 105 # we don't want to remove actual notification just the assignment
109 106 try:
110 107 notification = self.__get_notification(notification)
111 108 user = self.__get_user(user)
112 109 if notification and user:
113 110 obj = UserNotification.query()\
114 111 .filter(UserNotification.user == user)\
115 112 .filter(UserNotification.notification
116 113 == notification)\
117 114 .one()
118 115 self.sa.delete(obj)
119 116 return True
120 117 except Exception:
121 118 log.error(traceback.format_exc())
122 119 raise
123 120
124 121 def get_for_user(self, user):
125 122 user = self.__get_user(user)
126 123 return user.notifications
127 124
128 125 def get_unread_cnt_for_user(self, user):
129 126 user = self.__get_user(user)
130 127 return UserNotification.query()\
131 128 .filter(UserNotification.read == False)\
132 129 .filter(UserNotification.user == user).count()
133 130
134 131 def get_unread_for_user(self, user):
135 132 user = self.__get_user(user)
136 133 return [x.notification for x in UserNotification.query()\
137 134 .filter(UserNotification.read == False)\
138 135 .filter(UserNotification.user == user).all()]
139 136
140 137 def get_user_notification(self, user, notification):
141 138 user = self.__get_user(user)
142 139 notification = self.__get_notification(notification)
143 140
144 141 return UserNotification.query()\
145 142 .filter(UserNotification.notification == notification)\
146 143 .filter(UserNotification.user == user).scalar()
147 144
148 145 def make_description(self, notification, show_age=True):
149 146 """
150 147 Creates a human readable description based on properties
151 148 of notification object
152 149 """
153 150
154 151 _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
155 152 notification.TYPE_MESSAGE:_('sent message'),
156 153 notification.TYPE_MENTION:_('mentioned you')}
157 154 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
158 155
159 156 tmpl = "%(user)s %(action)s %(when)s"
160 157 if show_age:
161 158 when = h.age(notification.created_on)
162 159 else:
163 160 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
164 161 when = DTF(notification.created_on)
165 162 data = dict(user=notification.created_by_user.username,
166 163 action=_map[notification.type_],
167 164 when=when)
168 165 return tmpl % data
169 166
170 167
171 168 class EmailNotificationModel(BaseModel):
172 169
173 170 TYPE_CHANGESET_COMMENT = 'changeset_comment'
174 171 TYPE_PASSWORD_RESET = 'passoword_link'
175 172 TYPE_REGISTRATION = 'registration'
176 173 TYPE_DEFAULT = 'default'
177 174
178 175 def __init__(self):
179 self._template_root = config['pylons.paths']['templates'][0]
176 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
177 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
180 178
181 179 self.email_types = {
182 180 self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
183 181 self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
184 182 self.TYPE_REGISTRATION:'email_templates/registration.html',
185 183 self.TYPE_DEFAULT:'email_templates/default.html'
186 184 }
187 185
188 186 def get_email_tmpl(self, type_, **kwargs):
189 187 """
190 188 return generated template for email based on given type
191 189
192 190 :param type_:
193 191 """
192
194 193 base = self.email_types.get(type_, self.TYPE_DEFAULT)
195
196 lookup = config['pylons.app_globals'].mako_lookup
197 email_template = lookup.get_template(base)
194 email_template = self._tmpl_lookup.get_template(base)
198 195 # translator inject
199 196 _kwargs = {'_':_}
200 197 _kwargs.update(kwargs)
201 198 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
202 199 return email_template.render(**_kwargs)
203 200
204 201
@@ -1,93 +1,96 b''
1 1 """Pylons application test package
2 2
3 3 This package assumes the Pylons environment is already loaded, such as
4 4 when this script is imported from the `nosetests --with-pylons=test.ini`
5 5 command.
6 6
7 7 This module initializes the application via ``websetup`` (`paster
8 8 setup-app`) and provides the base testing objects.
9 9 """
10 10 import os
11 11 import time
12 12 import logging
13 13 from os.path import join as jn
14 14
15 15 from unittest import TestCase
16 from tempfile import _RandomNameSequence
16 17
17 18 from paste.deploy import loadapp
18 19 from paste.script.appinstall import SetupCommand
19 20 from pylons import config, url
20 21 from routes.util import URLGenerator
21 22 from webtest import TestApp
22 23
23 from rhodecode.model import meta
24 from rhodecode.model.meta import Session
24 25 from rhodecode.model.db import User
25 26
26 27 import pylons.test
27 28
28 29 os.environ['TZ'] = 'UTC'
29 30 time.tzset()
30 31
31 32 log = logging.getLogger(__name__)
32 33
33 34 __all__ = ['environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
34 35 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
35 36 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS' ]
36 37
37 38 # Invoke websetup with the current config file
38 39 # SetupCommand('setup-app').run([config_file])
39 40
40 41 ##RUNNING DESIRED TESTS
41 42 # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
42 43 # nosetests --pdb --pdb-failures
43 44 environ = {}
44 45
45 46 #SOME GLOBALS FOR TESTS
46 from tempfile import _RandomNameSequence
47
47 48 TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
48 49 TEST_USER_ADMIN_LOGIN = 'test_admin'
49 50 TEST_USER_ADMIN_PASS = 'test12'
50 51 HG_REPO = 'vcs_test_hg'
51 52 GIT_REPO = 'vcs_test_git'
52 53
53 54 NEW_HG_REPO = 'vcs_test_hg_new'
54 55 NEW_GIT_REPO = 'vcs_test_git_new'
55 56
56 57 HG_FORK = 'vcs_test_hg_fork'
57 58 GIT_FORK = 'vcs_test_git_fork'
58 59
59 60 class TestController(TestCase):
60 61
61 62 def __init__(self, *args, **kwargs):
62 63 wsgiapp = pylons.test.pylonsapp
63 64 config = wsgiapp.config
64 65
65 66 self.app = TestApp(wsgiapp)
66 67 url._push_object(URLGenerator(config['routes.map'], environ))
67 self.Session = meta.Session
68 self.Session = Session
68 69 self.index_location = config['app_conf']['index_dir']
69 70 TestCase.__init__(self, *args, **kwargs)
70 71
71 72 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
72 73 password=TEST_USER_ADMIN_PASS):
73 74 self._logged_username = username
74 75 response = self.app.post(url(controller='login', action='index'),
75 76 {'username':username,
76 77 'password':password})
77 78
78 79 if 'invalid user name' in response.body:
79 80 self.fail('could not login using %s %s' % (username, password))
80 81
81 82 self.assertEqual(response.status, '302 Found')
82 self.assertEqual(response.session['rhodecode_user'].get('username'),
83 username)
84 return response.follow()
83 ses = response.session['rhodecode_user']
84 self.assertEqual(ses.get('username'), username)
85 response = response.follow()
86 self.assertEqual(ses.get('is_authenticated'), True)
87
88 return response.session['rhodecode_user']
85 89
86 90 def _get_logged_user(self):
87 91 return User.get_by_username(self._logged_username)
88 92
89
90 93 def checkSessionFlash(self, response, msg):
91 94 self.assertTrue('flash' in response.session)
92 95 self.assertTrue(msg in response.session['flash'][0][1])
93 96
@@ -1,119 +1,119 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import Notification, User, UserNotification
3 3
4 4 from rhodecode.model.user import UserModel
5 5 from rhodecode.model.notification import NotificationModel
6 6 from rhodecode.model.meta import Session
7 7
8 8 class TestNotificationsController(TestController):
9 9
10 10
11 11 def tearDown(self):
12 12 for n in Notification.query().all():
13 13 inst = Notification.get(n.notification_id)
14 14 Session().delete(inst)
15 15 Session().commit()
16 16
17 17 def test_index(self):
18 18 self.log_user()
19 19
20 20 u1 = UserModel().create_or_update(username='u1', password='qweqwe',
21 21 email='u1@rhodecode.org',
22 22 name='u1', lastname='u1').user_id
23 23
24 24 response = self.app.get(url('notifications'))
25 25 self.assertTrue('''<div class="table">No notifications here yet</div>'''
26 26 in response.body)
27 27
28 28 cur_user = self._get_logged_user()
29 29
30 30 NotificationModel().create(created_by=u1, subject=u'test_notification_1',
31 31 body=u'notification_1',
32 32 recipients=[cur_user])
33 33 Session().commit()
34 34 response = self.app.get(url('notifications'))
35 35 self.assertTrue(u'test_notification_1' in response.body)
36 36
37 37 # def test_index_as_xml(self):
38 38 # response = self.app.get(url('formatted_notifications', format='xml'))
39 39 #
40 40 # def test_create(self):
41 41 # response = self.app.post(url('notifications'))
42 42 #
43 43 # def test_new(self):
44 44 # response = self.app.get(url('new_notification'))
45 45 #
46 46 # def test_new_as_xml(self):
47 47 # response = self.app.get(url('formatted_new_notification', format='xml'))
48 48 #
49 49 # def test_update(self):
50 50 # response = self.app.put(url('notification', notification_id=1))
51 51 #
52 52 # def test_update_browser_fakeout(self):
53 53 # response = self.app.post(url('notification', notification_id=1), params=dict(_method='put'))
54 54
55 55 def test_delete(self):
56 56 self.log_user()
57 57 cur_user = self._get_logged_user()
58 58
59 59 u1 = UserModel().create_or_update(username='u1', password='qweqwe',
60 60 email='u1@rhodecode.org',
61 61 name='u1', lastname='u1')
62 62 u2 = UserModel().create_or_update(username='u2', password='qweqwe',
63 63 email='u2@rhodecode.org',
64 64 name='u2', lastname='u2')
65 65
66 66 # make notifications
67 67 notification = NotificationModel().create(created_by=cur_user,
68 68 subject=u'test',
69 69 body=u'hi there',
70 70 recipients=[cur_user, u1, u2])
71 71 Session().commit()
72 72 u1 = User.get(u1.user_id)
73 73 u2 = User.get(u2.user_id)
74 74
75 75 # check DB
76 76 get_notif = lambda un:[x.notification for x in un]
77 77 self.assertEqual(get_notif(cur_user.notifications), [notification])
78 78 self.assertEqual(get_notif(u1.notifications), [notification])
79 79 self.assertEqual(get_notif(u2.notifications), [notification])
80 80 cur_usr_id = cur_user.user_id
81 81
82 82
83 83 response = self.app.delete(url('notification',
84 84 notification_id=
85 85 notification.notification_id))
86 86
87 87 cur_user = User.get(cur_usr_id)
88 88 self.assertEqual(cur_user.notifications, [])
89 89
90 90
91 91 # def test_delete_browser_fakeout(self):
92 92 # response = self.app.post(url('notification', notification_id=1), params=dict(_method='delete'))
93 93
94 94 def test_show(self):
95 95 self.log_user()
96 96 cur_user = self._get_logged_user()
97 97 u1 = UserModel().create_or_update(username='u1', password='qweqwe',
98 98 email='u1@rhodecode.org',
99 99 name='u1', lastname='u1')
100 100 u2 = UserModel().create_or_update(username='u2', password='qweqwe',
101 101 email='u2@rhodecode.org',
102 102 name='u2', lastname='u2')
103 103
104 104 notification = NotificationModel().create(created_by=cur_user,
105 subject='test',
105 subject=u'test',
106 106 body=u'hi there',
107 107 recipients=[cur_user, u1, u2])
108 108
109 109 response = self.app.get(url('notification',
110 110 notification_id=notification.notification_id))
111 111
112 112 # def test_show_as_xml(self):
113 113 # response = self.app.get(url('formatted_notification', notification_id=1, format='xml'))
114 114 #
115 115 # def test_edit(self):
116 116 # response = self.app.get(url('edit_notification', notification_id=1))
117 117 #
118 118 # def test_edit_as_xml(self):
119 119 # response = self.app.get(url('formatted_edit_notification', notification_id=1, format='xml'))
@@ -1,18 +1,18 b''
1 1 from rhodecode.tests import *
2 2
3 3 class TestBranchesController(TestController):
4 4
5 5 def test_index(self):
6 6 self.log_user()
7 response = self.app.get(url(controller='branches', action='index', repo_name=HG_REPO))
7 response = self.app.get(url(controller='branches',
8 action='index', repo_name=HG_REPO))
8 9
9 assert """<a href="/%s/changeset/27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</a>""" % HG_REPO in response.body, 'wrong info about default branch'
10 assert """<a href="/%s/changeset/97e8b885c04894463c51898e14387d80c30ed1ee">git</a>""" % HG_REPO in response.body, 'wrong info about default git'
11 assert """<a href="/%s/changeset/2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</a>""" % HG_REPO in response.body, 'wrong info about default web'
10 self.assertTrue("""<a href="/%s/changeset/27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</a>""" % HG_REPO in response.body)
11 self.assertTrue("""<a href="/%s/changeset/97e8b885c04894463c51898e14387d80c30ed1ee">git</a>""" % HG_REPO in response.body)
12 self.assertTrue("""<a href="/%s/changeset/2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</a>""" % HG_REPO in response.body)
12 13
13 14
14 15
15 16
16 17
17 18
18 # Test response...
@@ -1,143 +1,143 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import ChangesetComment, Notification, User, \
3 3 UserNotification
4 4
5 5 class TestChangeSetCommentrController(TestController):
6 6
7 7 def setUp(self):
8 8 for x in ChangesetComment.query().all():
9 9 self.Session().delete(x)
10 10 self.Session().commit()
11 11
12 12 for x in Notification.query().all():
13 13 self.Session().delete(x)
14 14 self.Session().commit()
15 15
16 16 def tearDown(self):
17 17 for x in ChangesetComment.query().all():
18 18 self.Session().delete(x)
19 19 self.Session().commit()
20 20
21 21 for x in Notification.query().all():
22 22 self.Session().delete(x)
23 23 self.Session().commit()
24 24
25 25 def test_create(self):
26 26 self.log_user()
27 27 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
28 28 text = u'CommentOnRevision'
29 29
30 30 params = {'text':text}
31 31 response = self.app.post(url(controller='changeset', action='comment',
32 32 repo_name=HG_REPO, revision=rev),
33 33 params=params)
34 34 # Test response...
35 35 self.assertEqual(response.status, '302 Found')
36 36 response.follow()
37 37
38 38 response = self.app.get(url(controller='changeset', action='index',
39 39 repo_name=HG_REPO, revision=rev))
40 40 # test DB
41 41 self.assertEqual(ChangesetComment.query().count(), 1)
42 42 self.assertTrue('''<div class="comments-number">%s '''
43 43 '''comment(s) (0 inline)</div>''' % 1 in response.body)
44 44
45 45
46 46 self.assertEqual(Notification.query().count(), 1)
47 47 notification = Notification.query().all()[0]
48 48
49 49 self.assertEqual(notification.type_, Notification.TYPE_CHANGESET_COMMENT)
50 50 self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
51 51 'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
52 52
53 53 def test_create_inline(self):
54 54 self.log_user()
55 55 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
56 56 text = u'CommentOnRevision'
57 57 f_path = 'vcs/web/simplevcs/views/repository.py'
58 58 line = 'n1'
59 59
60 60 params = {'text':text, 'f_path':f_path, 'line':line}
61 61 response = self.app.post(url(controller='changeset', action='comment',
62 62 repo_name=HG_REPO, revision=rev),
63 63 params=params)
64 64 # Test response...
65 65 self.assertEqual(response.status, '302 Found')
66 66 response.follow()
67 67
68 68 response = self.app.get(url(controller='changeset', action='index',
69 69 repo_name=HG_REPO, revision=rev))
70 70 #test DB
71 71 self.assertEqual(ChangesetComment.query().count(), 1)
72 72 self.assertTrue('''<div class="comments-number">0 comment(s)'''
73 73 ''' (%s inline)</div>''' % 1 in response.body)
74 74 self.assertTrue('''<div class="inline-comment-placeholder-line"'''
75 75 ''' line="n1" target_id="vcswebsimplevcsviews'''
76 76 '''repositorypy">''' in response.body)
77 77
78 78 self.assertEqual(Notification.query().count(), 1)
79 79 notification = Notification.query().all()[0]
80 80
81 81 self.assertEqual(notification.type_, Notification.TYPE_CHANGESET_COMMENT)
82 82 self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
83 83 'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
84 84
85 85 def test_create_with_mention(self):
86 86 self.log_user()
87 87
88 88 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
89 89 text = u'@test_regular check CommentOnRevision'
90 90
91 91 params = {'text':text}
92 92 response = self.app.post(url(controller='changeset', action='comment',
93 93 repo_name=HG_REPO, revision=rev),
94 94 params=params)
95 95 # Test response...
96 96 self.assertEqual(response.status, '302 Found')
97 97 response.follow()
98 98
99 99 response = self.app.get(url(controller='changeset', action='index',
100 100 repo_name=HG_REPO, revision=rev))
101 101 # test DB
102 102 self.assertEqual(ChangesetComment.query().count(), 1)
103 103 self.assertTrue('''<div class="comments-number">%s '''
104 104 '''comment(s) (0 inline)</div>''' % 1 in response.body)
105 105
106 106
107 107 self.assertEqual(Notification.query().count(), 2)
108 108 users = [x.user.username for x in UserNotification.query().all()]
109 109
110 110 # test_regular get's notification by @mention
111 self.assertEqual(users, [u'test_admin', u'test_regular'])
111 self.assertEqual(sorted(users), [u'test_admin', u'test_regular'])
112 112
113 113 def test_delete(self):
114 114 self.log_user()
115 115 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
116 116 text = u'CommentOnRevision'
117 117
118 118 params = {'text':text}
119 119 response = self.app.post(url(controller='changeset', action='comment',
120 120 repo_name=HG_REPO, revision=rev),
121 121 params=params)
122 122
123 123 comments = ChangesetComment.query().all()
124 124 self.assertEqual(len(comments), 1)
125 125 comment_id = comments[0].comment_id
126 126
127 127
128 128 self.app.delete(url(controller='changeset',
129 129 action='delete_comment',
130 130 repo_name=HG_REPO,
131 comment_id = comment_id))
131 comment_id=comment_id))
132 132
133 133 comments = ChangesetComment.query().all()
134 134 self.assertEqual(len(comments), 0)
135 135
136 136 response = self.app.get(url(controller='changeset', action='index',
137 137 repo_name=HG_REPO, revision=rev))
138 138 self.assertTrue('''<div class="comments-number">0 comment(s)'''
139 139 ''' (0 inline)</div>''' in response.body)
140 140
141 141
142 142
143 143
@@ -1,41 +1,83 b''
1 1 from rhodecode.tests import *
2 2
3 3 from rhodecode.model.db import Repository
4 4
5 5 class TestForksController(TestController):
6 6
7 7 def test_index(self):
8 8 self.log_user()
9 9 repo_name = HG_REPO
10 10 response = self.app.get(url(controller='forks', action='forks',
11 11 repo_name=repo_name))
12 12
13 13 self.assertTrue("""There are no forks yet""" in response.body)
14 14
15 15
16 16 def test_index_with_fork(self):
17 17 self.log_user()
18 18
19 19 # create a fork
20 20 fork_name = HG_FORK
21 21 description = 'fork of vcs test'
22 22 repo_name = HG_REPO
23 response = self.app.post(url(controller='settings',
23 org_repo = Repository.get_by_repo_name(repo_name)
24 response = self.app.post(url(controller='forks',
24 25 action='fork_create',
25 26 repo_name=repo_name),
26 {'fork_name':fork_name,
27 {'repo_name':fork_name,
28 'repo_group':'',
29 'fork_parent_id':org_repo.repo_id,
27 30 'repo_type':'hg',
28 31 'description':description,
29 32 'private':'False'})
30 33
31 34 response = self.app.get(url(controller='forks', action='forks',
32 35 repo_name=repo_name))
33 36
34 37
35 38 self.assertTrue("""<a href="/%s/summary">"""
36 39 """vcs_test_hg_fork</a>""" % fork_name
37 40 in response.body)
38 41
39 42 #remove this fork
40 43 response = self.app.delete(url('repo', repo_name=fork_name))
41 44
45
46
47
48 def test_z_fork_create(self):
49 self.log_user()
50 fork_name = HG_FORK
51 description = 'fork of vcs test'
52 repo_name = HG_REPO
53 org_repo = Repository.get_by_repo_name(repo_name)
54 response = self.app.post(url(controller='forks', action='fork_create',
55 repo_name=repo_name),
56 {'repo_name':fork_name,
57 'repo_group':'',
58 'fork_parent_id':org_repo.repo_id,
59 'repo_type':'hg',
60 'description':description,
61 'private':'False'})
62
63 #test if we have a message that fork is ok
64 self.assertTrue('forked %s repository as %s' \
65 % (repo_name, fork_name) in response.session['flash'][0])
66
67 #test if the fork was created in the database
68 fork_repo = self.Session().query(Repository)\
69 .filter(Repository.repo_name == fork_name).one()
70
71 self.assertEqual(fork_repo.repo_name, fork_name)
72 self.assertEqual(fork_repo.fork.repo_name, repo_name)
73
74
75 #test if fork is visible in the list ?
76 response = response.follow()
77
78
79 #check if fork is marked as fork
80 response = self.app.get(url(controller='summary', action='index',
81 repo_name=fork_name))
82
83 self.assertTrue('Fork of %s' % repo_name in response.body)
@@ -1,49 +1,10 b''
1 1 from rhodecode.model.db import Repository
2 2 from rhodecode.tests import *
3 3
4 4 class TestSettingsController(TestController):
5 5
6 6 def test_index(self):
7 7 self.log_user()
8 8 response = self.app.get(url(controller='settings', action='index',
9 9 repo_name=HG_REPO))
10 10 # Test response...
11
12 def test_fork(self):
13 self.log_user()
14 response = self.app.get(url(controller='settings', action='fork',
15 repo_name=HG_REPO))
16
17
18 def test_fork_create(self):
19 self.log_user()
20 fork_name = HG_FORK
21 description = 'fork of vcs test'
22 repo_name = HG_REPO
23 response = self.app.post(url(controller='settings', action='fork_create',
24 repo_name=repo_name),
25 {'fork_name':fork_name,
26 'repo_type':'hg',
27 'description':description,
28 'private':'False'})
29
30 #test if we have a message that fork is ok
31 assert 'forked %s repository as %s' \
32 % (repo_name, fork_name) in response.session['flash'][0], 'No flash message about fork'
33
34 #test if the fork was created in the database
35 fork_repo = self.Session().query(Repository).filter(Repository.repo_name == fork_name).one()
36
37 assert fork_repo.repo_name == fork_name, 'wrong name of repo name in new db fork repo'
38 assert fork_repo.fork.repo_name == repo_name, 'wrong fork parrent'
39
40
41 #test if fork is visible in the list ?
42 response = response.follow()
43
44
45 #check if fork is marked as fork
46 response = self.app.get(url(controller='summary', action='index',
47 repo_name=fork_name))
48
49 assert 'Fork of %s' % repo_name in response.body, 'no message about that this repo is a fork'
General Comments 0
You need to be logged in to leave comments. Login now