##// END OF EJS Templates
notification model should only send html emails not generating multipart ones
marcink -
r3448:cf665eb8 beta
parent child Browse files
Show More
@@ -1,391 +1,391 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.celerylib.tasks
3 rhodecode.lib.celerylib.tasks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode task modules, containing all task that suppose to be run
6 RhodeCode task modules, containing all task that suppose to be run
7 by celery daemon
7 by celery daemon
8
8
9 :created_on: Oct 6, 2010
9 :created_on: Oct 6, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 from celery.decorators import task
26 from celery.decorators import task
27
27
28 import os
28 import os
29 import traceback
29 import traceback
30 import logging
30 import logging
31 from os.path import join as jn
31 from os.path import join as jn
32
32
33 from time import mktime
33 from time import mktime
34 from operator import itemgetter
34 from operator import itemgetter
35 from string import lower
35 from string import lower
36
36
37 from pylons import config, url
37 from pylons import config, url
38 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39
39
40 from rhodecode.lib.vcs import get_backend
40 from rhodecode.lib.vcs import get_backend
41
41
42 from rhodecode import CELERY_ON, CELERY_EAGER
42 from rhodecode import CELERY_ON, CELERY_EAGER
43 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.lib.utils2 import safe_str
44 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
44 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
45 str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
45 str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
46 from rhodecode.lib.helpers import person
46 from rhodecode.lib.helpers import person
47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
48 from rhodecode.lib.utils import add_cache, action_logger
48 from rhodecode.lib.utils import add_cache, action_logger
49 from rhodecode.lib.compat import json, OrderedDict
49 from rhodecode.lib.compat import json, OrderedDict
50 from rhodecode.lib.hooks import log_create_repository
50 from rhodecode.lib.hooks import log_create_repository
51
51
52 from rhodecode.model.db import Statistics, Repository, User
52 from rhodecode.model.db import Statistics, Repository, User
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54
54
55
55
56 add_cache(config)
56 add_cache(config)
57
57
58 __all__ = ['whoosh_index', 'get_commits_stats',
58 __all__ = ['whoosh_index', 'get_commits_stats',
59 'reset_user_password', 'send_email']
59 'reset_user_password', 'send_email']
60
60
61
61
62 def get_logger(cls):
62 def get_logger(cls):
63 if CELERY_ON:
63 if CELERY_ON:
64 try:
64 try:
65 log = cls.get_logger()
65 log = cls.get_logger()
66 except:
66 except:
67 log = logging.getLogger(__name__)
67 log = logging.getLogger(__name__)
68 else:
68 else:
69 log = logging.getLogger(__name__)
69 log = logging.getLogger(__name__)
70
70
71 return log
71 return log
72
72
73
73
74 @task(ignore_result=True)
74 @task(ignore_result=True)
75 @locked_task
75 @locked_task
76 @dbsession
76 @dbsession
77 def whoosh_index(repo_location, full_index):
77 def whoosh_index(repo_location, full_index):
78 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
78 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
79 log = get_logger(whoosh_index)
79 log = get_logger(whoosh_index)
80 DBS = get_session()
80 DBS = get_session()
81
81
82 index_location = config['index_dir']
82 index_location = config['index_dir']
83 WhooshIndexingDaemon(index_location=index_location,
83 WhooshIndexingDaemon(index_location=index_location,
84 repo_location=repo_location, sa=DBS)\
84 repo_location=repo_location, sa=DBS)\
85 .run(full_index=full_index)
85 .run(full_index=full_index)
86
86
87
87
88 @task(ignore_result=True)
88 @task(ignore_result=True)
89 @dbsession
89 @dbsession
90 def get_commits_stats(repo_name, ts_min_y, ts_max_y, recurse_limit=100):
90 def get_commits_stats(repo_name, ts_min_y, ts_max_y, recurse_limit=100):
91 log = get_logger(get_commits_stats)
91 log = get_logger(get_commits_stats)
92 DBS = get_session()
92 DBS = get_session()
93 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
93 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
94 ts_max_y)
94 ts_max_y)
95 lockkey_path = config['app_conf']['cache_dir']
95 lockkey_path = config['app_conf']['cache_dir']
96
96
97 log.info('running task with lockkey %s' % lockkey)
97 log.info('running task with lockkey %s' % lockkey)
98
98
99 try:
99 try:
100 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
100 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
101
101
102 # for js data compatibility cleans the key for person from '
102 # for js data compatibility cleans the key for person from '
103 akc = lambda k: person(k).replace('"', "")
103 akc = lambda k: person(k).replace('"', "")
104
104
105 co_day_auth_aggr = {}
105 co_day_auth_aggr = {}
106 commits_by_day_aggregate = {}
106 commits_by_day_aggregate = {}
107 repo = Repository.get_by_repo_name(repo_name)
107 repo = Repository.get_by_repo_name(repo_name)
108 if repo is None:
108 if repo is None:
109 return True
109 return True
110
110
111 repo = repo.scm_instance
111 repo = repo.scm_instance
112 repo_size = repo.count()
112 repo_size = repo.count()
113 # return if repo have no revisions
113 # return if repo have no revisions
114 if repo_size < 1:
114 if repo_size < 1:
115 lock.release()
115 lock.release()
116 return True
116 return True
117
117
118 skip_date_limit = True
118 skip_date_limit = True
119 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
119 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
120 last_rev = None
120 last_rev = None
121 last_cs = None
121 last_cs = None
122 timegetter = itemgetter('time')
122 timegetter = itemgetter('time')
123
123
124 dbrepo = DBS.query(Repository)\
124 dbrepo = DBS.query(Repository)\
125 .filter(Repository.repo_name == repo_name).scalar()
125 .filter(Repository.repo_name == repo_name).scalar()
126 cur_stats = DBS.query(Statistics)\
126 cur_stats = DBS.query(Statistics)\
127 .filter(Statistics.repository == dbrepo).scalar()
127 .filter(Statistics.repository == dbrepo).scalar()
128
128
129 if cur_stats is not None:
129 if cur_stats is not None:
130 last_rev = cur_stats.stat_on_revision
130 last_rev = cur_stats.stat_on_revision
131
131
132 if last_rev == repo.get_changeset().revision and repo_size > 1:
132 if last_rev == repo.get_changeset().revision and repo_size > 1:
133 # pass silently without any work if we're not on first revision or
133 # pass silently without any work if we're not on first revision or
134 # current state of parsing revision(from db marker) is the
134 # current state of parsing revision(from db marker) is the
135 # last revision
135 # last revision
136 lock.release()
136 lock.release()
137 return True
137 return True
138
138
139 if cur_stats:
139 if cur_stats:
140 commits_by_day_aggregate = OrderedDict(json.loads(
140 commits_by_day_aggregate = OrderedDict(json.loads(
141 cur_stats.commit_activity_combined))
141 cur_stats.commit_activity_combined))
142 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
142 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
143
143
144 log.debug('starting parsing %s' % parse_limit)
144 log.debug('starting parsing %s' % parse_limit)
145 lmktime = mktime
145 lmktime = mktime
146
146
147 last_rev = last_rev + 1 if last_rev >= 0 else 0
147 last_rev = last_rev + 1 if last_rev >= 0 else 0
148 log.debug('Getting revisions from %s to %s' % (
148 log.debug('Getting revisions from %s to %s' % (
149 last_rev, last_rev + parse_limit)
149 last_rev, last_rev + parse_limit)
150 )
150 )
151 for cs in repo[last_rev:last_rev + parse_limit]:
151 for cs in repo[last_rev:last_rev + parse_limit]:
152 log.debug('parsing %s' % cs)
152 log.debug('parsing %s' % cs)
153 last_cs = cs # remember last parsed changeset
153 last_cs = cs # remember last parsed changeset
154 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
154 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
155 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
155 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
156
156
157 if akc(cs.author) in co_day_auth_aggr:
157 if akc(cs.author) in co_day_auth_aggr:
158 try:
158 try:
159 l = [timegetter(x) for x in
159 l = [timegetter(x) for x in
160 co_day_auth_aggr[akc(cs.author)]['data']]
160 co_day_auth_aggr[akc(cs.author)]['data']]
161 time_pos = l.index(k)
161 time_pos = l.index(k)
162 except ValueError:
162 except ValueError:
163 time_pos = False
163 time_pos = False
164
164
165 if time_pos >= 0 and time_pos is not False:
165 if time_pos >= 0 and time_pos is not False:
166
166
167 datadict = \
167 datadict = \
168 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
168 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
169
169
170 datadict["commits"] += 1
170 datadict["commits"] += 1
171 datadict["added"] += len(cs.added)
171 datadict["added"] += len(cs.added)
172 datadict["changed"] += len(cs.changed)
172 datadict["changed"] += len(cs.changed)
173 datadict["removed"] += len(cs.removed)
173 datadict["removed"] += len(cs.removed)
174
174
175 else:
175 else:
176 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
176 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
177
177
178 datadict = {"time": k,
178 datadict = {"time": k,
179 "commits": 1,
179 "commits": 1,
180 "added": len(cs.added),
180 "added": len(cs.added),
181 "changed": len(cs.changed),
181 "changed": len(cs.changed),
182 "removed": len(cs.removed),
182 "removed": len(cs.removed),
183 }
183 }
184 co_day_auth_aggr[akc(cs.author)]['data']\
184 co_day_auth_aggr[akc(cs.author)]['data']\
185 .append(datadict)
185 .append(datadict)
186
186
187 else:
187 else:
188 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
188 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
189 co_day_auth_aggr[akc(cs.author)] = {
189 co_day_auth_aggr[akc(cs.author)] = {
190 "label": akc(cs.author),
190 "label": akc(cs.author),
191 "data": [{"time":k,
191 "data": [{"time":k,
192 "commits":1,
192 "commits":1,
193 "added":len(cs.added),
193 "added":len(cs.added),
194 "changed":len(cs.changed),
194 "changed":len(cs.changed),
195 "removed":len(cs.removed),
195 "removed":len(cs.removed),
196 }],
196 }],
197 "schema": ["commits"],
197 "schema": ["commits"],
198 }
198 }
199
199
200 #gather all data by day
200 #gather all data by day
201 if k in commits_by_day_aggregate:
201 if k in commits_by_day_aggregate:
202 commits_by_day_aggregate[k] += 1
202 commits_by_day_aggregate[k] += 1
203 else:
203 else:
204 commits_by_day_aggregate[k] = 1
204 commits_by_day_aggregate[k] = 1
205
205
206 overview_data = sorted(commits_by_day_aggregate.items(),
206 overview_data = sorted(commits_by_day_aggregate.items(),
207 key=itemgetter(0))
207 key=itemgetter(0))
208
208
209 if not co_day_auth_aggr:
209 if not co_day_auth_aggr:
210 co_day_auth_aggr[akc(repo.contact)] = {
210 co_day_auth_aggr[akc(repo.contact)] = {
211 "label": akc(repo.contact),
211 "label": akc(repo.contact),
212 "data": [0, 1],
212 "data": [0, 1],
213 "schema": ["commits"],
213 "schema": ["commits"],
214 }
214 }
215
215
216 stats = cur_stats if cur_stats else Statistics()
216 stats = cur_stats if cur_stats else Statistics()
217 stats.commit_activity = json.dumps(co_day_auth_aggr)
217 stats.commit_activity = json.dumps(co_day_auth_aggr)
218 stats.commit_activity_combined = json.dumps(overview_data)
218 stats.commit_activity_combined = json.dumps(overview_data)
219
219
220 log.debug('last revison %s' % last_rev)
220 log.debug('last revison %s' % last_rev)
221 leftovers = len(repo.revisions[last_rev:])
221 leftovers = len(repo.revisions[last_rev:])
222 log.debug('revisions to parse %s' % leftovers)
222 log.debug('revisions to parse %s' % leftovers)
223
223
224 if last_rev == 0 or leftovers < parse_limit:
224 if last_rev == 0 or leftovers < parse_limit:
225 log.debug('getting code trending stats')
225 log.debug('getting code trending stats')
226 stats.languages = json.dumps(__get_codes_stats(repo_name))
226 stats.languages = json.dumps(__get_codes_stats(repo_name))
227
227
228 try:
228 try:
229 stats.repository = dbrepo
229 stats.repository = dbrepo
230 stats.stat_on_revision = last_cs.revision if last_cs else 0
230 stats.stat_on_revision = last_cs.revision if last_cs else 0
231 DBS.add(stats)
231 DBS.add(stats)
232 DBS.commit()
232 DBS.commit()
233 except:
233 except:
234 log.error(traceback.format_exc())
234 log.error(traceback.format_exc())
235 DBS.rollback()
235 DBS.rollback()
236 lock.release()
236 lock.release()
237 return False
237 return False
238
238
239 # final release
239 # final release
240 lock.release()
240 lock.release()
241
241
242 # execute another task if celery is enabled
242 # execute another task if celery is enabled
243 if len(repo.revisions) > 1 and CELERY_ON and recurse_limit > 0:
243 if len(repo.revisions) > 1 and CELERY_ON and recurse_limit > 0:
244 recurse_limit -= 1
244 recurse_limit -= 1
245 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y,
245 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y,
246 recurse_limit)
246 recurse_limit)
247 if recurse_limit <= 0:
247 if recurse_limit <= 0:
248 log.debug('Breaking recursive mode due to reach of recurse limit')
248 log.debug('Breaking recursive mode due to reach of recurse limit')
249 return True
249 return True
250 except LockHeld:
250 except LockHeld:
251 log.info('LockHeld')
251 log.info('LockHeld')
252 return 'Task with key %s already running' % lockkey
252 return 'Task with key %s already running' % lockkey
253
253
254
254
255 @task(ignore_result=True)
255 @task(ignore_result=True)
256 @dbsession
256 @dbsession
257 def send_email(recipients, subject, body, html_body=''):
257 def send_email(recipients, subject, body='', html_body=''):
258 """
258 """
259 Sends an email with defined parameters from the .ini files.
259 Sends an email with defined parameters from the .ini files.
260
260
261 :param recipients: list of recipients, it this is empty the defined email
261 :param recipients: list of recipients, it this is empty the defined email
262 address from field 'email_to' is used instead
262 address from field 'email_to' is used instead
263 :param subject: subject of the mail
263 :param subject: subject of the mail
264 :param body: body of the mail
264 :param body: body of the mail
265 :param html_body: html version of body
265 :param html_body: html version of body
266 """
266 """
267 log = get_logger(send_email)
267 log = get_logger(send_email)
268 DBS = get_session()
268 DBS = get_session()
269
269
270 email_config = config
270 email_config = config
271 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
271 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
272 if not recipients:
272 if not recipients:
273 # if recipients are not defined we send to email_config + all admins
273 # if recipients are not defined we send to email_config + all admins
274 admins = [u.email for u in User.query()
274 admins = [u.email for u in User.query()
275 .filter(User.admin == True).all()]
275 .filter(User.admin == True).all()]
276 recipients = [email_config.get('email_to')] + admins
276 recipients = [email_config.get('email_to')] + admins
277
277
278 mail_from = email_config.get('app_email_from', 'RhodeCode')
278 mail_from = email_config.get('app_email_from', 'RhodeCode')
279 user = email_config.get('smtp_username')
279 user = email_config.get('smtp_username')
280 passwd = email_config.get('smtp_password')
280 passwd = email_config.get('smtp_password')
281 mail_server = email_config.get('smtp_server')
281 mail_server = email_config.get('smtp_server')
282 mail_port = email_config.get('smtp_port')
282 mail_port = email_config.get('smtp_port')
283 tls = str2bool(email_config.get('smtp_use_tls'))
283 tls = str2bool(email_config.get('smtp_use_tls'))
284 ssl = str2bool(email_config.get('smtp_use_ssl'))
284 ssl = str2bool(email_config.get('smtp_use_ssl'))
285 debug = str2bool(config.get('debug'))
285 debug = str2bool(email_config.get('debug'))
286 smtp_auth = email_config.get('smtp_auth')
286 smtp_auth = email_config.get('smtp_auth')
287
287
288 if not mail_server:
288 if not mail_server:
289 log.error("SMTP mail server not configured - cannot send mail")
289 log.error("SMTP mail server not configured - cannot send mail")
290 return False
290 return False
291
291
292 try:
292 try:
293 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
293 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
294 mail_port, ssl, tls, debug=debug)
294 mail_port, ssl, tls, debug=debug)
295 m.send(recipients, subject, body, html_body)
295 m.send(recipients, subject, body, html_body)
296 except:
296 except:
297 log.error('Mail sending failed')
297 log.error('Mail sending failed')
298 log.error(traceback.format_exc())
298 log.error(traceback.format_exc())
299 return False
299 return False
300 return True
300 return True
301
301
302
302
303 @task(ignore_result=True)
303 @task(ignore_result=True)
304 @dbsession
304 @dbsession
305 def create_repo_fork(form_data, cur_user):
305 def create_repo_fork(form_data, cur_user):
306 """
306 """
307 Creates a fork of repository using interval VCS methods
307 Creates a fork of repository using interval VCS methods
308
308
309 :param form_data:
309 :param form_data:
310 :param cur_user:
310 :param cur_user:
311 """
311 """
312 from rhodecode.model.repo import RepoModel
312 from rhodecode.model.repo import RepoModel
313 from rhodecode.model.user import UserModel
313 from rhodecode.model.user import UserModel
314
314
315 log = get_logger(create_repo_fork)
315 log = get_logger(create_repo_fork)
316 DBS = get_session()
316 DBS = get_session()
317
317
318 base_path = Repository.base_path()
318 base_path = Repository.base_path()
319 cur_user = UserModel(DBS)._get_user(cur_user)
319 cur_user = UserModel(DBS)._get_user(cur_user)
320
320
321 fork_name = form_data['repo_name_full']
321 fork_name = form_data['repo_name_full']
322 repo_type = form_data['repo_type']
322 repo_type = form_data['repo_type']
323 description = form_data['description']
323 description = form_data['description']
324 owner = cur_user
324 owner = cur_user
325 private = form_data['private']
325 private = form_data['private']
326 clone_uri = form_data.get('clone_uri')
326 clone_uri = form_data.get('clone_uri')
327 repos_group = form_data['repo_group']
327 repos_group = form_data['repo_group']
328 landing_rev = form_data['landing_rev']
328 landing_rev = form_data['landing_rev']
329 copy_fork_permissions = form_data.get('copy_permissions')
329 copy_fork_permissions = form_data.get('copy_permissions')
330 fork_of = RepoModel(DBS)._get_repo(form_data.get('fork_parent_id'))
330 fork_of = RepoModel(DBS)._get_repo(form_data.get('fork_parent_id'))
331
331
332 fork_repo = RepoModel(DBS).create_repo(
332 fork_repo = RepoModel(DBS).create_repo(
333 fork_name, repo_type, description, owner, private, clone_uri,
333 fork_name, repo_type, description, owner, private, clone_uri,
334 repos_group, landing_rev, just_db=True, fork_of=fork_of,
334 repos_group, landing_rev, just_db=True, fork_of=fork_of,
335 copy_fork_permissions=copy_fork_permissions
335 copy_fork_permissions=copy_fork_permissions
336 )
336 )
337
337
338 update_after_clone = form_data['update_after_clone']
338 update_after_clone = form_data['update_after_clone']
339
339
340 source_repo_path = os.path.join(base_path, fork_of.repo_name)
340 source_repo_path = os.path.join(base_path, fork_of.repo_name)
341 destination_fork_path = os.path.join(base_path, fork_name)
341 destination_fork_path = os.path.join(base_path, fork_name)
342
342
343 log.info('creating fork of %s as %s', source_repo_path,
343 log.info('creating fork of %s as %s', source_repo_path,
344 destination_fork_path)
344 destination_fork_path)
345 backend = get_backend(repo_type)
345 backend = get_backend(repo_type)
346
346
347 if repo_type == 'git':
347 if repo_type == 'git':
348 r = backend(safe_str(destination_fork_path), create=True,
348 r = backend(safe_str(destination_fork_path), create=True,
349 src_url=safe_str(source_repo_path),
349 src_url=safe_str(source_repo_path),
350 update_after_clone=update_after_clone,
350 update_after_clone=update_after_clone,
351 bare=True)
351 bare=True)
352 # add rhodecode hook into this repo
352 # add rhodecode hook into this repo
353 ScmModel().install_git_hook(repo=r)
353 ScmModel().install_git_hook(repo=r)
354 elif repo_type == 'hg':
354 elif repo_type == 'hg':
355 r = backend(safe_str(destination_fork_path), create=True,
355 r = backend(safe_str(destination_fork_path), create=True,
356 src_url=safe_str(source_repo_path),
356 src_url=safe_str(source_repo_path),
357 update_after_clone=update_after_clone)
357 update_after_clone=update_after_clone)
358 else:
358 else:
359 raise Exception('Unknown backend type %s' % repo_type)
359 raise Exception('Unknown backend type %s' % repo_type)
360
360
361 log_create_repository(fork_repo.get_dict(), created_by=cur_user.username)
361 log_create_repository(fork_repo.get_dict(), created_by=cur_user.username)
362
362
363 action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
363 action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
364 fork_of.repo_name, '', DBS)
364 fork_of.repo_name, '', DBS)
365
365
366 action_logger(cur_user, 'user_created_fork:%s' % fork_name,
366 action_logger(cur_user, 'user_created_fork:%s' % fork_name,
367 fork_name, '', DBS)
367 fork_name, '', DBS)
368 # finally commit at latest possible stage
368 # finally commit at latest possible stage
369 DBS.commit()
369 DBS.commit()
370 fork_repo.update_changeset_cache()
370 fork_repo.update_changeset_cache()
371
371
372
372
373 def __get_codes_stats(repo_name):
373 def __get_codes_stats(repo_name):
374 from rhodecode.config.conf import LANGUAGES_EXTENSIONS_MAP
374 from rhodecode.config.conf import LANGUAGES_EXTENSIONS_MAP
375 repo = Repository.get_by_repo_name(repo_name).scm_instance
375 repo = Repository.get_by_repo_name(repo_name).scm_instance
376
376
377 tip = repo.get_changeset()
377 tip = repo.get_changeset()
378 code_stats = {}
378 code_stats = {}
379
379
380 def aggregate(cs):
380 def aggregate(cs):
381 for f in cs[2]:
381 for f in cs[2]:
382 ext = lower(f.extension)
382 ext = lower(f.extension)
383 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
383 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
384 if ext in code_stats:
384 if ext in code_stats:
385 code_stats[ext] += 1
385 code_stats[ext] += 1
386 else:
386 else:
387 code_stats[ext] = 1
387 code_stats[ext] = 1
388
388
389 map(aggregate, tip.walk('/'))
389 map(aggregate, tip.walk('/'))
390
390
391 return code_stats or {}
391 return code_stats or {}
@@ -1,282 +1,283 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.notification
3 rhodecode.model.notification
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Model for notifications
6 Model for notifications
7
7
8
8
9 :created_on: Nov 20, 2011
9 :created_on: Nov 20, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from pylons import tmpl_context as c
31 from pylons import tmpl_context as c
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 import rhodecode
34 import rhodecode
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.model import BaseModel
36 from rhodecode.model import BaseModel
37 from rhodecode.model.db import Notification, User, UserNotification
37 from rhodecode.model.db import Notification, User, UserNotification
38 from rhodecode.model.meta import Session
38 from rhodecode.model.meta import Session
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class NotificationModel(BaseModel):
43 class NotificationModel(BaseModel):
44
44
45 cls = Notification
45 cls = Notification
46
46
47 def __get_notification(self, notification):
47 def __get_notification(self, notification):
48 if isinstance(notification, Notification):
48 if isinstance(notification, Notification):
49 return notification
49 return notification
50 elif isinstance(notification, (int, long)):
50 elif isinstance(notification, (int, long)):
51 return Notification.get(notification)
51 return Notification.get(notification)
52 else:
52 else:
53 if notification:
53 if notification:
54 raise Exception('notification must be int, long or Instance'
54 raise Exception('notification must be int, long or Instance'
55 ' of Notification got %s' % type(notification))
55 ' of Notification got %s' % type(notification))
56
56
57 def create(self, created_by, subject, body, recipients=None,
57 def create(self, created_by, subject, body, recipients=None,
58 type_=Notification.TYPE_MESSAGE, with_email=True,
58 type_=Notification.TYPE_MESSAGE, with_email=True,
59 email_kwargs={}, email_subject=None):
59 email_kwargs={}, email_subject=None):
60 """
60 """
61
61
62 Creates notification of given type
62 Creates notification of given type
63
63
64 :param created_by: int, str or User instance. User who created this
64 :param created_by: int, str or User instance. User who created this
65 notification
65 notification
66 :param subject:
66 :param subject:
67 :param body:
67 :param body:
68 :param recipients: list of int, str or User objects, when None
68 :param recipients: list of int, str or User objects, when None
69 is given send to all admins
69 is given send to all admins
70 :param type_: type of notification
70 :param type_: type of notification
71 :param with_email: send email with this notification
71 :param with_email: send email with this notification
72 :param email_kwargs: additional dict to pass as args to email template
72 :param email_kwargs: additional dict to pass as args to email template
73 :param email_subject: use given subject as email subject
73 :param email_subject: use given subject as email subject
74 """
74 """
75 from rhodecode.lib.celerylib import tasks, run_task
75 from rhodecode.lib.celerylib import tasks, run_task
76
76
77 if recipients and not getattr(recipients, '__iter__', False):
77 if recipients and not getattr(recipients, '__iter__', False):
78 raise Exception('recipients must be a list or iterable')
78 raise Exception('recipients must be a list or iterable')
79
79
80 created_by_obj = self._get_user(created_by)
80 created_by_obj = self._get_user(created_by)
81
81
82 if recipients:
82 if recipients:
83 recipients_objs = []
83 recipients_objs = []
84 for u in recipients:
84 for u in recipients:
85 obj = self._get_user(u)
85 obj = self._get_user(u)
86 if obj:
86 if obj:
87 recipients_objs.append(obj)
87 recipients_objs.append(obj)
88 recipients_objs = set(recipients_objs)
88 recipients_objs = set(recipients_objs)
89 log.debug('sending notifications %s to %s' % (
89 log.debug('sending notifications %s to %s' % (
90 type_, recipients_objs)
90 type_, recipients_objs)
91 )
91 )
92 else:
92 else:
93 # empty recipients means to all admins
93 # empty recipients means to all admins
94 recipients_objs = User.query().filter(User.admin == True).all()
94 recipients_objs = User.query().filter(User.admin == True).all()
95 log.debug('sending notifications %s to admins: %s' % (
95 log.debug('sending notifications %s to admins: %s' % (
96 type_, recipients_objs)
96 type_, recipients_objs)
97 )
97 )
98 notif = Notification.create(
98 notif = Notification.create(
99 created_by=created_by_obj, subject=subject,
99 created_by=created_by_obj, subject=subject,
100 body=body, recipients=recipients_objs, type_=type_
100 body=body, recipients=recipients_objs, type_=type_
101 )
101 )
102
102
103 if with_email is False:
103 if with_email is False:
104 return notif
104 return notif
105
105
106 #don't send email to person who created this comment
106 #don't send email to person who created this comment
107 rec_objs = set(recipients_objs).difference(set([created_by_obj]))
107 rec_objs = set(recipients_objs).difference(set([created_by_obj]))
108
108
109 # send email with notification to all other participants
109 # send email with notification to all other participants
110 for rec in rec_objs:
110 for rec in rec_objs:
111 if not email_subject:
111 if not email_subject:
112 email_subject = NotificationModel().make_description(notif, show_age=False)
112 email_subject = NotificationModel()\
113 .make_description(notif, show_age=False)
113 type_ = type_
114 type_ = type_
114 email_body = body
115 email_body = None # we set body to none, we just send HTML emails
115 ## this is passed into template
116 ## this is passed into template
116 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
117 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
117 kwargs.update(email_kwargs)
118 kwargs.update(email_kwargs)
118 email_body_html = EmailNotificationModel()\
119 email_body_html = EmailNotificationModel()\
119 .get_email_tmpl(type_, **kwargs)
120 .get_email_tmpl(type_, **kwargs)
120
121
121 run_task(tasks.send_email, rec.email, email_subject, email_body,
122 run_task(tasks.send_email, rec.email, email_subject, email_body,
122 email_body_html)
123 email_body_html)
123
124
124 return notif
125 return notif
125
126
126 def delete(self, user, notification):
127 def delete(self, user, notification):
127 # we don't want to remove actual notification just the assignment
128 # we don't want to remove actual notification just the assignment
128 try:
129 try:
129 notification = self.__get_notification(notification)
130 notification = self.__get_notification(notification)
130 user = self._get_user(user)
131 user = self._get_user(user)
131 if notification and user:
132 if notification and user:
132 obj = UserNotification.query()\
133 obj = UserNotification.query()\
133 .filter(UserNotification.user == user)\
134 .filter(UserNotification.user == user)\
134 .filter(UserNotification.notification
135 .filter(UserNotification.notification
135 == notification)\
136 == notification)\
136 .one()
137 .one()
137 Session().delete(obj)
138 Session().delete(obj)
138 return True
139 return True
139 except Exception:
140 except Exception:
140 log.error(traceback.format_exc())
141 log.error(traceback.format_exc())
141 raise
142 raise
142
143
143 def get_for_user(self, user, filter_=None):
144 def get_for_user(self, user, filter_=None):
144 """
145 """
145 Get mentions for given user, filter them if filter dict is given
146 Get mentions for given user, filter them if filter dict is given
146
147
147 :param user:
148 :param user:
148 :param filter:
149 :param filter:
149 """
150 """
150 user = self._get_user(user)
151 user = self._get_user(user)
151
152
152 q = UserNotification.query()\
153 q = UserNotification.query()\
153 .filter(UserNotification.user == user)\
154 .filter(UserNotification.user == user)\
154 .join((Notification, UserNotification.notification_id ==
155 .join((Notification, UserNotification.notification_id ==
155 Notification.notification_id))
156 Notification.notification_id))
156
157
157 if filter_:
158 if filter_:
158 q = q.filter(Notification.type_.in_(filter_))
159 q = q.filter(Notification.type_.in_(filter_))
159
160
160 return q.all()
161 return q.all()
161
162
162 def mark_read(self, user, notification):
163 def mark_read(self, user, notification):
163 try:
164 try:
164 notification = self.__get_notification(notification)
165 notification = self.__get_notification(notification)
165 user = self._get_user(user)
166 user = self._get_user(user)
166 if notification and user:
167 if notification and user:
167 obj = UserNotification.query()\
168 obj = UserNotification.query()\
168 .filter(UserNotification.user == user)\
169 .filter(UserNotification.user == user)\
169 .filter(UserNotification.notification
170 .filter(UserNotification.notification
170 == notification)\
171 == notification)\
171 .one()
172 .one()
172 obj.read = True
173 obj.read = True
173 Session().add(obj)
174 Session().add(obj)
174 return True
175 return True
175 except Exception:
176 except Exception:
176 log.error(traceback.format_exc())
177 log.error(traceback.format_exc())
177 raise
178 raise
178
179
179 def mark_all_read_for_user(self, user, filter_=None):
180 def mark_all_read_for_user(self, user, filter_=None):
180 user = self._get_user(user)
181 user = self._get_user(user)
181 q = UserNotification.query()\
182 q = UserNotification.query()\
182 .filter(UserNotification.user == user)\
183 .filter(UserNotification.user == user)\
183 .filter(UserNotification.read == False)\
184 .filter(UserNotification.read == False)\
184 .join((Notification, UserNotification.notification_id ==
185 .join((Notification, UserNotification.notification_id ==
185 Notification.notification_id))
186 Notification.notification_id))
186 if filter_:
187 if filter_:
187 q = q.filter(Notification.type_.in_(filter_))
188 q = q.filter(Notification.type_.in_(filter_))
188
189
189 # this is a little inefficient but sqlalchemy doesn't support
190 # this is a little inefficient but sqlalchemy doesn't support
190 # update on joined tables :(
191 # update on joined tables :(
191 for obj in q.all():
192 for obj in q.all():
192 obj.read = True
193 obj.read = True
193 Session().add(obj)
194 Session().add(obj)
194
195
195 def get_unread_cnt_for_user(self, user):
196 def get_unread_cnt_for_user(self, user):
196 user = self._get_user(user)
197 user = self._get_user(user)
197 return UserNotification.query()\
198 return UserNotification.query()\
198 .filter(UserNotification.read == False)\
199 .filter(UserNotification.read == False)\
199 .filter(UserNotification.user == user).count()
200 .filter(UserNotification.user == user).count()
200
201
201 def get_unread_for_user(self, user):
202 def get_unread_for_user(self, user):
202 user = self._get_user(user)
203 user = self._get_user(user)
203 return [x.notification for x in UserNotification.query()\
204 return [x.notification for x in UserNotification.query()\
204 .filter(UserNotification.read == False)\
205 .filter(UserNotification.read == False)\
205 .filter(UserNotification.user == user).all()]
206 .filter(UserNotification.user == user).all()]
206
207
207 def get_user_notification(self, user, notification):
208 def get_user_notification(self, user, notification):
208 user = self._get_user(user)
209 user = self._get_user(user)
209 notification = self.__get_notification(notification)
210 notification = self.__get_notification(notification)
210
211
211 return UserNotification.query()\
212 return UserNotification.query()\
212 .filter(UserNotification.notification == notification)\
213 .filter(UserNotification.notification == notification)\
213 .filter(UserNotification.user == user).scalar()
214 .filter(UserNotification.user == user).scalar()
214
215
215 def make_description(self, notification, show_age=True):
216 def make_description(self, notification, show_age=True):
216 """
217 """
217 Creates a human readable description based on properties
218 Creates a human readable description based on properties
218 of notification object
219 of notification object
219 """
220 """
220 #alias
221 #alias
221 _n = notification
222 _n = notification
222 _map = {
223 _map = {
223 _n.TYPE_CHANGESET_COMMENT: _('commented on changeset at %(when)s'),
224 _n.TYPE_CHANGESET_COMMENT: _('commented on changeset at %(when)s'),
224 _n.TYPE_MESSAGE: _('sent message at %(when)s'),
225 _n.TYPE_MESSAGE: _('sent message at %(when)s'),
225 _n.TYPE_MENTION: _('mentioned you at %(when)s'),
226 _n.TYPE_MENTION: _('mentioned you at %(when)s'),
226 _n.TYPE_REGISTRATION: _('registered in RhodeCode at %(when)s'),
227 _n.TYPE_REGISTRATION: _('registered in RhodeCode at %(when)s'),
227 _n.TYPE_PULL_REQUEST: _('opened new pull request at %(when)s'),
228 _n.TYPE_PULL_REQUEST: _('opened new pull request at %(when)s'),
228 _n.TYPE_PULL_REQUEST_COMMENT: _('commented on pull request at %(when)s')
229 _n.TYPE_PULL_REQUEST_COMMENT: _('commented on pull request at %(when)s')
229 }
230 }
230
231
231 # action == _map string
232 # action == _map string
232 tmpl = "%(user)s %(action)s "
233 tmpl = "%(user)s %(action)s "
233 if show_age:
234 if show_age:
234 when = h.age(notification.created_on)
235 when = h.age(notification.created_on)
235 else:
236 else:
236 when = h.fmt_date(notification.created_on)
237 when = h.fmt_date(notification.created_on)
237
238
238 data = dict(
239 data = dict(
239 user=notification.created_by_user.username,
240 user=notification.created_by_user.username,
240 action=_map[notification.type_] % {'when': when},
241 action=_map[notification.type_] % {'when': when},
241 )
242 )
242 return tmpl % data
243 return tmpl % data
243
244
244
245
245 class EmailNotificationModel(BaseModel):
246 class EmailNotificationModel(BaseModel):
246
247
247 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
248 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
248 TYPE_PASSWORD_RESET = 'password_link'
249 TYPE_PASSWORD_RESET = 'password_link'
249 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
250 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
250 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
251 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
251 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
252 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
252 TYPE_DEFAULT = 'default'
253 TYPE_DEFAULT = 'default'
253
254
254 def __init__(self):
255 def __init__(self):
255 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
256 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
256 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
257 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
257
258
258 self.email_types = {
259 self.email_types = {
259 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
260 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
260 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
261 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
261 self.TYPE_REGISTRATION: 'email_templates/registration.html',
262 self.TYPE_REGISTRATION: 'email_templates/registration.html',
262 self.TYPE_DEFAULT: 'email_templates/default.html',
263 self.TYPE_DEFAULT: 'email_templates/default.html',
263 self.TYPE_PULL_REQUEST: 'email_templates/pull_request.html',
264 self.TYPE_PULL_REQUEST: 'email_templates/pull_request.html',
264 self.TYPE_PULL_REQUEST_COMMENT: 'email_templates/pull_request_comment.html',
265 self.TYPE_PULL_REQUEST_COMMENT: 'email_templates/pull_request_comment.html',
265 }
266 }
266
267
267 def get_email_tmpl(self, type_, **kwargs):
268 def get_email_tmpl(self, type_, **kwargs):
268 """
269 """
269 return generated template for email based on given type
270 return generated template for email based on given type
270
271
271 :param type_:
272 :param type_:
272 """
273 """
273
274
274 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
275 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
275 email_template = self._tmpl_lookup.get_template(base)
276 email_template = self._tmpl_lookup.get_template(base)
276 # translator and helpers inject
277 # translator and helpers inject
277 _kwargs = {'_': _,
278 _kwargs = {'_': _,
278 'h': h,
279 'h': h,
279 'c': c}
280 'c': c}
280 _kwargs.update(kwargs)
281 _kwargs.update(kwargs)
281 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
282 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
282 return email_template.render(**_kwargs)
283 return email_template.render(**_kwargs)
General Comments 0
You need to be logged in to leave comments. Login now