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