##// END OF EJS Templates
bugfix, when user had no repos he would see all repos in my account
marcink -
r765:d92fc9b5 beta
parent child Browse files
Show More
@@ -1,335 +1,336
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # settings controller for pylons
3 # settings controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on July 14, 2010
21 Created on July 14, 2010
22 settings controller for pylons
22 settings controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
27 config
27 config
28 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
31 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
32 HasPermissionAnyDecorator
32 HasPermissionAnyDecorator
33 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.base import BaseController, render
34 from rhodecode.lib.celerylib import tasks, run_task
34 from rhodecode.lib.celerylib import tasks, run_task
35 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
35 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
36 set_rhodecode_config
36 set_rhodecode_config
37 from rhodecode.model.db import RhodeCodeUi, Repository
37 from rhodecode.model.db import RhodeCodeUi, Repository
38 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
38 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
39 ApplicationUiSettingsForm
39 ApplicationUiSettingsForm
40 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.settings import SettingsModel
41 from rhodecode.model.settings import SettingsModel
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43 from sqlalchemy import func
43 from sqlalchemy import func
44 import formencode
44 import formencode
45 import logging
45 import logging
46 import traceback
46 import traceback
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class SettingsController(BaseController):
51 class SettingsController(BaseController):
52 """REST Controller styled on the Atom Publishing Protocol"""
52 """REST Controller styled on the Atom Publishing Protocol"""
53 # To properly map this controller, ensure your config/routing.py
53 # To properly map this controller, ensure your config/routing.py
54 # file has a resource setup:
54 # file has a resource setup:
55 # map.resource('setting', 'settings', controller='admin/settings',
55 # map.resource('setting', 'settings', controller='admin/settings',
56 # path_prefix='/admin', name_prefix='admin_')
56 # path_prefix='/admin', name_prefix='admin_')
57
57
58
58
59 @LoginRequired()
59 @LoginRequired()
60 def __before__(self):
60 def __before__(self):
61 c.admin_user = session.get('admin_user')
61 c.admin_user = session.get('admin_user')
62 c.admin_username = session.get('admin_username')
62 c.admin_username = session.get('admin_username')
63 super(SettingsController, self).__before__()
63 super(SettingsController, self).__before__()
64
64
65
65
66 @HasPermissionAllDecorator('hg.admin')
66 @HasPermissionAllDecorator('hg.admin')
67 def index(self, format='html'):
67 def index(self, format='html'):
68 """GET /admin/settings: All items in the collection"""
68 """GET /admin/settings: All items in the collection"""
69 # url('admin_settings')
69 # url('admin_settings')
70
70
71 defaults = SettingsModel().get_app_settings()
71 defaults = SettingsModel().get_app_settings()
72 defaults.update(self.get_hg_ui_settings())
72 defaults.update(self.get_hg_ui_settings())
73 return htmlfill.render(
73 return htmlfill.render(
74 render('admin/settings/settings.html'),
74 render('admin/settings/settings.html'),
75 defaults=defaults,
75 defaults=defaults,
76 encoding="UTF-8",
76 encoding="UTF-8",
77 force_defaults=False
77 force_defaults=False
78 )
78 )
79
79
80 @HasPermissionAllDecorator('hg.admin')
80 @HasPermissionAllDecorator('hg.admin')
81 def create(self):
81 def create(self):
82 """POST /admin/settings: Create a new item"""
82 """POST /admin/settings: Create a new item"""
83 # url('admin_settings')
83 # url('admin_settings')
84
84
85 @HasPermissionAllDecorator('hg.admin')
85 @HasPermissionAllDecorator('hg.admin')
86 def new(self, format='html'):
86 def new(self, format='html'):
87 """GET /admin/settings/new: Form to create a new item"""
87 """GET /admin/settings/new: Form to create a new item"""
88 # url('admin_new_setting')
88 # url('admin_new_setting')
89
89
90 @HasPermissionAllDecorator('hg.admin')
90 @HasPermissionAllDecorator('hg.admin')
91 def update(self, setting_id):
91 def update(self, setting_id):
92 """PUT /admin/settings/setting_id: Update an existing item"""
92 """PUT /admin/settings/setting_id: Update an existing item"""
93 # Forms posted to this method should contain a hidden field:
93 # Forms posted to this method should contain a hidden field:
94 # <input type="hidden" name="_method" value="PUT" />
94 # <input type="hidden" name="_method" value="PUT" />
95 # Or using helpers:
95 # Or using helpers:
96 # h.form(url('admin_setting', setting_id=ID),
96 # h.form(url('admin_setting', setting_id=ID),
97 # method='put')
97 # method='put')
98 # url('admin_setting', setting_id=ID)
98 # url('admin_setting', setting_id=ID)
99 if setting_id == 'mapping':
99 if setting_id == 'mapping':
100 rm_obsolete = request.POST.get('destroy', False)
100 rm_obsolete = request.POST.get('destroy', False)
101 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
101 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
102
102
103 initial = ScmModel().repo_scan(g.paths[0][1], g.baseui)
103 initial = ScmModel().repo_scan(g.paths[0][1], g.baseui)
104 for repo_name in initial.keys():
104 for repo_name in initial.keys():
105 invalidate_cache('get_repo_cached_%s' % repo_name)
105 invalidate_cache('get_repo_cached_%s' % repo_name)
106
106
107 repo2db_mapper(initial, rm_obsolete)
107 repo2db_mapper(initial, rm_obsolete)
108
108
109 h.flash(_('Repositories successfully rescanned'), category='success')
109 h.flash(_('Repositories successfully rescanned'), category='success')
110
110
111 if setting_id == 'whoosh':
111 if setting_id == 'whoosh':
112 repo_location = self.get_hg_ui_settings()['paths_root_path']
112 repo_location = self.get_hg_ui_settings()['paths_root_path']
113 full_index = request.POST.get('full_index', False)
113 full_index = request.POST.get('full_index', False)
114 task = run_task(tasks.whoosh_index, repo_location, full_index)
114 task = run_task(tasks.whoosh_index, repo_location, full_index)
115
115
116 h.flash(_('Whoosh reindex task scheduled'), category='success')
116 h.flash(_('Whoosh reindex task scheduled'), category='success')
117 if setting_id == 'global':
117 if setting_id == 'global':
118
118
119 application_form = ApplicationSettingsForm()()
119 application_form = ApplicationSettingsForm()()
120 try:
120 try:
121 form_result = application_form.to_python(dict(request.POST))
121 form_result = application_form.to_python(dict(request.POST))
122 settings_model = SettingsModel()
122 settings_model = SettingsModel()
123 try:
123 try:
124 hgsettings1 = settings_model.get('title')
124 hgsettings1 = settings_model.get('title')
125 hgsettings1.app_settings_value = form_result['rhodecode_title']
125 hgsettings1.app_settings_value = form_result['rhodecode_title']
126
126
127 hgsettings2 = settings_model.get('realm')
127 hgsettings2 = settings_model.get('realm')
128 hgsettings2.app_settings_value = form_result['rhodecode_realm']
128 hgsettings2.app_settings_value = form_result['rhodecode_realm']
129
129
130
130
131 self.sa.add(hgsettings1)
131 self.sa.add(hgsettings1)
132 self.sa.add(hgsettings2)
132 self.sa.add(hgsettings2)
133 self.sa.commit()
133 self.sa.commit()
134 set_rhodecode_config(config)
134 set_rhodecode_config(config)
135 h.flash(_('Updated application settings'),
135 h.flash(_('Updated application settings'),
136 category='success')
136 category='success')
137
137
138 except:
138 except:
139 log.error(traceback.format_exc())
139 log.error(traceback.format_exc())
140 h.flash(_('error occurred during updating application settings'),
140 h.flash(_('error occurred during updating application settings'),
141 category='error')
141 category='error')
142
142
143 self.sa.rollback()
143 self.sa.rollback()
144
144
145
145
146 except formencode.Invalid, errors:
146 except formencode.Invalid, errors:
147 return htmlfill.render(
147 return htmlfill.render(
148 render('admin/settings/settings.html'),
148 render('admin/settings/settings.html'),
149 defaults=errors.value,
149 defaults=errors.value,
150 errors=errors.error_dict or {},
150 errors=errors.error_dict or {},
151 prefix_error=False,
151 prefix_error=False,
152 encoding="UTF-8")
152 encoding="UTF-8")
153
153
154 if setting_id == 'mercurial':
154 if setting_id == 'mercurial':
155 application_form = ApplicationUiSettingsForm()()
155 application_form = ApplicationUiSettingsForm()()
156 try:
156 try:
157 form_result = application_form.to_python(dict(request.POST))
157 form_result = application_form.to_python(dict(request.POST))
158
158
159 try:
159 try:
160
160
161 hgsettings1 = self.sa.query(RhodeCodeUi)\
161 hgsettings1 = self.sa.query(RhodeCodeUi)\
162 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
162 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
163 hgsettings1.ui_value = form_result['web_push_ssl']
163 hgsettings1.ui_value = form_result['web_push_ssl']
164
164
165 hgsettings2 = self.sa.query(RhodeCodeUi)\
165 hgsettings2 = self.sa.query(RhodeCodeUi)\
166 .filter(RhodeCodeUi.ui_key == '/').one()
166 .filter(RhodeCodeUi.ui_key == '/').one()
167 hgsettings2.ui_value = form_result['paths_root_path']
167 hgsettings2.ui_value = form_result['paths_root_path']
168
168
169
169
170 #HOOKS
170 #HOOKS
171 hgsettings3 = self.sa.query(RhodeCodeUi)\
171 hgsettings3 = self.sa.query(RhodeCodeUi)\
172 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
172 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
173 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
173 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
174
174
175 hgsettings4 = self.sa.query(RhodeCodeUi)\
175 hgsettings4 = self.sa.query(RhodeCodeUi)\
176 .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one()
176 .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one()
177 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
177 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
178
178
179 hgsettings5 = self.sa.query(RhodeCodeUi)\
179 hgsettings5 = self.sa.query(RhodeCodeUi)\
180 .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one()
180 .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one()
181 hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger'])
181 hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger'])
182
182
183 hgsettings6 = self.sa.query(RhodeCodeUi)\
183 hgsettings6 = self.sa.query(RhodeCodeUi)\
184 .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one()
184 .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one()
185 hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger'])
185 hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger'])
186
186
187
187
188 self.sa.add(hgsettings1)
188 self.sa.add(hgsettings1)
189 self.sa.add(hgsettings2)
189 self.sa.add(hgsettings2)
190 self.sa.add(hgsettings3)
190 self.sa.add(hgsettings3)
191 self.sa.add(hgsettings4)
191 self.sa.add(hgsettings4)
192 self.sa.add(hgsettings5)
192 self.sa.add(hgsettings5)
193 self.sa.add(hgsettings6)
193 self.sa.add(hgsettings6)
194 self.sa.commit()
194 self.sa.commit()
195
195
196 h.flash(_('Updated mercurial settings'),
196 h.flash(_('Updated mercurial settings'),
197 category='success')
197 category='success')
198
198
199 except:
199 except:
200 log.error(traceback.format_exc())
200 log.error(traceback.format_exc())
201 h.flash(_('error occurred during updating application settings'),
201 h.flash(_('error occurred during updating application settings'),
202 category='error')
202 category='error')
203
203
204 self.sa.rollback()
204 self.sa.rollback()
205
205
206
206
207 except formencode.Invalid, errors:
207 except formencode.Invalid, errors:
208 return htmlfill.render(
208 return htmlfill.render(
209 render('admin/settings/settings.html'),
209 render('admin/settings/settings.html'),
210 defaults=errors.value,
210 defaults=errors.value,
211 errors=errors.error_dict or {},
211 errors=errors.error_dict or {},
212 prefix_error=False,
212 prefix_error=False,
213 encoding="UTF-8")
213 encoding="UTF-8")
214
214
215
215
216
216
217 return redirect(url('admin_settings'))
217 return redirect(url('admin_settings'))
218
218
219 @HasPermissionAllDecorator('hg.admin')
219 @HasPermissionAllDecorator('hg.admin')
220 def delete(self, setting_id):
220 def delete(self, setting_id):
221 """DELETE /admin/settings/setting_id: Delete an existing item"""
221 """DELETE /admin/settings/setting_id: Delete an existing item"""
222 # Forms posted to this method should contain a hidden field:
222 # Forms posted to this method should contain a hidden field:
223 # <input type="hidden" name="_method" value="DELETE" />
223 # <input type="hidden" name="_method" value="DELETE" />
224 # Or using helpers:
224 # Or using helpers:
225 # h.form(url('admin_setting', setting_id=ID),
225 # h.form(url('admin_setting', setting_id=ID),
226 # method='delete')
226 # method='delete')
227 # url('admin_setting', setting_id=ID)
227 # url('admin_setting', setting_id=ID)
228
228
229 @HasPermissionAllDecorator('hg.admin')
229 @HasPermissionAllDecorator('hg.admin')
230 def show(self, setting_id, format='html'):
230 def show(self, setting_id, format='html'):
231 """GET /admin/settings/setting_id: Show a specific item"""
231 """GET /admin/settings/setting_id: Show a specific item"""
232 # url('admin_setting', setting_id=ID)
232 # url('admin_setting', setting_id=ID)
233
233
234 @HasPermissionAllDecorator('hg.admin')
234 @HasPermissionAllDecorator('hg.admin')
235 def edit(self, setting_id, format='html'):
235 def edit(self, setting_id, format='html'):
236 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
236 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
237 # url('admin_edit_setting', setting_id=ID)
237 # url('admin_edit_setting', setting_id=ID)
238
238
239
239
240 def my_account(self):
240 def my_account(self):
241 """
241 """
242 GET /_admin/my_account Displays info about my account
242 GET /_admin/my_account Displays info about my account
243 """
243 """
244
244
245 # url('admin_settings_my_account')
245 # url('admin_settings_my_account')
246 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
246 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
247 all_repos = self.sa.query(Repository)\
247 all_repos = self.sa.query(Repository)\
248 .filter(Repository.user_id == c.user.user_id)\
248 .filter(Repository.user_id == c.user.user_id)\
249 .order_by(func.lower(Repository.repo_name))\
249 .order_by(func.lower(Repository.repo_name))\
250 .all()
250 .all()
251
251 c.user_repos = ScmModel().get_repos(all_repos)
252 c.user_repos = ScmModel().get_repos(all_repos)
252
253
253 if c.user.username == 'default':
254 if c.user.username == 'default':
254 h.flash(_("You can't edit this user since it's"
255 h.flash(_("You can't edit this user since it's"
255 " crucial for entire application"), category='warning')
256 " crucial for entire application"), category='warning')
256 return redirect(url('users'))
257 return redirect(url('users'))
257
258
258 defaults = c.user.__dict__
259 defaults = c.user.__dict__
259 return htmlfill.render(
260 return htmlfill.render(
260 render('admin/users/user_edit_my_account.html'),
261 render('admin/users/user_edit_my_account.html'),
261 defaults=defaults,
262 defaults=defaults,
262 encoding="UTF-8",
263 encoding="UTF-8",
263 force_defaults=False
264 force_defaults=False
264 )
265 )
265
266
266 def my_account_update(self):
267 def my_account_update(self):
267 """PUT /_admin/my_account_update: Update an existing item"""
268 """PUT /_admin/my_account_update: Update an existing item"""
268 # Forms posted to this method should contain a hidden field:
269 # Forms posted to this method should contain a hidden field:
269 # <input type="hidden" name="_method" value="PUT" />
270 # <input type="hidden" name="_method" value="PUT" />
270 # Or using helpers:
271 # Or using helpers:
271 # h.form(url('admin_settings_my_account_update'),
272 # h.form(url('admin_settings_my_account_update'),
272 # method='put')
273 # method='put')
273 # url('admin_settings_my_account_update', id=ID)
274 # url('admin_settings_my_account_update', id=ID)
274 user_model = UserModel()
275 user_model = UserModel()
275 uid = c.rhodecode_user.user_id
276 uid = c.rhodecode_user.user_id
276 _form = UserForm(edit=True, old_data={'user_id':uid,
277 _form = UserForm(edit=True, old_data={'user_id':uid,
277 'email':c.rhodecode_user.email})()
278 'email':c.rhodecode_user.email})()
278 form_result = {}
279 form_result = {}
279 try:
280 try:
280 form_result = _form.to_python(dict(request.POST))
281 form_result = _form.to_python(dict(request.POST))
281 user_model.update_my_account(uid, form_result)
282 user_model.update_my_account(uid, form_result)
282 h.flash(_('Your account was updated succesfully'),
283 h.flash(_('Your account was updated succesfully'),
283 category='success')
284 category='success')
284
285
285 except formencode.Invalid, errors:
286 except formencode.Invalid, errors:
286 c.user = user_model.get(c.rhodecode_user.user_id, cache=False)
287 c.user = user_model.get(c.rhodecode_user.user_id, cache=False)
287 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
288 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
288 all_repos = self.sa.query(Repository)\
289 all_repos = self.sa.query(Repository)\
289 .filter(Repository.user_id == c.user.user_id)\
290 .filter(Repository.user_id == c.user.user_id)\
290 .order_by(func.lower(Repository.repo_name))\
291 .order_by(func.lower(Repository.repo_name))\
291 .all()
292 .all()
292 c.user_repos = ScmModel().get_repos(all_repos)
293 c.user_repos = ScmModel().get_repos(all_repos)
293
294
294 return htmlfill.render(
295 return htmlfill.render(
295 render('admin/users/user_edit_my_account.html'),
296 render('admin/users/user_edit_my_account.html'),
296 defaults=errors.value,
297 defaults=errors.value,
297 errors=errors.error_dict or {},
298 errors=errors.error_dict or {},
298 prefix_error=False,
299 prefix_error=False,
299 encoding="UTF-8")
300 encoding="UTF-8")
300 except Exception:
301 except Exception:
301 log.error(traceback.format_exc())
302 log.error(traceback.format_exc())
302 h.flash(_('error occured during update of user %s') \
303 h.flash(_('error occured during update of user %s') \
303 % form_result.get('username'), category='error')
304 % form_result.get('username'), category='error')
304
305
305 return redirect(url('my_account'))
306 return redirect(url('my_account'))
306
307
307 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
308 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
308 def create_repository(self):
309 def create_repository(self):
309 """GET /_admin/create_repository: Form to create a new item"""
310 """GET /_admin/create_repository: Form to create a new item"""
310 new_repo = request.GET.get('repo', '')
311 new_repo = request.GET.get('repo', '')
311 c.new_repo = h.repo_name_slug(new_repo)
312 c.new_repo = h.repo_name_slug(new_repo)
312
313
313 return render('admin/repos/repo_add_create_repository.html')
314 return render('admin/repos/repo_add_create_repository.html')
314
315
315 def get_hg_ui_settings(self):
316 def get_hg_ui_settings(self):
316 ret = self.sa.query(RhodeCodeUi).all()
317 ret = self.sa.query(RhodeCodeUi).all()
317
318
318 if not ret:
319 if not ret:
319 raise Exception('Could not get application ui settings !')
320 raise Exception('Could not get application ui settings !')
320 settings = {}
321 settings = {}
321 for each in ret:
322 for each in ret:
322 k = each.ui_key
323 k = each.ui_key
323 v = each.ui_value
324 v = each.ui_value
324 if k == '/':
325 if k == '/':
325 k = 'root_path'
326 k = 'root_path'
326
327
327 if k.find('.') != -1:
328 if k.find('.') != -1:
328 k = k.replace('.', '_')
329 k = k.replace('.', '_')
329
330
330 if each.ui_section == 'hooks':
331 if each.ui_section == 'hooks':
331 v = each.ui_active
332 v = each.ui_active
332
333
333 settings[each.ui_section + '_' + k] = v
334 settings[each.ui_section + '_' + k] = v
334
335
335 return settings
336 return settings
General Comments 0
You need to be logged in to leave comments. Login now