##// END OF EJS Templates
code: unified coverage notes to # pragma: no cover
marcink -
r3282:5c2818aa default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,106 +1,106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Single source for redirection links.
22 Single source for redirection links.
23
23
24 Goal of this module is to provide a single source of truth regarding external
24 Goal of this module is to provide a single source of truth regarding external
25 links. The data inside this module is used to configure the routing
25 links. The data inside this module is used to configure the routing
26 system of Enterprise and it is used also as a base to check if this data
26 system of Enterprise and it is used also as a base to check if this data
27 and our server configuration are in sync.
27 and our server configuration are in sync.
28
28
29 .. py:data:: link_config
29 .. py:data:: link_config
30
30
31 Contains the configuration for external links. Each item is supposed to be
31 Contains the configuration for external links. Each item is supposed to be
32 a `dict` like this example::
32 a `dict` like this example::
33
33
34 {"name": "url_name",
34 {"name": "url_name",
35 "target": "https://rhodecode.com/r1/enterprise/keyword/",
35 "target": "https://rhodecode.com/r1/enterprise/keyword/",
36 "external_target": "https://example.com/some-page.html",
36 "external_target": "https://example.com/some-page.html",
37 }
37 }
38
38
39 then you can retrieve the url by simply calling the URL function:
39 then you can retrieve the url by simply calling the URL function:
40
40
41 `h.route_path('url_name')`
41 `h.route_path('url_name')`
42
42
43 The redirection must be first implemented in our servers before
43 The redirection must be first implemented in our servers before
44 you can see it working.
44 you can see it working.
45 """
45 """
46 # flake8: noqa
46 # pragma: no cover
47 from __future__ import unicode_literals
47 from __future__ import unicode_literals
48
48
49 link_config = [
49 link_config = [
50 {
50 {
51 "name": "enterprise_docs",
51 "name": "enterprise_docs",
52 "target": "https://rhodecode.com/r1/enterprise/docs/",
52 "target": "https://rhodecode.com/r1/enterprise/docs/",
53 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
53 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
54 },
54 },
55 {
55 {
56 "name": "enterprise_log_file_locations",
56 "name": "enterprise_log_file_locations",
57 "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
57 "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
58 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
58 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
59 },
59 },
60 {
60 {
61 "name": "enterprise_issue_tracker_settings",
61 "name": "enterprise_issue_tracker_settings",
62 "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
62 "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
63 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
63 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
64 },
64 },
65 {
65 {
66 "name": "enterprise_svn_setup",
66 "name": "enterprise_svn_setup",
67 "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
67 "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
68 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
68 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
69 },
69 },
70 {
70 {
71 "name": "enterprise_license_convert_from_old",
71 "name": "enterprise_license_convert_from_old",
72 "target": "https://rhodecode.com/r1/enterprise/convert-license/",
72 "target": "https://rhodecode.com/r1/enterprise/convert-license/",
73 "external_target": "https://rhodecode.com/u/license-upgrade",
73 "external_target": "https://rhodecode.com/u/license-upgrade",
74 },
74 },
75 {
75 {
76 "name": "rst_help",
76 "name": "rst_help",
77 "target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
77 "target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
78 "external_target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
78 "external_target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
79 },
79 },
80 {
80 {
81 "name": "markdown_help",
81 "name": "markdown_help",
82 "target": "https://daringfireball.net/projects/markdown/syntax",
82 "target": "https://daringfireball.net/projects/markdown/syntax",
83 "external_target": "https://daringfireball.net/projects/markdown/syntax",
83 "external_target": "https://daringfireball.net/projects/markdown/syntax",
84 },
84 },
85 {
85 {
86 "name": "rhodecode_official",
86 "name": "rhodecode_official",
87 "target": "https://rhodecode.com",
87 "target": "https://rhodecode.com",
88 "external_target": "https://rhodecode.com/",
88 "external_target": "https://rhodecode.com/",
89 },
89 },
90 {
90 {
91 "name": "rhodecode_support",
91 "name": "rhodecode_support",
92 "target": "https://rhodecode.com/help/",
92 "target": "https://rhodecode.com/help/",
93 "external_target": "https://rhodecode.com/support",
93 "external_target": "https://rhodecode.com/support",
94 },
94 },
95 {
95 {
96 "name": "rhodecode_translations",
96 "name": "rhodecode_translations",
97 "target": "https://rhodecode.com/translate/enterprise",
97 "target": "https://rhodecode.com/translate/enterprise",
98 "external_target": "https://www.transifex.com/rhodecode/RhodeCode/",
98 "external_target": "https://www.transifex.com/rhodecode/RhodeCode/",
99 },
99 },
100
100
101 ]
101 ]
102
102
103
103
104 def connect_redirection_links(config):
104 def connect_redirection_links(config):
105 for link in link_config:
105 for link in link_config:
106 config.add_route(link['name'], link['target'], static=True)
106 config.add_route(link['name'], link['target'], static=True)
@@ -1,78 +1,78 b''
1 # Copyright (C) 2016-2018 RhodeCode GmbH
1 # Copyright (C) 2016-2018 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
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 Affero General Public License
12 # You should have received a copy of the GNU Affero 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 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import logging
19 import logging
20 from pyramid.threadlocal import get_current_registry
20 from pyramid.threadlocal import get_current_registry
21 from rhodecode.events.base import RhodeCodeIntegrationEvent
21 from rhodecode.events.base import RhodeCodeIntegrationEvent
22
22
23
23
24 log = logging.getLogger(__name__)
24 log = logging.getLogger(__name__)
25
25
26
26
27 def trigger(event, registry=None):
27 def trigger(event, registry=None):
28 """
28 """
29 Helper method to send an event. This wraps the pyramid logic to send an
29 Helper method to send an event. This wraps the pyramid logic to send an
30 event.
30 event.
31 """
31 """
32 # For the first step we are using pyramids thread locals here. If the
32 # For the first step we are using pyramids thread locals here. If the
33 # event mechanism works out as a good solution we should think about
33 # event mechanism works out as a good solution we should think about
34 # passing the registry as an argument to get rid of it.
34 # passing the registry as an argument to get rid of it.
35 event_name = event.__class__
35 event_name = event.__class__
36 log.debug('event %s sent for execution', event_name)
36 log.debug('event %s sent for execution', event_name)
37 registry = registry or get_current_registry()
37 registry = registry or get_current_registry()
38 registry.notify(event)
38 registry.notify(event)
39 log.debug('event %s triggered using registry %s', event_name, registry)
39 log.debug('event %s triggered using registry %s', event_name, registry)
40
40
41 # Send the events to integrations directly
41 # Send the events to integrations directly
42 from rhodecode.integrations import integrations_event_handler
42 from rhodecode.integrations import integrations_event_handler
43 if isinstance(event, RhodeCodeIntegrationEvent):
43 if isinstance(event, RhodeCodeIntegrationEvent):
44 integrations_event_handler(event)
44 integrations_event_handler(event)
45
45
46
46
47 from rhodecode.events.user import ( # noqa
47 from rhodecode.events.user import ( # pragma: no cover
48 UserPreCreate,
48 UserPreCreate,
49 UserPostCreate,
49 UserPostCreate,
50 UserPreUpdate,
50 UserPreUpdate,
51 UserRegistered,
51 UserRegistered,
52 UserPermissionsChange,
52 UserPermissionsChange,
53 )
53 )
54
54
55 from rhodecode.events.repo import ( # noqa
55 from rhodecode.events.repo import ( # pragma: no cover
56 RepoEvent,
56 RepoEvent,
57 RepoPreCreateEvent, RepoCreateEvent,
57 RepoPreCreateEvent, RepoCreateEvent,
58 RepoPreDeleteEvent, RepoDeleteEvent,
58 RepoPreDeleteEvent, RepoDeleteEvent,
59 RepoPrePushEvent, RepoPushEvent,
59 RepoPrePushEvent, RepoPushEvent,
60 RepoPrePullEvent, RepoPullEvent,
60 RepoPrePullEvent, RepoPullEvent,
61 )
61 )
62
62
63 from rhodecode.events.repo_group import ( # noqa
63 from rhodecode.events.repo_group import ( # pragma: no cover
64 RepoGroupEvent,
64 RepoGroupEvent,
65 RepoGroupCreateEvent,
65 RepoGroupCreateEvent,
66 RepoGroupUpdateEvent,
66 RepoGroupUpdateEvent,
67 RepoGroupDeleteEvent,
67 RepoGroupDeleteEvent,
68 )
68 )
69
69
70 from rhodecode.events.pullrequest import ( # noqa
70 from rhodecode.events.pullrequest import ( # pragma: no cover
71 PullRequestEvent,
71 PullRequestEvent,
72 PullRequestCreateEvent,
72 PullRequestCreateEvent,
73 PullRequestUpdateEvent,
73 PullRequestUpdateEvent,
74 PullRequestCommentEvent,
74 PullRequestCommentEvent,
75 PullRequestReviewEvent,
75 PullRequestReviewEvent,
76 PullRequestMergeEvent,
76 PullRequestMergeEvent,
77 PullRequestCloseEvent,
77 PullRequestCloseEvent,
78 )
78 )
@@ -1,302 +1,302 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 """
20 """
21 Celery loader, run with::
21 Celery loader, run with::
22
22
23 celery worker \
23 celery worker \
24 --beat \
24 --beat \
25 --app rhodecode.lib.celerylib.loader \
25 --app rhodecode.lib.celerylib.loader \
26 --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
26 --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
27 --loglevel DEBUG --ini=._dev/dev.ini
27 --loglevel DEBUG --ini=._dev/dev.ini
28 """
28 """
29 import os
29 import os
30 import logging
30 import logging
31 import importlib
31 import importlib
32
32
33 from celery import Celery
33 from celery import Celery
34 from celery import signals
34 from celery import signals
35 from celery import Task
35 from celery import Task
36 from celery import exceptions # noqa
36 from celery import exceptions # pragma: no cover
37 from kombu.serialization import register
37 from kombu.serialization import register
38 from pyramid.threadlocal import get_current_request
38 from pyramid.threadlocal import get_current_request
39
39
40 import rhodecode
40 import rhodecode
41
41
42 from rhodecode.lib.auth import AuthUser
42 from rhodecode.lib.auth import AuthUser
43 from rhodecode.lib.celerylib.utils import get_ini_config, parse_ini_vars
43 from rhodecode.lib.celerylib.utils import get_ini_config, parse_ini_vars
44 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.ext_json import json
45 from rhodecode.lib.pyramid_utils import bootstrap, setup_logging, prepare_request
45 from rhodecode.lib.pyramid_utils import bootstrap, setup_logging, prepare_request
46 from rhodecode.lib.utils2 import str2bool
46 from rhodecode.lib.utils2 import str2bool
47 from rhodecode.model import meta
47 from rhodecode.model import meta
48
48
49
49
50 register('json_ext', json.dumps, json.loads,
50 register('json_ext', json.dumps, json.loads,
51 content_type='application/x-json-ext',
51 content_type='application/x-json-ext',
52 content_encoding='utf-8')
52 content_encoding='utf-8')
53
53
54 log = logging.getLogger('celery.rhodecode.loader')
54 log = logging.getLogger('celery.rhodecode.loader')
55
55
56
56
57 def add_preload_arguments(parser):
57 def add_preload_arguments(parser):
58 parser.add_argument(
58 parser.add_argument(
59 '--ini', default=None,
59 '--ini', default=None,
60 help='Path to ini configuration file.'
60 help='Path to ini configuration file.'
61 )
61 )
62 parser.add_argument(
62 parser.add_argument(
63 '--ini-var', default=None,
63 '--ini-var', default=None,
64 help='Comma separated list of key=value to pass to ini.'
64 help='Comma separated list of key=value to pass to ini.'
65 )
65 )
66
66
67
67
68 def get_logger(obj):
68 def get_logger(obj):
69 custom_log = logging.getLogger(
69 custom_log = logging.getLogger(
70 'rhodecode.task.{}'.format(obj.__class__.__name__))
70 'rhodecode.task.{}'.format(obj.__class__.__name__))
71
71
72 if rhodecode.CELERY_ENABLED:
72 if rhodecode.CELERY_ENABLED:
73 try:
73 try:
74 custom_log = obj.get_logger()
74 custom_log = obj.get_logger()
75 except Exception:
75 except Exception:
76 pass
76 pass
77
77
78 return custom_log
78 return custom_log
79
79
80
80
81 imports = ['rhodecode.lib.celerylib.tasks']
81 imports = ['rhodecode.lib.celerylib.tasks']
82
82
83 try:
83 try:
84 # try if we have EE tasks available
84 # try if we have EE tasks available
85 importlib.import_module('rc_ee')
85 importlib.import_module('rc_ee')
86 imports.append('rc_ee.lib.celerylib.tasks')
86 imports.append('rc_ee.lib.celerylib.tasks')
87 except ImportError:
87 except ImportError:
88 pass
88 pass
89
89
90
90
91 base_celery_config = {
91 base_celery_config = {
92 'result_backend': 'rpc://',
92 'result_backend': 'rpc://',
93 'result_expires': 60 * 60 * 24,
93 'result_expires': 60 * 60 * 24,
94 'result_persistent': True,
94 'result_persistent': True,
95 'imports': imports,
95 'imports': imports,
96 'worker_max_tasks_per_child': 100,
96 'worker_max_tasks_per_child': 100,
97 'accept_content': ['json_ext'],
97 'accept_content': ['json_ext'],
98 'task_serializer': 'json_ext',
98 'task_serializer': 'json_ext',
99 'result_serializer': 'json_ext',
99 'result_serializer': 'json_ext',
100 'worker_hijack_root_logger': False,
100 'worker_hijack_root_logger': False,
101 'database_table_names': {
101 'database_table_names': {
102 'task': 'beat_taskmeta',
102 'task': 'beat_taskmeta',
103 'group': 'beat_groupmeta',
103 'group': 'beat_groupmeta',
104 }
104 }
105 }
105 }
106 # init main celery app
106 # init main celery app
107 celery_app = Celery()
107 celery_app = Celery()
108 celery_app.user_options['preload'].add(add_preload_arguments)
108 celery_app.user_options['preload'].add(add_preload_arguments)
109 ini_file_glob = None
109 ini_file_glob = None
110
110
111
111
112 @signals.setup_logging.connect
112 @signals.setup_logging.connect
113 def setup_logging_callback(**kwargs):
113 def setup_logging_callback(**kwargs):
114 setup_logging(ini_file_glob)
114 setup_logging(ini_file_glob)
115
115
116
116
117 @signals.user_preload_options.connect
117 @signals.user_preload_options.connect
118 def on_preload_parsed(options, **kwargs):
118 def on_preload_parsed(options, **kwargs):
119 ini_location = options['ini']
119 ini_location = options['ini']
120 ini_vars = options['ini_var']
120 ini_vars = options['ini_var']
121 celery_app.conf['INI_PYRAMID'] = options['ini']
121 celery_app.conf['INI_PYRAMID'] = options['ini']
122
122
123 if ini_location is None:
123 if ini_location is None:
124 print('You must provide the paste --ini argument')
124 print('You must provide the paste --ini argument')
125 exit(-1)
125 exit(-1)
126
126
127 options = None
127 options = None
128 if ini_vars is not None:
128 if ini_vars is not None:
129 options = parse_ini_vars(ini_vars)
129 options = parse_ini_vars(ini_vars)
130
130
131 global ini_file_glob
131 global ini_file_glob
132 ini_file_glob = ini_location
132 ini_file_glob = ini_location
133
133
134 log.debug('Bootstrapping RhodeCode application...')
134 log.debug('Bootstrapping RhodeCode application...')
135 env = bootstrap(ini_location, options=options)
135 env = bootstrap(ini_location, options=options)
136
136
137 setup_celery_app(
137 setup_celery_app(
138 app=env['app'], root=env['root'], request=env['request'],
138 app=env['app'], root=env['root'], request=env['request'],
139 registry=env['registry'], closer=env['closer'],
139 registry=env['registry'], closer=env['closer'],
140 ini_location=ini_location)
140 ini_location=ini_location)
141
141
142 # fix the global flag even if it's disabled via .ini file because this
142 # fix the global flag even if it's disabled via .ini file because this
143 # is a worker code that doesn't need this to be disabled.
143 # is a worker code that doesn't need this to be disabled.
144 rhodecode.CELERY_ENABLED = True
144 rhodecode.CELERY_ENABLED = True
145
145
146
146
147 @signals.task_success.connect
147 @signals.task_success.connect
148 def task_success_signal(result, **kwargs):
148 def task_success_signal(result, **kwargs):
149 meta.Session.commit()
149 meta.Session.commit()
150 closer = celery_app.conf['PYRAMID_CLOSER']
150 closer = celery_app.conf['PYRAMID_CLOSER']
151 if closer:
151 if closer:
152 closer()
152 closer()
153
153
154
154
155 @signals.task_retry.connect
155 @signals.task_retry.connect
156 def task_retry_signal(
156 def task_retry_signal(
157 request, reason, einfo, **kwargs):
157 request, reason, einfo, **kwargs):
158 meta.Session.remove()
158 meta.Session.remove()
159 closer = celery_app.conf['PYRAMID_CLOSER']
159 closer = celery_app.conf['PYRAMID_CLOSER']
160 if closer:
160 if closer:
161 closer()
161 closer()
162
162
163
163
164 @signals.task_failure.connect
164 @signals.task_failure.connect
165 def task_failure_signal(
165 def task_failure_signal(
166 task_id, exception, args, kwargs, traceback, einfo, **kargs):
166 task_id, exception, args, kwargs, traceback, einfo, **kargs):
167 from rhodecode.lib.exc_tracking import store_exception
167 from rhodecode.lib.exc_tracking import store_exception
168
168
169 meta.Session.remove()
169 meta.Session.remove()
170
170
171 # simulate sys.exc_info()
171 # simulate sys.exc_info()
172 exc_info = (einfo.type, einfo.exception, einfo.tb)
172 exc_info = (einfo.type, einfo.exception, einfo.tb)
173 store_exception(id(exc_info), exc_info, prefix='celery_rhodecode')
173 store_exception(id(exc_info), exc_info, prefix='celery_rhodecode')
174
174
175 closer = celery_app.conf['PYRAMID_CLOSER']
175 closer = celery_app.conf['PYRAMID_CLOSER']
176 if closer:
176 if closer:
177 closer()
177 closer()
178
178
179
179
180 @signals.task_revoked.connect
180 @signals.task_revoked.connect
181 def task_revoked_signal(
181 def task_revoked_signal(
182 request, terminated, signum, expired, **kwargs):
182 request, terminated, signum, expired, **kwargs):
183 closer = celery_app.conf['PYRAMID_CLOSER']
183 closer = celery_app.conf['PYRAMID_CLOSER']
184 if closer:
184 if closer:
185 closer()
185 closer()
186
186
187
187
188 def setup_celery_app(app, root, request, registry, closer, ini_location):
188 def setup_celery_app(app, root, request, registry, closer, ini_location):
189 ini_dir = os.path.dirname(os.path.abspath(ini_location))
189 ini_dir = os.path.dirname(os.path.abspath(ini_location))
190 celery_config = base_celery_config
190 celery_config = base_celery_config
191 celery_config.update({
191 celery_config.update({
192 # store celerybeat scheduler db where the .ini file is
192 # store celerybeat scheduler db where the .ini file is
193 'beat_schedule_filename': os.path.join(ini_dir, 'celerybeat-schedule'),
193 'beat_schedule_filename': os.path.join(ini_dir, 'celerybeat-schedule'),
194 })
194 })
195 ini_settings = get_ini_config(ini_location)
195 ini_settings = get_ini_config(ini_location)
196 log.debug('Got custom celery conf: %s', ini_settings)
196 log.debug('Got custom celery conf: %s', ini_settings)
197
197
198 celery_config.update(ini_settings)
198 celery_config.update(ini_settings)
199 celery_app.config_from_object(celery_config)
199 celery_app.config_from_object(celery_config)
200
200
201 celery_app.conf.update({'PYRAMID_APP': app})
201 celery_app.conf.update({'PYRAMID_APP': app})
202 celery_app.conf.update({'PYRAMID_ROOT': root})
202 celery_app.conf.update({'PYRAMID_ROOT': root})
203 celery_app.conf.update({'PYRAMID_REQUEST': request})
203 celery_app.conf.update({'PYRAMID_REQUEST': request})
204 celery_app.conf.update({'PYRAMID_REGISTRY': registry})
204 celery_app.conf.update({'PYRAMID_REGISTRY': registry})
205 celery_app.conf.update({'PYRAMID_CLOSER': closer})
205 celery_app.conf.update({'PYRAMID_CLOSER': closer})
206
206
207
207
208 def configure_celery(config, ini_location):
208 def configure_celery(config, ini_location):
209 """
209 """
210 Helper that is called from our application creation logic. It gives
210 Helper that is called from our application creation logic. It gives
211 connection info into running webapp and allows execution of tasks from
211 connection info into running webapp and allows execution of tasks from
212 RhodeCode itself
212 RhodeCode itself
213 """
213 """
214 # store some globals into rhodecode
214 # store some globals into rhodecode
215 rhodecode.CELERY_ENABLED = str2bool(
215 rhodecode.CELERY_ENABLED = str2bool(
216 config.registry.settings.get('use_celery'))
216 config.registry.settings.get('use_celery'))
217 if rhodecode.CELERY_ENABLED:
217 if rhodecode.CELERY_ENABLED:
218 log.info('Configuring celery based on `%s` file', ini_location)
218 log.info('Configuring celery based on `%s` file', ini_location)
219 setup_celery_app(
219 setup_celery_app(
220 app=None, root=None, request=None, registry=config.registry,
220 app=None, root=None, request=None, registry=config.registry,
221 closer=None, ini_location=ini_location)
221 closer=None, ini_location=ini_location)
222
222
223
223
224 def maybe_prepare_env(req):
224 def maybe_prepare_env(req):
225 environ = {}
225 environ = {}
226 try:
226 try:
227 environ.update({
227 environ.update({
228 'PATH_INFO': req.environ['PATH_INFO'],
228 'PATH_INFO': req.environ['PATH_INFO'],
229 'SCRIPT_NAME': req.environ['SCRIPT_NAME'],
229 'SCRIPT_NAME': req.environ['SCRIPT_NAME'],
230 'HTTP_HOST':
230 'HTTP_HOST':
231 req.environ.get('HTTP_HOST', req.environ['SERVER_NAME']),
231 req.environ.get('HTTP_HOST', req.environ['SERVER_NAME']),
232 'SERVER_NAME': req.environ['SERVER_NAME'],
232 'SERVER_NAME': req.environ['SERVER_NAME'],
233 'SERVER_PORT': req.environ['SERVER_PORT'],
233 'SERVER_PORT': req.environ['SERVER_PORT'],
234 'wsgi.url_scheme': req.environ['wsgi.url_scheme'],
234 'wsgi.url_scheme': req.environ['wsgi.url_scheme'],
235 })
235 })
236 except Exception:
236 except Exception:
237 pass
237 pass
238
238
239 return environ
239 return environ
240
240
241
241
242 class RequestContextTask(Task):
242 class RequestContextTask(Task):
243 """
243 """
244 This is a celery task which will create a rhodecode app instance context
244 This is a celery task which will create a rhodecode app instance context
245 for the task, patch pyramid with the original request
245 for the task, patch pyramid with the original request
246 that created the task and also add the user to the context.
246 that created the task and also add the user to the context.
247 """
247 """
248
248
249 def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,
249 def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,
250 link=None, link_error=None, shadow=None, **options):
250 link=None, link_error=None, shadow=None, **options):
251 """ queue the job to run (we are in web request context here) """
251 """ queue the job to run (we are in web request context here) """
252
252
253 req = get_current_request()
253 req = get_current_request()
254
254
255 # web case
255 # web case
256 if hasattr(req, 'user'):
256 if hasattr(req, 'user'):
257 ip_addr = req.user.ip_addr
257 ip_addr = req.user.ip_addr
258 user_id = req.user.user_id
258 user_id = req.user.user_id
259
259
260 # api case
260 # api case
261 elif hasattr(req, 'rpc_user'):
261 elif hasattr(req, 'rpc_user'):
262 ip_addr = req.rpc_user.ip_addr
262 ip_addr = req.rpc_user.ip_addr
263 user_id = req.rpc_user.user_id
263 user_id = req.rpc_user.user_id
264 else:
264 else:
265 raise Exception(
265 raise Exception(
266 'Unable to fetch required data from request: {}. \n'
266 'Unable to fetch required data from request: {}. \n'
267 'This task is required to be executed from context of '
267 'This task is required to be executed from context of '
268 'request in a webapp'.format(repr(req)))
268 'request in a webapp'.format(repr(req)))
269
269
270 if req:
270 if req:
271 # we hook into kwargs since it is the only way to pass our data to
271 # we hook into kwargs since it is the only way to pass our data to
272 # the celery worker
272 # the celery worker
273 environ = maybe_prepare_env(req)
273 environ = maybe_prepare_env(req)
274 options['headers'] = options.get('headers', {})
274 options['headers'] = options.get('headers', {})
275 options['headers'].update({
275 options['headers'].update({
276 'rhodecode_proxy_data': {
276 'rhodecode_proxy_data': {
277 'environ': environ,
277 'environ': environ,
278 'auth_user': {
278 'auth_user': {
279 'ip_addr': ip_addr,
279 'ip_addr': ip_addr,
280 'user_id': user_id
280 'user_id': user_id
281 },
281 },
282 }
282 }
283 })
283 })
284
284
285 return super(RequestContextTask, self).apply_async(
285 return super(RequestContextTask, self).apply_async(
286 args, kwargs, task_id, producer, link, link_error, shadow, **options)
286 args, kwargs, task_id, producer, link, link_error, shadow, **options)
287
287
288 def __call__(self, *args, **kwargs):
288 def __call__(self, *args, **kwargs):
289 """ rebuild the context and then run task on celery worker """
289 """ rebuild the context and then run task on celery worker """
290
290
291 proxy_data = getattr(self.request, 'rhodecode_proxy_data', None)
291 proxy_data = getattr(self.request, 'rhodecode_proxy_data', None)
292 if not proxy_data:
292 if not proxy_data:
293 return super(RequestContextTask, self).__call__(*args, **kwargs)
293 return super(RequestContextTask, self).__call__(*args, **kwargs)
294
294
295 log.debug('using celery proxy data to run task: %r', proxy_data)
295 log.debug('using celery proxy data to run task: %r', proxy_data)
296 # re-inject and register threadlocals for proper routing support
296 # re-inject and register threadlocals for proper routing support
297 request = prepare_request(proxy_data['environ'])
297 request = prepare_request(proxy_data['environ'])
298 request.user = AuthUser(user_id=proxy_data['auth_user']['user_id'],
298 request.user = AuthUser(user_id=proxy_data['auth_user']['user_id'],
299 ip_addr=proxy_data['auth_user']['ip_addr'])
299 ip_addr=proxy_data['auth_user']['ip_addr'])
300
300
301 return super(RequestContextTask, self).__call__(*args, **kwargs)
301 return super(RequestContextTask, self).__call__(*args, **kwargs)
302
302
@@ -1,4334 +1,4334 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37 from sqlalchemy import (
37 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
38 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
41 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false
42 from sqlalchemy.sql.expression import true, false
43 from sqlalchemy.sql.functions import coalesce, count # noqa
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
45 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # noqa
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from beaker.cache import cache_region
50 from beaker.cache import cache_region
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
52
52
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54
54
55 from rhodecode.translation import _
55 from rhodecode.translation import _
56 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.utils2 import (
58 from rhodecode.lib.utils2 import (
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 glob2re, StrictAttributeDict, cleaned_uri)
61 glob2re, StrictAttributeDict, cleaned_uri)
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 JsonRaw
63 JsonRaw
64 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.encrypt import AESCipher
66 from rhodecode.lib.encrypt import AESCipher
67
67
68 from rhodecode.model.meta import Base, Session
68 from rhodecode.model.meta import Base, Session
69
69
70 URL_SEP = '/'
70 URL_SEP = '/'
71 log = logging.getLogger(__name__)
71 log = logging.getLogger(__name__)
72
72
73 # =============================================================================
73 # =============================================================================
74 # BASE CLASSES
74 # BASE CLASSES
75 # =============================================================================
75 # =============================================================================
76
76
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # beaker.session.secret if first is not set.
78 # beaker.session.secret if first is not set.
79 # and initialized at environment.py
79 # and initialized at environment.py
80 ENCRYPTION_KEY = None
80 ENCRYPTION_KEY = None
81
81
82 # used to sort permissions by types, '#' used here is not allowed to be in
82 # used to sort permissions by types, '#' used here is not allowed to be in
83 # usernames, and it's very early in sorted string.printable table.
83 # usernames, and it's very early in sorted string.printable table.
84 PERMISSION_TYPE_SORT = {
84 PERMISSION_TYPE_SORT = {
85 'admin': '####',
85 'admin': '####',
86 'write': '###',
86 'write': '###',
87 'read': '##',
87 'read': '##',
88 'none': '#',
88 'none': '#',
89 }
89 }
90
90
91
91
92 def display_user_sort(obj):
92 def display_user_sort(obj):
93 """
93 """
94 Sort function used to sort permissions in .permissions() function of
94 Sort function used to sort permissions in .permissions() function of
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 of all other resources
96 of all other resources
97 """
97 """
98
98
99 if obj.username == User.DEFAULT_USER:
99 if obj.username == User.DEFAULT_USER:
100 return '#####'
100 return '#####'
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 return prefix + obj.username
102 return prefix + obj.username
103
103
104
104
105 def display_user_group_sort(obj):
105 def display_user_group_sort(obj):
106 """
106 """
107 Sort function used to sort permissions in .permissions() function of
107 Sort function used to sort permissions in .permissions() function of
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 of all other resources
109 of all other resources
110 """
110 """
111
111
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 return prefix + obj.users_group_name
113 return prefix + obj.users_group_name
114
114
115
115
116 def _hash_key(k):
116 def _hash_key(k):
117 return md5_safe(k)
117 return md5_safe(k)
118
118
119
119
120 def in_filter_generator(qry, items, limit=500):
120 def in_filter_generator(qry, items, limit=500):
121 """
121 """
122 Splits IN() into multiple with OR
122 Splits IN() into multiple with OR
123 e.g.::
123 e.g.::
124 cnt = Repository.query().filter(
124 cnt = Repository.query().filter(
125 or_(
125 or_(
126 *in_filter_generator(Repository.repo_id, range(100000))
126 *in_filter_generator(Repository.repo_id, range(100000))
127 )).count()
127 )).count()
128 """
128 """
129 if not items:
129 if not items:
130 # empty list will cause empty query which might cause security issues
130 # empty list will cause empty query which might cause security issues
131 # this can lead to hidden unpleasant results
131 # this can lead to hidden unpleasant results
132 items = [-1]
132 items = [-1]
133
133
134 parts = []
134 parts = []
135 for chunk in xrange(0, len(items), limit):
135 for chunk in xrange(0, len(items), limit):
136 parts.append(
136 parts.append(
137 qry.in_(items[chunk: chunk + limit])
137 qry.in_(items[chunk: chunk + limit])
138 )
138 )
139
139
140 return parts
140 return parts
141
141
142
142
143 class EncryptedTextValue(TypeDecorator):
143 class EncryptedTextValue(TypeDecorator):
144 """
144 """
145 Special column for encrypted long text data, use like::
145 Special column for encrypted long text data, use like::
146
146
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
148
148
149 This column is intelligent so if value is in unencrypted form it return
149 This column is intelligent so if value is in unencrypted form it return
150 unencrypted form, but on save it always encrypts
150 unencrypted form, but on save it always encrypts
151 """
151 """
152 impl = Text
152 impl = Text
153
153
154 def process_bind_param(self, value, dialect):
154 def process_bind_param(self, value, dialect):
155 if not value:
155 if not value:
156 return value
156 return value
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
158 # protect against double encrypting if someone manually starts
158 # protect against double encrypting if someone manually starts
159 # doing
159 # doing
160 raise ValueError('value needs to be in unencrypted format, ie. '
160 raise ValueError('value needs to be in unencrypted format, ie. '
161 'not starting with enc$aes')
161 'not starting with enc$aes')
162 return 'enc$aes_hmac$%s' % AESCipher(
162 return 'enc$aes_hmac$%s' % AESCipher(
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
164
164
165 def process_result_value(self, value, dialect):
165 def process_result_value(self, value, dialect):
166 import rhodecode
166 import rhodecode
167
167
168 if not value:
168 if not value:
169 return value
169 return value
170
170
171 parts = value.split('$', 3)
171 parts = value.split('$', 3)
172 if not len(parts) == 3:
172 if not len(parts) == 3:
173 # probably not encrypted values
173 # probably not encrypted values
174 return value
174 return value
175 else:
175 else:
176 if parts[0] != 'enc':
176 if parts[0] != 'enc':
177 # parts ok but without our header ?
177 # parts ok but without our header ?
178 return value
178 return value
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
180 'rhodecode.encrypted_values.strict') or True)
180 'rhodecode.encrypted_values.strict') or True)
181 # at that stage we know it's our encryption
181 # at that stage we know it's our encryption
182 if parts[1] == 'aes':
182 if parts[1] == 'aes':
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
184 elif parts[1] == 'aes_hmac':
184 elif parts[1] == 'aes_hmac':
185 decrypted_data = AESCipher(
185 decrypted_data = AESCipher(
186 ENCRYPTION_KEY, hmac=True,
186 ENCRYPTION_KEY, hmac=True,
187 strict_verification=enc_strict_mode).decrypt(parts[2])
187 strict_verification=enc_strict_mode).decrypt(parts[2])
188 else:
188 else:
189 raise ValueError(
189 raise ValueError(
190 'Encryption type part is wrong, must be `aes` '
190 'Encryption type part is wrong, must be `aes` '
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
192 return decrypted_data
192 return decrypted_data
193
193
194
194
195 class BaseModel(object):
195 class BaseModel(object):
196 """
196 """
197 Base Model for all classes
197 Base Model for all classes
198 """
198 """
199
199
200 @classmethod
200 @classmethod
201 def _get_keys(cls):
201 def _get_keys(cls):
202 """return column names for this model """
202 """return column names for this model """
203 return class_mapper(cls).c.keys()
203 return class_mapper(cls).c.keys()
204
204
205 def get_dict(self):
205 def get_dict(self):
206 """
206 """
207 return dict with keys and values corresponding
207 return dict with keys and values corresponding
208 to this model data """
208 to this model data """
209
209
210 d = {}
210 d = {}
211 for k in self._get_keys():
211 for k in self._get_keys():
212 d[k] = getattr(self, k)
212 d[k] = getattr(self, k)
213
213
214 # also use __json__() if present to get additional fields
214 # also use __json__() if present to get additional fields
215 _json_attr = getattr(self, '__json__', None)
215 _json_attr = getattr(self, '__json__', None)
216 if _json_attr:
216 if _json_attr:
217 # update with attributes from __json__
217 # update with attributes from __json__
218 if callable(_json_attr):
218 if callable(_json_attr):
219 _json_attr = _json_attr()
219 _json_attr = _json_attr()
220 for k, val in _json_attr.iteritems():
220 for k, val in _json_attr.iteritems():
221 d[k] = val
221 d[k] = val
222 return d
222 return d
223
223
224 def get_appstruct(self):
224 def get_appstruct(self):
225 """return list with keys and values tuples corresponding
225 """return list with keys and values tuples corresponding
226 to this model data """
226 to this model data """
227
227
228 lst = []
228 lst = []
229 for k in self._get_keys():
229 for k in self._get_keys():
230 lst.append((k, getattr(self, k),))
230 lst.append((k, getattr(self, k),))
231 return lst
231 return lst
232
232
233 def populate_obj(self, populate_dict):
233 def populate_obj(self, populate_dict):
234 """populate model with data from given populate_dict"""
234 """populate model with data from given populate_dict"""
235
235
236 for k in self._get_keys():
236 for k in self._get_keys():
237 if k in populate_dict:
237 if k in populate_dict:
238 setattr(self, k, populate_dict[k])
238 setattr(self, k, populate_dict[k])
239
239
240 @classmethod
240 @classmethod
241 def query(cls):
241 def query(cls):
242 return Session().query(cls)
242 return Session().query(cls)
243
243
244 @classmethod
244 @classmethod
245 def get(cls, id_):
245 def get(cls, id_):
246 if id_:
246 if id_:
247 return cls.query().get(id_)
247 return cls.query().get(id_)
248
248
249 @classmethod
249 @classmethod
250 def get_or_404(cls, id_):
250 def get_or_404(cls, id_):
251 from pyramid.httpexceptions import HTTPNotFound
251 from pyramid.httpexceptions import HTTPNotFound
252
252
253 try:
253 try:
254 id_ = int(id_)
254 id_ = int(id_)
255 except (TypeError, ValueError):
255 except (TypeError, ValueError):
256 raise HTTPNotFound()
256 raise HTTPNotFound()
257
257
258 res = cls.query().get(id_)
258 res = cls.query().get(id_)
259 if not res:
259 if not res:
260 raise HTTPNotFound()
260 raise HTTPNotFound()
261 return res
261 return res
262
262
263 @classmethod
263 @classmethod
264 def getAll(cls):
264 def getAll(cls):
265 # deprecated and left for backward compatibility
265 # deprecated and left for backward compatibility
266 return cls.get_all()
266 return cls.get_all()
267
267
268 @classmethod
268 @classmethod
269 def get_all(cls):
269 def get_all(cls):
270 return cls.query().all()
270 return cls.query().all()
271
271
272 @classmethod
272 @classmethod
273 def delete(cls, id_):
273 def delete(cls, id_):
274 obj = cls.query().get(id_)
274 obj = cls.query().get(id_)
275 Session().delete(obj)
275 Session().delete(obj)
276
276
277 @classmethod
277 @classmethod
278 def identity_cache(cls, session, attr_name, value):
278 def identity_cache(cls, session, attr_name, value):
279 exist_in_session = []
279 exist_in_session = []
280 for (item_cls, pkey), instance in session.identity_map.items():
280 for (item_cls, pkey), instance in session.identity_map.items():
281 if cls == item_cls and getattr(instance, attr_name) == value:
281 if cls == item_cls and getattr(instance, attr_name) == value:
282 exist_in_session.append(instance)
282 exist_in_session.append(instance)
283 if exist_in_session:
283 if exist_in_session:
284 if len(exist_in_session) == 1:
284 if len(exist_in_session) == 1:
285 return exist_in_session[0]
285 return exist_in_session[0]
286 log.exception(
286 log.exception(
287 'multiple objects with attr %s and '
287 'multiple objects with attr %s and '
288 'value %s found with same name: %r',
288 'value %s found with same name: %r',
289 attr_name, value, exist_in_session)
289 attr_name, value, exist_in_session)
290
290
291 def __repr__(self):
291 def __repr__(self):
292 if hasattr(self, '__unicode__'):
292 if hasattr(self, '__unicode__'):
293 # python repr needs to return str
293 # python repr needs to return str
294 try:
294 try:
295 return safe_str(self.__unicode__())
295 return safe_str(self.__unicode__())
296 except UnicodeDecodeError:
296 except UnicodeDecodeError:
297 pass
297 pass
298 return '<DB:%s>' % (self.__class__.__name__)
298 return '<DB:%s>' % (self.__class__.__name__)
299
299
300
300
301 class RhodeCodeSetting(Base, BaseModel):
301 class RhodeCodeSetting(Base, BaseModel):
302 __tablename__ = 'rhodecode_settings'
302 __tablename__ = 'rhodecode_settings'
303 __table_args__ = (
303 __table_args__ = (
304 UniqueConstraint('app_settings_name'),
304 UniqueConstraint('app_settings_name'),
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
307 )
307 )
308
308
309 SETTINGS_TYPES = {
309 SETTINGS_TYPES = {
310 'str': safe_str,
310 'str': safe_str,
311 'int': safe_int,
311 'int': safe_int,
312 'unicode': safe_unicode,
312 'unicode': safe_unicode,
313 'bool': str2bool,
313 'bool': str2bool,
314 'list': functools.partial(aslist, sep=',')
314 'list': functools.partial(aslist, sep=',')
315 }
315 }
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
317 GLOBAL_CONF_KEY = 'app_settings'
317 GLOBAL_CONF_KEY = 'app_settings'
318
318
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
323
323
324 def __init__(self, key='', val='', type='unicode'):
324 def __init__(self, key='', val='', type='unicode'):
325 self.app_settings_name = key
325 self.app_settings_name = key
326 self.app_settings_type = type
326 self.app_settings_type = type
327 self.app_settings_value = val
327 self.app_settings_value = val
328
328
329 @validates('_app_settings_value')
329 @validates('_app_settings_value')
330 def validate_settings_value(self, key, val):
330 def validate_settings_value(self, key, val):
331 assert type(val) == unicode
331 assert type(val) == unicode
332 return val
332 return val
333
333
334 @hybrid_property
334 @hybrid_property
335 def app_settings_value(self):
335 def app_settings_value(self):
336 v = self._app_settings_value
336 v = self._app_settings_value
337 _type = self.app_settings_type
337 _type = self.app_settings_type
338 if _type:
338 if _type:
339 _type = self.app_settings_type.split('.')[0]
339 _type = self.app_settings_type.split('.')[0]
340 # decode the encrypted value
340 # decode the encrypted value
341 if 'encrypted' in self.app_settings_type:
341 if 'encrypted' in self.app_settings_type:
342 cipher = EncryptedTextValue()
342 cipher = EncryptedTextValue()
343 v = safe_unicode(cipher.process_result_value(v, None))
343 v = safe_unicode(cipher.process_result_value(v, None))
344
344
345 converter = self.SETTINGS_TYPES.get(_type) or \
345 converter = self.SETTINGS_TYPES.get(_type) or \
346 self.SETTINGS_TYPES['unicode']
346 self.SETTINGS_TYPES['unicode']
347 return converter(v)
347 return converter(v)
348
348
349 @app_settings_value.setter
349 @app_settings_value.setter
350 def app_settings_value(self, val):
350 def app_settings_value(self, val):
351 """
351 """
352 Setter that will always make sure we use unicode in app_settings_value
352 Setter that will always make sure we use unicode in app_settings_value
353
353
354 :param val:
354 :param val:
355 """
355 """
356 val = safe_unicode(val)
356 val = safe_unicode(val)
357 # encode the encrypted value
357 # encode the encrypted value
358 if 'encrypted' in self.app_settings_type:
358 if 'encrypted' in self.app_settings_type:
359 cipher = EncryptedTextValue()
359 cipher = EncryptedTextValue()
360 val = safe_unicode(cipher.process_bind_param(val, None))
360 val = safe_unicode(cipher.process_bind_param(val, None))
361 self._app_settings_value = val
361 self._app_settings_value = val
362
362
363 @hybrid_property
363 @hybrid_property
364 def app_settings_type(self):
364 def app_settings_type(self):
365 return self._app_settings_type
365 return self._app_settings_type
366
366
367 @app_settings_type.setter
367 @app_settings_type.setter
368 def app_settings_type(self, val):
368 def app_settings_type(self, val):
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
370 raise Exception('type must be one of %s got %s'
370 raise Exception('type must be one of %s got %s'
371 % (self.SETTINGS_TYPES.keys(), val))
371 % (self.SETTINGS_TYPES.keys(), val))
372 self._app_settings_type = val
372 self._app_settings_type = val
373
373
374 def __unicode__(self):
374 def __unicode__(self):
375 return u"<%s('%s:%s[%s]')>" % (
375 return u"<%s('%s:%s[%s]')>" % (
376 self.__class__.__name__,
376 self.__class__.__name__,
377 self.app_settings_name, self.app_settings_value,
377 self.app_settings_name, self.app_settings_value,
378 self.app_settings_type
378 self.app_settings_type
379 )
379 )
380
380
381
381
382 class RhodeCodeUi(Base, BaseModel):
382 class RhodeCodeUi(Base, BaseModel):
383 __tablename__ = 'rhodecode_ui'
383 __tablename__ = 'rhodecode_ui'
384 __table_args__ = (
384 __table_args__ = (
385 UniqueConstraint('ui_key'),
385 UniqueConstraint('ui_key'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
391 # HG
391 # HG
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
393 HOOK_PULL = 'outgoing.pull_logger'
393 HOOK_PULL = 'outgoing.pull_logger'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
396 HOOK_PUSH = 'changegroup.push_logger'
396 HOOK_PUSH = 'changegroup.push_logger'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
398
398
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
400 # git part is currently hardcoded.
400 # git part is currently hardcoded.
401
401
402 # SVN PATTERNS
402 # SVN PATTERNS
403 SVN_BRANCH_ID = 'vcs_svn_branch'
403 SVN_BRANCH_ID = 'vcs_svn_branch'
404 SVN_TAG_ID = 'vcs_svn_tag'
404 SVN_TAG_ID = 'vcs_svn_tag'
405
405
406 ui_id = Column(
406 ui_id = Column(
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
408 primary_key=True)
408 primary_key=True)
409 ui_section = Column(
409 ui_section = Column(
410 "ui_section", String(255), nullable=True, unique=None, default=None)
410 "ui_section", String(255), nullable=True, unique=None, default=None)
411 ui_key = Column(
411 ui_key = Column(
412 "ui_key", String(255), nullable=True, unique=None, default=None)
412 "ui_key", String(255), nullable=True, unique=None, default=None)
413 ui_value = Column(
413 ui_value = Column(
414 "ui_value", String(255), nullable=True, unique=None, default=None)
414 "ui_value", String(255), nullable=True, unique=None, default=None)
415 ui_active = Column(
415 ui_active = Column(
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
417
417
418 def __repr__(self):
418 def __repr__(self):
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
420 self.ui_key, self.ui_value)
420 self.ui_key, self.ui_value)
421
421
422
422
423 class RepoRhodeCodeSetting(Base, BaseModel):
423 class RepoRhodeCodeSetting(Base, BaseModel):
424 __tablename__ = 'repo_rhodecode_settings'
424 __tablename__ = 'repo_rhodecode_settings'
425 __table_args__ = (
425 __table_args__ = (
426 UniqueConstraint(
426 UniqueConstraint(
427 'app_settings_name', 'repository_id',
427 'app_settings_name', 'repository_id',
428 name='uq_repo_rhodecode_setting_name_repo_id'),
428 name='uq_repo_rhodecode_setting_name_repo_id'),
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
431 )
431 )
432
432
433 repository_id = Column(
433 repository_id = Column(
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
435 nullable=False)
435 nullable=False)
436 app_settings_id = Column(
436 app_settings_id = Column(
437 "app_settings_id", Integer(), nullable=False, unique=True,
437 "app_settings_id", Integer(), nullable=False, unique=True,
438 default=None, primary_key=True)
438 default=None, primary_key=True)
439 app_settings_name = Column(
439 app_settings_name = Column(
440 "app_settings_name", String(255), nullable=True, unique=None,
440 "app_settings_name", String(255), nullable=True, unique=None,
441 default=None)
441 default=None)
442 _app_settings_value = Column(
442 _app_settings_value = Column(
443 "app_settings_value", String(4096), nullable=True, unique=None,
443 "app_settings_value", String(4096), nullable=True, unique=None,
444 default=None)
444 default=None)
445 _app_settings_type = Column(
445 _app_settings_type = Column(
446 "app_settings_type", String(255), nullable=True, unique=None,
446 "app_settings_type", String(255), nullable=True, unique=None,
447 default=None)
447 default=None)
448
448
449 repository = relationship('Repository')
449 repository = relationship('Repository')
450
450
451 def __init__(self, repository_id, key='', val='', type='unicode'):
451 def __init__(self, repository_id, key='', val='', type='unicode'):
452 self.repository_id = repository_id
452 self.repository_id = repository_id
453 self.app_settings_name = key
453 self.app_settings_name = key
454 self.app_settings_type = type
454 self.app_settings_type = type
455 self.app_settings_value = val
455 self.app_settings_value = val
456
456
457 @validates('_app_settings_value')
457 @validates('_app_settings_value')
458 def validate_settings_value(self, key, val):
458 def validate_settings_value(self, key, val):
459 assert type(val) == unicode
459 assert type(val) == unicode
460 return val
460 return val
461
461
462 @hybrid_property
462 @hybrid_property
463 def app_settings_value(self):
463 def app_settings_value(self):
464 v = self._app_settings_value
464 v = self._app_settings_value
465 type_ = self.app_settings_type
465 type_ = self.app_settings_type
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
468 return converter(v)
468 return converter(v)
469
469
470 @app_settings_value.setter
470 @app_settings_value.setter
471 def app_settings_value(self, val):
471 def app_settings_value(self, val):
472 """
472 """
473 Setter that will always make sure we use unicode in app_settings_value
473 Setter that will always make sure we use unicode in app_settings_value
474
474
475 :param val:
475 :param val:
476 """
476 """
477 self._app_settings_value = safe_unicode(val)
477 self._app_settings_value = safe_unicode(val)
478
478
479 @hybrid_property
479 @hybrid_property
480 def app_settings_type(self):
480 def app_settings_type(self):
481 return self._app_settings_type
481 return self._app_settings_type
482
482
483 @app_settings_type.setter
483 @app_settings_type.setter
484 def app_settings_type(self, val):
484 def app_settings_type(self, val):
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
486 if val not in SETTINGS_TYPES:
486 if val not in SETTINGS_TYPES:
487 raise Exception('type must be one of %s got %s'
487 raise Exception('type must be one of %s got %s'
488 % (SETTINGS_TYPES.keys(), val))
488 % (SETTINGS_TYPES.keys(), val))
489 self._app_settings_type = val
489 self._app_settings_type = val
490
490
491 def __unicode__(self):
491 def __unicode__(self):
492 return u"<%s('%s:%s:%s[%s]')>" % (
492 return u"<%s('%s:%s:%s[%s]')>" % (
493 self.__class__.__name__, self.repository.repo_name,
493 self.__class__.__name__, self.repository.repo_name,
494 self.app_settings_name, self.app_settings_value,
494 self.app_settings_name, self.app_settings_value,
495 self.app_settings_type
495 self.app_settings_type
496 )
496 )
497
497
498
498
499 class RepoRhodeCodeUi(Base, BaseModel):
499 class RepoRhodeCodeUi(Base, BaseModel):
500 __tablename__ = 'repo_rhodecode_ui'
500 __tablename__ = 'repo_rhodecode_ui'
501 __table_args__ = (
501 __table_args__ = (
502 UniqueConstraint(
502 UniqueConstraint(
503 'repository_id', 'ui_section', 'ui_key',
503 'repository_id', 'ui_section', 'ui_key',
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
507 )
507 )
508
508
509 repository_id = Column(
509 repository_id = Column(
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
511 nullable=False)
511 nullable=False)
512 ui_id = Column(
512 ui_id = Column(
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
514 primary_key=True)
514 primary_key=True)
515 ui_section = Column(
515 ui_section = Column(
516 "ui_section", String(255), nullable=True, unique=None, default=None)
516 "ui_section", String(255), nullable=True, unique=None, default=None)
517 ui_key = Column(
517 ui_key = Column(
518 "ui_key", String(255), nullable=True, unique=None, default=None)
518 "ui_key", String(255), nullable=True, unique=None, default=None)
519 ui_value = Column(
519 ui_value = Column(
520 "ui_value", String(255), nullable=True, unique=None, default=None)
520 "ui_value", String(255), nullable=True, unique=None, default=None)
521 ui_active = Column(
521 ui_active = Column(
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
523
523
524 repository = relationship('Repository')
524 repository = relationship('Repository')
525
525
526 def __repr__(self):
526 def __repr__(self):
527 return '<%s[%s:%s]%s=>%s]>' % (
527 return '<%s[%s:%s]%s=>%s]>' % (
528 self.__class__.__name__, self.repository.repo_name,
528 self.__class__.__name__, self.repository.repo_name,
529 self.ui_section, self.ui_key, self.ui_value)
529 self.ui_section, self.ui_key, self.ui_value)
530
530
531
531
532 class User(Base, BaseModel):
532 class User(Base, BaseModel):
533 __tablename__ = 'users'
533 __tablename__ = 'users'
534 __table_args__ = (
534 __table_args__ = (
535 UniqueConstraint('username'), UniqueConstraint('email'),
535 UniqueConstraint('username'), UniqueConstraint('email'),
536 Index('u_username_idx', 'username'),
536 Index('u_username_idx', 'username'),
537 Index('u_email_idx', 'email'),
537 Index('u_email_idx', 'email'),
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
540 )
540 )
541 DEFAULT_USER = 'default'
541 DEFAULT_USER = 'default'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
544
544
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
555
555
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
562
562
563 user_log = relationship('UserLog')
563 user_log = relationship('UserLog')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
565
565
566 repositories = relationship('Repository')
566 repositories = relationship('Repository')
567 repository_groups = relationship('RepoGroup')
567 repository_groups = relationship('RepoGroup')
568 user_groups = relationship('UserGroup')
568 user_groups = relationship('UserGroup')
569
569
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
572
572
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
576
576
577 group_member = relationship('UserGroupMember', cascade='all')
577 group_member = relationship('UserGroupMember', cascade='all')
578
578
579 notifications = relationship('UserNotification', cascade='all')
579 notifications = relationship('UserNotification', cascade='all')
580 # notifications assigned to this user
580 # notifications assigned to this user
581 user_created_notifications = relationship('Notification', cascade='all')
581 user_created_notifications = relationship('Notification', cascade='all')
582 # comments created by this user
582 # comments created by this user
583 user_comments = relationship('ChangesetComment', cascade='all')
583 user_comments = relationship('ChangesetComment', cascade='all')
584 # user profile extra info
584 # user profile extra info
585 user_emails = relationship('UserEmailMap', cascade='all')
585 user_emails = relationship('UserEmailMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
589
589
590 # gists
590 # gists
591 user_gists = relationship('Gist', cascade='all')
591 user_gists = relationship('Gist', cascade='all')
592 # user pull requests
592 # user pull requests
593 user_pull_requests = relationship('PullRequest', cascade='all')
593 user_pull_requests = relationship('PullRequest', cascade='all')
594 # external identities
594 # external identities
595 extenal_identities = relationship(
595 extenal_identities = relationship(
596 'ExternalIdentity',
596 'ExternalIdentity',
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
598 cascade='all')
598 cascade='all')
599 # review rules
599 # review rules
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
601
601
602 def __unicode__(self):
602 def __unicode__(self):
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
604 self.user_id, self.username)
604 self.user_id, self.username)
605
605
606 @hybrid_property
606 @hybrid_property
607 def email(self):
607 def email(self):
608 return self._email
608 return self._email
609
609
610 @email.setter
610 @email.setter
611 def email(self, val):
611 def email(self, val):
612 self._email = val.lower() if val else None
612 self._email = val.lower() if val else None
613
613
614 @hybrid_property
614 @hybrid_property
615 def first_name(self):
615 def first_name(self):
616 from rhodecode.lib import helpers as h
616 from rhodecode.lib import helpers as h
617 if self.name:
617 if self.name:
618 return h.escape(self.name)
618 return h.escape(self.name)
619 return self.name
619 return self.name
620
620
621 @hybrid_property
621 @hybrid_property
622 def last_name(self):
622 def last_name(self):
623 from rhodecode.lib import helpers as h
623 from rhodecode.lib import helpers as h
624 if self.lastname:
624 if self.lastname:
625 return h.escape(self.lastname)
625 return h.escape(self.lastname)
626 return self.lastname
626 return self.lastname
627
627
628 @hybrid_property
628 @hybrid_property
629 def api_key(self):
629 def api_key(self):
630 """
630 """
631 Fetch if exist an auth-token with role ALL connected to this user
631 Fetch if exist an auth-token with role ALL connected to this user
632 """
632 """
633 user_auth_token = UserApiKeys.query()\
633 user_auth_token = UserApiKeys.query()\
634 .filter(UserApiKeys.user_id == self.user_id)\
634 .filter(UserApiKeys.user_id == self.user_id)\
635 .filter(or_(UserApiKeys.expires == -1,
635 .filter(or_(UserApiKeys.expires == -1,
636 UserApiKeys.expires >= time.time()))\
636 UserApiKeys.expires >= time.time()))\
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
638 if user_auth_token:
638 if user_auth_token:
639 user_auth_token = user_auth_token.api_key
639 user_auth_token = user_auth_token.api_key
640
640
641 return user_auth_token
641 return user_auth_token
642
642
643 @api_key.setter
643 @api_key.setter
644 def api_key(self, val):
644 def api_key(self, val):
645 # don't allow to set API key this is deprecated for now
645 # don't allow to set API key this is deprecated for now
646 self._api_key = None
646 self._api_key = None
647
647
648 @property
648 @property
649 def reviewer_pull_requests(self):
649 def reviewer_pull_requests(self):
650 return PullRequestReviewers.query() \
650 return PullRequestReviewers.query() \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
653 .all()
653 .all()
654
654
655 @property
655 @property
656 def firstname(self):
656 def firstname(self):
657 # alias for future
657 # alias for future
658 return self.name
658 return self.name
659
659
660 @property
660 @property
661 def emails(self):
661 def emails(self):
662 other = UserEmailMap.query()\
662 other = UserEmailMap.query()\
663 .filter(UserEmailMap.user == self) \
663 .filter(UserEmailMap.user == self) \
664 .order_by(UserEmailMap.email_id.asc()) \
664 .order_by(UserEmailMap.email_id.asc()) \
665 .all()
665 .all()
666 return [self.email] + [x.email for x in other]
666 return [self.email] + [x.email for x in other]
667
667
668 @property
668 @property
669 def auth_tokens(self):
669 def auth_tokens(self):
670 auth_tokens = self.get_auth_tokens()
670 auth_tokens = self.get_auth_tokens()
671 return [x.api_key for x in auth_tokens]
671 return [x.api_key for x in auth_tokens]
672
672
673 def get_auth_tokens(self):
673 def get_auth_tokens(self):
674 return UserApiKeys.query()\
674 return UserApiKeys.query()\
675 .filter(UserApiKeys.user == self)\
675 .filter(UserApiKeys.user == self)\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
677 .all()
677 .all()
678
678
679 @property
679 @property
680 def feed_token(self):
680 def feed_token(self):
681 return self.get_feed_token()
681 return self.get_feed_token()
682
682
683 def get_feed_token(self):
683 def get_feed_token(self):
684 feed_tokens = UserApiKeys.query()\
684 feed_tokens = UserApiKeys.query()\
685 .filter(UserApiKeys.user == self)\
685 .filter(UserApiKeys.user == self)\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
687 .all()
687 .all()
688 if feed_tokens:
688 if feed_tokens:
689 return feed_tokens[0].api_key
689 return feed_tokens[0].api_key
690 return 'NO_FEED_TOKEN_AVAILABLE'
690 return 'NO_FEED_TOKEN_AVAILABLE'
691
691
692 @classmethod
692 @classmethod
693 def get(cls, user_id, cache=False):
693 def get(cls, user_id, cache=False):
694 if not user_id:
694 if not user_id:
695 return
695 return
696
696
697 user = cls.query()
697 user = cls.query()
698 if cache:
698 if cache:
699 user = user.options(
699 user = user.options(
700 FromCache("sql_cache_short", "get_users_%s" % user_id))
700 FromCache("sql_cache_short", "get_users_%s" % user_id))
701 return user.get(user_id)
701 return user.get(user_id)
702
702
703 @classmethod
703 @classmethod
704 def extra_valid_auth_tokens(cls, user, role=None):
704 def extra_valid_auth_tokens(cls, user, role=None):
705 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
705 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
706 .filter(or_(UserApiKeys.expires == -1,
706 .filter(or_(UserApiKeys.expires == -1,
707 UserApiKeys.expires >= time.time()))
707 UserApiKeys.expires >= time.time()))
708 if role:
708 if role:
709 tokens = tokens.filter(or_(UserApiKeys.role == role,
709 tokens = tokens.filter(or_(UserApiKeys.role == role,
710 UserApiKeys.role == UserApiKeys.ROLE_ALL))
710 UserApiKeys.role == UserApiKeys.ROLE_ALL))
711 return tokens.all()
711 return tokens.all()
712
712
713 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
713 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
714 from rhodecode.lib import auth
714 from rhodecode.lib import auth
715
715
716 log.debug('Trying to authenticate user: %s via auth-token, '
716 log.debug('Trying to authenticate user: %s via auth-token, '
717 'and roles: %s', self, roles)
717 'and roles: %s', self, roles)
718
718
719 if not auth_token:
719 if not auth_token:
720 return False
720 return False
721
721
722 crypto_backend = auth.crypto_backend()
722 crypto_backend = auth.crypto_backend()
723
723
724 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
724 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
725 tokens_q = UserApiKeys.query()\
725 tokens_q = UserApiKeys.query()\
726 .filter(UserApiKeys.user_id == self.user_id)\
726 .filter(UserApiKeys.user_id == self.user_id)\
727 .filter(or_(UserApiKeys.expires == -1,
727 .filter(or_(UserApiKeys.expires == -1,
728 UserApiKeys.expires >= time.time()))
728 UserApiKeys.expires >= time.time()))
729
729
730 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
730 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
731
731
732 plain_tokens = []
732 plain_tokens = []
733 hash_tokens = []
733 hash_tokens = []
734
734
735 for token in tokens_q.all():
735 for token in tokens_q.all():
736 # verify scope first
736 # verify scope first
737 if token.repo_id:
737 if token.repo_id:
738 # token has a scope, we need to verify it
738 # token has a scope, we need to verify it
739 if scope_repo_id != token.repo_id:
739 if scope_repo_id != token.repo_id:
740 log.debug(
740 log.debug(
741 'Scope mismatch: token has a set repo scope: %s, '
741 'Scope mismatch: token has a set repo scope: %s, '
742 'and calling scope is:%s, skipping further checks',
742 'and calling scope is:%s, skipping further checks',
743 token.repo, scope_repo_id)
743 token.repo, scope_repo_id)
744 # token has a scope, and it doesn't match, skip token
744 # token has a scope, and it doesn't match, skip token
745 continue
745 continue
746
746
747 if token.api_key.startswith(crypto_backend.ENC_PREF):
747 if token.api_key.startswith(crypto_backend.ENC_PREF):
748 hash_tokens.append(token.api_key)
748 hash_tokens.append(token.api_key)
749 else:
749 else:
750 plain_tokens.append(token.api_key)
750 plain_tokens.append(token.api_key)
751
751
752 is_plain_match = auth_token in plain_tokens
752 is_plain_match = auth_token in plain_tokens
753 if is_plain_match:
753 if is_plain_match:
754 return True
754 return True
755
755
756 for hashed in hash_tokens:
756 for hashed in hash_tokens:
757 # TODO(marcink): this is expensive to calculate, but most secure
757 # TODO(marcink): this is expensive to calculate, but most secure
758 match = crypto_backend.hash_check(auth_token, hashed)
758 match = crypto_backend.hash_check(auth_token, hashed)
759 if match:
759 if match:
760 return True
760 return True
761
761
762 return False
762 return False
763
763
764 @property
764 @property
765 def ip_addresses(self):
765 def ip_addresses(self):
766 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
766 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
767 return [x.ip_addr for x in ret]
767 return [x.ip_addr for x in ret]
768
768
769 @property
769 @property
770 def username_and_name(self):
770 def username_and_name(self):
771 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
771 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
772
772
773 @property
773 @property
774 def username_or_name_or_email(self):
774 def username_or_name_or_email(self):
775 full_name = self.full_name if self.full_name is not ' ' else None
775 full_name = self.full_name if self.full_name is not ' ' else None
776 return self.username or full_name or self.email
776 return self.username or full_name or self.email
777
777
778 @property
778 @property
779 def full_name(self):
779 def full_name(self):
780 return '%s %s' % (self.first_name, self.last_name)
780 return '%s %s' % (self.first_name, self.last_name)
781
781
782 @property
782 @property
783 def full_name_or_username(self):
783 def full_name_or_username(self):
784 return ('%s %s' % (self.first_name, self.last_name)
784 return ('%s %s' % (self.first_name, self.last_name)
785 if (self.first_name and self.last_name) else self.username)
785 if (self.first_name and self.last_name) else self.username)
786
786
787 @property
787 @property
788 def full_contact(self):
788 def full_contact(self):
789 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
789 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
790
790
791 @property
791 @property
792 def short_contact(self):
792 def short_contact(self):
793 return '%s %s' % (self.first_name, self.last_name)
793 return '%s %s' % (self.first_name, self.last_name)
794
794
795 @property
795 @property
796 def is_admin(self):
796 def is_admin(self):
797 return self.admin
797 return self.admin
798
798
799 def AuthUser(self, **kwargs):
799 def AuthUser(self, **kwargs):
800 """
800 """
801 Returns instance of AuthUser for this user
801 Returns instance of AuthUser for this user
802 """
802 """
803 from rhodecode.lib.auth import AuthUser
803 from rhodecode.lib.auth import AuthUser
804 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
804 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
805
805
806 @hybrid_property
806 @hybrid_property
807 def user_data(self):
807 def user_data(self):
808 if not self._user_data:
808 if not self._user_data:
809 return {}
809 return {}
810
810
811 try:
811 try:
812 return json.loads(self._user_data)
812 return json.loads(self._user_data)
813 except TypeError:
813 except TypeError:
814 return {}
814 return {}
815
815
816 @user_data.setter
816 @user_data.setter
817 def user_data(self, val):
817 def user_data(self, val):
818 if not isinstance(val, dict):
818 if not isinstance(val, dict):
819 raise Exception('user_data must be dict, got %s' % type(val))
819 raise Exception('user_data must be dict, got %s' % type(val))
820 try:
820 try:
821 self._user_data = json.dumps(val)
821 self._user_data = json.dumps(val)
822 except Exception:
822 except Exception:
823 log.error(traceback.format_exc())
823 log.error(traceback.format_exc())
824
824
825 @classmethod
825 @classmethod
826 def get_by_username(cls, username, case_insensitive=False,
826 def get_by_username(cls, username, case_insensitive=False,
827 cache=False, identity_cache=False):
827 cache=False, identity_cache=False):
828 session = Session()
828 session = Session()
829
829
830 if case_insensitive:
830 if case_insensitive:
831 q = cls.query().filter(
831 q = cls.query().filter(
832 func.lower(cls.username) == func.lower(username))
832 func.lower(cls.username) == func.lower(username))
833 else:
833 else:
834 q = cls.query().filter(cls.username == username)
834 q = cls.query().filter(cls.username == username)
835
835
836 if cache:
836 if cache:
837 if identity_cache:
837 if identity_cache:
838 val = cls.identity_cache(session, 'username', username)
838 val = cls.identity_cache(session, 'username', username)
839 if val:
839 if val:
840 return val
840 return val
841 else:
841 else:
842 cache_key = "get_user_by_name_%s" % _hash_key(username)
842 cache_key = "get_user_by_name_%s" % _hash_key(username)
843 q = q.options(
843 q = q.options(
844 FromCache("sql_cache_short", cache_key))
844 FromCache("sql_cache_short", cache_key))
845
845
846 return q.scalar()
846 return q.scalar()
847
847
848 @classmethod
848 @classmethod
849 def get_by_auth_token(cls, auth_token, cache=False):
849 def get_by_auth_token(cls, auth_token, cache=False):
850 q = UserApiKeys.query()\
850 q = UserApiKeys.query()\
851 .filter(UserApiKeys.api_key == auth_token)\
851 .filter(UserApiKeys.api_key == auth_token)\
852 .filter(or_(UserApiKeys.expires == -1,
852 .filter(or_(UserApiKeys.expires == -1,
853 UserApiKeys.expires >= time.time()))
853 UserApiKeys.expires >= time.time()))
854 if cache:
854 if cache:
855 q = q.options(
855 q = q.options(
856 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
856 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
857
857
858 match = q.first()
858 match = q.first()
859 if match:
859 if match:
860 return match.user
860 return match.user
861
861
862 @classmethod
862 @classmethod
863 def get_by_email(cls, email, case_insensitive=False, cache=False):
863 def get_by_email(cls, email, case_insensitive=False, cache=False):
864
864
865 if case_insensitive:
865 if case_insensitive:
866 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
866 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
867
867
868 else:
868 else:
869 q = cls.query().filter(cls.email == email)
869 q = cls.query().filter(cls.email == email)
870
870
871 email_key = _hash_key(email)
871 email_key = _hash_key(email)
872 if cache:
872 if cache:
873 q = q.options(
873 q = q.options(
874 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
874 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
875
875
876 ret = q.scalar()
876 ret = q.scalar()
877 if ret is None:
877 if ret is None:
878 q = UserEmailMap.query()
878 q = UserEmailMap.query()
879 # try fetching in alternate email map
879 # try fetching in alternate email map
880 if case_insensitive:
880 if case_insensitive:
881 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
881 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
882 else:
882 else:
883 q = q.filter(UserEmailMap.email == email)
883 q = q.filter(UserEmailMap.email == email)
884 q = q.options(joinedload(UserEmailMap.user))
884 q = q.options(joinedload(UserEmailMap.user))
885 if cache:
885 if cache:
886 q = q.options(
886 q = q.options(
887 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
887 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
888 ret = getattr(q.scalar(), 'user', None)
888 ret = getattr(q.scalar(), 'user', None)
889
889
890 return ret
890 return ret
891
891
892 @classmethod
892 @classmethod
893 def get_from_cs_author(cls, author):
893 def get_from_cs_author(cls, author):
894 """
894 """
895 Tries to get User objects out of commit author string
895 Tries to get User objects out of commit author string
896
896
897 :param author:
897 :param author:
898 """
898 """
899 from rhodecode.lib.helpers import email, author_name
899 from rhodecode.lib.helpers import email, author_name
900 # Valid email in the attribute passed, see if they're in the system
900 # Valid email in the attribute passed, see if they're in the system
901 _email = email(author)
901 _email = email(author)
902 if _email:
902 if _email:
903 user = cls.get_by_email(_email, case_insensitive=True)
903 user = cls.get_by_email(_email, case_insensitive=True)
904 if user:
904 if user:
905 return user
905 return user
906 # Maybe we can match by username?
906 # Maybe we can match by username?
907 _author = author_name(author)
907 _author = author_name(author)
908 user = cls.get_by_username(_author, case_insensitive=True)
908 user = cls.get_by_username(_author, case_insensitive=True)
909 if user:
909 if user:
910 return user
910 return user
911
911
912 def update_userdata(self, **kwargs):
912 def update_userdata(self, **kwargs):
913 usr = self
913 usr = self
914 old = usr.user_data
914 old = usr.user_data
915 old.update(**kwargs)
915 old.update(**kwargs)
916 usr.user_data = old
916 usr.user_data = old
917 Session().add(usr)
917 Session().add(usr)
918 log.debug('updated userdata with ', kwargs)
918 log.debug('updated userdata with ', kwargs)
919
919
920 def update_lastlogin(self):
920 def update_lastlogin(self):
921 """Update user lastlogin"""
921 """Update user lastlogin"""
922 self.last_login = datetime.datetime.now()
922 self.last_login = datetime.datetime.now()
923 Session().add(self)
923 Session().add(self)
924 log.debug('updated user %s lastlogin', self.username)
924 log.debug('updated user %s lastlogin', self.username)
925
925
926 def update_lastactivity(self):
926 def update_lastactivity(self):
927 """Update user lastactivity"""
927 """Update user lastactivity"""
928 self.last_activity = datetime.datetime.now()
928 self.last_activity = datetime.datetime.now()
929 Session().add(self)
929 Session().add(self)
930 log.debug('updated user `%s` last activity', self.username)
930 log.debug('updated user `%s` last activity', self.username)
931
931
932 def update_password(self, new_password):
932 def update_password(self, new_password):
933 from rhodecode.lib.auth import get_crypt_password
933 from rhodecode.lib.auth import get_crypt_password
934
934
935 self.password = get_crypt_password(new_password)
935 self.password = get_crypt_password(new_password)
936 Session().add(self)
936 Session().add(self)
937
937
938 @classmethod
938 @classmethod
939 def get_first_super_admin(cls):
939 def get_first_super_admin(cls):
940 user = User.query().filter(User.admin == true()).first()
940 user = User.query().filter(User.admin == true()).first()
941 if user is None:
941 if user is None:
942 raise Exception('FATAL: Missing administrative account!')
942 raise Exception('FATAL: Missing administrative account!')
943 return user
943 return user
944
944
945 @classmethod
945 @classmethod
946 def get_all_super_admins(cls):
946 def get_all_super_admins(cls):
947 """
947 """
948 Returns all admin accounts sorted by username
948 Returns all admin accounts sorted by username
949 """
949 """
950 return User.query().filter(User.admin == true())\
950 return User.query().filter(User.admin == true())\
951 .order_by(User.username.asc()).all()
951 .order_by(User.username.asc()).all()
952
952
953 @classmethod
953 @classmethod
954 def get_default_user(cls, cache=False, refresh=False):
954 def get_default_user(cls, cache=False, refresh=False):
955 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
955 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
956 if user is None:
956 if user is None:
957 raise Exception('FATAL: Missing default account!')
957 raise Exception('FATAL: Missing default account!')
958 if refresh:
958 if refresh:
959 # The default user might be based on outdated state which
959 # The default user might be based on outdated state which
960 # has been loaded from the cache.
960 # has been loaded from the cache.
961 # A call to refresh() ensures that the
961 # A call to refresh() ensures that the
962 # latest state from the database is used.
962 # latest state from the database is used.
963 Session().refresh(user)
963 Session().refresh(user)
964 return user
964 return user
965
965
966 def _get_default_perms(self, user, suffix=''):
966 def _get_default_perms(self, user, suffix=''):
967 from rhodecode.model.permission import PermissionModel
967 from rhodecode.model.permission import PermissionModel
968 return PermissionModel().get_default_perms(user.user_perms, suffix)
968 return PermissionModel().get_default_perms(user.user_perms, suffix)
969
969
970 def get_default_perms(self, suffix=''):
970 def get_default_perms(self, suffix=''):
971 return self._get_default_perms(self, suffix)
971 return self._get_default_perms(self, suffix)
972
972
973 def get_api_data(self, include_secrets=False, details='full'):
973 def get_api_data(self, include_secrets=False, details='full'):
974 """
974 """
975 Common function for generating user related data for API
975 Common function for generating user related data for API
976
976
977 :param include_secrets: By default secrets in the API data will be replaced
977 :param include_secrets: By default secrets in the API data will be replaced
978 by a placeholder value to prevent exposing this data by accident. In case
978 by a placeholder value to prevent exposing this data by accident. In case
979 this data shall be exposed, set this flag to ``True``.
979 this data shall be exposed, set this flag to ``True``.
980
980
981 :param details: details can be 'basic|full' basic gives only a subset of
981 :param details: details can be 'basic|full' basic gives only a subset of
982 the available user information that includes user_id, name and emails.
982 the available user information that includes user_id, name and emails.
983 """
983 """
984 user = self
984 user = self
985 user_data = self.user_data
985 user_data = self.user_data
986 data = {
986 data = {
987 'user_id': user.user_id,
987 'user_id': user.user_id,
988 'username': user.username,
988 'username': user.username,
989 'firstname': user.name,
989 'firstname': user.name,
990 'lastname': user.lastname,
990 'lastname': user.lastname,
991 'email': user.email,
991 'email': user.email,
992 'emails': user.emails,
992 'emails': user.emails,
993 }
993 }
994 if details == 'basic':
994 if details == 'basic':
995 return data
995 return data
996
996
997 auth_token_length = 40
997 auth_token_length = 40
998 auth_token_replacement = '*' * auth_token_length
998 auth_token_replacement = '*' * auth_token_length
999
999
1000 extras = {
1000 extras = {
1001 'auth_tokens': [auth_token_replacement],
1001 'auth_tokens': [auth_token_replacement],
1002 'active': user.active,
1002 'active': user.active,
1003 'admin': user.admin,
1003 'admin': user.admin,
1004 'extern_type': user.extern_type,
1004 'extern_type': user.extern_type,
1005 'extern_name': user.extern_name,
1005 'extern_name': user.extern_name,
1006 'last_login': user.last_login,
1006 'last_login': user.last_login,
1007 'last_activity': user.last_activity,
1007 'last_activity': user.last_activity,
1008 'ip_addresses': user.ip_addresses,
1008 'ip_addresses': user.ip_addresses,
1009 'language': user_data.get('language')
1009 'language': user_data.get('language')
1010 }
1010 }
1011 data.update(extras)
1011 data.update(extras)
1012
1012
1013 if include_secrets:
1013 if include_secrets:
1014 data['auth_tokens'] = user.auth_tokens
1014 data['auth_tokens'] = user.auth_tokens
1015 return data
1015 return data
1016
1016
1017 def __json__(self):
1017 def __json__(self):
1018 data = {
1018 data = {
1019 'full_name': self.full_name,
1019 'full_name': self.full_name,
1020 'full_name_or_username': self.full_name_or_username,
1020 'full_name_or_username': self.full_name_or_username,
1021 'short_contact': self.short_contact,
1021 'short_contact': self.short_contact,
1022 'full_contact': self.full_contact,
1022 'full_contact': self.full_contact,
1023 }
1023 }
1024 data.update(self.get_api_data())
1024 data.update(self.get_api_data())
1025 return data
1025 return data
1026
1026
1027
1027
1028 class UserApiKeys(Base, BaseModel):
1028 class UserApiKeys(Base, BaseModel):
1029 __tablename__ = 'user_api_keys'
1029 __tablename__ = 'user_api_keys'
1030 __table_args__ = (
1030 __table_args__ = (
1031 Index('uak_api_key_idx', 'api_key', unique=True),
1031 Index('uak_api_key_idx', 'api_key', unique=True),
1032 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1032 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1033 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1033 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1034 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1034 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1035 )
1035 )
1036 __mapper_args__ = {}
1036 __mapper_args__ = {}
1037
1037
1038 # ApiKey role
1038 # ApiKey role
1039 ROLE_ALL = 'token_role_all'
1039 ROLE_ALL = 'token_role_all'
1040 ROLE_HTTP = 'token_role_http'
1040 ROLE_HTTP = 'token_role_http'
1041 ROLE_VCS = 'token_role_vcs'
1041 ROLE_VCS = 'token_role_vcs'
1042 ROLE_API = 'token_role_api'
1042 ROLE_API = 'token_role_api'
1043 ROLE_FEED = 'token_role_feed'
1043 ROLE_FEED = 'token_role_feed'
1044 ROLE_PASSWORD_RESET = 'token_password_reset'
1044 ROLE_PASSWORD_RESET = 'token_password_reset'
1045
1045
1046 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1046 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1047
1047
1048 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1048 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1049 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1049 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1050 api_key = Column("api_key", String(255), nullable=False, unique=True)
1050 api_key = Column("api_key", String(255), nullable=False, unique=True)
1051 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1051 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1052 expires = Column('expires', Float(53), nullable=False)
1052 expires = Column('expires', Float(53), nullable=False)
1053 role = Column('role', String(255), nullable=True)
1053 role = Column('role', String(255), nullable=True)
1054 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1054 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1055
1055
1056 # scope columns
1056 # scope columns
1057 repo_id = Column(
1057 repo_id = Column(
1058 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1058 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1059 nullable=True, unique=None, default=None)
1059 nullable=True, unique=None, default=None)
1060 repo = relationship('Repository', lazy='joined')
1060 repo = relationship('Repository', lazy='joined')
1061
1061
1062 repo_group_id = Column(
1062 repo_group_id = Column(
1063 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1063 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1064 nullable=True, unique=None, default=None)
1064 nullable=True, unique=None, default=None)
1065 repo_group = relationship('RepoGroup', lazy='joined')
1065 repo_group = relationship('RepoGroup', lazy='joined')
1066
1066
1067 user = relationship('User', lazy='joined')
1067 user = relationship('User', lazy='joined')
1068
1068
1069 def __unicode__(self):
1069 def __unicode__(self):
1070 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1070 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1071
1071
1072 def __json__(self):
1072 def __json__(self):
1073 data = {
1073 data = {
1074 'auth_token': self.api_key,
1074 'auth_token': self.api_key,
1075 'role': self.role,
1075 'role': self.role,
1076 'scope': self.scope_humanized,
1076 'scope': self.scope_humanized,
1077 'expired': self.expired
1077 'expired': self.expired
1078 }
1078 }
1079 return data
1079 return data
1080
1080
1081 def get_api_data(self, include_secrets=False):
1081 def get_api_data(self, include_secrets=False):
1082 data = self.__json__()
1082 data = self.__json__()
1083 if include_secrets:
1083 if include_secrets:
1084 return data
1084 return data
1085 else:
1085 else:
1086 data['auth_token'] = self.token_obfuscated
1086 data['auth_token'] = self.token_obfuscated
1087 return data
1087 return data
1088
1088
1089 @hybrid_property
1089 @hybrid_property
1090 def description_safe(self):
1090 def description_safe(self):
1091 from rhodecode.lib import helpers as h
1091 from rhodecode.lib import helpers as h
1092 return h.escape(self.description)
1092 return h.escape(self.description)
1093
1093
1094 @property
1094 @property
1095 def expired(self):
1095 def expired(self):
1096 if self.expires == -1:
1096 if self.expires == -1:
1097 return False
1097 return False
1098 return time.time() > self.expires
1098 return time.time() > self.expires
1099
1099
1100 @classmethod
1100 @classmethod
1101 def _get_role_name(cls, role):
1101 def _get_role_name(cls, role):
1102 return {
1102 return {
1103 cls.ROLE_ALL: _('all'),
1103 cls.ROLE_ALL: _('all'),
1104 cls.ROLE_HTTP: _('http/web interface'),
1104 cls.ROLE_HTTP: _('http/web interface'),
1105 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1105 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1106 cls.ROLE_API: _('api calls'),
1106 cls.ROLE_API: _('api calls'),
1107 cls.ROLE_FEED: _('feed access'),
1107 cls.ROLE_FEED: _('feed access'),
1108 }.get(role, role)
1108 }.get(role, role)
1109
1109
1110 @property
1110 @property
1111 def role_humanized(self):
1111 def role_humanized(self):
1112 return self._get_role_name(self.role)
1112 return self._get_role_name(self.role)
1113
1113
1114 def _get_scope(self):
1114 def _get_scope(self):
1115 if self.repo:
1115 if self.repo:
1116 return repr(self.repo)
1116 return repr(self.repo)
1117 if self.repo_group:
1117 if self.repo_group:
1118 return repr(self.repo_group) + ' (recursive)'
1118 return repr(self.repo_group) + ' (recursive)'
1119 return 'global'
1119 return 'global'
1120
1120
1121 @property
1121 @property
1122 def scope_humanized(self):
1122 def scope_humanized(self):
1123 return self._get_scope()
1123 return self._get_scope()
1124
1124
1125 @property
1125 @property
1126 def token_obfuscated(self):
1126 def token_obfuscated(self):
1127 if self.api_key:
1127 if self.api_key:
1128 return self.api_key[:4] + "****"
1128 return self.api_key[:4] + "****"
1129
1129
1130
1130
1131 class UserEmailMap(Base, BaseModel):
1131 class UserEmailMap(Base, BaseModel):
1132 __tablename__ = 'user_email_map'
1132 __tablename__ = 'user_email_map'
1133 __table_args__ = (
1133 __table_args__ = (
1134 Index('uem_email_idx', 'email'),
1134 Index('uem_email_idx', 'email'),
1135 UniqueConstraint('email'),
1135 UniqueConstraint('email'),
1136 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1136 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1137 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1137 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1138 )
1138 )
1139 __mapper_args__ = {}
1139 __mapper_args__ = {}
1140
1140
1141 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1141 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1142 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1142 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1143 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1143 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1144 user = relationship('User', lazy='joined')
1144 user = relationship('User', lazy='joined')
1145
1145
1146 @validates('_email')
1146 @validates('_email')
1147 def validate_email(self, key, email):
1147 def validate_email(self, key, email):
1148 # check if this email is not main one
1148 # check if this email is not main one
1149 main_email = Session().query(User).filter(User.email == email).scalar()
1149 main_email = Session().query(User).filter(User.email == email).scalar()
1150 if main_email is not None:
1150 if main_email is not None:
1151 raise AttributeError('email %s is present is user table' % email)
1151 raise AttributeError('email %s is present is user table' % email)
1152 return email
1152 return email
1153
1153
1154 @hybrid_property
1154 @hybrid_property
1155 def email(self):
1155 def email(self):
1156 return self._email
1156 return self._email
1157
1157
1158 @email.setter
1158 @email.setter
1159 def email(self, val):
1159 def email(self, val):
1160 self._email = val.lower() if val else None
1160 self._email = val.lower() if val else None
1161
1161
1162
1162
1163 class UserIpMap(Base, BaseModel):
1163 class UserIpMap(Base, BaseModel):
1164 __tablename__ = 'user_ip_map'
1164 __tablename__ = 'user_ip_map'
1165 __table_args__ = (
1165 __table_args__ = (
1166 UniqueConstraint('user_id', 'ip_addr'),
1166 UniqueConstraint('user_id', 'ip_addr'),
1167 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1167 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1168 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1168 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1169 )
1169 )
1170 __mapper_args__ = {}
1170 __mapper_args__ = {}
1171
1171
1172 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1172 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1174 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1174 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1175 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1175 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1176 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1176 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1177 user = relationship('User', lazy='joined')
1177 user = relationship('User', lazy='joined')
1178
1178
1179 @hybrid_property
1179 @hybrid_property
1180 def description_safe(self):
1180 def description_safe(self):
1181 from rhodecode.lib import helpers as h
1181 from rhodecode.lib import helpers as h
1182 return h.escape(self.description)
1182 return h.escape(self.description)
1183
1183
1184 @classmethod
1184 @classmethod
1185 def _get_ip_range(cls, ip_addr):
1185 def _get_ip_range(cls, ip_addr):
1186 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1186 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1187 return [str(net.network_address), str(net.broadcast_address)]
1187 return [str(net.network_address), str(net.broadcast_address)]
1188
1188
1189 def __json__(self):
1189 def __json__(self):
1190 return {
1190 return {
1191 'ip_addr': self.ip_addr,
1191 'ip_addr': self.ip_addr,
1192 'ip_range': self._get_ip_range(self.ip_addr),
1192 'ip_range': self._get_ip_range(self.ip_addr),
1193 }
1193 }
1194
1194
1195 def __unicode__(self):
1195 def __unicode__(self):
1196 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1196 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1197 self.user_id, self.ip_addr)
1197 self.user_id, self.ip_addr)
1198
1198
1199
1199
1200 class UserSshKeys(Base, BaseModel):
1200 class UserSshKeys(Base, BaseModel):
1201 __tablename__ = 'user_ssh_keys'
1201 __tablename__ = 'user_ssh_keys'
1202 __table_args__ = (
1202 __table_args__ = (
1203 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1203 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1204
1204
1205 UniqueConstraint('ssh_key_fingerprint'),
1205 UniqueConstraint('ssh_key_fingerprint'),
1206
1206
1207 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1207 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1208 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1208 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1209 )
1209 )
1210 __mapper_args__ = {}
1210 __mapper_args__ = {}
1211
1211
1212 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1212 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1213 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1213 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1214 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1214 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1215
1215
1216 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1216 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1217
1217
1218 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1218 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1219 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1219 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1220 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1220 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1221
1221
1222 user = relationship('User', lazy='joined')
1222 user = relationship('User', lazy='joined')
1223
1223
1224 def __json__(self):
1224 def __json__(self):
1225 data = {
1225 data = {
1226 'ssh_fingerprint': self.ssh_key_fingerprint,
1226 'ssh_fingerprint': self.ssh_key_fingerprint,
1227 'description': self.description,
1227 'description': self.description,
1228 'created_on': self.created_on
1228 'created_on': self.created_on
1229 }
1229 }
1230 return data
1230 return data
1231
1231
1232 def get_api_data(self):
1232 def get_api_data(self):
1233 data = self.__json__()
1233 data = self.__json__()
1234 return data
1234 return data
1235
1235
1236
1236
1237 class UserLog(Base, BaseModel):
1237 class UserLog(Base, BaseModel):
1238 __tablename__ = 'user_logs'
1238 __tablename__ = 'user_logs'
1239 __table_args__ = (
1239 __table_args__ = (
1240 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1240 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1241 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1241 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1242 )
1242 )
1243 VERSION_1 = 'v1'
1243 VERSION_1 = 'v1'
1244 VERSION_2 = 'v2'
1244 VERSION_2 = 'v2'
1245 VERSIONS = [VERSION_1, VERSION_2]
1245 VERSIONS = [VERSION_1, VERSION_2]
1246
1246
1247 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1247 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1248 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1248 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1249 username = Column("username", String(255), nullable=True, unique=None, default=None)
1249 username = Column("username", String(255), nullable=True, unique=None, default=None)
1250 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1250 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1251 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1251 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1252 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1252 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1253 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1253 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1254 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1254 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1255
1255
1256 version = Column("version", String(255), nullable=True, default=VERSION_1)
1256 version = Column("version", String(255), nullable=True, default=VERSION_1)
1257 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1257 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1258 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1258 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1259
1259
1260 def __unicode__(self):
1260 def __unicode__(self):
1261 return u"<%s('id:%s:%s')>" % (
1261 return u"<%s('id:%s:%s')>" % (
1262 self.__class__.__name__, self.repository_name, self.action)
1262 self.__class__.__name__, self.repository_name, self.action)
1263
1263
1264 def __json__(self):
1264 def __json__(self):
1265 return {
1265 return {
1266 'user_id': self.user_id,
1266 'user_id': self.user_id,
1267 'username': self.username,
1267 'username': self.username,
1268 'repository_id': self.repository_id,
1268 'repository_id': self.repository_id,
1269 'repository_name': self.repository_name,
1269 'repository_name': self.repository_name,
1270 'user_ip': self.user_ip,
1270 'user_ip': self.user_ip,
1271 'action_date': self.action_date,
1271 'action_date': self.action_date,
1272 'action': self.action,
1272 'action': self.action,
1273 }
1273 }
1274
1274
1275 @hybrid_property
1275 @hybrid_property
1276 def entry_id(self):
1276 def entry_id(self):
1277 return self.user_log_id
1277 return self.user_log_id
1278
1278
1279 @property
1279 @property
1280 def action_as_day(self):
1280 def action_as_day(self):
1281 return datetime.date(*self.action_date.timetuple()[:3])
1281 return datetime.date(*self.action_date.timetuple()[:3])
1282
1282
1283 user = relationship('User')
1283 user = relationship('User')
1284 repository = relationship('Repository', cascade='')
1284 repository = relationship('Repository', cascade='')
1285
1285
1286
1286
1287 class UserGroup(Base, BaseModel):
1287 class UserGroup(Base, BaseModel):
1288 __tablename__ = 'users_groups'
1288 __tablename__ = 'users_groups'
1289 __table_args__ = (
1289 __table_args__ = (
1290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1291 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1291 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1292 )
1292 )
1293
1293
1294 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1294 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1295 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1295 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1296 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1296 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1297 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1297 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1298 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1298 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1299 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1299 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1300 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1300 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1301 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1301 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1302
1302
1303 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1303 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1304 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1304 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1305 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1305 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1306 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1306 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1307 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1307 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1308 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1308 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1309
1309
1310 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1310 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1311 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1311 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1312
1312
1313 @classmethod
1313 @classmethod
1314 def _load_group_data(cls, column):
1314 def _load_group_data(cls, column):
1315 if not column:
1315 if not column:
1316 return {}
1316 return {}
1317
1317
1318 try:
1318 try:
1319 return json.loads(column) or {}
1319 return json.loads(column) or {}
1320 except TypeError:
1320 except TypeError:
1321 return {}
1321 return {}
1322
1322
1323 @hybrid_property
1323 @hybrid_property
1324 def description_safe(self):
1324 def description_safe(self):
1325 from rhodecode.lib import helpers as h
1325 from rhodecode.lib import helpers as h
1326 return h.escape(self.description)
1326 return h.escape(self.description)
1327
1327
1328 @hybrid_property
1328 @hybrid_property
1329 def group_data(self):
1329 def group_data(self):
1330 return self._load_group_data(self._group_data)
1330 return self._load_group_data(self._group_data)
1331
1331
1332 @group_data.expression
1332 @group_data.expression
1333 def group_data(self, **kwargs):
1333 def group_data(self, **kwargs):
1334 return self._group_data
1334 return self._group_data
1335
1335
1336 @group_data.setter
1336 @group_data.setter
1337 def group_data(self, val):
1337 def group_data(self, val):
1338 try:
1338 try:
1339 self._group_data = json.dumps(val)
1339 self._group_data = json.dumps(val)
1340 except Exception:
1340 except Exception:
1341 log.error(traceback.format_exc())
1341 log.error(traceback.format_exc())
1342
1342
1343 def __unicode__(self):
1343 def __unicode__(self):
1344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1345 self.users_group_id,
1345 self.users_group_id,
1346 self.users_group_name)
1346 self.users_group_name)
1347
1347
1348 @classmethod
1348 @classmethod
1349 def get_by_group_name(cls, group_name, cache=False,
1349 def get_by_group_name(cls, group_name, cache=False,
1350 case_insensitive=False):
1350 case_insensitive=False):
1351 if case_insensitive:
1351 if case_insensitive:
1352 q = cls.query().filter(func.lower(cls.users_group_name) ==
1352 q = cls.query().filter(func.lower(cls.users_group_name) ==
1353 func.lower(group_name))
1353 func.lower(group_name))
1354
1354
1355 else:
1355 else:
1356 q = cls.query().filter(cls.users_group_name == group_name)
1356 q = cls.query().filter(cls.users_group_name == group_name)
1357 if cache:
1357 if cache:
1358 q = q.options(
1358 q = q.options(
1359 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1359 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1360 return q.scalar()
1360 return q.scalar()
1361
1361
1362 @classmethod
1362 @classmethod
1363 def get(cls, user_group_id, cache=False):
1363 def get(cls, user_group_id, cache=False):
1364 if not user_group_id:
1364 if not user_group_id:
1365 return
1365 return
1366
1366
1367 user_group = cls.query()
1367 user_group = cls.query()
1368 if cache:
1368 if cache:
1369 user_group = user_group.options(
1369 user_group = user_group.options(
1370 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1370 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1371 return user_group.get(user_group_id)
1371 return user_group.get(user_group_id)
1372
1372
1373 def permissions(self, with_admins=True, with_owner=True):
1373 def permissions(self, with_admins=True, with_owner=True):
1374 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1374 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1375 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1375 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1376 joinedload(UserUserGroupToPerm.user),
1376 joinedload(UserUserGroupToPerm.user),
1377 joinedload(UserUserGroupToPerm.permission),)
1377 joinedload(UserUserGroupToPerm.permission),)
1378
1378
1379 # get owners and admins and permissions. We do a trick of re-writing
1379 # get owners and admins and permissions. We do a trick of re-writing
1380 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1380 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1381 # has a global reference and changing one object propagates to all
1381 # has a global reference and changing one object propagates to all
1382 # others. This means if admin is also an owner admin_row that change
1382 # others. This means if admin is also an owner admin_row that change
1383 # would propagate to both objects
1383 # would propagate to both objects
1384 perm_rows = []
1384 perm_rows = []
1385 for _usr in q.all():
1385 for _usr in q.all():
1386 usr = AttributeDict(_usr.user.get_dict())
1386 usr = AttributeDict(_usr.user.get_dict())
1387 usr.permission = _usr.permission.permission_name
1387 usr.permission = _usr.permission.permission_name
1388 perm_rows.append(usr)
1388 perm_rows.append(usr)
1389
1389
1390 # filter the perm rows by 'default' first and then sort them by
1390 # filter the perm rows by 'default' first and then sort them by
1391 # admin,write,read,none permissions sorted again alphabetically in
1391 # admin,write,read,none permissions sorted again alphabetically in
1392 # each group
1392 # each group
1393 perm_rows = sorted(perm_rows, key=display_user_sort)
1393 perm_rows = sorted(perm_rows, key=display_user_sort)
1394
1394
1395 _admin_perm = 'usergroup.admin'
1395 _admin_perm = 'usergroup.admin'
1396 owner_row = []
1396 owner_row = []
1397 if with_owner:
1397 if with_owner:
1398 usr = AttributeDict(self.user.get_dict())
1398 usr = AttributeDict(self.user.get_dict())
1399 usr.owner_row = True
1399 usr.owner_row = True
1400 usr.permission = _admin_perm
1400 usr.permission = _admin_perm
1401 owner_row.append(usr)
1401 owner_row.append(usr)
1402
1402
1403 super_admin_rows = []
1403 super_admin_rows = []
1404 if with_admins:
1404 if with_admins:
1405 for usr in User.get_all_super_admins():
1405 for usr in User.get_all_super_admins():
1406 # if this admin is also owner, don't double the record
1406 # if this admin is also owner, don't double the record
1407 if usr.user_id == owner_row[0].user_id:
1407 if usr.user_id == owner_row[0].user_id:
1408 owner_row[0].admin_row = True
1408 owner_row[0].admin_row = True
1409 else:
1409 else:
1410 usr = AttributeDict(usr.get_dict())
1410 usr = AttributeDict(usr.get_dict())
1411 usr.admin_row = True
1411 usr.admin_row = True
1412 usr.permission = _admin_perm
1412 usr.permission = _admin_perm
1413 super_admin_rows.append(usr)
1413 super_admin_rows.append(usr)
1414
1414
1415 return super_admin_rows + owner_row + perm_rows
1415 return super_admin_rows + owner_row + perm_rows
1416
1416
1417 def permission_user_groups(self):
1417 def permission_user_groups(self):
1418 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1418 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1419 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1419 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1420 joinedload(UserGroupUserGroupToPerm.target_user_group),
1420 joinedload(UserGroupUserGroupToPerm.target_user_group),
1421 joinedload(UserGroupUserGroupToPerm.permission),)
1421 joinedload(UserGroupUserGroupToPerm.permission),)
1422
1422
1423 perm_rows = []
1423 perm_rows = []
1424 for _user_group in q.all():
1424 for _user_group in q.all():
1425 usr = AttributeDict(_user_group.user_group.get_dict())
1425 usr = AttributeDict(_user_group.user_group.get_dict())
1426 usr.permission = _user_group.permission.permission_name
1426 usr.permission = _user_group.permission.permission_name
1427 perm_rows.append(usr)
1427 perm_rows.append(usr)
1428
1428
1429 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1429 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1430 return perm_rows
1430 return perm_rows
1431
1431
1432 def _get_default_perms(self, user_group, suffix=''):
1432 def _get_default_perms(self, user_group, suffix=''):
1433 from rhodecode.model.permission import PermissionModel
1433 from rhodecode.model.permission import PermissionModel
1434 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1434 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1435
1435
1436 def get_default_perms(self, suffix=''):
1436 def get_default_perms(self, suffix=''):
1437 return self._get_default_perms(self, suffix)
1437 return self._get_default_perms(self, suffix)
1438
1438
1439 def get_api_data(self, with_group_members=True, include_secrets=False):
1439 def get_api_data(self, with_group_members=True, include_secrets=False):
1440 """
1440 """
1441 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1441 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1442 basically forwarded.
1442 basically forwarded.
1443
1443
1444 """
1444 """
1445 user_group = self
1445 user_group = self
1446 data = {
1446 data = {
1447 'users_group_id': user_group.users_group_id,
1447 'users_group_id': user_group.users_group_id,
1448 'group_name': user_group.users_group_name,
1448 'group_name': user_group.users_group_name,
1449 'group_description': user_group.user_group_description,
1449 'group_description': user_group.user_group_description,
1450 'active': user_group.users_group_active,
1450 'active': user_group.users_group_active,
1451 'owner': user_group.user.username,
1451 'owner': user_group.user.username,
1452 'owner_email': user_group.user.email,
1452 'owner_email': user_group.user.email,
1453 }
1453 }
1454
1454
1455 if with_group_members:
1455 if with_group_members:
1456 users = []
1456 users = []
1457 for user in user_group.members:
1457 for user in user_group.members:
1458 user = user.user
1458 user = user.user
1459 users.append(user.get_api_data(include_secrets=include_secrets))
1459 users.append(user.get_api_data(include_secrets=include_secrets))
1460 data['users'] = users
1460 data['users'] = users
1461
1461
1462 return data
1462 return data
1463
1463
1464
1464
1465 class UserGroupMember(Base, BaseModel):
1465 class UserGroupMember(Base, BaseModel):
1466 __tablename__ = 'users_groups_members'
1466 __tablename__ = 'users_groups_members'
1467 __table_args__ = (
1467 __table_args__ = (
1468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1470 )
1470 )
1471
1471
1472 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1472 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1473 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1473 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1474 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1474 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1475
1475
1476 user = relationship('User', lazy='joined')
1476 user = relationship('User', lazy='joined')
1477 users_group = relationship('UserGroup')
1477 users_group = relationship('UserGroup')
1478
1478
1479 def __init__(self, gr_id='', u_id=''):
1479 def __init__(self, gr_id='', u_id=''):
1480 self.users_group_id = gr_id
1480 self.users_group_id = gr_id
1481 self.user_id = u_id
1481 self.user_id = u_id
1482
1482
1483
1483
1484 class RepositoryField(Base, BaseModel):
1484 class RepositoryField(Base, BaseModel):
1485 __tablename__ = 'repositories_fields'
1485 __tablename__ = 'repositories_fields'
1486 __table_args__ = (
1486 __table_args__ = (
1487 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1487 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1488 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1488 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1489 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1489 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1490 )
1490 )
1491 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1491 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1492
1492
1493 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1493 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1494 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1494 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1495 field_key = Column("field_key", String(250))
1495 field_key = Column("field_key", String(250))
1496 field_label = Column("field_label", String(1024), nullable=False)
1496 field_label = Column("field_label", String(1024), nullable=False)
1497 field_value = Column("field_value", String(10000), nullable=False)
1497 field_value = Column("field_value", String(10000), nullable=False)
1498 field_desc = Column("field_desc", String(1024), nullable=False)
1498 field_desc = Column("field_desc", String(1024), nullable=False)
1499 field_type = Column("field_type", String(255), nullable=False, unique=None)
1499 field_type = Column("field_type", String(255), nullable=False, unique=None)
1500 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1500 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1501
1501
1502 repository = relationship('Repository')
1502 repository = relationship('Repository')
1503
1503
1504 @property
1504 @property
1505 def field_key_prefixed(self):
1505 def field_key_prefixed(self):
1506 return 'ex_%s' % self.field_key
1506 return 'ex_%s' % self.field_key
1507
1507
1508 @classmethod
1508 @classmethod
1509 def un_prefix_key(cls, key):
1509 def un_prefix_key(cls, key):
1510 if key.startswith(cls.PREFIX):
1510 if key.startswith(cls.PREFIX):
1511 return key[len(cls.PREFIX):]
1511 return key[len(cls.PREFIX):]
1512 return key
1512 return key
1513
1513
1514 @classmethod
1514 @classmethod
1515 def get_by_key_name(cls, key, repo):
1515 def get_by_key_name(cls, key, repo):
1516 row = cls.query()\
1516 row = cls.query()\
1517 .filter(cls.repository == repo)\
1517 .filter(cls.repository == repo)\
1518 .filter(cls.field_key == key).scalar()
1518 .filter(cls.field_key == key).scalar()
1519 return row
1519 return row
1520
1520
1521
1521
1522 class Repository(Base, BaseModel):
1522 class Repository(Base, BaseModel):
1523 __tablename__ = 'repositories'
1523 __tablename__ = 'repositories'
1524 __table_args__ = (
1524 __table_args__ = (
1525 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1525 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1526 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1526 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1527 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1527 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1528 )
1528 )
1529 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1529 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1530 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1530 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1531
1531
1532 STATE_CREATED = 'repo_state_created'
1532 STATE_CREATED = 'repo_state_created'
1533 STATE_PENDING = 'repo_state_pending'
1533 STATE_PENDING = 'repo_state_pending'
1534 STATE_ERROR = 'repo_state_error'
1534 STATE_ERROR = 'repo_state_error'
1535
1535
1536 LOCK_AUTOMATIC = 'lock_auto'
1536 LOCK_AUTOMATIC = 'lock_auto'
1537 LOCK_API = 'lock_api'
1537 LOCK_API = 'lock_api'
1538 LOCK_WEB = 'lock_web'
1538 LOCK_WEB = 'lock_web'
1539 LOCK_PULL = 'lock_pull'
1539 LOCK_PULL = 'lock_pull'
1540
1540
1541 NAME_SEP = URL_SEP
1541 NAME_SEP = URL_SEP
1542
1542
1543 repo_id = Column(
1543 repo_id = Column(
1544 "repo_id", Integer(), nullable=False, unique=True, default=None,
1544 "repo_id", Integer(), nullable=False, unique=True, default=None,
1545 primary_key=True)
1545 primary_key=True)
1546 _repo_name = Column(
1546 _repo_name = Column(
1547 "repo_name", Text(), nullable=False, default=None)
1547 "repo_name", Text(), nullable=False, default=None)
1548 _repo_name_hash = Column(
1548 _repo_name_hash = Column(
1549 "repo_name_hash", String(255), nullable=False, unique=True)
1549 "repo_name_hash", String(255), nullable=False, unique=True)
1550 repo_state = Column("repo_state", String(255), nullable=True)
1550 repo_state = Column("repo_state", String(255), nullable=True)
1551
1551
1552 clone_uri = Column(
1552 clone_uri = Column(
1553 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1553 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1554 default=None)
1554 default=None)
1555 repo_type = Column(
1555 repo_type = Column(
1556 "repo_type", String(255), nullable=False, unique=False, default=None)
1556 "repo_type", String(255), nullable=False, unique=False, default=None)
1557 user_id = Column(
1557 user_id = Column(
1558 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1558 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1559 unique=False, default=None)
1559 unique=False, default=None)
1560 private = Column(
1560 private = Column(
1561 "private", Boolean(), nullable=True, unique=None, default=None)
1561 "private", Boolean(), nullable=True, unique=None, default=None)
1562 enable_statistics = Column(
1562 enable_statistics = Column(
1563 "statistics", Boolean(), nullable=True, unique=None, default=True)
1563 "statistics", Boolean(), nullable=True, unique=None, default=True)
1564 enable_downloads = Column(
1564 enable_downloads = Column(
1565 "downloads", Boolean(), nullable=True, unique=None, default=True)
1565 "downloads", Boolean(), nullable=True, unique=None, default=True)
1566 description = Column(
1566 description = Column(
1567 "description", String(10000), nullable=True, unique=None, default=None)
1567 "description", String(10000), nullable=True, unique=None, default=None)
1568 created_on = Column(
1568 created_on = Column(
1569 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1569 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1570 default=datetime.datetime.now)
1570 default=datetime.datetime.now)
1571 updated_on = Column(
1571 updated_on = Column(
1572 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1572 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1573 default=datetime.datetime.now)
1573 default=datetime.datetime.now)
1574 _landing_revision = Column(
1574 _landing_revision = Column(
1575 "landing_revision", String(255), nullable=False, unique=False,
1575 "landing_revision", String(255), nullable=False, unique=False,
1576 default=None)
1576 default=None)
1577 enable_locking = Column(
1577 enable_locking = Column(
1578 "enable_locking", Boolean(), nullable=False, unique=None,
1578 "enable_locking", Boolean(), nullable=False, unique=None,
1579 default=False)
1579 default=False)
1580 _locked = Column(
1580 _locked = Column(
1581 "locked", String(255), nullable=True, unique=False, default=None)
1581 "locked", String(255), nullable=True, unique=False, default=None)
1582 _changeset_cache = Column(
1582 _changeset_cache = Column(
1583 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1583 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1584
1584
1585 fork_id = Column(
1585 fork_id = Column(
1586 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1586 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1587 nullable=True, unique=False, default=None)
1587 nullable=True, unique=False, default=None)
1588 group_id = Column(
1588 group_id = Column(
1589 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1589 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1590 unique=False, default=None)
1590 unique=False, default=None)
1591
1591
1592 user = relationship('User', lazy='joined')
1592 user = relationship('User', lazy='joined')
1593 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1593 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1594 group = relationship('RepoGroup', lazy='joined')
1594 group = relationship('RepoGroup', lazy='joined')
1595 repo_to_perm = relationship(
1595 repo_to_perm = relationship(
1596 'UserRepoToPerm', cascade='all',
1596 'UserRepoToPerm', cascade='all',
1597 order_by='UserRepoToPerm.repo_to_perm_id')
1597 order_by='UserRepoToPerm.repo_to_perm_id')
1598 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1598 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1599 stats = relationship('Statistics', cascade='all', uselist=False)
1599 stats = relationship('Statistics', cascade='all', uselist=False)
1600
1600
1601 followers = relationship(
1601 followers = relationship(
1602 'UserFollowing',
1602 'UserFollowing',
1603 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1603 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1604 cascade='all')
1604 cascade='all')
1605 extra_fields = relationship(
1605 extra_fields = relationship(
1606 'RepositoryField', cascade="all, delete, delete-orphan")
1606 'RepositoryField', cascade="all, delete, delete-orphan")
1607 logs = relationship('UserLog')
1607 logs = relationship('UserLog')
1608 comments = relationship(
1608 comments = relationship(
1609 'ChangesetComment', cascade="all, delete, delete-orphan")
1609 'ChangesetComment', cascade="all, delete, delete-orphan")
1610 pull_requests_source = relationship(
1610 pull_requests_source = relationship(
1611 'PullRequest',
1611 'PullRequest',
1612 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1612 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1613 cascade="all, delete, delete-orphan")
1613 cascade="all, delete, delete-orphan")
1614 pull_requests_target = relationship(
1614 pull_requests_target = relationship(
1615 'PullRequest',
1615 'PullRequest',
1616 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1616 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1617 cascade="all, delete, delete-orphan")
1617 cascade="all, delete, delete-orphan")
1618 ui = relationship('RepoRhodeCodeUi', cascade="all")
1618 ui = relationship('RepoRhodeCodeUi', cascade="all")
1619 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1619 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1620 integrations = relationship('Integration',
1620 integrations = relationship('Integration',
1621 cascade="all, delete, delete-orphan")
1621 cascade="all, delete, delete-orphan")
1622
1622
1623 def __unicode__(self):
1623 def __unicode__(self):
1624 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1624 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1625 safe_unicode(self.repo_name))
1625 safe_unicode(self.repo_name))
1626
1626
1627 @hybrid_property
1627 @hybrid_property
1628 def description_safe(self):
1628 def description_safe(self):
1629 from rhodecode.lib import helpers as h
1629 from rhodecode.lib import helpers as h
1630 return h.escape(self.description)
1630 return h.escape(self.description)
1631
1631
1632 @hybrid_property
1632 @hybrid_property
1633 def landing_rev(self):
1633 def landing_rev(self):
1634 # always should return [rev_type, rev]
1634 # always should return [rev_type, rev]
1635 if self._landing_revision:
1635 if self._landing_revision:
1636 _rev_info = self._landing_revision.split(':')
1636 _rev_info = self._landing_revision.split(':')
1637 if len(_rev_info) < 2:
1637 if len(_rev_info) < 2:
1638 _rev_info.insert(0, 'rev')
1638 _rev_info.insert(0, 'rev')
1639 return [_rev_info[0], _rev_info[1]]
1639 return [_rev_info[0], _rev_info[1]]
1640 return [None, None]
1640 return [None, None]
1641
1641
1642 @landing_rev.setter
1642 @landing_rev.setter
1643 def landing_rev(self, val):
1643 def landing_rev(self, val):
1644 if ':' not in val:
1644 if ':' not in val:
1645 raise ValueError('value must be delimited with `:` and consist '
1645 raise ValueError('value must be delimited with `:` and consist '
1646 'of <rev_type>:<rev>, got %s instead' % val)
1646 'of <rev_type>:<rev>, got %s instead' % val)
1647 self._landing_revision = val
1647 self._landing_revision = val
1648
1648
1649 @hybrid_property
1649 @hybrid_property
1650 def locked(self):
1650 def locked(self):
1651 if self._locked:
1651 if self._locked:
1652 user_id, timelocked, reason = self._locked.split(':')
1652 user_id, timelocked, reason = self._locked.split(':')
1653 lock_values = int(user_id), timelocked, reason
1653 lock_values = int(user_id), timelocked, reason
1654 else:
1654 else:
1655 lock_values = [None, None, None]
1655 lock_values = [None, None, None]
1656 return lock_values
1656 return lock_values
1657
1657
1658 @locked.setter
1658 @locked.setter
1659 def locked(self, val):
1659 def locked(self, val):
1660 if val and isinstance(val, (list, tuple)):
1660 if val and isinstance(val, (list, tuple)):
1661 self._locked = ':'.join(map(str, val))
1661 self._locked = ':'.join(map(str, val))
1662 else:
1662 else:
1663 self._locked = None
1663 self._locked = None
1664
1664
1665 @hybrid_property
1665 @hybrid_property
1666 def changeset_cache(self):
1666 def changeset_cache(self):
1667 from rhodecode.lib.vcs.backends.base import EmptyCommit
1667 from rhodecode.lib.vcs.backends.base import EmptyCommit
1668 dummy = EmptyCommit().__json__()
1668 dummy = EmptyCommit().__json__()
1669 if not self._changeset_cache:
1669 if not self._changeset_cache:
1670 return dummy
1670 return dummy
1671 try:
1671 try:
1672 return json.loads(self._changeset_cache)
1672 return json.loads(self._changeset_cache)
1673 except TypeError:
1673 except TypeError:
1674 return dummy
1674 return dummy
1675 except Exception:
1675 except Exception:
1676 log.error(traceback.format_exc())
1676 log.error(traceback.format_exc())
1677 return dummy
1677 return dummy
1678
1678
1679 @changeset_cache.setter
1679 @changeset_cache.setter
1680 def changeset_cache(self, val):
1680 def changeset_cache(self, val):
1681 try:
1681 try:
1682 self._changeset_cache = json.dumps(val)
1682 self._changeset_cache = json.dumps(val)
1683 except Exception:
1683 except Exception:
1684 log.error(traceback.format_exc())
1684 log.error(traceback.format_exc())
1685
1685
1686 @hybrid_property
1686 @hybrid_property
1687 def repo_name(self):
1687 def repo_name(self):
1688 return self._repo_name
1688 return self._repo_name
1689
1689
1690 @repo_name.setter
1690 @repo_name.setter
1691 def repo_name(self, value):
1691 def repo_name(self, value):
1692 self._repo_name = value
1692 self._repo_name = value
1693 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1693 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1694
1694
1695 @classmethod
1695 @classmethod
1696 def normalize_repo_name(cls, repo_name):
1696 def normalize_repo_name(cls, repo_name):
1697 """
1697 """
1698 Normalizes os specific repo_name to the format internally stored inside
1698 Normalizes os specific repo_name to the format internally stored inside
1699 database using URL_SEP
1699 database using URL_SEP
1700
1700
1701 :param cls:
1701 :param cls:
1702 :param repo_name:
1702 :param repo_name:
1703 """
1703 """
1704 return cls.NAME_SEP.join(repo_name.split(os.sep))
1704 return cls.NAME_SEP.join(repo_name.split(os.sep))
1705
1705
1706 @classmethod
1706 @classmethod
1707 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1707 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1708 session = Session()
1708 session = Session()
1709 q = session.query(cls).filter(cls.repo_name == repo_name)
1709 q = session.query(cls).filter(cls.repo_name == repo_name)
1710
1710
1711 if cache:
1711 if cache:
1712 if identity_cache:
1712 if identity_cache:
1713 val = cls.identity_cache(session, 'repo_name', repo_name)
1713 val = cls.identity_cache(session, 'repo_name', repo_name)
1714 if val:
1714 if val:
1715 return val
1715 return val
1716 else:
1716 else:
1717 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1717 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1718 q = q.options(
1718 q = q.options(
1719 FromCache("sql_cache_short", cache_key))
1719 FromCache("sql_cache_short", cache_key))
1720
1720
1721 return q.scalar()
1721 return q.scalar()
1722
1722
1723 @classmethod
1723 @classmethod
1724 def get_by_full_path(cls, repo_full_path):
1724 def get_by_full_path(cls, repo_full_path):
1725 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1725 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1726 repo_name = cls.normalize_repo_name(repo_name)
1726 repo_name = cls.normalize_repo_name(repo_name)
1727 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1727 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1728
1728
1729 @classmethod
1729 @classmethod
1730 def get_repo_forks(cls, repo_id):
1730 def get_repo_forks(cls, repo_id):
1731 return cls.query().filter(Repository.fork_id == repo_id)
1731 return cls.query().filter(Repository.fork_id == repo_id)
1732
1732
1733 @classmethod
1733 @classmethod
1734 def base_path(cls):
1734 def base_path(cls):
1735 """
1735 """
1736 Returns base path when all repos are stored
1736 Returns base path when all repos are stored
1737
1737
1738 :param cls:
1738 :param cls:
1739 """
1739 """
1740 q = Session().query(RhodeCodeUi)\
1740 q = Session().query(RhodeCodeUi)\
1741 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1741 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1742 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1742 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1743 return q.one().ui_value
1743 return q.one().ui_value
1744
1744
1745 @classmethod
1745 @classmethod
1746 def is_valid(cls, repo_name):
1746 def is_valid(cls, repo_name):
1747 """
1747 """
1748 returns True if given repo name is a valid filesystem repository
1748 returns True if given repo name is a valid filesystem repository
1749
1749
1750 :param cls:
1750 :param cls:
1751 :param repo_name:
1751 :param repo_name:
1752 """
1752 """
1753 from rhodecode.lib.utils import is_valid_repo
1753 from rhodecode.lib.utils import is_valid_repo
1754
1754
1755 return is_valid_repo(repo_name, cls.base_path())
1755 return is_valid_repo(repo_name, cls.base_path())
1756
1756
1757 @classmethod
1757 @classmethod
1758 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1758 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1759 case_insensitive=True):
1759 case_insensitive=True):
1760 q = Repository.query()
1760 q = Repository.query()
1761
1761
1762 if not isinstance(user_id, Optional):
1762 if not isinstance(user_id, Optional):
1763 q = q.filter(Repository.user_id == user_id)
1763 q = q.filter(Repository.user_id == user_id)
1764
1764
1765 if not isinstance(group_id, Optional):
1765 if not isinstance(group_id, Optional):
1766 q = q.filter(Repository.group_id == group_id)
1766 q = q.filter(Repository.group_id == group_id)
1767
1767
1768 if case_insensitive:
1768 if case_insensitive:
1769 q = q.order_by(func.lower(Repository.repo_name))
1769 q = q.order_by(func.lower(Repository.repo_name))
1770 else:
1770 else:
1771 q = q.order_by(Repository.repo_name)
1771 q = q.order_by(Repository.repo_name)
1772 return q.all()
1772 return q.all()
1773
1773
1774 @property
1774 @property
1775 def forks(self):
1775 def forks(self):
1776 """
1776 """
1777 Return forks of this repo
1777 Return forks of this repo
1778 """
1778 """
1779 return Repository.get_repo_forks(self.repo_id)
1779 return Repository.get_repo_forks(self.repo_id)
1780
1780
1781 @property
1781 @property
1782 def parent(self):
1782 def parent(self):
1783 """
1783 """
1784 Returns fork parent
1784 Returns fork parent
1785 """
1785 """
1786 return self.fork
1786 return self.fork
1787
1787
1788 @property
1788 @property
1789 def just_name(self):
1789 def just_name(self):
1790 return self.repo_name.split(self.NAME_SEP)[-1]
1790 return self.repo_name.split(self.NAME_SEP)[-1]
1791
1791
1792 @property
1792 @property
1793 def groups_with_parents(self):
1793 def groups_with_parents(self):
1794 groups = []
1794 groups = []
1795 if self.group is None:
1795 if self.group is None:
1796 return groups
1796 return groups
1797
1797
1798 cur_gr = self.group
1798 cur_gr = self.group
1799 groups.insert(0, cur_gr)
1799 groups.insert(0, cur_gr)
1800 while 1:
1800 while 1:
1801 gr = getattr(cur_gr, 'parent_group', None)
1801 gr = getattr(cur_gr, 'parent_group', None)
1802 cur_gr = cur_gr.parent_group
1802 cur_gr = cur_gr.parent_group
1803 if gr is None:
1803 if gr is None:
1804 break
1804 break
1805 groups.insert(0, gr)
1805 groups.insert(0, gr)
1806
1806
1807 return groups
1807 return groups
1808
1808
1809 @property
1809 @property
1810 def groups_and_repo(self):
1810 def groups_and_repo(self):
1811 return self.groups_with_parents, self
1811 return self.groups_with_parents, self
1812
1812
1813 @LazyProperty
1813 @LazyProperty
1814 def repo_path(self):
1814 def repo_path(self):
1815 """
1815 """
1816 Returns base full path for that repository means where it actually
1816 Returns base full path for that repository means where it actually
1817 exists on a filesystem
1817 exists on a filesystem
1818 """
1818 """
1819 q = Session().query(RhodeCodeUi).filter(
1819 q = Session().query(RhodeCodeUi).filter(
1820 RhodeCodeUi.ui_key == self.NAME_SEP)
1820 RhodeCodeUi.ui_key == self.NAME_SEP)
1821 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1821 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1822 return q.one().ui_value
1822 return q.one().ui_value
1823
1823
1824 @property
1824 @property
1825 def repo_full_path(self):
1825 def repo_full_path(self):
1826 p = [self.repo_path]
1826 p = [self.repo_path]
1827 # we need to split the name by / since this is how we store the
1827 # we need to split the name by / since this is how we store the
1828 # names in the database, but that eventually needs to be converted
1828 # names in the database, but that eventually needs to be converted
1829 # into a valid system path
1829 # into a valid system path
1830 p += self.repo_name.split(self.NAME_SEP)
1830 p += self.repo_name.split(self.NAME_SEP)
1831 return os.path.join(*map(safe_unicode, p))
1831 return os.path.join(*map(safe_unicode, p))
1832
1832
1833 @property
1833 @property
1834 def cache_keys(self):
1834 def cache_keys(self):
1835 """
1835 """
1836 Returns associated cache keys for that repo
1836 Returns associated cache keys for that repo
1837 """
1837 """
1838 return CacheKey.query()\
1838 return CacheKey.query()\
1839 .filter(CacheKey.cache_args == self.repo_name)\
1839 .filter(CacheKey.cache_args == self.repo_name)\
1840 .order_by(CacheKey.cache_key)\
1840 .order_by(CacheKey.cache_key)\
1841 .all()
1841 .all()
1842
1842
1843 def get_new_name(self, repo_name):
1843 def get_new_name(self, repo_name):
1844 """
1844 """
1845 returns new full repository name based on assigned group and new new
1845 returns new full repository name based on assigned group and new new
1846
1846
1847 :param group_name:
1847 :param group_name:
1848 """
1848 """
1849 path_prefix = self.group.full_path_splitted if self.group else []
1849 path_prefix = self.group.full_path_splitted if self.group else []
1850 return self.NAME_SEP.join(path_prefix + [repo_name])
1850 return self.NAME_SEP.join(path_prefix + [repo_name])
1851
1851
1852 @property
1852 @property
1853 def _config(self):
1853 def _config(self):
1854 """
1854 """
1855 Returns db based config object.
1855 Returns db based config object.
1856 """
1856 """
1857 from rhodecode.lib.utils import make_db_config
1857 from rhodecode.lib.utils import make_db_config
1858 return make_db_config(clear_session=False, repo=self)
1858 return make_db_config(clear_session=False, repo=self)
1859
1859
1860 def permissions(self, with_admins=True, with_owner=True):
1860 def permissions(self, with_admins=True, with_owner=True):
1861 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1861 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1862 q = q.options(joinedload(UserRepoToPerm.repository),
1862 q = q.options(joinedload(UserRepoToPerm.repository),
1863 joinedload(UserRepoToPerm.user),
1863 joinedload(UserRepoToPerm.user),
1864 joinedload(UserRepoToPerm.permission),)
1864 joinedload(UserRepoToPerm.permission),)
1865
1865
1866 # get owners and admins and permissions. We do a trick of re-writing
1866 # get owners and admins and permissions. We do a trick of re-writing
1867 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1867 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1868 # has a global reference and changing one object propagates to all
1868 # has a global reference and changing one object propagates to all
1869 # others. This means if admin is also an owner admin_row that change
1869 # others. This means if admin is also an owner admin_row that change
1870 # would propagate to both objects
1870 # would propagate to both objects
1871 perm_rows = []
1871 perm_rows = []
1872 for _usr in q.all():
1872 for _usr in q.all():
1873 usr = AttributeDict(_usr.user.get_dict())
1873 usr = AttributeDict(_usr.user.get_dict())
1874 usr.permission = _usr.permission.permission_name
1874 usr.permission = _usr.permission.permission_name
1875 perm_rows.append(usr)
1875 perm_rows.append(usr)
1876
1876
1877 # filter the perm rows by 'default' first and then sort them by
1877 # filter the perm rows by 'default' first and then sort them by
1878 # admin,write,read,none permissions sorted again alphabetically in
1878 # admin,write,read,none permissions sorted again alphabetically in
1879 # each group
1879 # each group
1880 perm_rows = sorted(perm_rows, key=display_user_sort)
1880 perm_rows = sorted(perm_rows, key=display_user_sort)
1881
1881
1882 _admin_perm = 'repository.admin'
1882 _admin_perm = 'repository.admin'
1883 owner_row = []
1883 owner_row = []
1884 if with_owner:
1884 if with_owner:
1885 usr = AttributeDict(self.user.get_dict())
1885 usr = AttributeDict(self.user.get_dict())
1886 usr.owner_row = True
1886 usr.owner_row = True
1887 usr.permission = _admin_perm
1887 usr.permission = _admin_perm
1888 owner_row.append(usr)
1888 owner_row.append(usr)
1889
1889
1890 super_admin_rows = []
1890 super_admin_rows = []
1891 if with_admins:
1891 if with_admins:
1892 for usr in User.get_all_super_admins():
1892 for usr in User.get_all_super_admins():
1893 # if this admin is also owner, don't double the record
1893 # if this admin is also owner, don't double the record
1894 if usr.user_id == owner_row[0].user_id:
1894 if usr.user_id == owner_row[0].user_id:
1895 owner_row[0].admin_row = True
1895 owner_row[0].admin_row = True
1896 else:
1896 else:
1897 usr = AttributeDict(usr.get_dict())
1897 usr = AttributeDict(usr.get_dict())
1898 usr.admin_row = True
1898 usr.admin_row = True
1899 usr.permission = _admin_perm
1899 usr.permission = _admin_perm
1900 super_admin_rows.append(usr)
1900 super_admin_rows.append(usr)
1901
1901
1902 return super_admin_rows + owner_row + perm_rows
1902 return super_admin_rows + owner_row + perm_rows
1903
1903
1904 def permission_user_groups(self):
1904 def permission_user_groups(self):
1905 q = UserGroupRepoToPerm.query().filter(
1905 q = UserGroupRepoToPerm.query().filter(
1906 UserGroupRepoToPerm.repository == self)
1906 UserGroupRepoToPerm.repository == self)
1907 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1907 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1908 joinedload(UserGroupRepoToPerm.users_group),
1908 joinedload(UserGroupRepoToPerm.users_group),
1909 joinedload(UserGroupRepoToPerm.permission),)
1909 joinedload(UserGroupRepoToPerm.permission),)
1910
1910
1911 perm_rows = []
1911 perm_rows = []
1912 for _user_group in q.all():
1912 for _user_group in q.all():
1913 usr = AttributeDict(_user_group.users_group.get_dict())
1913 usr = AttributeDict(_user_group.users_group.get_dict())
1914 usr.permission = _user_group.permission.permission_name
1914 usr.permission = _user_group.permission.permission_name
1915 perm_rows.append(usr)
1915 perm_rows.append(usr)
1916
1916
1917 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1917 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1918 return perm_rows
1918 return perm_rows
1919
1919
1920 def get_api_data(self, include_secrets=False):
1920 def get_api_data(self, include_secrets=False):
1921 """
1921 """
1922 Common function for generating repo api data
1922 Common function for generating repo api data
1923
1923
1924 :param include_secrets: See :meth:`User.get_api_data`.
1924 :param include_secrets: See :meth:`User.get_api_data`.
1925
1925
1926 """
1926 """
1927 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1927 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1928 # move this methods on models level.
1928 # move this methods on models level.
1929 from rhodecode.model.settings import SettingsModel
1929 from rhodecode.model.settings import SettingsModel
1930 from rhodecode.model.repo import RepoModel
1930 from rhodecode.model.repo import RepoModel
1931
1931
1932 repo = self
1932 repo = self
1933 _user_id, _time, _reason = self.locked
1933 _user_id, _time, _reason = self.locked
1934
1934
1935 data = {
1935 data = {
1936 'repo_id': repo.repo_id,
1936 'repo_id': repo.repo_id,
1937 'repo_name': repo.repo_name,
1937 'repo_name': repo.repo_name,
1938 'repo_type': repo.repo_type,
1938 'repo_type': repo.repo_type,
1939 'clone_uri': repo.clone_uri or '',
1939 'clone_uri': repo.clone_uri or '',
1940 'url': RepoModel().get_url(self),
1940 'url': RepoModel().get_url(self),
1941 'private': repo.private,
1941 'private': repo.private,
1942 'created_on': repo.created_on,
1942 'created_on': repo.created_on,
1943 'description': repo.description_safe,
1943 'description': repo.description_safe,
1944 'landing_rev': repo.landing_rev,
1944 'landing_rev': repo.landing_rev,
1945 'owner': repo.user.username,
1945 'owner': repo.user.username,
1946 'fork_of': repo.fork.repo_name if repo.fork else None,
1946 'fork_of': repo.fork.repo_name if repo.fork else None,
1947 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1947 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1948 'enable_statistics': repo.enable_statistics,
1948 'enable_statistics': repo.enable_statistics,
1949 'enable_locking': repo.enable_locking,
1949 'enable_locking': repo.enable_locking,
1950 'enable_downloads': repo.enable_downloads,
1950 'enable_downloads': repo.enable_downloads,
1951 'last_changeset': repo.changeset_cache,
1951 'last_changeset': repo.changeset_cache,
1952 'locked_by': User.get(_user_id).get_api_data(
1952 'locked_by': User.get(_user_id).get_api_data(
1953 include_secrets=include_secrets) if _user_id else None,
1953 include_secrets=include_secrets) if _user_id else None,
1954 'locked_date': time_to_datetime(_time) if _time else None,
1954 'locked_date': time_to_datetime(_time) if _time else None,
1955 'lock_reason': _reason if _reason else None,
1955 'lock_reason': _reason if _reason else None,
1956 }
1956 }
1957
1957
1958 # TODO: mikhail: should be per-repo settings here
1958 # TODO: mikhail: should be per-repo settings here
1959 rc_config = SettingsModel().get_all_settings()
1959 rc_config = SettingsModel().get_all_settings()
1960 repository_fields = str2bool(
1960 repository_fields = str2bool(
1961 rc_config.get('rhodecode_repository_fields'))
1961 rc_config.get('rhodecode_repository_fields'))
1962 if repository_fields:
1962 if repository_fields:
1963 for f in self.extra_fields:
1963 for f in self.extra_fields:
1964 data[f.field_key_prefixed] = f.field_value
1964 data[f.field_key_prefixed] = f.field_value
1965
1965
1966 return data
1966 return data
1967
1967
1968 @classmethod
1968 @classmethod
1969 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1969 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1970 if not lock_time:
1970 if not lock_time:
1971 lock_time = time.time()
1971 lock_time = time.time()
1972 if not lock_reason:
1972 if not lock_reason:
1973 lock_reason = cls.LOCK_AUTOMATIC
1973 lock_reason = cls.LOCK_AUTOMATIC
1974 repo.locked = [user_id, lock_time, lock_reason]
1974 repo.locked = [user_id, lock_time, lock_reason]
1975 Session().add(repo)
1975 Session().add(repo)
1976 Session().commit()
1976 Session().commit()
1977
1977
1978 @classmethod
1978 @classmethod
1979 def unlock(cls, repo):
1979 def unlock(cls, repo):
1980 repo.locked = None
1980 repo.locked = None
1981 Session().add(repo)
1981 Session().add(repo)
1982 Session().commit()
1982 Session().commit()
1983
1983
1984 @classmethod
1984 @classmethod
1985 def getlock(cls, repo):
1985 def getlock(cls, repo):
1986 return repo.locked
1986 return repo.locked
1987
1987
1988 def is_user_lock(self, user_id):
1988 def is_user_lock(self, user_id):
1989 if self.lock[0]:
1989 if self.lock[0]:
1990 lock_user_id = safe_int(self.lock[0])
1990 lock_user_id = safe_int(self.lock[0])
1991 user_id = safe_int(user_id)
1991 user_id = safe_int(user_id)
1992 # both are ints, and they are equal
1992 # both are ints, and they are equal
1993 return all([lock_user_id, user_id]) and lock_user_id == user_id
1993 return all([lock_user_id, user_id]) and lock_user_id == user_id
1994
1994
1995 return False
1995 return False
1996
1996
1997 def get_locking_state(self, action, user_id, only_when_enabled=True):
1997 def get_locking_state(self, action, user_id, only_when_enabled=True):
1998 """
1998 """
1999 Checks locking on this repository, if locking is enabled and lock is
1999 Checks locking on this repository, if locking is enabled and lock is
2000 present returns a tuple of make_lock, locked, locked_by.
2000 present returns a tuple of make_lock, locked, locked_by.
2001 make_lock can have 3 states None (do nothing) True, make lock
2001 make_lock can have 3 states None (do nothing) True, make lock
2002 False release lock, This value is later propagated to hooks, which
2002 False release lock, This value is later propagated to hooks, which
2003 do the locking. Think about this as signals passed to hooks what to do.
2003 do the locking. Think about this as signals passed to hooks what to do.
2004
2004
2005 """
2005 """
2006 # TODO: johbo: This is part of the business logic and should be moved
2006 # TODO: johbo: This is part of the business logic and should be moved
2007 # into the RepositoryModel.
2007 # into the RepositoryModel.
2008
2008
2009 if action not in ('push', 'pull'):
2009 if action not in ('push', 'pull'):
2010 raise ValueError("Invalid action value: %s" % repr(action))
2010 raise ValueError("Invalid action value: %s" % repr(action))
2011
2011
2012 # defines if locked error should be thrown to user
2012 # defines if locked error should be thrown to user
2013 currently_locked = False
2013 currently_locked = False
2014 # defines if new lock should be made, tri-state
2014 # defines if new lock should be made, tri-state
2015 make_lock = None
2015 make_lock = None
2016 repo = self
2016 repo = self
2017 user = User.get(user_id)
2017 user = User.get(user_id)
2018
2018
2019 lock_info = repo.locked
2019 lock_info = repo.locked
2020
2020
2021 if repo and (repo.enable_locking or not only_when_enabled):
2021 if repo and (repo.enable_locking or not only_when_enabled):
2022 if action == 'push':
2022 if action == 'push':
2023 # check if it's already locked !, if it is compare users
2023 # check if it's already locked !, if it is compare users
2024 locked_by_user_id = lock_info[0]
2024 locked_by_user_id = lock_info[0]
2025 if user.user_id == locked_by_user_id:
2025 if user.user_id == locked_by_user_id:
2026 log.debug(
2026 log.debug(
2027 'Got `push` action from user %s, now unlocking', user)
2027 'Got `push` action from user %s, now unlocking', user)
2028 # unlock if we have push from user who locked
2028 # unlock if we have push from user who locked
2029 make_lock = False
2029 make_lock = False
2030 else:
2030 else:
2031 # we're not the same user who locked, ban with
2031 # we're not the same user who locked, ban with
2032 # code defined in settings (default is 423 HTTP Locked) !
2032 # code defined in settings (default is 423 HTTP Locked) !
2033 log.debug('Repo %s is currently locked by %s', repo, user)
2033 log.debug('Repo %s is currently locked by %s', repo, user)
2034 currently_locked = True
2034 currently_locked = True
2035 elif action == 'pull':
2035 elif action == 'pull':
2036 # [0] user [1] date
2036 # [0] user [1] date
2037 if lock_info[0] and lock_info[1]:
2037 if lock_info[0] and lock_info[1]:
2038 log.debug('Repo %s is currently locked by %s', repo, user)
2038 log.debug('Repo %s is currently locked by %s', repo, user)
2039 currently_locked = True
2039 currently_locked = True
2040 else:
2040 else:
2041 log.debug('Setting lock on repo %s by %s', repo, user)
2041 log.debug('Setting lock on repo %s by %s', repo, user)
2042 make_lock = True
2042 make_lock = True
2043
2043
2044 else:
2044 else:
2045 log.debug('Repository %s do not have locking enabled', repo)
2045 log.debug('Repository %s do not have locking enabled', repo)
2046
2046
2047 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2047 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2048 make_lock, currently_locked, lock_info)
2048 make_lock, currently_locked, lock_info)
2049
2049
2050 from rhodecode.lib.auth import HasRepoPermissionAny
2050 from rhodecode.lib.auth import HasRepoPermissionAny
2051 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2051 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2052 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2052 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2053 # if we don't have at least write permission we cannot make a lock
2053 # if we don't have at least write permission we cannot make a lock
2054 log.debug('lock state reset back to FALSE due to lack '
2054 log.debug('lock state reset back to FALSE due to lack '
2055 'of at least read permission')
2055 'of at least read permission')
2056 make_lock = False
2056 make_lock = False
2057
2057
2058 return make_lock, currently_locked, lock_info
2058 return make_lock, currently_locked, lock_info
2059
2059
2060 @property
2060 @property
2061 def last_db_change(self):
2061 def last_db_change(self):
2062 return self.updated_on
2062 return self.updated_on
2063
2063
2064 @property
2064 @property
2065 def clone_uri_hidden(self):
2065 def clone_uri_hidden(self):
2066 clone_uri = self.clone_uri
2066 clone_uri = self.clone_uri
2067 if clone_uri:
2067 if clone_uri:
2068 import urlobject
2068 import urlobject
2069 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2069 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2070 if url_obj.password:
2070 if url_obj.password:
2071 clone_uri = url_obj.with_password('*****')
2071 clone_uri = url_obj.with_password('*****')
2072 return clone_uri
2072 return clone_uri
2073
2073
2074 def clone_url(self, **override):
2074 def clone_url(self, **override):
2075 from rhodecode.model.settings import SettingsModel
2075 from rhodecode.model.settings import SettingsModel
2076
2076
2077 uri_tmpl = None
2077 uri_tmpl = None
2078 if 'with_id' in override:
2078 if 'with_id' in override:
2079 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2079 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2080 del override['with_id']
2080 del override['with_id']
2081
2081
2082 if 'uri_tmpl' in override:
2082 if 'uri_tmpl' in override:
2083 uri_tmpl = override['uri_tmpl']
2083 uri_tmpl = override['uri_tmpl']
2084 del override['uri_tmpl']
2084 del override['uri_tmpl']
2085
2085
2086 # we didn't override our tmpl from **overrides
2086 # we didn't override our tmpl from **overrides
2087 if not uri_tmpl:
2087 if not uri_tmpl:
2088 rc_config = SettingsModel().get_all_settings(cache=True)
2088 rc_config = SettingsModel().get_all_settings(cache=True)
2089 uri_tmpl = rc_config.get(
2089 uri_tmpl = rc_config.get(
2090 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2090 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2091
2091
2092 request = get_current_request()
2092 request = get_current_request()
2093 return get_clone_url(request=request,
2093 return get_clone_url(request=request,
2094 uri_tmpl=uri_tmpl,
2094 uri_tmpl=uri_tmpl,
2095 repo_name=self.repo_name,
2095 repo_name=self.repo_name,
2096 repo_id=self.repo_id, **override)
2096 repo_id=self.repo_id, **override)
2097
2097
2098 def set_state(self, state):
2098 def set_state(self, state):
2099 self.repo_state = state
2099 self.repo_state = state
2100 Session().add(self)
2100 Session().add(self)
2101 #==========================================================================
2101 #==========================================================================
2102 # SCM PROPERTIES
2102 # SCM PROPERTIES
2103 #==========================================================================
2103 #==========================================================================
2104
2104
2105 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2105 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2106 return get_commit_safe(
2106 return get_commit_safe(
2107 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2107 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2108
2108
2109 def get_changeset(self, rev=None, pre_load=None):
2109 def get_changeset(self, rev=None, pre_load=None):
2110 warnings.warn("Use get_commit", DeprecationWarning)
2110 warnings.warn("Use get_commit", DeprecationWarning)
2111 commit_id = None
2111 commit_id = None
2112 commit_idx = None
2112 commit_idx = None
2113 if isinstance(rev, basestring):
2113 if isinstance(rev, basestring):
2114 commit_id = rev
2114 commit_id = rev
2115 else:
2115 else:
2116 commit_idx = rev
2116 commit_idx = rev
2117 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2117 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2118 pre_load=pre_load)
2118 pre_load=pre_load)
2119
2119
2120 def get_landing_commit(self):
2120 def get_landing_commit(self):
2121 """
2121 """
2122 Returns landing commit, or if that doesn't exist returns the tip
2122 Returns landing commit, or if that doesn't exist returns the tip
2123 """
2123 """
2124 _rev_type, _rev = self.landing_rev
2124 _rev_type, _rev = self.landing_rev
2125 commit = self.get_commit(_rev)
2125 commit = self.get_commit(_rev)
2126 if isinstance(commit, EmptyCommit):
2126 if isinstance(commit, EmptyCommit):
2127 return self.get_commit()
2127 return self.get_commit()
2128 return commit
2128 return commit
2129
2129
2130 def update_commit_cache(self, cs_cache=None, config=None):
2130 def update_commit_cache(self, cs_cache=None, config=None):
2131 """
2131 """
2132 Update cache of last changeset for repository, keys should be::
2132 Update cache of last changeset for repository, keys should be::
2133
2133
2134 short_id
2134 short_id
2135 raw_id
2135 raw_id
2136 revision
2136 revision
2137 parents
2137 parents
2138 message
2138 message
2139 date
2139 date
2140 author
2140 author
2141
2141
2142 :param cs_cache:
2142 :param cs_cache:
2143 """
2143 """
2144 from rhodecode.lib.vcs.backends.base import BaseChangeset
2144 from rhodecode.lib.vcs.backends.base import BaseChangeset
2145 if cs_cache is None:
2145 if cs_cache is None:
2146 # use no-cache version here
2146 # use no-cache version here
2147 scm_repo = self.scm_instance(cache=False, config=config)
2147 scm_repo = self.scm_instance(cache=False, config=config)
2148 if scm_repo:
2148 if scm_repo:
2149 cs_cache = scm_repo.get_commit(
2149 cs_cache = scm_repo.get_commit(
2150 pre_load=["author", "date", "message", "parents"])
2150 pre_load=["author", "date", "message", "parents"])
2151 else:
2151 else:
2152 cs_cache = EmptyCommit()
2152 cs_cache = EmptyCommit()
2153
2153
2154 if isinstance(cs_cache, BaseChangeset):
2154 if isinstance(cs_cache, BaseChangeset):
2155 cs_cache = cs_cache.__json__()
2155 cs_cache = cs_cache.__json__()
2156
2156
2157 def is_outdated(new_cs_cache):
2157 def is_outdated(new_cs_cache):
2158 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2158 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2159 new_cs_cache['revision'] != self.changeset_cache['revision']):
2159 new_cs_cache['revision'] != self.changeset_cache['revision']):
2160 return True
2160 return True
2161 return False
2161 return False
2162
2162
2163 # check if we have maybe already latest cached revision
2163 # check if we have maybe already latest cached revision
2164 if is_outdated(cs_cache) or not self.changeset_cache:
2164 if is_outdated(cs_cache) or not self.changeset_cache:
2165 _default = datetime.datetime.fromtimestamp(0)
2165 _default = datetime.datetime.fromtimestamp(0)
2166 last_change = cs_cache.get('date') or _default
2166 last_change = cs_cache.get('date') or _default
2167 log.debug('updated repo %s with new cs cache %s',
2167 log.debug('updated repo %s with new cs cache %s',
2168 self.repo_name, cs_cache)
2168 self.repo_name, cs_cache)
2169 self.updated_on = last_change
2169 self.updated_on = last_change
2170 self.changeset_cache = cs_cache
2170 self.changeset_cache = cs_cache
2171 Session().add(self)
2171 Session().add(self)
2172 Session().commit()
2172 Session().commit()
2173 else:
2173 else:
2174 log.debug('Skipping update_commit_cache for repo:`%s` '
2174 log.debug('Skipping update_commit_cache for repo:`%s` '
2175 'commit already with latest changes', self.repo_name)
2175 'commit already with latest changes', self.repo_name)
2176
2176
2177 @property
2177 @property
2178 def tip(self):
2178 def tip(self):
2179 return self.get_commit('tip')
2179 return self.get_commit('tip')
2180
2180
2181 @property
2181 @property
2182 def author(self):
2182 def author(self):
2183 return self.tip.author
2183 return self.tip.author
2184
2184
2185 @property
2185 @property
2186 def last_change(self):
2186 def last_change(self):
2187 return self.scm_instance().last_change
2187 return self.scm_instance().last_change
2188
2188
2189 def get_comments(self, revisions=None):
2189 def get_comments(self, revisions=None):
2190 """
2190 """
2191 Returns comments for this repository grouped by revisions
2191 Returns comments for this repository grouped by revisions
2192
2192
2193 :param revisions: filter query by revisions only
2193 :param revisions: filter query by revisions only
2194 """
2194 """
2195 cmts = ChangesetComment.query()\
2195 cmts = ChangesetComment.query()\
2196 .filter(ChangesetComment.repo == self)
2196 .filter(ChangesetComment.repo == self)
2197 if revisions:
2197 if revisions:
2198 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2198 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2199 grouped = collections.defaultdict(list)
2199 grouped = collections.defaultdict(list)
2200 for cmt in cmts.all():
2200 for cmt in cmts.all():
2201 grouped[cmt.revision].append(cmt)
2201 grouped[cmt.revision].append(cmt)
2202 return grouped
2202 return grouped
2203
2203
2204 def statuses(self, revisions=None):
2204 def statuses(self, revisions=None):
2205 """
2205 """
2206 Returns statuses for this repository
2206 Returns statuses for this repository
2207
2207
2208 :param revisions: list of revisions to get statuses for
2208 :param revisions: list of revisions to get statuses for
2209 """
2209 """
2210 statuses = ChangesetStatus.query()\
2210 statuses = ChangesetStatus.query()\
2211 .filter(ChangesetStatus.repo == self)\
2211 .filter(ChangesetStatus.repo == self)\
2212 .filter(ChangesetStatus.version == 0)
2212 .filter(ChangesetStatus.version == 0)
2213
2213
2214 if revisions:
2214 if revisions:
2215 # Try doing the filtering in chunks to avoid hitting limits
2215 # Try doing the filtering in chunks to avoid hitting limits
2216 size = 500
2216 size = 500
2217 status_results = []
2217 status_results = []
2218 for chunk in xrange(0, len(revisions), size):
2218 for chunk in xrange(0, len(revisions), size):
2219 status_results += statuses.filter(
2219 status_results += statuses.filter(
2220 ChangesetStatus.revision.in_(
2220 ChangesetStatus.revision.in_(
2221 revisions[chunk: chunk+size])
2221 revisions[chunk: chunk+size])
2222 ).all()
2222 ).all()
2223 else:
2223 else:
2224 status_results = statuses.all()
2224 status_results = statuses.all()
2225
2225
2226 grouped = {}
2226 grouped = {}
2227
2227
2228 # maybe we have open new pullrequest without a status?
2228 # maybe we have open new pullrequest without a status?
2229 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2229 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2230 status_lbl = ChangesetStatus.get_status_lbl(stat)
2230 status_lbl = ChangesetStatus.get_status_lbl(stat)
2231 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2231 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2232 for rev in pr.revisions:
2232 for rev in pr.revisions:
2233 pr_id = pr.pull_request_id
2233 pr_id = pr.pull_request_id
2234 pr_repo = pr.target_repo.repo_name
2234 pr_repo = pr.target_repo.repo_name
2235 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2235 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2236
2236
2237 for stat in status_results:
2237 for stat in status_results:
2238 pr_id = pr_repo = None
2238 pr_id = pr_repo = None
2239 if stat.pull_request:
2239 if stat.pull_request:
2240 pr_id = stat.pull_request.pull_request_id
2240 pr_id = stat.pull_request.pull_request_id
2241 pr_repo = stat.pull_request.target_repo.repo_name
2241 pr_repo = stat.pull_request.target_repo.repo_name
2242 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2242 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2243 pr_id, pr_repo]
2243 pr_id, pr_repo]
2244 return grouped
2244 return grouped
2245
2245
2246 # ==========================================================================
2246 # ==========================================================================
2247 # SCM CACHE INSTANCE
2247 # SCM CACHE INSTANCE
2248 # ==========================================================================
2248 # ==========================================================================
2249
2249
2250 def scm_instance(self, **kwargs):
2250 def scm_instance(self, **kwargs):
2251 import rhodecode
2251 import rhodecode
2252
2252
2253 # Passing a config will not hit the cache currently only used
2253 # Passing a config will not hit the cache currently only used
2254 # for repo2dbmapper
2254 # for repo2dbmapper
2255 config = kwargs.pop('config', None)
2255 config = kwargs.pop('config', None)
2256 cache = kwargs.pop('cache', None)
2256 cache = kwargs.pop('cache', None)
2257 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2257 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2258 # if cache is NOT defined use default global, else we have a full
2258 # if cache is NOT defined use default global, else we have a full
2259 # control over cache behaviour
2259 # control over cache behaviour
2260 if cache is None and full_cache and not config:
2260 if cache is None and full_cache and not config:
2261 return self._get_instance_cached()
2261 return self._get_instance_cached()
2262 return self._get_instance(cache=bool(cache), config=config)
2262 return self._get_instance(cache=bool(cache), config=config)
2263
2263
2264 def _get_instance_cached(self):
2264 def _get_instance_cached(self):
2265 self._get_instance()
2265 self._get_instance()
2266
2266
2267 def _get_instance(self, cache=True, config=None):
2267 def _get_instance(self, cache=True, config=None):
2268 config = config or self._config
2268 config = config or self._config
2269 custom_wire = {
2269 custom_wire = {
2270 'cache': cache # controls the vcs.remote cache
2270 'cache': cache # controls the vcs.remote cache
2271 }
2271 }
2272 repo = get_vcs_instance(
2272 repo = get_vcs_instance(
2273 repo_path=safe_str(self.repo_full_path),
2273 repo_path=safe_str(self.repo_full_path),
2274 config=config,
2274 config=config,
2275 with_wire=custom_wire,
2275 with_wire=custom_wire,
2276 create=False,
2276 create=False,
2277 _vcs_alias=self.repo_type)
2277 _vcs_alias=self.repo_type)
2278
2278
2279 return repo
2279 return repo
2280
2280
2281 def __json__(self):
2281 def __json__(self):
2282 return {'landing_rev': self.landing_rev}
2282 return {'landing_rev': self.landing_rev}
2283
2283
2284 def get_dict(self):
2284 def get_dict(self):
2285
2285
2286 # Since we transformed `repo_name` to a hybrid property, we need to
2286 # Since we transformed `repo_name` to a hybrid property, we need to
2287 # keep compatibility with the code which uses `repo_name` field.
2287 # keep compatibility with the code which uses `repo_name` field.
2288
2288
2289 result = super(Repository, self).get_dict()
2289 result = super(Repository, self).get_dict()
2290 result['repo_name'] = result.pop('_repo_name', None)
2290 result['repo_name'] = result.pop('_repo_name', None)
2291 return result
2291 return result
2292
2292
2293
2293
2294 class RepoGroup(Base, BaseModel):
2294 class RepoGroup(Base, BaseModel):
2295 __tablename__ = 'groups'
2295 __tablename__ = 'groups'
2296 __table_args__ = (
2296 __table_args__ = (
2297 UniqueConstraint('group_name', 'group_parent_id'),
2297 UniqueConstraint('group_name', 'group_parent_id'),
2298 CheckConstraint('group_id != group_parent_id'),
2298 CheckConstraint('group_id != group_parent_id'),
2299 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2299 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2300 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2300 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2301 )
2301 )
2302 __mapper_args__ = {'order_by': 'group_name'}
2302 __mapper_args__ = {'order_by': 'group_name'}
2303
2303
2304 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2304 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2305
2305
2306 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2306 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2307 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2307 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2308 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2308 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2309 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2309 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2310 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2310 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2311 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2311 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2312 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2312 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2313 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2313 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2314 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2314 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2315
2315
2316 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2316 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2317 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2317 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2318 parent_group = relationship('RepoGroup', remote_side=group_id)
2318 parent_group = relationship('RepoGroup', remote_side=group_id)
2319 user = relationship('User')
2319 user = relationship('User')
2320 integrations = relationship('Integration',
2320 integrations = relationship('Integration',
2321 cascade="all, delete, delete-orphan")
2321 cascade="all, delete, delete-orphan")
2322
2322
2323 def __init__(self, group_name='', parent_group=None):
2323 def __init__(self, group_name='', parent_group=None):
2324 self.group_name = group_name
2324 self.group_name = group_name
2325 self.parent_group = parent_group
2325 self.parent_group = parent_group
2326
2326
2327 def __unicode__(self):
2327 def __unicode__(self):
2328 return u"<%s('id:%s:%s')>" % (
2328 return u"<%s('id:%s:%s')>" % (
2329 self.__class__.__name__, self.group_id, self.group_name)
2329 self.__class__.__name__, self.group_id, self.group_name)
2330
2330
2331 @hybrid_property
2331 @hybrid_property
2332 def description_safe(self):
2332 def description_safe(self):
2333 from rhodecode.lib import helpers as h
2333 from rhodecode.lib import helpers as h
2334 return h.escape(self.group_description)
2334 return h.escape(self.group_description)
2335
2335
2336 @classmethod
2336 @classmethod
2337 def _generate_choice(cls, repo_group):
2337 def _generate_choice(cls, repo_group):
2338 from webhelpers.html import literal as _literal
2338 from webhelpers.html import literal as _literal
2339 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2339 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2340 return repo_group.group_id, _name(repo_group.full_path_splitted)
2340 return repo_group.group_id, _name(repo_group.full_path_splitted)
2341
2341
2342 @classmethod
2342 @classmethod
2343 def groups_choices(cls, groups=None, show_empty_group=True):
2343 def groups_choices(cls, groups=None, show_empty_group=True):
2344 if not groups:
2344 if not groups:
2345 groups = cls.query().all()
2345 groups = cls.query().all()
2346
2346
2347 repo_groups = []
2347 repo_groups = []
2348 if show_empty_group:
2348 if show_empty_group:
2349 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2349 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2350
2350
2351 repo_groups.extend([cls._generate_choice(x) for x in groups])
2351 repo_groups.extend([cls._generate_choice(x) for x in groups])
2352
2352
2353 repo_groups = sorted(
2353 repo_groups = sorted(
2354 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2354 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2355 return repo_groups
2355 return repo_groups
2356
2356
2357 @classmethod
2357 @classmethod
2358 def url_sep(cls):
2358 def url_sep(cls):
2359 return URL_SEP
2359 return URL_SEP
2360
2360
2361 @classmethod
2361 @classmethod
2362 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2362 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2363 if case_insensitive:
2363 if case_insensitive:
2364 gr = cls.query().filter(func.lower(cls.group_name)
2364 gr = cls.query().filter(func.lower(cls.group_name)
2365 == func.lower(group_name))
2365 == func.lower(group_name))
2366 else:
2366 else:
2367 gr = cls.query().filter(cls.group_name == group_name)
2367 gr = cls.query().filter(cls.group_name == group_name)
2368 if cache:
2368 if cache:
2369 name_key = _hash_key(group_name)
2369 name_key = _hash_key(group_name)
2370 gr = gr.options(
2370 gr = gr.options(
2371 FromCache("sql_cache_short", "get_group_%s" % name_key))
2371 FromCache("sql_cache_short", "get_group_%s" % name_key))
2372 return gr.scalar()
2372 return gr.scalar()
2373
2373
2374 @classmethod
2374 @classmethod
2375 def get_user_personal_repo_group(cls, user_id):
2375 def get_user_personal_repo_group(cls, user_id):
2376 user = User.get(user_id)
2376 user = User.get(user_id)
2377 if user.username == User.DEFAULT_USER:
2377 if user.username == User.DEFAULT_USER:
2378 return None
2378 return None
2379
2379
2380 return cls.query()\
2380 return cls.query()\
2381 .filter(cls.personal == true()) \
2381 .filter(cls.personal == true()) \
2382 .filter(cls.user == user).scalar()
2382 .filter(cls.user == user).scalar()
2383
2383
2384 @classmethod
2384 @classmethod
2385 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2385 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2386 case_insensitive=True):
2386 case_insensitive=True):
2387 q = RepoGroup.query()
2387 q = RepoGroup.query()
2388
2388
2389 if not isinstance(user_id, Optional):
2389 if not isinstance(user_id, Optional):
2390 q = q.filter(RepoGroup.user_id == user_id)
2390 q = q.filter(RepoGroup.user_id == user_id)
2391
2391
2392 if not isinstance(group_id, Optional):
2392 if not isinstance(group_id, Optional):
2393 q = q.filter(RepoGroup.group_parent_id == group_id)
2393 q = q.filter(RepoGroup.group_parent_id == group_id)
2394
2394
2395 if case_insensitive:
2395 if case_insensitive:
2396 q = q.order_by(func.lower(RepoGroup.group_name))
2396 q = q.order_by(func.lower(RepoGroup.group_name))
2397 else:
2397 else:
2398 q = q.order_by(RepoGroup.group_name)
2398 q = q.order_by(RepoGroup.group_name)
2399 return q.all()
2399 return q.all()
2400
2400
2401 @property
2401 @property
2402 def parents(self):
2402 def parents(self):
2403 parents_recursion_limit = 10
2403 parents_recursion_limit = 10
2404 groups = []
2404 groups = []
2405 if self.parent_group is None:
2405 if self.parent_group is None:
2406 return groups
2406 return groups
2407 cur_gr = self.parent_group
2407 cur_gr = self.parent_group
2408 groups.insert(0, cur_gr)
2408 groups.insert(0, cur_gr)
2409 cnt = 0
2409 cnt = 0
2410 while 1:
2410 while 1:
2411 cnt += 1
2411 cnt += 1
2412 gr = getattr(cur_gr, 'parent_group', None)
2412 gr = getattr(cur_gr, 'parent_group', None)
2413 cur_gr = cur_gr.parent_group
2413 cur_gr = cur_gr.parent_group
2414 if gr is None:
2414 if gr is None:
2415 break
2415 break
2416 if cnt == parents_recursion_limit:
2416 if cnt == parents_recursion_limit:
2417 # this will prevent accidental infinit loops
2417 # this will prevent accidental infinit loops
2418 log.error('more than %s parents found for group %s, stopping '
2418 log.error('more than %s parents found for group %s, stopping '
2419 'recursive parent fetching', parents_recursion_limit, self)
2419 'recursive parent fetching', parents_recursion_limit, self)
2420 break
2420 break
2421
2421
2422 groups.insert(0, gr)
2422 groups.insert(0, gr)
2423 return groups
2423 return groups
2424
2424
2425 @property
2425 @property
2426 def last_db_change(self):
2426 def last_db_change(self):
2427 return self.updated_on
2427 return self.updated_on
2428
2428
2429 @property
2429 @property
2430 def children(self):
2430 def children(self):
2431 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2431 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2432
2432
2433 @property
2433 @property
2434 def name(self):
2434 def name(self):
2435 return self.group_name.split(RepoGroup.url_sep())[-1]
2435 return self.group_name.split(RepoGroup.url_sep())[-1]
2436
2436
2437 @property
2437 @property
2438 def full_path(self):
2438 def full_path(self):
2439 return self.group_name
2439 return self.group_name
2440
2440
2441 @property
2441 @property
2442 def full_path_splitted(self):
2442 def full_path_splitted(self):
2443 return self.group_name.split(RepoGroup.url_sep())
2443 return self.group_name.split(RepoGroup.url_sep())
2444
2444
2445 @property
2445 @property
2446 def repositories(self):
2446 def repositories(self):
2447 return Repository.query()\
2447 return Repository.query()\
2448 .filter(Repository.group == self)\
2448 .filter(Repository.group == self)\
2449 .order_by(Repository.repo_name)
2449 .order_by(Repository.repo_name)
2450
2450
2451 @property
2451 @property
2452 def repositories_recursive_count(self):
2452 def repositories_recursive_count(self):
2453 cnt = self.repositories.count()
2453 cnt = self.repositories.count()
2454
2454
2455 def children_count(group):
2455 def children_count(group):
2456 cnt = 0
2456 cnt = 0
2457 for child in group.children:
2457 for child in group.children:
2458 cnt += child.repositories.count()
2458 cnt += child.repositories.count()
2459 cnt += children_count(child)
2459 cnt += children_count(child)
2460 return cnt
2460 return cnt
2461
2461
2462 return cnt + children_count(self)
2462 return cnt + children_count(self)
2463
2463
2464 def _recursive_objects(self, include_repos=True):
2464 def _recursive_objects(self, include_repos=True):
2465 all_ = []
2465 all_ = []
2466
2466
2467 def _get_members(root_gr):
2467 def _get_members(root_gr):
2468 if include_repos:
2468 if include_repos:
2469 for r in root_gr.repositories:
2469 for r in root_gr.repositories:
2470 all_.append(r)
2470 all_.append(r)
2471 childs = root_gr.children.all()
2471 childs = root_gr.children.all()
2472 if childs:
2472 if childs:
2473 for gr in childs:
2473 for gr in childs:
2474 all_.append(gr)
2474 all_.append(gr)
2475 _get_members(gr)
2475 _get_members(gr)
2476
2476
2477 _get_members(self)
2477 _get_members(self)
2478 return [self] + all_
2478 return [self] + all_
2479
2479
2480 def recursive_groups_and_repos(self):
2480 def recursive_groups_and_repos(self):
2481 """
2481 """
2482 Recursive return all groups, with repositories in those groups
2482 Recursive return all groups, with repositories in those groups
2483 """
2483 """
2484 return self._recursive_objects()
2484 return self._recursive_objects()
2485
2485
2486 def recursive_groups(self):
2486 def recursive_groups(self):
2487 """
2487 """
2488 Returns all children groups for this group including children of children
2488 Returns all children groups for this group including children of children
2489 """
2489 """
2490 return self._recursive_objects(include_repos=False)
2490 return self._recursive_objects(include_repos=False)
2491
2491
2492 def get_new_name(self, group_name):
2492 def get_new_name(self, group_name):
2493 """
2493 """
2494 returns new full group name based on parent and new name
2494 returns new full group name based on parent and new name
2495
2495
2496 :param group_name:
2496 :param group_name:
2497 """
2497 """
2498 path_prefix = (self.parent_group.full_path_splitted if
2498 path_prefix = (self.parent_group.full_path_splitted if
2499 self.parent_group else [])
2499 self.parent_group else [])
2500 return RepoGroup.url_sep().join(path_prefix + [group_name])
2500 return RepoGroup.url_sep().join(path_prefix + [group_name])
2501
2501
2502 def permissions(self, with_admins=True, with_owner=True):
2502 def permissions(self, with_admins=True, with_owner=True):
2503 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2503 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2504 q = q.options(joinedload(UserRepoGroupToPerm.group),
2504 q = q.options(joinedload(UserRepoGroupToPerm.group),
2505 joinedload(UserRepoGroupToPerm.user),
2505 joinedload(UserRepoGroupToPerm.user),
2506 joinedload(UserRepoGroupToPerm.permission),)
2506 joinedload(UserRepoGroupToPerm.permission),)
2507
2507
2508 # get owners and admins and permissions. We do a trick of re-writing
2508 # get owners and admins and permissions. We do a trick of re-writing
2509 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2509 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2510 # has a global reference and changing one object propagates to all
2510 # has a global reference and changing one object propagates to all
2511 # others. This means if admin is also an owner admin_row that change
2511 # others. This means if admin is also an owner admin_row that change
2512 # would propagate to both objects
2512 # would propagate to both objects
2513 perm_rows = []
2513 perm_rows = []
2514 for _usr in q.all():
2514 for _usr in q.all():
2515 usr = AttributeDict(_usr.user.get_dict())
2515 usr = AttributeDict(_usr.user.get_dict())
2516 usr.permission = _usr.permission.permission_name
2516 usr.permission = _usr.permission.permission_name
2517 perm_rows.append(usr)
2517 perm_rows.append(usr)
2518
2518
2519 # filter the perm rows by 'default' first and then sort them by
2519 # filter the perm rows by 'default' first and then sort them by
2520 # admin,write,read,none permissions sorted again alphabetically in
2520 # admin,write,read,none permissions sorted again alphabetically in
2521 # each group
2521 # each group
2522 perm_rows = sorted(perm_rows, key=display_user_sort)
2522 perm_rows = sorted(perm_rows, key=display_user_sort)
2523
2523
2524 _admin_perm = 'group.admin'
2524 _admin_perm = 'group.admin'
2525 owner_row = []
2525 owner_row = []
2526 if with_owner:
2526 if with_owner:
2527 usr = AttributeDict(self.user.get_dict())
2527 usr = AttributeDict(self.user.get_dict())
2528 usr.owner_row = True
2528 usr.owner_row = True
2529 usr.permission = _admin_perm
2529 usr.permission = _admin_perm
2530 owner_row.append(usr)
2530 owner_row.append(usr)
2531
2531
2532 super_admin_rows = []
2532 super_admin_rows = []
2533 if with_admins:
2533 if with_admins:
2534 for usr in User.get_all_super_admins():
2534 for usr in User.get_all_super_admins():
2535 # if this admin is also owner, don't double the record
2535 # if this admin is also owner, don't double the record
2536 if usr.user_id == owner_row[0].user_id:
2536 if usr.user_id == owner_row[0].user_id:
2537 owner_row[0].admin_row = True
2537 owner_row[0].admin_row = True
2538 else:
2538 else:
2539 usr = AttributeDict(usr.get_dict())
2539 usr = AttributeDict(usr.get_dict())
2540 usr.admin_row = True
2540 usr.admin_row = True
2541 usr.permission = _admin_perm
2541 usr.permission = _admin_perm
2542 super_admin_rows.append(usr)
2542 super_admin_rows.append(usr)
2543
2543
2544 return super_admin_rows + owner_row + perm_rows
2544 return super_admin_rows + owner_row + perm_rows
2545
2545
2546 def permission_user_groups(self):
2546 def permission_user_groups(self):
2547 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2547 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2548 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2548 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2549 joinedload(UserGroupRepoGroupToPerm.users_group),
2549 joinedload(UserGroupRepoGroupToPerm.users_group),
2550 joinedload(UserGroupRepoGroupToPerm.permission),)
2550 joinedload(UserGroupRepoGroupToPerm.permission),)
2551
2551
2552 perm_rows = []
2552 perm_rows = []
2553 for _user_group in q.all():
2553 for _user_group in q.all():
2554 usr = AttributeDict(_user_group.users_group.get_dict())
2554 usr = AttributeDict(_user_group.users_group.get_dict())
2555 usr.permission = _user_group.permission.permission_name
2555 usr.permission = _user_group.permission.permission_name
2556 perm_rows.append(usr)
2556 perm_rows.append(usr)
2557
2557
2558 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2558 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2559 return perm_rows
2559 return perm_rows
2560
2560
2561 def get_api_data(self):
2561 def get_api_data(self):
2562 """
2562 """
2563 Common function for generating api data
2563 Common function for generating api data
2564
2564
2565 """
2565 """
2566 group = self
2566 group = self
2567 data = {
2567 data = {
2568 'group_id': group.group_id,
2568 'group_id': group.group_id,
2569 'group_name': group.group_name,
2569 'group_name': group.group_name,
2570 'group_description': group.description_safe,
2570 'group_description': group.description_safe,
2571 'parent_group': group.parent_group.group_name if group.parent_group else None,
2571 'parent_group': group.parent_group.group_name if group.parent_group else None,
2572 'repositories': [x.repo_name for x in group.repositories],
2572 'repositories': [x.repo_name for x in group.repositories],
2573 'owner': group.user.username,
2573 'owner': group.user.username,
2574 }
2574 }
2575 return data
2575 return data
2576
2576
2577
2577
2578 class Permission(Base, BaseModel):
2578 class Permission(Base, BaseModel):
2579 __tablename__ = 'permissions'
2579 __tablename__ = 'permissions'
2580 __table_args__ = (
2580 __table_args__ = (
2581 Index('p_perm_name_idx', 'permission_name'),
2581 Index('p_perm_name_idx', 'permission_name'),
2582 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2582 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2583 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2583 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2584 )
2584 )
2585 PERMS = [
2585 PERMS = [
2586 ('hg.admin', _('RhodeCode Super Administrator')),
2586 ('hg.admin', _('RhodeCode Super Administrator')),
2587
2587
2588 ('repository.none', _('Repository no access')),
2588 ('repository.none', _('Repository no access')),
2589 ('repository.read', _('Repository read access')),
2589 ('repository.read', _('Repository read access')),
2590 ('repository.write', _('Repository write access')),
2590 ('repository.write', _('Repository write access')),
2591 ('repository.admin', _('Repository admin access')),
2591 ('repository.admin', _('Repository admin access')),
2592
2592
2593 ('group.none', _('Repository group no access')),
2593 ('group.none', _('Repository group no access')),
2594 ('group.read', _('Repository group read access')),
2594 ('group.read', _('Repository group read access')),
2595 ('group.write', _('Repository group write access')),
2595 ('group.write', _('Repository group write access')),
2596 ('group.admin', _('Repository group admin access')),
2596 ('group.admin', _('Repository group admin access')),
2597
2597
2598 ('usergroup.none', _('User group no access')),
2598 ('usergroup.none', _('User group no access')),
2599 ('usergroup.read', _('User group read access')),
2599 ('usergroup.read', _('User group read access')),
2600 ('usergroup.write', _('User group write access')),
2600 ('usergroup.write', _('User group write access')),
2601 ('usergroup.admin', _('User group admin access')),
2601 ('usergroup.admin', _('User group admin access')),
2602
2602
2603 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2603 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2604 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2604 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2605
2605
2606 ('hg.usergroup.create.false', _('User Group creation disabled')),
2606 ('hg.usergroup.create.false', _('User Group creation disabled')),
2607 ('hg.usergroup.create.true', _('User Group creation enabled')),
2607 ('hg.usergroup.create.true', _('User Group creation enabled')),
2608
2608
2609 ('hg.create.none', _('Repository creation disabled')),
2609 ('hg.create.none', _('Repository creation disabled')),
2610 ('hg.create.repository', _('Repository creation enabled')),
2610 ('hg.create.repository', _('Repository creation enabled')),
2611 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2611 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2612 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2612 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2613
2613
2614 ('hg.fork.none', _('Repository forking disabled')),
2614 ('hg.fork.none', _('Repository forking disabled')),
2615 ('hg.fork.repository', _('Repository forking enabled')),
2615 ('hg.fork.repository', _('Repository forking enabled')),
2616
2616
2617 ('hg.register.none', _('Registration disabled')),
2617 ('hg.register.none', _('Registration disabled')),
2618 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2618 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2619 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2619 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2620
2620
2621 ('hg.password_reset.enabled', _('Password reset enabled')),
2621 ('hg.password_reset.enabled', _('Password reset enabled')),
2622 ('hg.password_reset.hidden', _('Password reset hidden')),
2622 ('hg.password_reset.hidden', _('Password reset hidden')),
2623 ('hg.password_reset.disabled', _('Password reset disabled')),
2623 ('hg.password_reset.disabled', _('Password reset disabled')),
2624
2624
2625 ('hg.extern_activate.manual', _('Manual activation of external account')),
2625 ('hg.extern_activate.manual', _('Manual activation of external account')),
2626 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2626 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2627
2627
2628 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2628 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2629 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2629 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2630 ]
2630 ]
2631
2631
2632 # definition of system default permissions for DEFAULT user
2632 # definition of system default permissions for DEFAULT user
2633 DEFAULT_USER_PERMISSIONS = [
2633 DEFAULT_USER_PERMISSIONS = [
2634 'repository.read',
2634 'repository.read',
2635 'group.read',
2635 'group.read',
2636 'usergroup.read',
2636 'usergroup.read',
2637 'hg.create.repository',
2637 'hg.create.repository',
2638 'hg.repogroup.create.false',
2638 'hg.repogroup.create.false',
2639 'hg.usergroup.create.false',
2639 'hg.usergroup.create.false',
2640 'hg.create.write_on_repogroup.true',
2640 'hg.create.write_on_repogroup.true',
2641 'hg.fork.repository',
2641 'hg.fork.repository',
2642 'hg.register.manual_activate',
2642 'hg.register.manual_activate',
2643 'hg.password_reset.enabled',
2643 'hg.password_reset.enabled',
2644 'hg.extern_activate.auto',
2644 'hg.extern_activate.auto',
2645 'hg.inherit_default_perms.true',
2645 'hg.inherit_default_perms.true',
2646 ]
2646 ]
2647
2647
2648 # defines which permissions are more important higher the more important
2648 # defines which permissions are more important higher the more important
2649 # Weight defines which permissions are more important.
2649 # Weight defines which permissions are more important.
2650 # The higher number the more important.
2650 # The higher number the more important.
2651 PERM_WEIGHTS = {
2651 PERM_WEIGHTS = {
2652 'repository.none': 0,
2652 'repository.none': 0,
2653 'repository.read': 1,
2653 'repository.read': 1,
2654 'repository.write': 3,
2654 'repository.write': 3,
2655 'repository.admin': 4,
2655 'repository.admin': 4,
2656
2656
2657 'group.none': 0,
2657 'group.none': 0,
2658 'group.read': 1,
2658 'group.read': 1,
2659 'group.write': 3,
2659 'group.write': 3,
2660 'group.admin': 4,
2660 'group.admin': 4,
2661
2661
2662 'usergroup.none': 0,
2662 'usergroup.none': 0,
2663 'usergroup.read': 1,
2663 'usergroup.read': 1,
2664 'usergroup.write': 3,
2664 'usergroup.write': 3,
2665 'usergroup.admin': 4,
2665 'usergroup.admin': 4,
2666
2666
2667 'hg.repogroup.create.false': 0,
2667 'hg.repogroup.create.false': 0,
2668 'hg.repogroup.create.true': 1,
2668 'hg.repogroup.create.true': 1,
2669
2669
2670 'hg.usergroup.create.false': 0,
2670 'hg.usergroup.create.false': 0,
2671 'hg.usergroup.create.true': 1,
2671 'hg.usergroup.create.true': 1,
2672
2672
2673 'hg.fork.none': 0,
2673 'hg.fork.none': 0,
2674 'hg.fork.repository': 1,
2674 'hg.fork.repository': 1,
2675 'hg.create.none': 0,
2675 'hg.create.none': 0,
2676 'hg.create.repository': 1
2676 'hg.create.repository': 1
2677 }
2677 }
2678
2678
2679 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2679 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2680 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2680 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2681 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2681 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2682
2682
2683 def __unicode__(self):
2683 def __unicode__(self):
2684 return u"<%s('%s:%s')>" % (
2684 return u"<%s('%s:%s')>" % (
2685 self.__class__.__name__, self.permission_id, self.permission_name
2685 self.__class__.__name__, self.permission_id, self.permission_name
2686 )
2686 )
2687
2687
2688 @classmethod
2688 @classmethod
2689 def get_by_key(cls, key):
2689 def get_by_key(cls, key):
2690 return cls.query().filter(cls.permission_name == key).scalar()
2690 return cls.query().filter(cls.permission_name == key).scalar()
2691
2691
2692 @classmethod
2692 @classmethod
2693 def get_default_repo_perms(cls, user_id, repo_id=None):
2693 def get_default_repo_perms(cls, user_id, repo_id=None):
2694 q = Session().query(UserRepoToPerm, Repository, Permission)\
2694 q = Session().query(UserRepoToPerm, Repository, Permission)\
2695 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2695 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2696 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2696 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2697 .filter(UserRepoToPerm.user_id == user_id)
2697 .filter(UserRepoToPerm.user_id == user_id)
2698 if repo_id:
2698 if repo_id:
2699 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2699 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2700 return q.all()
2700 return q.all()
2701
2701
2702 @classmethod
2702 @classmethod
2703 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2703 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2704 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2704 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2705 .join(
2705 .join(
2706 Permission,
2706 Permission,
2707 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2707 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2708 .join(
2708 .join(
2709 Repository,
2709 Repository,
2710 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2710 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2711 .join(
2711 .join(
2712 UserGroup,
2712 UserGroup,
2713 UserGroupRepoToPerm.users_group_id ==
2713 UserGroupRepoToPerm.users_group_id ==
2714 UserGroup.users_group_id)\
2714 UserGroup.users_group_id)\
2715 .join(
2715 .join(
2716 UserGroupMember,
2716 UserGroupMember,
2717 UserGroupRepoToPerm.users_group_id ==
2717 UserGroupRepoToPerm.users_group_id ==
2718 UserGroupMember.users_group_id)\
2718 UserGroupMember.users_group_id)\
2719 .filter(
2719 .filter(
2720 UserGroupMember.user_id == user_id,
2720 UserGroupMember.user_id == user_id,
2721 UserGroup.users_group_active == true())
2721 UserGroup.users_group_active == true())
2722 if repo_id:
2722 if repo_id:
2723 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2723 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2724 return q.all()
2724 return q.all()
2725
2725
2726 @classmethod
2726 @classmethod
2727 def get_default_group_perms(cls, user_id, repo_group_id=None):
2727 def get_default_group_perms(cls, user_id, repo_group_id=None):
2728 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2728 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2729 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2729 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2730 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2730 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2731 .filter(UserRepoGroupToPerm.user_id == user_id)
2731 .filter(UserRepoGroupToPerm.user_id == user_id)
2732 if repo_group_id:
2732 if repo_group_id:
2733 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2733 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2734 return q.all()
2734 return q.all()
2735
2735
2736 @classmethod
2736 @classmethod
2737 def get_default_group_perms_from_user_group(
2737 def get_default_group_perms_from_user_group(
2738 cls, user_id, repo_group_id=None):
2738 cls, user_id, repo_group_id=None):
2739 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2739 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2740 .join(
2740 .join(
2741 Permission,
2741 Permission,
2742 UserGroupRepoGroupToPerm.permission_id ==
2742 UserGroupRepoGroupToPerm.permission_id ==
2743 Permission.permission_id)\
2743 Permission.permission_id)\
2744 .join(
2744 .join(
2745 RepoGroup,
2745 RepoGroup,
2746 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2746 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2747 .join(
2747 .join(
2748 UserGroup,
2748 UserGroup,
2749 UserGroupRepoGroupToPerm.users_group_id ==
2749 UserGroupRepoGroupToPerm.users_group_id ==
2750 UserGroup.users_group_id)\
2750 UserGroup.users_group_id)\
2751 .join(
2751 .join(
2752 UserGroupMember,
2752 UserGroupMember,
2753 UserGroupRepoGroupToPerm.users_group_id ==
2753 UserGroupRepoGroupToPerm.users_group_id ==
2754 UserGroupMember.users_group_id)\
2754 UserGroupMember.users_group_id)\
2755 .filter(
2755 .filter(
2756 UserGroupMember.user_id == user_id,
2756 UserGroupMember.user_id == user_id,
2757 UserGroup.users_group_active == true())
2757 UserGroup.users_group_active == true())
2758 if repo_group_id:
2758 if repo_group_id:
2759 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2759 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2760 return q.all()
2760 return q.all()
2761
2761
2762 @classmethod
2762 @classmethod
2763 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2763 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2764 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2764 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2765 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2765 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2766 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2766 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2767 .filter(UserUserGroupToPerm.user_id == user_id)
2767 .filter(UserUserGroupToPerm.user_id == user_id)
2768 if user_group_id:
2768 if user_group_id:
2769 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2769 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2770 return q.all()
2770 return q.all()
2771
2771
2772 @classmethod
2772 @classmethod
2773 def get_default_user_group_perms_from_user_group(
2773 def get_default_user_group_perms_from_user_group(
2774 cls, user_id, user_group_id=None):
2774 cls, user_id, user_group_id=None):
2775 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2775 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2776 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2776 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2777 .join(
2777 .join(
2778 Permission,
2778 Permission,
2779 UserGroupUserGroupToPerm.permission_id ==
2779 UserGroupUserGroupToPerm.permission_id ==
2780 Permission.permission_id)\
2780 Permission.permission_id)\
2781 .join(
2781 .join(
2782 TargetUserGroup,
2782 TargetUserGroup,
2783 UserGroupUserGroupToPerm.target_user_group_id ==
2783 UserGroupUserGroupToPerm.target_user_group_id ==
2784 TargetUserGroup.users_group_id)\
2784 TargetUserGroup.users_group_id)\
2785 .join(
2785 .join(
2786 UserGroup,
2786 UserGroup,
2787 UserGroupUserGroupToPerm.user_group_id ==
2787 UserGroupUserGroupToPerm.user_group_id ==
2788 UserGroup.users_group_id)\
2788 UserGroup.users_group_id)\
2789 .join(
2789 .join(
2790 UserGroupMember,
2790 UserGroupMember,
2791 UserGroupUserGroupToPerm.user_group_id ==
2791 UserGroupUserGroupToPerm.user_group_id ==
2792 UserGroupMember.users_group_id)\
2792 UserGroupMember.users_group_id)\
2793 .filter(
2793 .filter(
2794 UserGroupMember.user_id == user_id,
2794 UserGroupMember.user_id == user_id,
2795 UserGroup.users_group_active == true())
2795 UserGroup.users_group_active == true())
2796 if user_group_id:
2796 if user_group_id:
2797 q = q.filter(
2797 q = q.filter(
2798 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2798 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2799
2799
2800 return q.all()
2800 return q.all()
2801
2801
2802
2802
2803 class UserRepoToPerm(Base, BaseModel):
2803 class UserRepoToPerm(Base, BaseModel):
2804 __tablename__ = 'repo_to_perm'
2804 __tablename__ = 'repo_to_perm'
2805 __table_args__ = (
2805 __table_args__ = (
2806 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2806 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2807 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2807 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2808 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2808 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2809 )
2809 )
2810 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2810 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2811 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2811 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2812 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2812 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2813 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2813 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2814
2814
2815 user = relationship('User')
2815 user = relationship('User')
2816 repository = relationship('Repository')
2816 repository = relationship('Repository')
2817 permission = relationship('Permission')
2817 permission = relationship('Permission')
2818
2818
2819 @classmethod
2819 @classmethod
2820 def create(cls, user, repository, permission):
2820 def create(cls, user, repository, permission):
2821 n = cls()
2821 n = cls()
2822 n.user = user
2822 n.user = user
2823 n.repository = repository
2823 n.repository = repository
2824 n.permission = permission
2824 n.permission = permission
2825 Session().add(n)
2825 Session().add(n)
2826 return n
2826 return n
2827
2827
2828 def __unicode__(self):
2828 def __unicode__(self):
2829 return u'<%s => %s >' % (self.user, self.repository)
2829 return u'<%s => %s >' % (self.user, self.repository)
2830
2830
2831
2831
2832 class UserUserGroupToPerm(Base, BaseModel):
2832 class UserUserGroupToPerm(Base, BaseModel):
2833 __tablename__ = 'user_user_group_to_perm'
2833 __tablename__ = 'user_user_group_to_perm'
2834 __table_args__ = (
2834 __table_args__ = (
2835 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2835 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2836 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2836 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2837 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2837 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2838 )
2838 )
2839 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2839 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2840 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2840 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2841 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2841 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2842 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2842 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2843
2843
2844 user = relationship('User')
2844 user = relationship('User')
2845 user_group = relationship('UserGroup')
2845 user_group = relationship('UserGroup')
2846 permission = relationship('Permission')
2846 permission = relationship('Permission')
2847
2847
2848 @classmethod
2848 @classmethod
2849 def create(cls, user, user_group, permission):
2849 def create(cls, user, user_group, permission):
2850 n = cls()
2850 n = cls()
2851 n.user = user
2851 n.user = user
2852 n.user_group = user_group
2852 n.user_group = user_group
2853 n.permission = permission
2853 n.permission = permission
2854 Session().add(n)
2854 Session().add(n)
2855 return n
2855 return n
2856
2856
2857 def __unicode__(self):
2857 def __unicode__(self):
2858 return u'<%s => %s >' % (self.user, self.user_group)
2858 return u'<%s => %s >' % (self.user, self.user_group)
2859
2859
2860
2860
2861 class UserToPerm(Base, BaseModel):
2861 class UserToPerm(Base, BaseModel):
2862 __tablename__ = 'user_to_perm'
2862 __tablename__ = 'user_to_perm'
2863 __table_args__ = (
2863 __table_args__ = (
2864 UniqueConstraint('user_id', 'permission_id'),
2864 UniqueConstraint('user_id', 'permission_id'),
2865 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2865 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2866 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2866 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2867 )
2867 )
2868 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2868 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2869 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2869 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2870 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2870 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2871
2871
2872 user = relationship('User')
2872 user = relationship('User')
2873 permission = relationship('Permission', lazy='joined')
2873 permission = relationship('Permission', lazy='joined')
2874
2874
2875 def __unicode__(self):
2875 def __unicode__(self):
2876 return u'<%s => %s >' % (self.user, self.permission)
2876 return u'<%s => %s >' % (self.user, self.permission)
2877
2877
2878
2878
2879 class UserGroupRepoToPerm(Base, BaseModel):
2879 class UserGroupRepoToPerm(Base, BaseModel):
2880 __tablename__ = 'users_group_repo_to_perm'
2880 __tablename__ = 'users_group_repo_to_perm'
2881 __table_args__ = (
2881 __table_args__ = (
2882 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2882 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2885 )
2885 )
2886 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2886 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2887 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2887 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2889 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2889 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2890
2890
2891 users_group = relationship('UserGroup')
2891 users_group = relationship('UserGroup')
2892 permission = relationship('Permission')
2892 permission = relationship('Permission')
2893 repository = relationship('Repository')
2893 repository = relationship('Repository')
2894
2894
2895 @classmethod
2895 @classmethod
2896 def create(cls, users_group, repository, permission):
2896 def create(cls, users_group, repository, permission):
2897 n = cls()
2897 n = cls()
2898 n.users_group = users_group
2898 n.users_group = users_group
2899 n.repository = repository
2899 n.repository = repository
2900 n.permission = permission
2900 n.permission = permission
2901 Session().add(n)
2901 Session().add(n)
2902 return n
2902 return n
2903
2903
2904 def __unicode__(self):
2904 def __unicode__(self):
2905 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2905 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2906
2906
2907
2907
2908 class UserGroupUserGroupToPerm(Base, BaseModel):
2908 class UserGroupUserGroupToPerm(Base, BaseModel):
2909 __tablename__ = 'user_group_user_group_to_perm'
2909 __tablename__ = 'user_group_user_group_to_perm'
2910 __table_args__ = (
2910 __table_args__ = (
2911 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2911 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2912 CheckConstraint('target_user_group_id != user_group_id'),
2912 CheckConstraint('target_user_group_id != user_group_id'),
2913 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2913 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2914 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2914 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2915 )
2915 )
2916 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2916 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2917 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2917 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2918 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2918 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2919 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2919 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2920
2920
2921 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2921 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2922 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2922 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2923 permission = relationship('Permission')
2923 permission = relationship('Permission')
2924
2924
2925 @classmethod
2925 @classmethod
2926 def create(cls, target_user_group, user_group, permission):
2926 def create(cls, target_user_group, user_group, permission):
2927 n = cls()
2927 n = cls()
2928 n.target_user_group = target_user_group
2928 n.target_user_group = target_user_group
2929 n.user_group = user_group
2929 n.user_group = user_group
2930 n.permission = permission
2930 n.permission = permission
2931 Session().add(n)
2931 Session().add(n)
2932 return n
2932 return n
2933
2933
2934 def __unicode__(self):
2934 def __unicode__(self):
2935 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2935 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2936
2936
2937
2937
2938 class UserGroupToPerm(Base, BaseModel):
2938 class UserGroupToPerm(Base, BaseModel):
2939 __tablename__ = 'users_group_to_perm'
2939 __tablename__ = 'users_group_to_perm'
2940 __table_args__ = (
2940 __table_args__ = (
2941 UniqueConstraint('users_group_id', 'permission_id',),
2941 UniqueConstraint('users_group_id', 'permission_id',),
2942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2943 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2943 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2944 )
2944 )
2945 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2945 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2946 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2946 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2947 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2947 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2948
2948
2949 users_group = relationship('UserGroup')
2949 users_group = relationship('UserGroup')
2950 permission = relationship('Permission')
2950 permission = relationship('Permission')
2951
2951
2952
2952
2953 class UserRepoGroupToPerm(Base, BaseModel):
2953 class UserRepoGroupToPerm(Base, BaseModel):
2954 __tablename__ = 'user_repo_group_to_perm'
2954 __tablename__ = 'user_repo_group_to_perm'
2955 __table_args__ = (
2955 __table_args__ = (
2956 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2956 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2958 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2958 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2959 )
2959 )
2960
2960
2961 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2961 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2962 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2962 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2963 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2963 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2964 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2964 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2965
2965
2966 user = relationship('User')
2966 user = relationship('User')
2967 group = relationship('RepoGroup')
2967 group = relationship('RepoGroup')
2968 permission = relationship('Permission')
2968 permission = relationship('Permission')
2969
2969
2970 @classmethod
2970 @classmethod
2971 def create(cls, user, repository_group, permission):
2971 def create(cls, user, repository_group, permission):
2972 n = cls()
2972 n = cls()
2973 n.user = user
2973 n.user = user
2974 n.group = repository_group
2974 n.group = repository_group
2975 n.permission = permission
2975 n.permission = permission
2976 Session().add(n)
2976 Session().add(n)
2977 return n
2977 return n
2978
2978
2979
2979
2980 class UserGroupRepoGroupToPerm(Base, BaseModel):
2980 class UserGroupRepoGroupToPerm(Base, BaseModel):
2981 __tablename__ = 'users_group_repo_group_to_perm'
2981 __tablename__ = 'users_group_repo_group_to_perm'
2982 __table_args__ = (
2982 __table_args__ = (
2983 UniqueConstraint('users_group_id', 'group_id'),
2983 UniqueConstraint('users_group_id', 'group_id'),
2984 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2984 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2985 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2985 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2986 )
2986 )
2987
2987
2988 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2988 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2989 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2989 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2990 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2990 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2991 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2991 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2992
2992
2993 users_group = relationship('UserGroup')
2993 users_group = relationship('UserGroup')
2994 permission = relationship('Permission')
2994 permission = relationship('Permission')
2995 group = relationship('RepoGroup')
2995 group = relationship('RepoGroup')
2996
2996
2997 @classmethod
2997 @classmethod
2998 def create(cls, user_group, repository_group, permission):
2998 def create(cls, user_group, repository_group, permission):
2999 n = cls()
2999 n = cls()
3000 n.users_group = user_group
3000 n.users_group = user_group
3001 n.group = repository_group
3001 n.group = repository_group
3002 n.permission = permission
3002 n.permission = permission
3003 Session().add(n)
3003 Session().add(n)
3004 return n
3004 return n
3005
3005
3006 def __unicode__(self):
3006 def __unicode__(self):
3007 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3007 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3008
3008
3009
3009
3010 class Statistics(Base, BaseModel):
3010 class Statistics(Base, BaseModel):
3011 __tablename__ = 'statistics'
3011 __tablename__ = 'statistics'
3012 __table_args__ = (
3012 __table_args__ = (
3013 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3013 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3014 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3014 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3015 )
3015 )
3016 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3016 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3017 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3017 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3018 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3018 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3019 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3019 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3020 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3020 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3021 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3021 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3022
3022
3023 repository = relationship('Repository', single_parent=True)
3023 repository = relationship('Repository', single_parent=True)
3024
3024
3025
3025
3026 class UserFollowing(Base, BaseModel):
3026 class UserFollowing(Base, BaseModel):
3027 __tablename__ = 'user_followings'
3027 __tablename__ = 'user_followings'
3028 __table_args__ = (
3028 __table_args__ = (
3029 UniqueConstraint('user_id', 'follows_repository_id'),
3029 UniqueConstraint('user_id', 'follows_repository_id'),
3030 UniqueConstraint('user_id', 'follows_user_id'),
3030 UniqueConstraint('user_id', 'follows_user_id'),
3031 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3031 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3032 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3032 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3033 )
3033 )
3034
3034
3035 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3035 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3036 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3036 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3037 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3037 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3038 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3038 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3039 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3039 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3040
3040
3041 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3041 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3042
3042
3043 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3043 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3044 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3044 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3045
3045
3046 @classmethod
3046 @classmethod
3047 def get_repo_followers(cls, repo_id):
3047 def get_repo_followers(cls, repo_id):
3048 return cls.query().filter(cls.follows_repo_id == repo_id)
3048 return cls.query().filter(cls.follows_repo_id == repo_id)
3049
3049
3050
3050
3051 class CacheKey(Base, BaseModel):
3051 class CacheKey(Base, BaseModel):
3052 __tablename__ = 'cache_invalidation'
3052 __tablename__ = 'cache_invalidation'
3053 __table_args__ = (
3053 __table_args__ = (
3054 UniqueConstraint('cache_key'),
3054 UniqueConstraint('cache_key'),
3055 Index('key_idx', 'cache_key'),
3055 Index('key_idx', 'cache_key'),
3056 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3056 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3057 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3057 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3058 )
3058 )
3059 CACHE_TYPE_ATOM = 'ATOM'
3059 CACHE_TYPE_ATOM = 'ATOM'
3060 CACHE_TYPE_RSS = 'RSS'
3060 CACHE_TYPE_RSS = 'RSS'
3061 CACHE_TYPE_README = 'README'
3061 CACHE_TYPE_README = 'README'
3062
3062
3063 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3063 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3064 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3064 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3065 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3065 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3066 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3066 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3067
3067
3068 def __init__(self, cache_key, cache_args=''):
3068 def __init__(self, cache_key, cache_args=''):
3069 self.cache_key = cache_key
3069 self.cache_key = cache_key
3070 self.cache_args = cache_args
3070 self.cache_args = cache_args
3071 self.cache_active = False
3071 self.cache_active = False
3072
3072
3073 def __unicode__(self):
3073 def __unicode__(self):
3074 return u"<%s('%s:%s[%s]')>" % (
3074 return u"<%s('%s:%s[%s]')>" % (
3075 self.__class__.__name__,
3075 self.__class__.__name__,
3076 self.cache_id, self.cache_key, self.cache_active)
3076 self.cache_id, self.cache_key, self.cache_active)
3077
3077
3078 def _cache_key_partition(self):
3078 def _cache_key_partition(self):
3079 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3079 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3080 return prefix, repo_name, suffix
3080 return prefix, repo_name, suffix
3081
3081
3082 def get_prefix(self):
3082 def get_prefix(self):
3083 """
3083 """
3084 Try to extract prefix from existing cache key. The key could consist
3084 Try to extract prefix from existing cache key. The key could consist
3085 of prefix, repo_name, suffix
3085 of prefix, repo_name, suffix
3086 """
3086 """
3087 # this returns prefix, repo_name, suffix
3087 # this returns prefix, repo_name, suffix
3088 return self._cache_key_partition()[0]
3088 return self._cache_key_partition()[0]
3089
3089
3090 def get_suffix(self):
3090 def get_suffix(self):
3091 """
3091 """
3092 get suffix that might have been used in _get_cache_key to
3092 get suffix that might have been used in _get_cache_key to
3093 generate self.cache_key. Only used for informational purposes
3093 generate self.cache_key. Only used for informational purposes
3094 in repo_edit.mako.
3094 in repo_edit.mako.
3095 """
3095 """
3096 # prefix, repo_name, suffix
3096 # prefix, repo_name, suffix
3097 return self._cache_key_partition()[2]
3097 return self._cache_key_partition()[2]
3098
3098
3099 @classmethod
3099 @classmethod
3100 def delete_all_cache(cls):
3100 def delete_all_cache(cls):
3101 """
3101 """
3102 Delete all cache keys from database.
3102 Delete all cache keys from database.
3103 Should only be run when all instances are down and all entries
3103 Should only be run when all instances are down and all entries
3104 thus stale.
3104 thus stale.
3105 """
3105 """
3106 cls.query().delete()
3106 cls.query().delete()
3107 Session().commit()
3107 Session().commit()
3108
3108
3109 @classmethod
3109 @classmethod
3110 def get_cache_key(cls, repo_name, cache_type):
3110 def get_cache_key(cls, repo_name, cache_type):
3111 """
3111 """
3112
3112
3113 Generate a cache key for this process of RhodeCode instance.
3113 Generate a cache key for this process of RhodeCode instance.
3114 Prefix most likely will be process id or maybe explicitly set
3114 Prefix most likely will be process id or maybe explicitly set
3115 instance_id from .ini file.
3115 instance_id from .ini file.
3116 """
3116 """
3117 import rhodecode
3117 import rhodecode
3118 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3118 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3119
3119
3120 repo_as_unicode = safe_unicode(repo_name)
3120 repo_as_unicode = safe_unicode(repo_name)
3121 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3121 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3122 if cache_type else repo_as_unicode
3122 if cache_type else repo_as_unicode
3123
3123
3124 return u'{}{}'.format(prefix, key)
3124 return u'{}{}'.format(prefix, key)
3125
3125
3126 @classmethod
3126 @classmethod
3127 def set_invalidate(cls, repo_name, delete=False):
3127 def set_invalidate(cls, repo_name, delete=False):
3128 """
3128 """
3129 Mark all caches of a repo as invalid in the database.
3129 Mark all caches of a repo as invalid in the database.
3130 """
3130 """
3131
3131
3132 try:
3132 try:
3133 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3133 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3134 if delete:
3134 if delete:
3135 log.debug('cache objects deleted for repo %s',
3135 log.debug('cache objects deleted for repo %s',
3136 safe_str(repo_name))
3136 safe_str(repo_name))
3137 qry.delete()
3137 qry.delete()
3138 else:
3138 else:
3139 log.debug('cache objects marked as invalid for repo %s',
3139 log.debug('cache objects marked as invalid for repo %s',
3140 safe_str(repo_name))
3140 safe_str(repo_name))
3141 qry.update({"cache_active": False})
3141 qry.update({"cache_active": False})
3142
3142
3143 Session().commit()
3143 Session().commit()
3144 except Exception:
3144 except Exception:
3145 log.exception(
3145 log.exception(
3146 'Cache key invalidation failed for repository %s',
3146 'Cache key invalidation failed for repository %s',
3147 safe_str(repo_name))
3147 safe_str(repo_name))
3148 Session().rollback()
3148 Session().rollback()
3149
3149
3150 @classmethod
3150 @classmethod
3151 def get_active_cache(cls, cache_key):
3151 def get_active_cache(cls, cache_key):
3152 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3152 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3153 if inv_obj:
3153 if inv_obj:
3154 return inv_obj
3154 return inv_obj
3155 return None
3155 return None
3156
3156
3157
3157
3158 class ChangesetComment(Base, BaseModel):
3158 class ChangesetComment(Base, BaseModel):
3159 __tablename__ = 'changeset_comments'
3159 __tablename__ = 'changeset_comments'
3160 __table_args__ = (
3160 __table_args__ = (
3161 Index('cc_revision_idx', 'revision'),
3161 Index('cc_revision_idx', 'revision'),
3162 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3162 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3163 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3163 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3164 )
3164 )
3165
3165
3166 COMMENT_OUTDATED = u'comment_outdated'
3166 COMMENT_OUTDATED = u'comment_outdated'
3167 COMMENT_TYPE_NOTE = u'note'
3167 COMMENT_TYPE_NOTE = u'note'
3168 COMMENT_TYPE_TODO = u'todo'
3168 COMMENT_TYPE_TODO = u'todo'
3169 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3169 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3170
3170
3171 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3171 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3172 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3172 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3173 revision = Column('revision', String(40), nullable=True)
3173 revision = Column('revision', String(40), nullable=True)
3174 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3174 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3175 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3175 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3176 line_no = Column('line_no', Unicode(10), nullable=True)
3176 line_no = Column('line_no', Unicode(10), nullable=True)
3177 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3177 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3178 f_path = Column('f_path', Unicode(1000), nullable=True)
3178 f_path = Column('f_path', Unicode(1000), nullable=True)
3179 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3179 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3180 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3180 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3181 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3181 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3182 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3182 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3183 renderer = Column('renderer', Unicode(64), nullable=True)
3183 renderer = Column('renderer', Unicode(64), nullable=True)
3184 display_state = Column('display_state', Unicode(128), nullable=True)
3184 display_state = Column('display_state', Unicode(128), nullable=True)
3185
3185
3186 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3186 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3187 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3187 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3188 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3188 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3189 author = relationship('User', lazy='joined')
3189 author = relationship('User', lazy='joined')
3190 repo = relationship('Repository')
3190 repo = relationship('Repository')
3191 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3191 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3192 pull_request = relationship('PullRequest', lazy='joined')
3192 pull_request = relationship('PullRequest', lazy='joined')
3193 pull_request_version = relationship('PullRequestVersion')
3193 pull_request_version = relationship('PullRequestVersion')
3194
3194
3195 @classmethod
3195 @classmethod
3196 def get_users(cls, revision=None, pull_request_id=None):
3196 def get_users(cls, revision=None, pull_request_id=None):
3197 """
3197 """
3198 Returns user associated with this ChangesetComment. ie those
3198 Returns user associated with this ChangesetComment. ie those
3199 who actually commented
3199 who actually commented
3200
3200
3201 :param cls:
3201 :param cls:
3202 :param revision:
3202 :param revision:
3203 """
3203 """
3204 q = Session().query(User)\
3204 q = Session().query(User)\
3205 .join(ChangesetComment.author)
3205 .join(ChangesetComment.author)
3206 if revision:
3206 if revision:
3207 q = q.filter(cls.revision == revision)
3207 q = q.filter(cls.revision == revision)
3208 elif pull_request_id:
3208 elif pull_request_id:
3209 q = q.filter(cls.pull_request_id == pull_request_id)
3209 q = q.filter(cls.pull_request_id == pull_request_id)
3210 return q.all()
3210 return q.all()
3211
3211
3212 @classmethod
3212 @classmethod
3213 def get_index_from_version(cls, pr_version, versions):
3213 def get_index_from_version(cls, pr_version, versions):
3214 num_versions = [x.pull_request_version_id for x in versions]
3214 num_versions = [x.pull_request_version_id for x in versions]
3215 try:
3215 try:
3216 return num_versions.index(pr_version) +1
3216 return num_versions.index(pr_version) +1
3217 except (IndexError, ValueError):
3217 except (IndexError, ValueError):
3218 return
3218 return
3219
3219
3220 @property
3220 @property
3221 def outdated(self):
3221 def outdated(self):
3222 return self.display_state == self.COMMENT_OUTDATED
3222 return self.display_state == self.COMMENT_OUTDATED
3223
3223
3224 def outdated_at_version(self, version):
3224 def outdated_at_version(self, version):
3225 """
3225 """
3226 Checks if comment is outdated for given pull request version
3226 Checks if comment is outdated for given pull request version
3227 """
3227 """
3228 return self.outdated and self.pull_request_version_id != version
3228 return self.outdated and self.pull_request_version_id != version
3229
3229
3230 def older_than_version(self, version):
3230 def older_than_version(self, version):
3231 """
3231 """
3232 Checks if comment is made from previous version than given
3232 Checks if comment is made from previous version than given
3233 """
3233 """
3234 if version is None:
3234 if version is None:
3235 return self.pull_request_version_id is not None
3235 return self.pull_request_version_id is not None
3236
3236
3237 return self.pull_request_version_id < version
3237 return self.pull_request_version_id < version
3238
3238
3239 @property
3239 @property
3240 def resolved(self):
3240 def resolved(self):
3241 return self.resolved_by[0] if self.resolved_by else None
3241 return self.resolved_by[0] if self.resolved_by else None
3242
3242
3243 @property
3243 @property
3244 def is_todo(self):
3244 def is_todo(self):
3245 return self.comment_type == self.COMMENT_TYPE_TODO
3245 return self.comment_type == self.COMMENT_TYPE_TODO
3246
3246
3247 @property
3247 @property
3248 def is_inline(self):
3248 def is_inline(self):
3249 return self.line_no and self.f_path
3249 return self.line_no and self.f_path
3250
3250
3251 def get_index_version(self, versions):
3251 def get_index_version(self, versions):
3252 return self.get_index_from_version(
3252 return self.get_index_from_version(
3253 self.pull_request_version_id, versions)
3253 self.pull_request_version_id, versions)
3254
3254
3255 def __repr__(self):
3255 def __repr__(self):
3256 if self.comment_id:
3256 if self.comment_id:
3257 return '<DB:Comment #%s>' % self.comment_id
3257 return '<DB:Comment #%s>' % self.comment_id
3258 else:
3258 else:
3259 return '<DB:Comment at %#x>' % id(self)
3259 return '<DB:Comment at %#x>' % id(self)
3260
3260
3261 def get_api_data(self):
3261 def get_api_data(self):
3262 comment = self
3262 comment = self
3263 data = {
3263 data = {
3264 'comment_id': comment.comment_id,
3264 'comment_id': comment.comment_id,
3265 'comment_type': comment.comment_type,
3265 'comment_type': comment.comment_type,
3266 'comment_text': comment.text,
3266 'comment_text': comment.text,
3267 'comment_status': comment.status_change,
3267 'comment_status': comment.status_change,
3268 'comment_f_path': comment.f_path,
3268 'comment_f_path': comment.f_path,
3269 'comment_lineno': comment.line_no,
3269 'comment_lineno': comment.line_no,
3270 'comment_author': comment.author,
3270 'comment_author': comment.author,
3271 'comment_created_on': comment.created_on
3271 'comment_created_on': comment.created_on
3272 }
3272 }
3273 return data
3273 return data
3274
3274
3275 def __json__(self):
3275 def __json__(self):
3276 data = dict()
3276 data = dict()
3277 data.update(self.get_api_data())
3277 data.update(self.get_api_data())
3278 return data
3278 return data
3279
3279
3280
3280
3281 class ChangesetStatus(Base, BaseModel):
3281 class ChangesetStatus(Base, BaseModel):
3282 __tablename__ = 'changeset_statuses'
3282 __tablename__ = 'changeset_statuses'
3283 __table_args__ = (
3283 __table_args__ = (
3284 Index('cs_revision_idx', 'revision'),
3284 Index('cs_revision_idx', 'revision'),
3285 Index('cs_version_idx', 'version'),
3285 Index('cs_version_idx', 'version'),
3286 UniqueConstraint('repo_id', 'revision', 'version'),
3286 UniqueConstraint('repo_id', 'revision', 'version'),
3287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3288 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3288 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3289 )
3289 )
3290 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3290 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3291 STATUS_APPROVED = 'approved'
3291 STATUS_APPROVED = 'approved'
3292 STATUS_REJECTED = 'rejected'
3292 STATUS_REJECTED = 'rejected'
3293 STATUS_UNDER_REVIEW = 'under_review'
3293 STATUS_UNDER_REVIEW = 'under_review'
3294
3294
3295 STATUSES = [
3295 STATUSES = [
3296 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3296 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3297 (STATUS_APPROVED, _("Approved")),
3297 (STATUS_APPROVED, _("Approved")),
3298 (STATUS_REJECTED, _("Rejected")),
3298 (STATUS_REJECTED, _("Rejected")),
3299 (STATUS_UNDER_REVIEW, _("Under Review")),
3299 (STATUS_UNDER_REVIEW, _("Under Review")),
3300 ]
3300 ]
3301
3301
3302 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3302 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3303 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3303 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3305 revision = Column('revision', String(40), nullable=False)
3305 revision = Column('revision', String(40), nullable=False)
3306 status = Column('status', String(128), nullable=False, default=DEFAULT)
3306 status = Column('status', String(128), nullable=False, default=DEFAULT)
3307 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3307 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3308 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3308 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3309 version = Column('version', Integer(), nullable=False, default=0)
3309 version = Column('version', Integer(), nullable=False, default=0)
3310 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3310 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3311
3311
3312 author = relationship('User', lazy='joined')
3312 author = relationship('User', lazy='joined')
3313 repo = relationship('Repository')
3313 repo = relationship('Repository')
3314 comment = relationship('ChangesetComment', lazy='joined')
3314 comment = relationship('ChangesetComment', lazy='joined')
3315 pull_request = relationship('PullRequest', lazy='joined')
3315 pull_request = relationship('PullRequest', lazy='joined')
3316
3316
3317 def __unicode__(self):
3317 def __unicode__(self):
3318 return u"<%s('%s[v%s]:%s')>" % (
3318 return u"<%s('%s[v%s]:%s')>" % (
3319 self.__class__.__name__,
3319 self.__class__.__name__,
3320 self.status, self.version, self.author
3320 self.status, self.version, self.author
3321 )
3321 )
3322
3322
3323 @classmethod
3323 @classmethod
3324 def get_status_lbl(cls, value):
3324 def get_status_lbl(cls, value):
3325 return dict(cls.STATUSES).get(value)
3325 return dict(cls.STATUSES).get(value)
3326
3326
3327 @property
3327 @property
3328 def status_lbl(self):
3328 def status_lbl(self):
3329 return ChangesetStatus.get_status_lbl(self.status)
3329 return ChangesetStatus.get_status_lbl(self.status)
3330
3330
3331 def get_api_data(self):
3331 def get_api_data(self):
3332 status = self
3332 status = self
3333 data = {
3333 data = {
3334 'status_id': status.changeset_status_id,
3334 'status_id': status.changeset_status_id,
3335 'status': status.status,
3335 'status': status.status,
3336 }
3336 }
3337 return data
3337 return data
3338
3338
3339 def __json__(self):
3339 def __json__(self):
3340 data = dict()
3340 data = dict()
3341 data.update(self.get_api_data())
3341 data.update(self.get_api_data())
3342 return data
3342 return data
3343
3343
3344
3344
3345 class _PullRequestBase(BaseModel):
3345 class _PullRequestBase(BaseModel):
3346 """
3346 """
3347 Common attributes of pull request and version entries.
3347 Common attributes of pull request and version entries.
3348 """
3348 """
3349
3349
3350 # .status values
3350 # .status values
3351 STATUS_NEW = u'new'
3351 STATUS_NEW = u'new'
3352 STATUS_OPEN = u'open'
3352 STATUS_OPEN = u'open'
3353 STATUS_CLOSED = u'closed'
3353 STATUS_CLOSED = u'closed'
3354
3354
3355 title = Column('title', Unicode(255), nullable=True)
3355 title = Column('title', Unicode(255), nullable=True)
3356 description = Column(
3356 description = Column(
3357 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3357 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3358 nullable=True)
3358 nullable=True)
3359 # new/open/closed status of pull request (not approve/reject/etc)
3359 # new/open/closed status of pull request (not approve/reject/etc)
3360 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3360 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3361 created_on = Column(
3361 created_on = Column(
3362 'created_on', DateTime(timezone=False), nullable=False,
3362 'created_on', DateTime(timezone=False), nullable=False,
3363 default=datetime.datetime.now)
3363 default=datetime.datetime.now)
3364 updated_on = Column(
3364 updated_on = Column(
3365 'updated_on', DateTime(timezone=False), nullable=False,
3365 'updated_on', DateTime(timezone=False), nullable=False,
3366 default=datetime.datetime.now)
3366 default=datetime.datetime.now)
3367
3367
3368 @declared_attr
3368 @declared_attr
3369 def user_id(cls):
3369 def user_id(cls):
3370 return Column(
3370 return Column(
3371 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3371 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3372 unique=None)
3372 unique=None)
3373
3373
3374 # 500 revisions max
3374 # 500 revisions max
3375 _revisions = Column(
3375 _revisions = Column(
3376 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3376 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3377
3377
3378 @declared_attr
3378 @declared_attr
3379 def source_repo_id(cls):
3379 def source_repo_id(cls):
3380 # TODO: dan: rename column to source_repo_id
3380 # TODO: dan: rename column to source_repo_id
3381 return Column(
3381 return Column(
3382 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3382 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3383 nullable=False)
3383 nullable=False)
3384
3384
3385 source_ref = Column('org_ref', Unicode(255), nullable=False)
3385 source_ref = Column('org_ref', Unicode(255), nullable=False)
3386
3386
3387 @declared_attr
3387 @declared_attr
3388 def target_repo_id(cls):
3388 def target_repo_id(cls):
3389 # TODO: dan: rename column to target_repo_id
3389 # TODO: dan: rename column to target_repo_id
3390 return Column(
3390 return Column(
3391 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3391 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3392 nullable=False)
3392 nullable=False)
3393
3393
3394 target_ref = Column('other_ref', Unicode(255), nullable=False)
3394 target_ref = Column('other_ref', Unicode(255), nullable=False)
3395 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3395 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3396
3396
3397 # TODO: dan: rename column to last_merge_source_rev
3397 # TODO: dan: rename column to last_merge_source_rev
3398 _last_merge_source_rev = Column(
3398 _last_merge_source_rev = Column(
3399 'last_merge_org_rev', String(40), nullable=True)
3399 'last_merge_org_rev', String(40), nullable=True)
3400 # TODO: dan: rename column to last_merge_target_rev
3400 # TODO: dan: rename column to last_merge_target_rev
3401 _last_merge_target_rev = Column(
3401 _last_merge_target_rev = Column(
3402 'last_merge_other_rev', String(40), nullable=True)
3402 'last_merge_other_rev', String(40), nullable=True)
3403 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3403 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3404 merge_rev = Column('merge_rev', String(40), nullable=True)
3404 merge_rev = Column('merge_rev', String(40), nullable=True)
3405
3405
3406 reviewer_data = Column(
3406 reviewer_data = Column(
3407 'reviewer_data_json', MutationObj.as_mutable(
3407 'reviewer_data_json', MutationObj.as_mutable(
3408 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3408 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3409
3409
3410 @property
3410 @property
3411 def reviewer_data_json(self):
3411 def reviewer_data_json(self):
3412 return json.dumps(self.reviewer_data)
3412 return json.dumps(self.reviewer_data)
3413
3413
3414 @hybrid_property
3414 @hybrid_property
3415 def description_safe(self):
3415 def description_safe(self):
3416 from rhodecode.lib import helpers as h
3416 from rhodecode.lib import helpers as h
3417 return h.escape(self.description)
3417 return h.escape(self.description)
3418
3418
3419 @hybrid_property
3419 @hybrid_property
3420 def revisions(self):
3420 def revisions(self):
3421 return self._revisions.split(':') if self._revisions else []
3421 return self._revisions.split(':') if self._revisions else []
3422
3422
3423 @revisions.setter
3423 @revisions.setter
3424 def revisions(self, val):
3424 def revisions(self, val):
3425 self._revisions = ':'.join(val)
3425 self._revisions = ':'.join(val)
3426
3426
3427 @hybrid_property
3427 @hybrid_property
3428 def last_merge_status(self):
3428 def last_merge_status(self):
3429 return safe_int(self._last_merge_status)
3429 return safe_int(self._last_merge_status)
3430
3430
3431 @last_merge_status.setter
3431 @last_merge_status.setter
3432 def last_merge_status(self, val):
3432 def last_merge_status(self, val):
3433 self._last_merge_status = val
3433 self._last_merge_status = val
3434
3434
3435 @declared_attr
3435 @declared_attr
3436 def author(cls):
3436 def author(cls):
3437 return relationship('User', lazy='joined')
3437 return relationship('User', lazy='joined')
3438
3438
3439 @declared_attr
3439 @declared_attr
3440 def source_repo(cls):
3440 def source_repo(cls):
3441 return relationship(
3441 return relationship(
3442 'Repository',
3442 'Repository',
3443 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3443 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3444
3444
3445 @property
3445 @property
3446 def source_ref_parts(self):
3446 def source_ref_parts(self):
3447 return self.unicode_to_reference(self.source_ref)
3447 return self.unicode_to_reference(self.source_ref)
3448
3448
3449 @declared_attr
3449 @declared_attr
3450 def target_repo(cls):
3450 def target_repo(cls):
3451 return relationship(
3451 return relationship(
3452 'Repository',
3452 'Repository',
3453 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3453 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3454
3454
3455 @property
3455 @property
3456 def target_ref_parts(self):
3456 def target_ref_parts(self):
3457 return self.unicode_to_reference(self.target_ref)
3457 return self.unicode_to_reference(self.target_ref)
3458
3458
3459 @property
3459 @property
3460 def shadow_merge_ref(self):
3460 def shadow_merge_ref(self):
3461 return self.unicode_to_reference(self._shadow_merge_ref)
3461 return self.unicode_to_reference(self._shadow_merge_ref)
3462
3462
3463 @shadow_merge_ref.setter
3463 @shadow_merge_ref.setter
3464 def shadow_merge_ref(self, ref):
3464 def shadow_merge_ref(self, ref):
3465 self._shadow_merge_ref = self.reference_to_unicode(ref)
3465 self._shadow_merge_ref = self.reference_to_unicode(ref)
3466
3466
3467 def unicode_to_reference(self, raw):
3467 def unicode_to_reference(self, raw):
3468 """
3468 """
3469 Convert a unicode (or string) to a reference object.
3469 Convert a unicode (or string) to a reference object.
3470 If unicode evaluates to False it returns None.
3470 If unicode evaluates to False it returns None.
3471 """
3471 """
3472 if raw:
3472 if raw:
3473 refs = raw.split(':')
3473 refs = raw.split(':')
3474 return Reference(*refs)
3474 return Reference(*refs)
3475 else:
3475 else:
3476 return None
3476 return None
3477
3477
3478 def reference_to_unicode(self, ref):
3478 def reference_to_unicode(self, ref):
3479 """
3479 """
3480 Convert a reference object to unicode.
3480 Convert a reference object to unicode.
3481 If reference is None it returns None.
3481 If reference is None it returns None.
3482 """
3482 """
3483 if ref:
3483 if ref:
3484 return u':'.join(ref)
3484 return u':'.join(ref)
3485 else:
3485 else:
3486 return None
3486 return None
3487
3487
3488 def get_api_data(self, with_merge_state=True):
3488 def get_api_data(self, with_merge_state=True):
3489 from rhodecode.model.pull_request import PullRequestModel
3489 from rhodecode.model.pull_request import PullRequestModel
3490
3490
3491 pull_request = self
3491 pull_request = self
3492 if with_merge_state:
3492 if with_merge_state:
3493 merge_status = PullRequestModel().merge_status(pull_request)
3493 merge_status = PullRequestModel().merge_status(pull_request)
3494 merge_state = {
3494 merge_state = {
3495 'status': merge_status[0],
3495 'status': merge_status[0],
3496 'message': safe_unicode(merge_status[1]),
3496 'message': safe_unicode(merge_status[1]),
3497 }
3497 }
3498 else:
3498 else:
3499 merge_state = {'status': 'not_available',
3499 merge_state = {'status': 'not_available',
3500 'message': 'not_available'}
3500 'message': 'not_available'}
3501
3501
3502 merge_data = {
3502 merge_data = {
3503 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3503 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3504 'reference': (
3504 'reference': (
3505 pull_request.shadow_merge_ref._asdict()
3505 pull_request.shadow_merge_ref._asdict()
3506 if pull_request.shadow_merge_ref else None),
3506 if pull_request.shadow_merge_ref else None),
3507 }
3507 }
3508
3508
3509 data = {
3509 data = {
3510 'pull_request_id': pull_request.pull_request_id,
3510 'pull_request_id': pull_request.pull_request_id,
3511 'url': PullRequestModel().get_url(pull_request),
3511 'url': PullRequestModel().get_url(pull_request),
3512 'title': pull_request.title,
3512 'title': pull_request.title,
3513 'description': pull_request.description,
3513 'description': pull_request.description,
3514 'status': pull_request.status,
3514 'status': pull_request.status,
3515 'created_on': pull_request.created_on,
3515 'created_on': pull_request.created_on,
3516 'updated_on': pull_request.updated_on,
3516 'updated_on': pull_request.updated_on,
3517 'commit_ids': pull_request.revisions,
3517 'commit_ids': pull_request.revisions,
3518 'review_status': pull_request.calculated_review_status(),
3518 'review_status': pull_request.calculated_review_status(),
3519 'mergeable': merge_state,
3519 'mergeable': merge_state,
3520 'source': {
3520 'source': {
3521 'clone_url': pull_request.source_repo.clone_url(),
3521 'clone_url': pull_request.source_repo.clone_url(),
3522 'repository': pull_request.source_repo.repo_name,
3522 'repository': pull_request.source_repo.repo_name,
3523 'reference': {
3523 'reference': {
3524 'name': pull_request.source_ref_parts.name,
3524 'name': pull_request.source_ref_parts.name,
3525 'type': pull_request.source_ref_parts.type,
3525 'type': pull_request.source_ref_parts.type,
3526 'commit_id': pull_request.source_ref_parts.commit_id,
3526 'commit_id': pull_request.source_ref_parts.commit_id,
3527 },
3527 },
3528 },
3528 },
3529 'target': {
3529 'target': {
3530 'clone_url': pull_request.target_repo.clone_url(),
3530 'clone_url': pull_request.target_repo.clone_url(),
3531 'repository': pull_request.target_repo.repo_name,
3531 'repository': pull_request.target_repo.repo_name,
3532 'reference': {
3532 'reference': {
3533 'name': pull_request.target_ref_parts.name,
3533 'name': pull_request.target_ref_parts.name,
3534 'type': pull_request.target_ref_parts.type,
3534 'type': pull_request.target_ref_parts.type,
3535 'commit_id': pull_request.target_ref_parts.commit_id,
3535 'commit_id': pull_request.target_ref_parts.commit_id,
3536 },
3536 },
3537 },
3537 },
3538 'merge': merge_data,
3538 'merge': merge_data,
3539 'author': pull_request.author.get_api_data(include_secrets=False,
3539 'author': pull_request.author.get_api_data(include_secrets=False,
3540 details='basic'),
3540 details='basic'),
3541 'reviewers': [
3541 'reviewers': [
3542 {
3542 {
3543 'user': reviewer.get_api_data(include_secrets=False,
3543 'user': reviewer.get_api_data(include_secrets=False,
3544 details='basic'),
3544 details='basic'),
3545 'reasons': reasons,
3545 'reasons': reasons,
3546 'review_status': st[0][1].status if st else 'not_reviewed',
3546 'review_status': st[0][1].status if st else 'not_reviewed',
3547 }
3547 }
3548 for reviewer, reasons, mandatory, st in
3548 for reviewer, reasons, mandatory, st in
3549 pull_request.reviewers_statuses()
3549 pull_request.reviewers_statuses()
3550 ]
3550 ]
3551 }
3551 }
3552
3552
3553 return data
3553 return data
3554
3554
3555
3555
3556 class PullRequest(Base, _PullRequestBase):
3556 class PullRequest(Base, _PullRequestBase):
3557 __tablename__ = 'pull_requests'
3557 __tablename__ = 'pull_requests'
3558 __table_args__ = (
3558 __table_args__ = (
3559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3560 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3560 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3561 )
3561 )
3562
3562
3563 pull_request_id = Column(
3563 pull_request_id = Column(
3564 'pull_request_id', Integer(), nullable=False, primary_key=True)
3564 'pull_request_id', Integer(), nullable=False, primary_key=True)
3565
3565
3566 def __repr__(self):
3566 def __repr__(self):
3567 if self.pull_request_id:
3567 if self.pull_request_id:
3568 return '<DB:PullRequest #%s>' % self.pull_request_id
3568 return '<DB:PullRequest #%s>' % self.pull_request_id
3569 else:
3569 else:
3570 return '<DB:PullRequest at %#x>' % id(self)
3570 return '<DB:PullRequest at %#x>' % id(self)
3571
3571
3572 reviewers = relationship('PullRequestReviewers',
3572 reviewers = relationship('PullRequestReviewers',
3573 cascade="all, delete, delete-orphan")
3573 cascade="all, delete, delete-orphan")
3574 statuses = relationship('ChangesetStatus',
3574 statuses = relationship('ChangesetStatus',
3575 cascade="all, delete, delete-orphan")
3575 cascade="all, delete, delete-orphan")
3576 comments = relationship('ChangesetComment',
3576 comments = relationship('ChangesetComment',
3577 cascade="all, delete, delete-orphan")
3577 cascade="all, delete, delete-orphan")
3578 versions = relationship('PullRequestVersion',
3578 versions = relationship('PullRequestVersion',
3579 cascade="all, delete, delete-orphan",
3579 cascade="all, delete, delete-orphan",
3580 lazy='dynamic')
3580 lazy='dynamic')
3581
3581
3582 @classmethod
3582 @classmethod
3583 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3583 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3584 internal_methods=None):
3584 internal_methods=None):
3585
3585
3586 class PullRequestDisplay(object):
3586 class PullRequestDisplay(object):
3587 """
3587 """
3588 Special object wrapper for showing PullRequest data via Versions
3588 Special object wrapper for showing PullRequest data via Versions
3589 It mimics PR object as close as possible. This is read only object
3589 It mimics PR object as close as possible. This is read only object
3590 just for display
3590 just for display
3591 """
3591 """
3592
3592
3593 def __init__(self, attrs, internal=None):
3593 def __init__(self, attrs, internal=None):
3594 self.attrs = attrs
3594 self.attrs = attrs
3595 # internal have priority over the given ones via attrs
3595 # internal have priority over the given ones via attrs
3596 self.internal = internal or ['versions']
3596 self.internal = internal or ['versions']
3597
3597
3598 def __getattr__(self, item):
3598 def __getattr__(self, item):
3599 if item in self.internal:
3599 if item in self.internal:
3600 return getattr(self, item)
3600 return getattr(self, item)
3601 try:
3601 try:
3602 return self.attrs[item]
3602 return self.attrs[item]
3603 except KeyError:
3603 except KeyError:
3604 raise AttributeError(
3604 raise AttributeError(
3605 '%s object has no attribute %s' % (self, item))
3605 '%s object has no attribute %s' % (self, item))
3606
3606
3607 def __repr__(self):
3607 def __repr__(self):
3608 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3608 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3609
3609
3610 def versions(self):
3610 def versions(self):
3611 return pull_request_obj.versions.order_by(
3611 return pull_request_obj.versions.order_by(
3612 PullRequestVersion.pull_request_version_id).all()
3612 PullRequestVersion.pull_request_version_id).all()
3613
3613
3614 def is_closed(self):
3614 def is_closed(self):
3615 return pull_request_obj.is_closed()
3615 return pull_request_obj.is_closed()
3616
3616
3617 @property
3617 @property
3618 def pull_request_version_id(self):
3618 def pull_request_version_id(self):
3619 return getattr(pull_request_obj, 'pull_request_version_id', None)
3619 return getattr(pull_request_obj, 'pull_request_version_id', None)
3620
3620
3621 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3621 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3622
3622
3623 attrs.author = StrictAttributeDict(
3623 attrs.author = StrictAttributeDict(
3624 pull_request_obj.author.get_api_data())
3624 pull_request_obj.author.get_api_data())
3625 if pull_request_obj.target_repo:
3625 if pull_request_obj.target_repo:
3626 attrs.target_repo = StrictAttributeDict(
3626 attrs.target_repo = StrictAttributeDict(
3627 pull_request_obj.target_repo.get_api_data())
3627 pull_request_obj.target_repo.get_api_data())
3628 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3628 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3629
3629
3630 if pull_request_obj.source_repo:
3630 if pull_request_obj.source_repo:
3631 attrs.source_repo = StrictAttributeDict(
3631 attrs.source_repo = StrictAttributeDict(
3632 pull_request_obj.source_repo.get_api_data())
3632 pull_request_obj.source_repo.get_api_data())
3633 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3633 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3634
3634
3635 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3635 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3636 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3636 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3637 attrs.revisions = pull_request_obj.revisions
3637 attrs.revisions = pull_request_obj.revisions
3638
3638
3639 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3639 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3640 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3640 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3641 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3641 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3642
3642
3643 return PullRequestDisplay(attrs, internal=internal_methods)
3643 return PullRequestDisplay(attrs, internal=internal_methods)
3644
3644
3645 def is_closed(self):
3645 def is_closed(self):
3646 return self.status == self.STATUS_CLOSED
3646 return self.status == self.STATUS_CLOSED
3647
3647
3648 def __json__(self):
3648 def __json__(self):
3649 return {
3649 return {
3650 'revisions': self.revisions,
3650 'revisions': self.revisions,
3651 }
3651 }
3652
3652
3653 def calculated_review_status(self):
3653 def calculated_review_status(self):
3654 from rhodecode.model.changeset_status import ChangesetStatusModel
3654 from rhodecode.model.changeset_status import ChangesetStatusModel
3655 return ChangesetStatusModel().calculated_review_status(self)
3655 return ChangesetStatusModel().calculated_review_status(self)
3656
3656
3657 def reviewers_statuses(self):
3657 def reviewers_statuses(self):
3658 from rhodecode.model.changeset_status import ChangesetStatusModel
3658 from rhodecode.model.changeset_status import ChangesetStatusModel
3659 return ChangesetStatusModel().reviewers_statuses(self)
3659 return ChangesetStatusModel().reviewers_statuses(self)
3660
3660
3661 @property
3661 @property
3662 def workspace_id(self):
3662 def workspace_id(self):
3663 from rhodecode.model.pull_request import PullRequestModel
3663 from rhodecode.model.pull_request import PullRequestModel
3664 return PullRequestModel()._workspace_id(self)
3664 return PullRequestModel()._workspace_id(self)
3665
3665
3666 def get_shadow_repo(self):
3666 def get_shadow_repo(self):
3667 workspace_id = self.workspace_id
3667 workspace_id = self.workspace_id
3668 vcs_obj = self.target_repo.scm_instance()
3668 vcs_obj = self.target_repo.scm_instance()
3669 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3669 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3670 workspace_id)
3670 workspace_id)
3671 return vcs_obj._get_shadow_instance(shadow_repository_path)
3671 return vcs_obj._get_shadow_instance(shadow_repository_path)
3672
3672
3673
3673
3674 class PullRequestVersion(Base, _PullRequestBase):
3674 class PullRequestVersion(Base, _PullRequestBase):
3675 __tablename__ = 'pull_request_versions'
3675 __tablename__ = 'pull_request_versions'
3676 __table_args__ = (
3676 __table_args__ = (
3677 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3677 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3678 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3678 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3679 )
3679 )
3680
3680
3681 pull_request_version_id = Column(
3681 pull_request_version_id = Column(
3682 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3682 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3683 pull_request_id = Column(
3683 pull_request_id = Column(
3684 'pull_request_id', Integer(),
3684 'pull_request_id', Integer(),
3685 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3685 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3686 pull_request = relationship('PullRequest')
3686 pull_request = relationship('PullRequest')
3687
3687
3688 def __repr__(self):
3688 def __repr__(self):
3689 if self.pull_request_version_id:
3689 if self.pull_request_version_id:
3690 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3690 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3691 else:
3691 else:
3692 return '<DB:PullRequestVersion at %#x>' % id(self)
3692 return '<DB:PullRequestVersion at %#x>' % id(self)
3693
3693
3694 @property
3694 @property
3695 def reviewers(self):
3695 def reviewers(self):
3696 return self.pull_request.reviewers
3696 return self.pull_request.reviewers
3697
3697
3698 @property
3698 @property
3699 def versions(self):
3699 def versions(self):
3700 return self.pull_request.versions
3700 return self.pull_request.versions
3701
3701
3702 def is_closed(self):
3702 def is_closed(self):
3703 # calculate from original
3703 # calculate from original
3704 return self.pull_request.status == self.STATUS_CLOSED
3704 return self.pull_request.status == self.STATUS_CLOSED
3705
3705
3706 def calculated_review_status(self):
3706 def calculated_review_status(self):
3707 return self.pull_request.calculated_review_status()
3707 return self.pull_request.calculated_review_status()
3708
3708
3709 def reviewers_statuses(self):
3709 def reviewers_statuses(self):
3710 return self.pull_request.reviewers_statuses()
3710 return self.pull_request.reviewers_statuses()
3711
3711
3712
3712
3713 class PullRequestReviewers(Base, BaseModel):
3713 class PullRequestReviewers(Base, BaseModel):
3714 __tablename__ = 'pull_request_reviewers'
3714 __tablename__ = 'pull_request_reviewers'
3715 __table_args__ = (
3715 __table_args__ = (
3716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3718 )
3718 )
3719
3719
3720 @hybrid_property
3720 @hybrid_property
3721 def reasons(self):
3721 def reasons(self):
3722 if not self._reasons:
3722 if not self._reasons:
3723 return []
3723 return []
3724 return self._reasons
3724 return self._reasons
3725
3725
3726 @reasons.setter
3726 @reasons.setter
3727 def reasons(self, val):
3727 def reasons(self, val):
3728 val = val or []
3728 val = val or []
3729 if any(not isinstance(x, basestring) for x in val):
3729 if any(not isinstance(x, basestring) for x in val):
3730 raise Exception('invalid reasons type, must be list of strings')
3730 raise Exception('invalid reasons type, must be list of strings')
3731 self._reasons = val
3731 self._reasons = val
3732
3732
3733 pull_requests_reviewers_id = Column(
3733 pull_requests_reviewers_id = Column(
3734 'pull_requests_reviewers_id', Integer(), nullable=False,
3734 'pull_requests_reviewers_id', Integer(), nullable=False,
3735 primary_key=True)
3735 primary_key=True)
3736 pull_request_id = Column(
3736 pull_request_id = Column(
3737 "pull_request_id", Integer(),
3737 "pull_request_id", Integer(),
3738 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3738 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3739 user_id = Column(
3739 user_id = Column(
3740 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3740 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3741 _reasons = Column(
3741 _reasons = Column(
3742 'reason', MutationList.as_mutable(
3742 'reason', MutationList.as_mutable(
3743 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3743 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3744 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3744 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3745 user = relationship('User')
3745 user = relationship('User')
3746 pull_request = relationship('PullRequest')
3746 pull_request = relationship('PullRequest')
3747
3747
3748
3748
3749 class Notification(Base, BaseModel):
3749 class Notification(Base, BaseModel):
3750 __tablename__ = 'notifications'
3750 __tablename__ = 'notifications'
3751 __table_args__ = (
3751 __table_args__ = (
3752 Index('notification_type_idx', 'type'),
3752 Index('notification_type_idx', 'type'),
3753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3755 )
3755 )
3756
3756
3757 TYPE_CHANGESET_COMMENT = u'cs_comment'
3757 TYPE_CHANGESET_COMMENT = u'cs_comment'
3758 TYPE_MESSAGE = u'message'
3758 TYPE_MESSAGE = u'message'
3759 TYPE_MENTION = u'mention'
3759 TYPE_MENTION = u'mention'
3760 TYPE_REGISTRATION = u'registration'
3760 TYPE_REGISTRATION = u'registration'
3761 TYPE_PULL_REQUEST = u'pull_request'
3761 TYPE_PULL_REQUEST = u'pull_request'
3762 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3762 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3763
3763
3764 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3764 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3765 subject = Column('subject', Unicode(512), nullable=True)
3765 subject = Column('subject', Unicode(512), nullable=True)
3766 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3766 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3767 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3767 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3768 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3768 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3769 type_ = Column('type', Unicode(255))
3769 type_ = Column('type', Unicode(255))
3770
3770
3771 created_by_user = relationship('User')
3771 created_by_user = relationship('User')
3772 notifications_to_users = relationship('UserNotification', lazy='joined',
3772 notifications_to_users = relationship('UserNotification', lazy='joined',
3773 cascade="all, delete, delete-orphan")
3773 cascade="all, delete, delete-orphan")
3774
3774
3775 @property
3775 @property
3776 def recipients(self):
3776 def recipients(self):
3777 return [x.user for x in UserNotification.query()\
3777 return [x.user for x in UserNotification.query()\
3778 .filter(UserNotification.notification == self)\
3778 .filter(UserNotification.notification == self)\
3779 .order_by(UserNotification.user_id.asc()).all()]
3779 .order_by(UserNotification.user_id.asc()).all()]
3780
3780
3781 @classmethod
3781 @classmethod
3782 def create(cls, created_by, subject, body, recipients, type_=None):
3782 def create(cls, created_by, subject, body, recipients, type_=None):
3783 if type_ is None:
3783 if type_ is None:
3784 type_ = Notification.TYPE_MESSAGE
3784 type_ = Notification.TYPE_MESSAGE
3785
3785
3786 notification = cls()
3786 notification = cls()
3787 notification.created_by_user = created_by
3787 notification.created_by_user = created_by
3788 notification.subject = subject
3788 notification.subject = subject
3789 notification.body = body
3789 notification.body = body
3790 notification.type_ = type_
3790 notification.type_ = type_
3791 notification.created_on = datetime.datetime.now()
3791 notification.created_on = datetime.datetime.now()
3792
3792
3793 for u in recipients:
3793 for u in recipients:
3794 assoc = UserNotification()
3794 assoc = UserNotification()
3795 assoc.notification = notification
3795 assoc.notification = notification
3796
3796
3797 # if created_by is inside recipients mark his notification
3797 # if created_by is inside recipients mark his notification
3798 # as read
3798 # as read
3799 if u.user_id == created_by.user_id:
3799 if u.user_id == created_by.user_id:
3800 assoc.read = True
3800 assoc.read = True
3801
3801
3802 u.notifications.append(assoc)
3802 u.notifications.append(assoc)
3803 Session().add(notification)
3803 Session().add(notification)
3804
3804
3805 return notification
3805 return notification
3806
3806
3807
3807
3808 class UserNotification(Base, BaseModel):
3808 class UserNotification(Base, BaseModel):
3809 __tablename__ = 'user_to_notification'
3809 __tablename__ = 'user_to_notification'
3810 __table_args__ = (
3810 __table_args__ = (
3811 UniqueConstraint('user_id', 'notification_id'),
3811 UniqueConstraint('user_id', 'notification_id'),
3812 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3812 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3813 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3813 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3814 )
3814 )
3815 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3815 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3816 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3816 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3817 read = Column('read', Boolean, default=False)
3817 read = Column('read', Boolean, default=False)
3818 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3818 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3819
3819
3820 user = relationship('User', lazy="joined")
3820 user = relationship('User', lazy="joined")
3821 notification = relationship('Notification', lazy="joined",
3821 notification = relationship('Notification', lazy="joined",
3822 order_by=lambda: Notification.created_on.desc(),)
3822 order_by=lambda: Notification.created_on.desc(),)
3823
3823
3824 def mark_as_read(self):
3824 def mark_as_read(self):
3825 self.read = True
3825 self.read = True
3826 Session().add(self)
3826 Session().add(self)
3827
3827
3828
3828
3829 class Gist(Base, BaseModel):
3829 class Gist(Base, BaseModel):
3830 __tablename__ = 'gists'
3830 __tablename__ = 'gists'
3831 __table_args__ = (
3831 __table_args__ = (
3832 Index('g_gist_access_id_idx', 'gist_access_id'),
3832 Index('g_gist_access_id_idx', 'gist_access_id'),
3833 Index('g_created_on_idx', 'created_on'),
3833 Index('g_created_on_idx', 'created_on'),
3834 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3834 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3835 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3835 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3836 )
3836 )
3837 GIST_PUBLIC = u'public'
3837 GIST_PUBLIC = u'public'
3838 GIST_PRIVATE = u'private'
3838 GIST_PRIVATE = u'private'
3839 DEFAULT_FILENAME = u'gistfile1.txt'
3839 DEFAULT_FILENAME = u'gistfile1.txt'
3840
3840
3841 ACL_LEVEL_PUBLIC = u'acl_public'
3841 ACL_LEVEL_PUBLIC = u'acl_public'
3842 ACL_LEVEL_PRIVATE = u'acl_private'
3842 ACL_LEVEL_PRIVATE = u'acl_private'
3843
3843
3844 gist_id = Column('gist_id', Integer(), primary_key=True)
3844 gist_id = Column('gist_id', Integer(), primary_key=True)
3845 gist_access_id = Column('gist_access_id', Unicode(250))
3845 gist_access_id = Column('gist_access_id', Unicode(250))
3846 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3846 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3847 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3847 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3848 gist_expires = Column('gist_expires', Float(53), nullable=False)
3848 gist_expires = Column('gist_expires', Float(53), nullable=False)
3849 gist_type = Column('gist_type', Unicode(128), nullable=False)
3849 gist_type = Column('gist_type', Unicode(128), nullable=False)
3850 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3850 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3851 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3851 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3852 acl_level = Column('acl_level', Unicode(128), nullable=True)
3852 acl_level = Column('acl_level', Unicode(128), nullable=True)
3853
3853
3854 owner = relationship('User')
3854 owner = relationship('User')
3855
3855
3856 def __repr__(self):
3856 def __repr__(self):
3857 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3857 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3858
3858
3859 @hybrid_property
3859 @hybrid_property
3860 def description_safe(self):
3860 def description_safe(self):
3861 from rhodecode.lib import helpers as h
3861 from rhodecode.lib import helpers as h
3862 return h.escape(self.gist_description)
3862 return h.escape(self.gist_description)
3863
3863
3864 @classmethod
3864 @classmethod
3865 def get_or_404(cls, id_):
3865 def get_or_404(cls, id_):
3866 from pyramid.httpexceptions import HTTPNotFound
3866 from pyramid.httpexceptions import HTTPNotFound
3867
3867
3868 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3868 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3869 if not res:
3869 if not res:
3870 raise HTTPNotFound()
3870 raise HTTPNotFound()
3871 return res
3871 return res
3872
3872
3873 @classmethod
3873 @classmethod
3874 def get_by_access_id(cls, gist_access_id):
3874 def get_by_access_id(cls, gist_access_id):
3875 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3875 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3876
3876
3877 def gist_url(self):
3877 def gist_url(self):
3878 from rhodecode.model.gist import GistModel
3878 from rhodecode.model.gist import GistModel
3879 return GistModel().get_url(self)
3879 return GistModel().get_url(self)
3880
3880
3881 @classmethod
3881 @classmethod
3882 def base_path(cls):
3882 def base_path(cls):
3883 """
3883 """
3884 Returns base path when all gists are stored
3884 Returns base path when all gists are stored
3885
3885
3886 :param cls:
3886 :param cls:
3887 """
3887 """
3888 from rhodecode.model.gist import GIST_STORE_LOC
3888 from rhodecode.model.gist import GIST_STORE_LOC
3889 q = Session().query(RhodeCodeUi)\
3889 q = Session().query(RhodeCodeUi)\
3890 .filter(RhodeCodeUi.ui_key == URL_SEP)
3890 .filter(RhodeCodeUi.ui_key == URL_SEP)
3891 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3891 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3892 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3892 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3893
3893
3894 def get_api_data(self):
3894 def get_api_data(self):
3895 """
3895 """
3896 Common function for generating gist related data for API
3896 Common function for generating gist related data for API
3897 """
3897 """
3898 gist = self
3898 gist = self
3899 data = {
3899 data = {
3900 'gist_id': gist.gist_id,
3900 'gist_id': gist.gist_id,
3901 'type': gist.gist_type,
3901 'type': gist.gist_type,
3902 'access_id': gist.gist_access_id,
3902 'access_id': gist.gist_access_id,
3903 'description': gist.gist_description,
3903 'description': gist.gist_description,
3904 'url': gist.gist_url(),
3904 'url': gist.gist_url(),
3905 'expires': gist.gist_expires,
3905 'expires': gist.gist_expires,
3906 'created_on': gist.created_on,
3906 'created_on': gist.created_on,
3907 'modified_at': gist.modified_at,
3907 'modified_at': gist.modified_at,
3908 'content': None,
3908 'content': None,
3909 'acl_level': gist.acl_level,
3909 'acl_level': gist.acl_level,
3910 }
3910 }
3911 return data
3911 return data
3912
3912
3913 def __json__(self):
3913 def __json__(self):
3914 data = dict(
3914 data = dict(
3915 )
3915 )
3916 data.update(self.get_api_data())
3916 data.update(self.get_api_data())
3917 return data
3917 return data
3918 # SCM functions
3918 # SCM functions
3919
3919
3920 def scm_instance(self, **kwargs):
3920 def scm_instance(self, **kwargs):
3921 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3921 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3922 return get_vcs_instance(
3922 return get_vcs_instance(
3923 repo_path=safe_str(full_repo_path), create=False)
3923 repo_path=safe_str(full_repo_path), create=False)
3924
3924
3925
3925
3926 class ExternalIdentity(Base, BaseModel):
3926 class ExternalIdentity(Base, BaseModel):
3927 __tablename__ = 'external_identities'
3927 __tablename__ = 'external_identities'
3928 __table_args__ = (
3928 __table_args__ = (
3929 Index('local_user_id_idx', 'local_user_id'),
3929 Index('local_user_id_idx', 'local_user_id'),
3930 Index('external_id_idx', 'external_id'),
3930 Index('external_id_idx', 'external_id'),
3931 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3931 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3932 'mysql_charset': 'utf8'})
3932 'mysql_charset': 'utf8'})
3933
3933
3934 external_id = Column('external_id', Unicode(255), default=u'',
3934 external_id = Column('external_id', Unicode(255), default=u'',
3935 primary_key=True)
3935 primary_key=True)
3936 external_username = Column('external_username', Unicode(1024), default=u'')
3936 external_username = Column('external_username', Unicode(1024), default=u'')
3937 local_user_id = Column('local_user_id', Integer(),
3937 local_user_id = Column('local_user_id', Integer(),
3938 ForeignKey('users.user_id'), primary_key=True)
3938 ForeignKey('users.user_id'), primary_key=True)
3939 provider_name = Column('provider_name', Unicode(255), default=u'',
3939 provider_name = Column('provider_name', Unicode(255), default=u'',
3940 primary_key=True)
3940 primary_key=True)
3941 access_token = Column('access_token', String(1024), default=u'')
3941 access_token = Column('access_token', String(1024), default=u'')
3942 alt_token = Column('alt_token', String(1024), default=u'')
3942 alt_token = Column('alt_token', String(1024), default=u'')
3943 token_secret = Column('token_secret', String(1024), default=u'')
3943 token_secret = Column('token_secret', String(1024), default=u'')
3944
3944
3945 @classmethod
3945 @classmethod
3946 def by_external_id_and_provider(cls, external_id, provider_name,
3946 def by_external_id_and_provider(cls, external_id, provider_name,
3947 local_user_id=None):
3947 local_user_id=None):
3948 """
3948 """
3949 Returns ExternalIdentity instance based on search params
3949 Returns ExternalIdentity instance based on search params
3950
3950
3951 :param external_id:
3951 :param external_id:
3952 :param provider_name:
3952 :param provider_name:
3953 :return: ExternalIdentity
3953 :return: ExternalIdentity
3954 """
3954 """
3955 query = cls.query()
3955 query = cls.query()
3956 query = query.filter(cls.external_id == external_id)
3956 query = query.filter(cls.external_id == external_id)
3957 query = query.filter(cls.provider_name == provider_name)
3957 query = query.filter(cls.provider_name == provider_name)
3958 if local_user_id:
3958 if local_user_id:
3959 query = query.filter(cls.local_user_id == local_user_id)
3959 query = query.filter(cls.local_user_id == local_user_id)
3960 return query.first()
3960 return query.first()
3961
3961
3962 @classmethod
3962 @classmethod
3963 def user_by_external_id_and_provider(cls, external_id, provider_name):
3963 def user_by_external_id_and_provider(cls, external_id, provider_name):
3964 """
3964 """
3965 Returns User instance based on search params
3965 Returns User instance based on search params
3966
3966
3967 :param external_id:
3967 :param external_id:
3968 :param provider_name:
3968 :param provider_name:
3969 :return: User
3969 :return: User
3970 """
3970 """
3971 query = User.query()
3971 query = User.query()
3972 query = query.filter(cls.external_id == external_id)
3972 query = query.filter(cls.external_id == external_id)
3973 query = query.filter(cls.provider_name == provider_name)
3973 query = query.filter(cls.provider_name == provider_name)
3974 query = query.filter(User.user_id == cls.local_user_id)
3974 query = query.filter(User.user_id == cls.local_user_id)
3975 return query.first()
3975 return query.first()
3976
3976
3977 @classmethod
3977 @classmethod
3978 def by_local_user_id(cls, local_user_id):
3978 def by_local_user_id(cls, local_user_id):
3979 """
3979 """
3980 Returns all tokens for user
3980 Returns all tokens for user
3981
3981
3982 :param local_user_id:
3982 :param local_user_id:
3983 :return: ExternalIdentity
3983 :return: ExternalIdentity
3984 """
3984 """
3985 query = cls.query()
3985 query = cls.query()
3986 query = query.filter(cls.local_user_id == local_user_id)
3986 query = query.filter(cls.local_user_id == local_user_id)
3987 return query
3987 return query
3988
3988
3989
3989
3990 class Integration(Base, BaseModel):
3990 class Integration(Base, BaseModel):
3991 __tablename__ = 'integrations'
3991 __tablename__ = 'integrations'
3992 __table_args__ = (
3992 __table_args__ = (
3993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3995 )
3995 )
3996
3996
3997 integration_id = Column('integration_id', Integer(), primary_key=True)
3997 integration_id = Column('integration_id', Integer(), primary_key=True)
3998 integration_type = Column('integration_type', String(255))
3998 integration_type = Column('integration_type', String(255))
3999 enabled = Column('enabled', Boolean(), nullable=False)
3999 enabled = Column('enabled', Boolean(), nullable=False)
4000 name = Column('name', String(255), nullable=False)
4000 name = Column('name', String(255), nullable=False)
4001 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4001 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4002 default=False)
4002 default=False)
4003
4003
4004 settings = Column(
4004 settings = Column(
4005 'settings_json', MutationObj.as_mutable(
4005 'settings_json', MutationObj.as_mutable(
4006 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4006 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4007 repo_id = Column(
4007 repo_id = Column(
4008 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4008 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4009 nullable=True, unique=None, default=None)
4009 nullable=True, unique=None, default=None)
4010 repo = relationship('Repository', lazy='joined')
4010 repo = relationship('Repository', lazy='joined')
4011
4011
4012 repo_group_id = Column(
4012 repo_group_id = Column(
4013 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4013 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4014 nullable=True, unique=None, default=None)
4014 nullable=True, unique=None, default=None)
4015 repo_group = relationship('RepoGroup', lazy='joined')
4015 repo_group = relationship('RepoGroup', lazy='joined')
4016
4016
4017 @property
4017 @property
4018 def scope(self):
4018 def scope(self):
4019 if self.repo:
4019 if self.repo:
4020 return repr(self.repo)
4020 return repr(self.repo)
4021 if self.repo_group:
4021 if self.repo_group:
4022 if self.child_repos_only:
4022 if self.child_repos_only:
4023 return repr(self.repo_group) + ' (child repos only)'
4023 return repr(self.repo_group) + ' (child repos only)'
4024 else:
4024 else:
4025 return repr(self.repo_group) + ' (recursive)'
4025 return repr(self.repo_group) + ' (recursive)'
4026 if self.child_repos_only:
4026 if self.child_repos_only:
4027 return 'root_repos'
4027 return 'root_repos'
4028 return 'global'
4028 return 'global'
4029
4029
4030 def __repr__(self):
4030 def __repr__(self):
4031 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4031 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4032
4032
4033
4033
4034 class RepoReviewRuleUser(Base, BaseModel):
4034 class RepoReviewRuleUser(Base, BaseModel):
4035 __tablename__ = 'repo_review_rules_users'
4035 __tablename__ = 'repo_review_rules_users'
4036 __table_args__ = (
4036 __table_args__ = (
4037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4039 )
4039 )
4040 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4040 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4041 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4041 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4043 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4043 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4044 user = relationship('User')
4044 user = relationship('User')
4045
4045
4046 def rule_data(self):
4046 def rule_data(self):
4047 return {
4047 return {
4048 'mandatory': self.mandatory
4048 'mandatory': self.mandatory
4049 }
4049 }
4050
4050
4051
4051
4052 class RepoReviewRuleUserGroup(Base, BaseModel):
4052 class RepoReviewRuleUserGroup(Base, BaseModel):
4053 __tablename__ = 'repo_review_rules_users_groups'
4053 __tablename__ = 'repo_review_rules_users_groups'
4054 __table_args__ = (
4054 __table_args__ = (
4055 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4055 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4056 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4056 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4057 )
4057 )
4058 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4058 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4059 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4059 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4060 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4060 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4061 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4061 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4062 users_group = relationship('UserGroup')
4062 users_group = relationship('UserGroup')
4063
4063
4064 def rule_data(self):
4064 def rule_data(self):
4065 return {
4065 return {
4066 'mandatory': self.mandatory
4066 'mandatory': self.mandatory
4067 }
4067 }
4068
4068
4069
4069
4070 class RepoReviewRule(Base, BaseModel):
4070 class RepoReviewRule(Base, BaseModel):
4071 __tablename__ = 'repo_review_rules'
4071 __tablename__ = 'repo_review_rules'
4072 __table_args__ = (
4072 __table_args__ = (
4073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4074 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4074 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4075 )
4075 )
4076
4076
4077 repo_review_rule_id = Column(
4077 repo_review_rule_id = Column(
4078 'repo_review_rule_id', Integer(), primary_key=True)
4078 'repo_review_rule_id', Integer(), primary_key=True)
4079 repo_id = Column(
4079 repo_id = Column(
4080 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4080 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4081 repo = relationship('Repository', backref='review_rules')
4081 repo = relationship('Repository', backref='review_rules')
4082
4082
4083 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4083 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4084 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4084 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4085
4085
4086 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4086 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4087 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4087 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4088 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4088 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4089 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4089 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4090
4090
4091 rule_users = relationship('RepoReviewRuleUser')
4091 rule_users = relationship('RepoReviewRuleUser')
4092 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4092 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4093
4093
4094 @hybrid_property
4094 @hybrid_property
4095 def branch_pattern(self):
4095 def branch_pattern(self):
4096 return self._branch_pattern or '*'
4096 return self._branch_pattern or '*'
4097
4097
4098 def _validate_glob(self, value):
4098 def _validate_glob(self, value):
4099 re.compile('^' + glob2re(value) + '$')
4099 re.compile('^' + glob2re(value) + '$')
4100
4100
4101 @branch_pattern.setter
4101 @branch_pattern.setter
4102 def branch_pattern(self, value):
4102 def branch_pattern(self, value):
4103 self._validate_glob(value)
4103 self._validate_glob(value)
4104 self._branch_pattern = value or '*'
4104 self._branch_pattern = value or '*'
4105
4105
4106 @hybrid_property
4106 @hybrid_property
4107 def file_pattern(self):
4107 def file_pattern(self):
4108 return self._file_pattern or '*'
4108 return self._file_pattern or '*'
4109
4109
4110 @file_pattern.setter
4110 @file_pattern.setter
4111 def file_pattern(self, value):
4111 def file_pattern(self, value):
4112 self._validate_glob(value)
4112 self._validate_glob(value)
4113 self._file_pattern = value or '*'
4113 self._file_pattern = value or '*'
4114
4114
4115 def matches(self, branch, files_changed):
4115 def matches(self, branch, files_changed):
4116 """
4116 """
4117 Check if this review rule matches a branch/files in a pull request
4117 Check if this review rule matches a branch/files in a pull request
4118
4118
4119 :param branch: branch name for the commit
4119 :param branch: branch name for the commit
4120 :param files_changed: list of file paths changed in the pull request
4120 :param files_changed: list of file paths changed in the pull request
4121 """
4121 """
4122
4122
4123 branch = branch or ''
4123 branch = branch or ''
4124 files_changed = files_changed or []
4124 files_changed = files_changed or []
4125
4125
4126 branch_matches = True
4126 branch_matches = True
4127 if branch:
4127 if branch:
4128 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4128 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4129 branch_matches = bool(branch_regex.search(branch))
4129 branch_matches = bool(branch_regex.search(branch))
4130
4130
4131 files_matches = True
4131 files_matches = True
4132 if self.file_pattern != '*':
4132 if self.file_pattern != '*':
4133 files_matches = False
4133 files_matches = False
4134 file_regex = re.compile(glob2re(self.file_pattern))
4134 file_regex = re.compile(glob2re(self.file_pattern))
4135 for filename in files_changed:
4135 for filename in files_changed:
4136 if file_regex.search(filename):
4136 if file_regex.search(filename):
4137 files_matches = True
4137 files_matches = True
4138 break
4138 break
4139
4139
4140 return branch_matches and files_matches
4140 return branch_matches and files_matches
4141
4141
4142 @property
4142 @property
4143 def review_users(self):
4143 def review_users(self):
4144 """ Returns the users which this rule applies to """
4144 """ Returns the users which this rule applies to """
4145
4145
4146 users = collections.OrderedDict()
4146 users = collections.OrderedDict()
4147
4147
4148 for rule_user in self.rule_users:
4148 for rule_user in self.rule_users:
4149 if rule_user.user.active:
4149 if rule_user.user.active:
4150 if rule_user.user not in users:
4150 if rule_user.user not in users:
4151 users[rule_user.user.username] = {
4151 users[rule_user.user.username] = {
4152 'user': rule_user.user,
4152 'user': rule_user.user,
4153 'source': 'user',
4153 'source': 'user',
4154 'source_data': {},
4154 'source_data': {},
4155 'data': rule_user.rule_data()
4155 'data': rule_user.rule_data()
4156 }
4156 }
4157
4157
4158 for rule_user_group in self.rule_user_groups:
4158 for rule_user_group in self.rule_user_groups:
4159 source_data = {
4159 source_data = {
4160 'name': rule_user_group.users_group.users_group_name,
4160 'name': rule_user_group.users_group.users_group_name,
4161 'members': len(rule_user_group.users_group.members)
4161 'members': len(rule_user_group.users_group.members)
4162 }
4162 }
4163 for member in rule_user_group.users_group.members:
4163 for member in rule_user_group.users_group.members:
4164 if member.user.active:
4164 if member.user.active:
4165 users[member.user.username] = {
4165 users[member.user.username] = {
4166 'user': member.user,
4166 'user': member.user,
4167 'source': 'user_group',
4167 'source': 'user_group',
4168 'source_data': source_data,
4168 'source_data': source_data,
4169 'data': rule_user_group.rule_data()
4169 'data': rule_user_group.rule_data()
4170 }
4170 }
4171
4171
4172 return users
4172 return users
4173
4173
4174 def __repr__(self):
4174 def __repr__(self):
4175 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4175 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4176 self.repo_review_rule_id, self.repo)
4176 self.repo_review_rule_id, self.repo)
4177
4177
4178
4178
4179 class ScheduleEntry(Base, BaseModel):
4179 class ScheduleEntry(Base, BaseModel):
4180 __tablename__ = 'schedule_entries'
4180 __tablename__ = 'schedule_entries'
4181 __table_args__ = (
4181 __table_args__ = (
4182 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4182 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4183 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4183 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4184 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4184 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4185 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4185 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4186 )
4186 )
4187 schedule_types = ['crontab', 'timedelta', 'integer']
4187 schedule_types = ['crontab', 'timedelta', 'integer']
4188 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4188 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4189
4189
4190 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4190 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4191 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4191 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4192 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4192 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4193
4193
4194 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4194 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4195 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4195 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4196
4196
4197 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4197 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4198 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4198 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4199
4199
4200 # task
4200 # task
4201 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4201 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4202 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4202 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4203 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4203 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4204 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4204 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4205
4205
4206 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4206 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4207 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4207 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4208
4208
4209 @hybrid_property
4209 @hybrid_property
4210 def schedule_type(self):
4210 def schedule_type(self):
4211 return self._schedule_type
4211 return self._schedule_type
4212
4212
4213 @schedule_type.setter
4213 @schedule_type.setter
4214 def schedule_type(self, val):
4214 def schedule_type(self, val):
4215 if val not in self.schedule_types:
4215 if val not in self.schedule_types:
4216 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4216 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4217 val, self.schedule_type))
4217 val, self.schedule_type))
4218
4218
4219 self._schedule_type = val
4219 self._schedule_type = val
4220
4220
4221 @classmethod
4221 @classmethod
4222 def get_uid(cls, obj):
4222 def get_uid(cls, obj):
4223 args = obj.task_args
4223 args = obj.task_args
4224 kwargs = obj.task_kwargs
4224 kwargs = obj.task_kwargs
4225 if isinstance(args, JsonRaw):
4225 if isinstance(args, JsonRaw):
4226 try:
4226 try:
4227 args = json.loads(args)
4227 args = json.loads(args)
4228 except ValueError:
4228 except ValueError:
4229 args = tuple()
4229 args = tuple()
4230
4230
4231 if isinstance(kwargs, JsonRaw):
4231 if isinstance(kwargs, JsonRaw):
4232 try:
4232 try:
4233 kwargs = json.loads(kwargs)
4233 kwargs = json.loads(kwargs)
4234 except ValueError:
4234 except ValueError:
4235 kwargs = dict()
4235 kwargs = dict()
4236
4236
4237 dot_notation = obj.task_dot_notation
4237 dot_notation = obj.task_dot_notation
4238 val = '.'.join(map(safe_str, [
4238 val = '.'.join(map(safe_str, [
4239 sorted(dot_notation), args, sorted(kwargs.items())]))
4239 sorted(dot_notation), args, sorted(kwargs.items())]))
4240 return hashlib.sha1(val).hexdigest()
4240 return hashlib.sha1(val).hexdigest()
4241
4241
4242 @classmethod
4242 @classmethod
4243 def get_by_schedule_name(cls, schedule_name):
4243 def get_by_schedule_name(cls, schedule_name):
4244 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4244 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4245
4245
4246 @classmethod
4246 @classmethod
4247 def get_by_schedule_id(cls, schedule_id):
4247 def get_by_schedule_id(cls, schedule_id):
4248 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4248 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4249
4249
4250 @property
4250 @property
4251 def task(self):
4251 def task(self):
4252 return self.task_dot_notation
4252 return self.task_dot_notation
4253
4253
4254 @property
4254 @property
4255 def schedule(self):
4255 def schedule(self):
4256 from rhodecode.lib.celerylib.utils import raw_2_schedule
4256 from rhodecode.lib.celerylib.utils import raw_2_schedule
4257 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4257 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4258 return schedule
4258 return schedule
4259
4259
4260 @property
4260 @property
4261 def args(self):
4261 def args(self):
4262 try:
4262 try:
4263 return list(self.task_args or [])
4263 return list(self.task_args or [])
4264 except ValueError:
4264 except ValueError:
4265 return list()
4265 return list()
4266
4266
4267 @property
4267 @property
4268 def kwargs(self):
4268 def kwargs(self):
4269 try:
4269 try:
4270 return dict(self.task_kwargs or {})
4270 return dict(self.task_kwargs or {})
4271 except ValueError:
4271 except ValueError:
4272 return dict()
4272 return dict()
4273
4273
4274 def _as_raw(self, val):
4274 def _as_raw(self, val):
4275 if hasattr(val, 'de_coerce'):
4275 if hasattr(val, 'de_coerce'):
4276 val = val.de_coerce()
4276 val = val.de_coerce()
4277 if val:
4277 if val:
4278 val = json.dumps(val)
4278 val = json.dumps(val)
4279
4279
4280 return val
4280 return val
4281
4281
4282 @property
4282 @property
4283 def schedule_definition_raw(self):
4283 def schedule_definition_raw(self):
4284 return self._as_raw(self.schedule_definition)
4284 return self._as_raw(self.schedule_definition)
4285
4285
4286 @property
4286 @property
4287 def args_raw(self):
4287 def args_raw(self):
4288 return self._as_raw(self.task_args)
4288 return self._as_raw(self.task_args)
4289
4289
4290 @property
4290 @property
4291 def kwargs_raw(self):
4291 def kwargs_raw(self):
4292 return self._as_raw(self.task_kwargs)
4292 return self._as_raw(self.task_kwargs)
4293
4293
4294 def __repr__(self):
4294 def __repr__(self):
4295 return '<DB:ScheduleEntry({}:{})>'.format(
4295 return '<DB:ScheduleEntry({}:{})>'.format(
4296 self.schedule_entry_id, self.schedule_name)
4296 self.schedule_entry_id, self.schedule_name)
4297
4297
4298
4298
4299 @event.listens_for(ScheduleEntry, 'before_update')
4299 @event.listens_for(ScheduleEntry, 'before_update')
4300 def update_task_uid(mapper, connection, target):
4300 def update_task_uid(mapper, connection, target):
4301 target.task_uid = ScheduleEntry.get_uid(target)
4301 target.task_uid = ScheduleEntry.get_uid(target)
4302
4302
4303
4303
4304 @event.listens_for(ScheduleEntry, 'before_insert')
4304 @event.listens_for(ScheduleEntry, 'before_insert')
4305 def set_task_uid(mapper, connection, target):
4305 def set_task_uid(mapper, connection, target):
4306 target.task_uid = ScheduleEntry.get_uid(target)
4306 target.task_uid = ScheduleEntry.get_uid(target)
4307
4307
4308
4308
4309 class DbMigrateVersion(Base, BaseModel):
4309 class DbMigrateVersion(Base, BaseModel):
4310 __tablename__ = 'db_migrate_version'
4310 __tablename__ = 'db_migrate_version'
4311 __table_args__ = (
4311 __table_args__ = (
4312 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4312 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4313 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4313 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4314 )
4314 )
4315 repository_id = Column('repository_id', String(250), primary_key=True)
4315 repository_id = Column('repository_id', String(250), primary_key=True)
4316 repository_path = Column('repository_path', Text)
4316 repository_path = Column('repository_path', Text)
4317 version = Column('version', Integer)
4317 version = Column('version', Integer)
4318
4318
4319
4319
4320 class DbSession(Base, BaseModel):
4320 class DbSession(Base, BaseModel):
4321 __tablename__ = 'db_session'
4321 __tablename__ = 'db_session'
4322 __table_args__ = (
4322 __table_args__ = (
4323 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4323 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4324 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4324 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4325 )
4325 )
4326
4326
4327 def __repr__(self):
4327 def __repr__(self):
4328 return '<DB:DbSession({})>'.format(self.id)
4328 return '<DB:DbSession({})>'.format(self.id)
4329
4329
4330 id = Column('id', Integer())
4330 id = Column('id', Integer())
4331 namespace = Column('namespace', String(255), primary_key=True)
4331 namespace = Column('namespace', String(255), primary_key=True)
4332 accessed = Column('accessed', DateTime, nullable=False)
4332 accessed = Column('accessed', DateTime, nullable=False)
4333 created = Column('created', DateTime, nullable=False)
4333 created = Column('created', DateTime, nullable=False)
4334 data = Column('data', PickleType, nullable=False)
4334 data = Column('data', PickleType, nullable=False)
@@ -1,4587 +1,4587 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37 from sqlalchemy import (
37 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
38 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
41 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false
42 from sqlalchemy.sql.expression import true, false
43 from sqlalchemy.sql.functions import coalesce, count # noqa
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
45 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # noqa
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from beaker.cache import cache_region
50 from beaker.cache import cache_region
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
52
52
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54
54
55 from rhodecode.translation import _
55 from rhodecode.translation import _
56 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.utils2 import (
58 from rhodecode.lib.utils2 import (
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
59 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 glob2re, StrictAttributeDict, cleaned_uri)
61 glob2re, StrictAttributeDict, cleaned_uri)
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 JsonRaw
63 JsonRaw
64 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.encrypt import AESCipher
66 from rhodecode.lib.encrypt import AESCipher
67
67
68 from rhodecode.model.meta import Base, Session
68 from rhodecode.model.meta import Base, Session
69
69
70 URL_SEP = '/'
70 URL_SEP = '/'
71 log = logging.getLogger(__name__)
71 log = logging.getLogger(__name__)
72
72
73 # =============================================================================
73 # =============================================================================
74 # BASE CLASSES
74 # BASE CLASSES
75 # =============================================================================
75 # =============================================================================
76
76
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # beaker.session.secret if first is not set.
78 # beaker.session.secret if first is not set.
79 # and initialized at environment.py
79 # and initialized at environment.py
80 ENCRYPTION_KEY = None
80 ENCRYPTION_KEY = None
81
81
82 # used to sort permissions by types, '#' used here is not allowed to be in
82 # used to sort permissions by types, '#' used here is not allowed to be in
83 # usernames, and it's very early in sorted string.printable table.
83 # usernames, and it's very early in sorted string.printable table.
84 PERMISSION_TYPE_SORT = {
84 PERMISSION_TYPE_SORT = {
85 'admin': '####',
85 'admin': '####',
86 'write': '###',
86 'write': '###',
87 'read': '##',
87 'read': '##',
88 'none': '#',
88 'none': '#',
89 }
89 }
90
90
91
91
92 def display_user_sort(obj):
92 def display_user_sort(obj):
93 """
93 """
94 Sort function used to sort permissions in .permissions() function of
94 Sort function used to sort permissions in .permissions() function of
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 of all other resources
96 of all other resources
97 """
97 """
98
98
99 if obj.username == User.DEFAULT_USER:
99 if obj.username == User.DEFAULT_USER:
100 return '#####'
100 return '#####'
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 return prefix + obj.username
102 return prefix + obj.username
103
103
104
104
105 def display_user_group_sort(obj):
105 def display_user_group_sort(obj):
106 """
106 """
107 Sort function used to sort permissions in .permissions() function of
107 Sort function used to sort permissions in .permissions() function of
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 of all other resources
109 of all other resources
110 """
110 """
111
111
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 return prefix + obj.users_group_name
113 return prefix + obj.users_group_name
114
114
115
115
116 def _hash_key(k):
116 def _hash_key(k):
117 return md5_safe(k)
117 return md5_safe(k)
118
118
119
119
120 def in_filter_generator(qry, items, limit=500):
120 def in_filter_generator(qry, items, limit=500):
121 """
121 """
122 Splits IN() into multiple with OR
122 Splits IN() into multiple with OR
123 e.g.::
123 e.g.::
124 cnt = Repository.query().filter(
124 cnt = Repository.query().filter(
125 or_(
125 or_(
126 *in_filter_generator(Repository.repo_id, range(100000))
126 *in_filter_generator(Repository.repo_id, range(100000))
127 )).count()
127 )).count()
128 """
128 """
129 if not items:
129 if not items:
130 # empty list will cause empty query which might cause security issues
130 # empty list will cause empty query which might cause security issues
131 # this can lead to hidden unpleasant results
131 # this can lead to hidden unpleasant results
132 items = [-1]
132 items = [-1]
133
133
134 parts = []
134 parts = []
135 for chunk in xrange(0, len(items), limit):
135 for chunk in xrange(0, len(items), limit):
136 parts.append(
136 parts.append(
137 qry.in_(items[chunk: chunk + limit])
137 qry.in_(items[chunk: chunk + limit])
138 )
138 )
139
139
140 return parts
140 return parts
141
141
142
142
143 class EncryptedTextValue(TypeDecorator):
143 class EncryptedTextValue(TypeDecorator):
144 """
144 """
145 Special column for encrypted long text data, use like::
145 Special column for encrypted long text data, use like::
146
146
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
147 value = Column("encrypted_value", EncryptedValue(), nullable=False)
148
148
149 This column is intelligent so if value is in unencrypted form it return
149 This column is intelligent so if value is in unencrypted form it return
150 unencrypted form, but on save it always encrypts
150 unencrypted form, but on save it always encrypts
151 """
151 """
152 impl = Text
152 impl = Text
153
153
154 def process_bind_param(self, value, dialect):
154 def process_bind_param(self, value, dialect):
155 if not value:
155 if not value:
156 return value
156 return value
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
157 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
158 # protect against double encrypting if someone manually starts
158 # protect against double encrypting if someone manually starts
159 # doing
159 # doing
160 raise ValueError('value needs to be in unencrypted format, ie. '
160 raise ValueError('value needs to be in unencrypted format, ie. '
161 'not starting with enc$aes')
161 'not starting with enc$aes')
162 return 'enc$aes_hmac$%s' % AESCipher(
162 return 'enc$aes_hmac$%s' % AESCipher(
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
163 ENCRYPTION_KEY, hmac=True).encrypt(value)
164
164
165 def process_result_value(self, value, dialect):
165 def process_result_value(self, value, dialect):
166 import rhodecode
166 import rhodecode
167
167
168 if not value:
168 if not value:
169 return value
169 return value
170
170
171 parts = value.split('$', 3)
171 parts = value.split('$', 3)
172 if not len(parts) == 3:
172 if not len(parts) == 3:
173 # probably not encrypted values
173 # probably not encrypted values
174 return value
174 return value
175 else:
175 else:
176 if parts[0] != 'enc':
176 if parts[0] != 'enc':
177 # parts ok but without our header ?
177 # parts ok but without our header ?
178 return value
178 return value
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
179 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
180 'rhodecode.encrypted_values.strict') or True)
180 'rhodecode.encrypted_values.strict') or True)
181 # at that stage we know it's our encryption
181 # at that stage we know it's our encryption
182 if parts[1] == 'aes':
182 if parts[1] == 'aes':
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
183 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
184 elif parts[1] == 'aes_hmac':
184 elif parts[1] == 'aes_hmac':
185 decrypted_data = AESCipher(
185 decrypted_data = AESCipher(
186 ENCRYPTION_KEY, hmac=True,
186 ENCRYPTION_KEY, hmac=True,
187 strict_verification=enc_strict_mode).decrypt(parts[2])
187 strict_verification=enc_strict_mode).decrypt(parts[2])
188 else:
188 else:
189 raise ValueError(
189 raise ValueError(
190 'Encryption type part is wrong, must be `aes` '
190 'Encryption type part is wrong, must be `aes` '
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
191 'or `aes_hmac`, got `%s` instead' % (parts[1]))
192 return decrypted_data
192 return decrypted_data
193
193
194
194
195 class BaseModel(object):
195 class BaseModel(object):
196 """
196 """
197 Base Model for all classes
197 Base Model for all classes
198 """
198 """
199
199
200 @classmethod
200 @classmethod
201 def _get_keys(cls):
201 def _get_keys(cls):
202 """return column names for this model """
202 """return column names for this model """
203 return class_mapper(cls).c.keys()
203 return class_mapper(cls).c.keys()
204
204
205 def get_dict(self):
205 def get_dict(self):
206 """
206 """
207 return dict with keys and values corresponding
207 return dict with keys and values corresponding
208 to this model data """
208 to this model data """
209
209
210 d = {}
210 d = {}
211 for k in self._get_keys():
211 for k in self._get_keys():
212 d[k] = getattr(self, k)
212 d[k] = getattr(self, k)
213
213
214 # also use __json__() if present to get additional fields
214 # also use __json__() if present to get additional fields
215 _json_attr = getattr(self, '__json__', None)
215 _json_attr = getattr(self, '__json__', None)
216 if _json_attr:
216 if _json_attr:
217 # update with attributes from __json__
217 # update with attributes from __json__
218 if callable(_json_attr):
218 if callable(_json_attr):
219 _json_attr = _json_attr()
219 _json_attr = _json_attr()
220 for k, val in _json_attr.iteritems():
220 for k, val in _json_attr.iteritems():
221 d[k] = val
221 d[k] = val
222 return d
222 return d
223
223
224 def get_appstruct(self):
224 def get_appstruct(self):
225 """return list with keys and values tuples corresponding
225 """return list with keys and values tuples corresponding
226 to this model data """
226 to this model data """
227
227
228 lst = []
228 lst = []
229 for k in self._get_keys():
229 for k in self._get_keys():
230 lst.append((k, getattr(self, k),))
230 lst.append((k, getattr(self, k),))
231 return lst
231 return lst
232
232
233 def populate_obj(self, populate_dict):
233 def populate_obj(self, populate_dict):
234 """populate model with data from given populate_dict"""
234 """populate model with data from given populate_dict"""
235
235
236 for k in self._get_keys():
236 for k in self._get_keys():
237 if k in populate_dict:
237 if k in populate_dict:
238 setattr(self, k, populate_dict[k])
238 setattr(self, k, populate_dict[k])
239
239
240 @classmethod
240 @classmethod
241 def query(cls):
241 def query(cls):
242 return Session().query(cls)
242 return Session().query(cls)
243
243
244 @classmethod
244 @classmethod
245 def get(cls, id_):
245 def get(cls, id_):
246 if id_:
246 if id_:
247 return cls.query().get(id_)
247 return cls.query().get(id_)
248
248
249 @classmethod
249 @classmethod
250 def get_or_404(cls, id_):
250 def get_or_404(cls, id_):
251 from pyramid.httpexceptions import HTTPNotFound
251 from pyramid.httpexceptions import HTTPNotFound
252
252
253 try:
253 try:
254 id_ = int(id_)
254 id_ = int(id_)
255 except (TypeError, ValueError):
255 except (TypeError, ValueError):
256 raise HTTPNotFound()
256 raise HTTPNotFound()
257
257
258 res = cls.query().get(id_)
258 res = cls.query().get(id_)
259 if not res:
259 if not res:
260 raise HTTPNotFound()
260 raise HTTPNotFound()
261 return res
261 return res
262
262
263 @classmethod
263 @classmethod
264 def getAll(cls):
264 def getAll(cls):
265 # deprecated and left for backward compatibility
265 # deprecated and left for backward compatibility
266 return cls.get_all()
266 return cls.get_all()
267
267
268 @classmethod
268 @classmethod
269 def get_all(cls):
269 def get_all(cls):
270 return cls.query().all()
270 return cls.query().all()
271
271
272 @classmethod
272 @classmethod
273 def delete(cls, id_):
273 def delete(cls, id_):
274 obj = cls.query().get(id_)
274 obj = cls.query().get(id_)
275 Session().delete(obj)
275 Session().delete(obj)
276
276
277 @classmethod
277 @classmethod
278 def identity_cache(cls, session, attr_name, value):
278 def identity_cache(cls, session, attr_name, value):
279 exist_in_session = []
279 exist_in_session = []
280 for (item_cls, pkey), instance in session.identity_map.items():
280 for (item_cls, pkey), instance in session.identity_map.items():
281 if cls == item_cls and getattr(instance, attr_name) == value:
281 if cls == item_cls and getattr(instance, attr_name) == value:
282 exist_in_session.append(instance)
282 exist_in_session.append(instance)
283 if exist_in_session:
283 if exist_in_session:
284 if len(exist_in_session) == 1:
284 if len(exist_in_session) == 1:
285 return exist_in_session[0]
285 return exist_in_session[0]
286 log.exception(
286 log.exception(
287 'multiple objects with attr %s and '
287 'multiple objects with attr %s and '
288 'value %s found with same name: %r',
288 'value %s found with same name: %r',
289 attr_name, value, exist_in_session)
289 attr_name, value, exist_in_session)
290
290
291 def __repr__(self):
291 def __repr__(self):
292 if hasattr(self, '__unicode__'):
292 if hasattr(self, '__unicode__'):
293 # python repr needs to return str
293 # python repr needs to return str
294 try:
294 try:
295 return safe_str(self.__unicode__())
295 return safe_str(self.__unicode__())
296 except UnicodeDecodeError:
296 except UnicodeDecodeError:
297 pass
297 pass
298 return '<DB:%s>' % (self.__class__.__name__)
298 return '<DB:%s>' % (self.__class__.__name__)
299
299
300
300
301 class RhodeCodeSetting(Base, BaseModel):
301 class RhodeCodeSetting(Base, BaseModel):
302 __tablename__ = 'rhodecode_settings'
302 __tablename__ = 'rhodecode_settings'
303 __table_args__ = (
303 __table_args__ = (
304 UniqueConstraint('app_settings_name'),
304 UniqueConstraint('app_settings_name'),
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
307 )
307 )
308
308
309 SETTINGS_TYPES = {
309 SETTINGS_TYPES = {
310 'str': safe_str,
310 'str': safe_str,
311 'int': safe_int,
311 'int': safe_int,
312 'unicode': safe_unicode,
312 'unicode': safe_unicode,
313 'bool': str2bool,
313 'bool': str2bool,
314 'list': functools.partial(aslist, sep=',')
314 'list': functools.partial(aslist, sep=',')
315 }
315 }
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
316 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
317 GLOBAL_CONF_KEY = 'app_settings'
317 GLOBAL_CONF_KEY = 'app_settings'
318
318
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
319 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
320 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
321 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
322 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
323
323
324 def __init__(self, key='', val='', type='unicode'):
324 def __init__(self, key='', val='', type='unicode'):
325 self.app_settings_name = key
325 self.app_settings_name = key
326 self.app_settings_type = type
326 self.app_settings_type = type
327 self.app_settings_value = val
327 self.app_settings_value = val
328
328
329 @validates('_app_settings_value')
329 @validates('_app_settings_value')
330 def validate_settings_value(self, key, val):
330 def validate_settings_value(self, key, val):
331 assert type(val) == unicode
331 assert type(val) == unicode
332 return val
332 return val
333
333
334 @hybrid_property
334 @hybrid_property
335 def app_settings_value(self):
335 def app_settings_value(self):
336 v = self._app_settings_value
336 v = self._app_settings_value
337 _type = self.app_settings_type
337 _type = self.app_settings_type
338 if _type:
338 if _type:
339 _type = self.app_settings_type.split('.')[0]
339 _type = self.app_settings_type.split('.')[0]
340 # decode the encrypted value
340 # decode the encrypted value
341 if 'encrypted' in self.app_settings_type:
341 if 'encrypted' in self.app_settings_type:
342 cipher = EncryptedTextValue()
342 cipher = EncryptedTextValue()
343 v = safe_unicode(cipher.process_result_value(v, None))
343 v = safe_unicode(cipher.process_result_value(v, None))
344
344
345 converter = self.SETTINGS_TYPES.get(_type) or \
345 converter = self.SETTINGS_TYPES.get(_type) or \
346 self.SETTINGS_TYPES['unicode']
346 self.SETTINGS_TYPES['unicode']
347 return converter(v)
347 return converter(v)
348
348
349 @app_settings_value.setter
349 @app_settings_value.setter
350 def app_settings_value(self, val):
350 def app_settings_value(self, val):
351 """
351 """
352 Setter that will always make sure we use unicode in app_settings_value
352 Setter that will always make sure we use unicode in app_settings_value
353
353
354 :param val:
354 :param val:
355 """
355 """
356 val = safe_unicode(val)
356 val = safe_unicode(val)
357 # encode the encrypted value
357 # encode the encrypted value
358 if 'encrypted' in self.app_settings_type:
358 if 'encrypted' in self.app_settings_type:
359 cipher = EncryptedTextValue()
359 cipher = EncryptedTextValue()
360 val = safe_unicode(cipher.process_bind_param(val, None))
360 val = safe_unicode(cipher.process_bind_param(val, None))
361 self._app_settings_value = val
361 self._app_settings_value = val
362
362
363 @hybrid_property
363 @hybrid_property
364 def app_settings_type(self):
364 def app_settings_type(self):
365 return self._app_settings_type
365 return self._app_settings_type
366
366
367 @app_settings_type.setter
367 @app_settings_type.setter
368 def app_settings_type(self, val):
368 def app_settings_type(self, val):
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
369 if val.split('.')[0] not in self.SETTINGS_TYPES:
370 raise Exception('type must be one of %s got %s'
370 raise Exception('type must be one of %s got %s'
371 % (self.SETTINGS_TYPES.keys(), val))
371 % (self.SETTINGS_TYPES.keys(), val))
372 self._app_settings_type = val
372 self._app_settings_type = val
373
373
374 def __unicode__(self):
374 def __unicode__(self):
375 return u"<%s('%s:%s[%s]')>" % (
375 return u"<%s('%s:%s[%s]')>" % (
376 self.__class__.__name__,
376 self.__class__.__name__,
377 self.app_settings_name, self.app_settings_value,
377 self.app_settings_name, self.app_settings_value,
378 self.app_settings_type
378 self.app_settings_type
379 )
379 )
380
380
381
381
382 class RhodeCodeUi(Base, BaseModel):
382 class RhodeCodeUi(Base, BaseModel):
383 __tablename__ = 'rhodecode_ui'
383 __tablename__ = 'rhodecode_ui'
384 __table_args__ = (
384 __table_args__ = (
385 UniqueConstraint('ui_key'),
385 UniqueConstraint('ui_key'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
390 HOOK_REPO_SIZE = 'changegroup.repo_size'
391 # HG
391 # HG
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
392 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
393 HOOK_PULL = 'outgoing.pull_logger'
393 HOOK_PULL = 'outgoing.pull_logger'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
394 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
395 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
396 HOOK_PUSH = 'changegroup.push_logger'
396 HOOK_PUSH = 'changegroup.push_logger'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
397 HOOK_PUSH_KEY = 'pushkey.key_push'
398
398
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
399 # TODO: johbo: Unify way how hooks are configured for git and hg,
400 # git part is currently hardcoded.
400 # git part is currently hardcoded.
401
401
402 # SVN PATTERNS
402 # SVN PATTERNS
403 SVN_BRANCH_ID = 'vcs_svn_branch'
403 SVN_BRANCH_ID = 'vcs_svn_branch'
404 SVN_TAG_ID = 'vcs_svn_tag'
404 SVN_TAG_ID = 'vcs_svn_tag'
405
405
406 ui_id = Column(
406 ui_id = Column(
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
407 "ui_id", Integer(), nullable=False, unique=True, default=None,
408 primary_key=True)
408 primary_key=True)
409 ui_section = Column(
409 ui_section = Column(
410 "ui_section", String(255), nullable=True, unique=None, default=None)
410 "ui_section", String(255), nullable=True, unique=None, default=None)
411 ui_key = Column(
411 ui_key = Column(
412 "ui_key", String(255), nullable=True, unique=None, default=None)
412 "ui_key", String(255), nullable=True, unique=None, default=None)
413 ui_value = Column(
413 ui_value = Column(
414 "ui_value", String(255), nullable=True, unique=None, default=None)
414 "ui_value", String(255), nullable=True, unique=None, default=None)
415 ui_active = Column(
415 ui_active = Column(
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
416 "ui_active", Boolean(), nullable=True, unique=None, default=True)
417
417
418 def __repr__(self):
418 def __repr__(self):
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
419 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
420 self.ui_key, self.ui_value)
420 self.ui_key, self.ui_value)
421
421
422
422
423 class RepoRhodeCodeSetting(Base, BaseModel):
423 class RepoRhodeCodeSetting(Base, BaseModel):
424 __tablename__ = 'repo_rhodecode_settings'
424 __tablename__ = 'repo_rhodecode_settings'
425 __table_args__ = (
425 __table_args__ = (
426 UniqueConstraint(
426 UniqueConstraint(
427 'app_settings_name', 'repository_id',
427 'app_settings_name', 'repository_id',
428 name='uq_repo_rhodecode_setting_name_repo_id'),
428 name='uq_repo_rhodecode_setting_name_repo_id'),
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
430 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
431 )
431 )
432
432
433 repository_id = Column(
433 repository_id = Column(
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
434 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
435 nullable=False)
435 nullable=False)
436 app_settings_id = Column(
436 app_settings_id = Column(
437 "app_settings_id", Integer(), nullable=False, unique=True,
437 "app_settings_id", Integer(), nullable=False, unique=True,
438 default=None, primary_key=True)
438 default=None, primary_key=True)
439 app_settings_name = Column(
439 app_settings_name = Column(
440 "app_settings_name", String(255), nullable=True, unique=None,
440 "app_settings_name", String(255), nullable=True, unique=None,
441 default=None)
441 default=None)
442 _app_settings_value = Column(
442 _app_settings_value = Column(
443 "app_settings_value", String(4096), nullable=True, unique=None,
443 "app_settings_value", String(4096), nullable=True, unique=None,
444 default=None)
444 default=None)
445 _app_settings_type = Column(
445 _app_settings_type = Column(
446 "app_settings_type", String(255), nullable=True, unique=None,
446 "app_settings_type", String(255), nullable=True, unique=None,
447 default=None)
447 default=None)
448
448
449 repository = relationship('Repository')
449 repository = relationship('Repository')
450
450
451 def __init__(self, repository_id, key='', val='', type='unicode'):
451 def __init__(self, repository_id, key='', val='', type='unicode'):
452 self.repository_id = repository_id
452 self.repository_id = repository_id
453 self.app_settings_name = key
453 self.app_settings_name = key
454 self.app_settings_type = type
454 self.app_settings_type = type
455 self.app_settings_value = val
455 self.app_settings_value = val
456
456
457 @validates('_app_settings_value')
457 @validates('_app_settings_value')
458 def validate_settings_value(self, key, val):
458 def validate_settings_value(self, key, val):
459 assert type(val) == unicode
459 assert type(val) == unicode
460 return val
460 return val
461
461
462 @hybrid_property
462 @hybrid_property
463 def app_settings_value(self):
463 def app_settings_value(self):
464 v = self._app_settings_value
464 v = self._app_settings_value
465 type_ = self.app_settings_type
465 type_ = self.app_settings_type
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
466 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
467 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
468 return converter(v)
468 return converter(v)
469
469
470 @app_settings_value.setter
470 @app_settings_value.setter
471 def app_settings_value(self, val):
471 def app_settings_value(self, val):
472 """
472 """
473 Setter that will always make sure we use unicode in app_settings_value
473 Setter that will always make sure we use unicode in app_settings_value
474
474
475 :param val:
475 :param val:
476 """
476 """
477 self._app_settings_value = safe_unicode(val)
477 self._app_settings_value = safe_unicode(val)
478
478
479 @hybrid_property
479 @hybrid_property
480 def app_settings_type(self):
480 def app_settings_type(self):
481 return self._app_settings_type
481 return self._app_settings_type
482
482
483 @app_settings_type.setter
483 @app_settings_type.setter
484 def app_settings_type(self, val):
484 def app_settings_type(self, val):
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
485 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
486 if val not in SETTINGS_TYPES:
486 if val not in SETTINGS_TYPES:
487 raise Exception('type must be one of %s got %s'
487 raise Exception('type must be one of %s got %s'
488 % (SETTINGS_TYPES.keys(), val))
488 % (SETTINGS_TYPES.keys(), val))
489 self._app_settings_type = val
489 self._app_settings_type = val
490
490
491 def __unicode__(self):
491 def __unicode__(self):
492 return u"<%s('%s:%s:%s[%s]')>" % (
492 return u"<%s('%s:%s:%s[%s]')>" % (
493 self.__class__.__name__, self.repository.repo_name,
493 self.__class__.__name__, self.repository.repo_name,
494 self.app_settings_name, self.app_settings_value,
494 self.app_settings_name, self.app_settings_value,
495 self.app_settings_type
495 self.app_settings_type
496 )
496 )
497
497
498
498
499 class RepoRhodeCodeUi(Base, BaseModel):
499 class RepoRhodeCodeUi(Base, BaseModel):
500 __tablename__ = 'repo_rhodecode_ui'
500 __tablename__ = 'repo_rhodecode_ui'
501 __table_args__ = (
501 __table_args__ = (
502 UniqueConstraint(
502 UniqueConstraint(
503 'repository_id', 'ui_section', 'ui_key',
503 'repository_id', 'ui_section', 'ui_key',
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
504 name='uq_repo_rhodecode_ui_repository_id_section_key'),
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
507 )
507 )
508
508
509 repository_id = Column(
509 repository_id = Column(
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
510 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
511 nullable=False)
511 nullable=False)
512 ui_id = Column(
512 ui_id = Column(
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
513 "ui_id", Integer(), nullable=False, unique=True, default=None,
514 primary_key=True)
514 primary_key=True)
515 ui_section = Column(
515 ui_section = Column(
516 "ui_section", String(255), nullable=True, unique=None, default=None)
516 "ui_section", String(255), nullable=True, unique=None, default=None)
517 ui_key = Column(
517 ui_key = Column(
518 "ui_key", String(255), nullable=True, unique=None, default=None)
518 "ui_key", String(255), nullable=True, unique=None, default=None)
519 ui_value = Column(
519 ui_value = Column(
520 "ui_value", String(255), nullable=True, unique=None, default=None)
520 "ui_value", String(255), nullable=True, unique=None, default=None)
521 ui_active = Column(
521 ui_active = Column(
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
522 "ui_active", Boolean(), nullable=True, unique=None, default=True)
523
523
524 repository = relationship('Repository')
524 repository = relationship('Repository')
525
525
526 def __repr__(self):
526 def __repr__(self):
527 return '<%s[%s:%s]%s=>%s]>' % (
527 return '<%s[%s:%s]%s=>%s]>' % (
528 self.__class__.__name__, self.repository.repo_name,
528 self.__class__.__name__, self.repository.repo_name,
529 self.ui_section, self.ui_key, self.ui_value)
529 self.ui_section, self.ui_key, self.ui_value)
530
530
531
531
532 class User(Base, BaseModel):
532 class User(Base, BaseModel):
533 __tablename__ = 'users'
533 __tablename__ = 'users'
534 __table_args__ = (
534 __table_args__ = (
535 UniqueConstraint('username'), UniqueConstraint('email'),
535 UniqueConstraint('username'), UniqueConstraint('email'),
536 Index('u_username_idx', 'username'),
536 Index('u_username_idx', 'username'),
537 Index('u_email_idx', 'email'),
537 Index('u_email_idx', 'email'),
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
540 )
540 )
541 DEFAULT_USER = 'default'
541 DEFAULT_USER = 'default'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
542 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
543 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
544
544
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
545 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
546 username = Column("username", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
547 password = Column("password", String(255), nullable=True, unique=None, default=None)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
548 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
549 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
550 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
551 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
552 _email = Column("email", String(255), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
553 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
554 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
555
555
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
556 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
557 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
558 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
559 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
561 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
562
562
563 user_log = relationship('UserLog')
563 user_log = relationship('UserLog')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
564 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
565
565
566 repositories = relationship('Repository')
566 repositories = relationship('Repository')
567 repository_groups = relationship('RepoGroup')
567 repository_groups = relationship('RepoGroup')
568 user_groups = relationship('UserGroup')
568 user_groups = relationship('UserGroup')
569
569
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
570 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
571 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
572
572
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
573 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
574 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
575 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
576
576
577 group_member = relationship('UserGroupMember', cascade='all')
577 group_member = relationship('UserGroupMember', cascade='all')
578
578
579 notifications = relationship('UserNotification', cascade='all')
579 notifications = relationship('UserNotification', cascade='all')
580 # notifications assigned to this user
580 # notifications assigned to this user
581 user_created_notifications = relationship('Notification', cascade='all')
581 user_created_notifications = relationship('Notification', cascade='all')
582 # comments created by this user
582 # comments created by this user
583 user_comments = relationship('ChangesetComment', cascade='all')
583 user_comments = relationship('ChangesetComment', cascade='all')
584 # user profile extra info
584 # user profile extra info
585 user_emails = relationship('UserEmailMap', cascade='all')
585 user_emails = relationship('UserEmailMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
586 user_ip_map = relationship('UserIpMap', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
587 user_auth_tokens = relationship('UserApiKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
588 user_ssh_keys = relationship('UserSshKeys', cascade='all')
589
589
590 # gists
590 # gists
591 user_gists = relationship('Gist', cascade='all')
591 user_gists = relationship('Gist', cascade='all')
592 # user pull requests
592 # user pull requests
593 user_pull_requests = relationship('PullRequest', cascade='all')
593 user_pull_requests = relationship('PullRequest', cascade='all')
594 # external identities
594 # external identities
595 extenal_identities = relationship(
595 extenal_identities = relationship(
596 'ExternalIdentity',
596 'ExternalIdentity',
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
597 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
598 cascade='all')
598 cascade='all')
599 # review rules
599 # review rules
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
600 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
601
601
602 def __unicode__(self):
602 def __unicode__(self):
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
603 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
604 self.user_id, self.username)
604 self.user_id, self.username)
605
605
606 @hybrid_property
606 @hybrid_property
607 def email(self):
607 def email(self):
608 return self._email
608 return self._email
609
609
610 @email.setter
610 @email.setter
611 def email(self, val):
611 def email(self, val):
612 self._email = val.lower() if val else None
612 self._email = val.lower() if val else None
613
613
614 @hybrid_property
614 @hybrid_property
615 def first_name(self):
615 def first_name(self):
616 from rhodecode.lib import helpers as h
616 from rhodecode.lib import helpers as h
617 if self.name:
617 if self.name:
618 return h.escape(self.name)
618 return h.escape(self.name)
619 return self.name
619 return self.name
620
620
621 @hybrid_property
621 @hybrid_property
622 def last_name(self):
622 def last_name(self):
623 from rhodecode.lib import helpers as h
623 from rhodecode.lib import helpers as h
624 if self.lastname:
624 if self.lastname:
625 return h.escape(self.lastname)
625 return h.escape(self.lastname)
626 return self.lastname
626 return self.lastname
627
627
628 @hybrid_property
628 @hybrid_property
629 def api_key(self):
629 def api_key(self):
630 """
630 """
631 Fetch if exist an auth-token with role ALL connected to this user
631 Fetch if exist an auth-token with role ALL connected to this user
632 """
632 """
633 user_auth_token = UserApiKeys.query()\
633 user_auth_token = UserApiKeys.query()\
634 .filter(UserApiKeys.user_id == self.user_id)\
634 .filter(UserApiKeys.user_id == self.user_id)\
635 .filter(or_(UserApiKeys.expires == -1,
635 .filter(or_(UserApiKeys.expires == -1,
636 UserApiKeys.expires >= time.time()))\
636 UserApiKeys.expires >= time.time()))\
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
637 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
638 if user_auth_token:
638 if user_auth_token:
639 user_auth_token = user_auth_token.api_key
639 user_auth_token = user_auth_token.api_key
640
640
641 return user_auth_token
641 return user_auth_token
642
642
643 @api_key.setter
643 @api_key.setter
644 def api_key(self, val):
644 def api_key(self, val):
645 # don't allow to set API key this is deprecated for now
645 # don't allow to set API key this is deprecated for now
646 self._api_key = None
646 self._api_key = None
647
647
648 @property
648 @property
649 def reviewer_pull_requests(self):
649 def reviewer_pull_requests(self):
650 return PullRequestReviewers.query() \
650 return PullRequestReviewers.query() \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
651 .options(joinedload(PullRequestReviewers.pull_request)) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
652 .filter(PullRequestReviewers.user_id == self.user_id) \
653 .all()
653 .all()
654
654
655 @property
655 @property
656 def firstname(self):
656 def firstname(self):
657 # alias for future
657 # alias for future
658 return self.name
658 return self.name
659
659
660 @property
660 @property
661 def emails(self):
661 def emails(self):
662 other = UserEmailMap.query()\
662 other = UserEmailMap.query()\
663 .filter(UserEmailMap.user == self) \
663 .filter(UserEmailMap.user == self) \
664 .order_by(UserEmailMap.email_id.asc()) \
664 .order_by(UserEmailMap.email_id.asc()) \
665 .all()
665 .all()
666 return [self.email] + [x.email for x in other]
666 return [self.email] + [x.email for x in other]
667
667
668 @property
668 @property
669 def auth_tokens(self):
669 def auth_tokens(self):
670 auth_tokens = self.get_auth_tokens()
670 auth_tokens = self.get_auth_tokens()
671 return [x.api_key for x in auth_tokens]
671 return [x.api_key for x in auth_tokens]
672
672
673 def get_auth_tokens(self):
673 def get_auth_tokens(self):
674 return UserApiKeys.query()\
674 return UserApiKeys.query()\
675 .filter(UserApiKeys.user == self)\
675 .filter(UserApiKeys.user == self)\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
676 .order_by(UserApiKeys.user_api_key_id.asc())\
677 .all()
677 .all()
678
678
679 @LazyProperty
679 @LazyProperty
680 def feed_token(self):
680 def feed_token(self):
681 return self.get_feed_token()
681 return self.get_feed_token()
682
682
683 def get_feed_token(self, cache=True):
683 def get_feed_token(self, cache=True):
684 feed_tokens = UserApiKeys.query()\
684 feed_tokens = UserApiKeys.query()\
685 .filter(UserApiKeys.user == self)\
685 .filter(UserApiKeys.user == self)\
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
686 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
687 if cache:
687 if cache:
688 feed_tokens = feed_tokens.options(
688 feed_tokens = feed_tokens.options(
689 FromCache("long_term", "get_user_feed_token_%s" % self.user_id))
689 FromCache("long_term", "get_user_feed_token_%s" % self.user_id))
690
690
691 feed_tokens = feed_tokens.all()
691 feed_tokens = feed_tokens.all()
692 if feed_tokens:
692 if feed_tokens:
693 return feed_tokens[0].api_key
693 return feed_tokens[0].api_key
694 return 'NO_FEED_TOKEN_AVAILABLE'
694 return 'NO_FEED_TOKEN_AVAILABLE'
695
695
696 @classmethod
696 @classmethod
697 def get(cls, user_id, cache=False):
697 def get(cls, user_id, cache=False):
698 if not user_id:
698 if not user_id:
699 return
699 return
700
700
701 user = cls.query()
701 user = cls.query()
702 if cache:
702 if cache:
703 user = user.options(
703 user = user.options(
704 FromCache("sql_cache_short", "get_users_%s" % user_id))
704 FromCache("sql_cache_short", "get_users_%s" % user_id))
705 return user.get(user_id)
705 return user.get(user_id)
706
706
707 @classmethod
707 @classmethod
708 def extra_valid_auth_tokens(cls, user, role=None):
708 def extra_valid_auth_tokens(cls, user, role=None):
709 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
709 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
710 .filter(or_(UserApiKeys.expires == -1,
710 .filter(or_(UserApiKeys.expires == -1,
711 UserApiKeys.expires >= time.time()))
711 UserApiKeys.expires >= time.time()))
712 if role:
712 if role:
713 tokens = tokens.filter(or_(UserApiKeys.role == role,
713 tokens = tokens.filter(or_(UserApiKeys.role == role,
714 UserApiKeys.role == UserApiKeys.ROLE_ALL))
714 UserApiKeys.role == UserApiKeys.ROLE_ALL))
715 return tokens.all()
715 return tokens.all()
716
716
717 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
717 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
718 from rhodecode.lib import auth
718 from rhodecode.lib import auth
719
719
720 log.debug('Trying to authenticate user: %s via auth-token, '
720 log.debug('Trying to authenticate user: %s via auth-token, '
721 'and roles: %s', self, roles)
721 'and roles: %s', self, roles)
722
722
723 if not auth_token:
723 if not auth_token:
724 return False
724 return False
725
725
726 crypto_backend = auth.crypto_backend()
726 crypto_backend = auth.crypto_backend()
727
727
728 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
728 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
729 tokens_q = UserApiKeys.query()\
729 tokens_q = UserApiKeys.query()\
730 .filter(UserApiKeys.user_id == self.user_id)\
730 .filter(UserApiKeys.user_id == self.user_id)\
731 .filter(or_(UserApiKeys.expires == -1,
731 .filter(or_(UserApiKeys.expires == -1,
732 UserApiKeys.expires >= time.time()))
732 UserApiKeys.expires >= time.time()))
733
733
734 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
734 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
735
735
736 plain_tokens = []
736 plain_tokens = []
737 hash_tokens = []
737 hash_tokens = []
738
738
739 for token in tokens_q.all():
739 for token in tokens_q.all():
740 # verify scope first
740 # verify scope first
741 if token.repo_id:
741 if token.repo_id:
742 # token has a scope, we need to verify it
742 # token has a scope, we need to verify it
743 if scope_repo_id != token.repo_id:
743 if scope_repo_id != token.repo_id:
744 log.debug(
744 log.debug(
745 'Scope mismatch: token has a set repo scope: %s, '
745 'Scope mismatch: token has a set repo scope: %s, '
746 'and calling scope is:%s, skipping further checks',
746 'and calling scope is:%s, skipping further checks',
747 token.repo, scope_repo_id)
747 token.repo, scope_repo_id)
748 # token has a scope, and it doesn't match, skip token
748 # token has a scope, and it doesn't match, skip token
749 continue
749 continue
750
750
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
752 hash_tokens.append(token.api_key)
752 hash_tokens.append(token.api_key)
753 else:
753 else:
754 plain_tokens.append(token.api_key)
754 plain_tokens.append(token.api_key)
755
755
756 is_plain_match = auth_token in plain_tokens
756 is_plain_match = auth_token in plain_tokens
757 if is_plain_match:
757 if is_plain_match:
758 return True
758 return True
759
759
760 for hashed in hash_tokens:
760 for hashed in hash_tokens:
761 # TODO(marcink): this is expensive to calculate, but most secure
761 # TODO(marcink): this is expensive to calculate, but most secure
762 match = crypto_backend.hash_check(auth_token, hashed)
762 match = crypto_backend.hash_check(auth_token, hashed)
763 if match:
763 if match:
764 return True
764 return True
765
765
766 return False
766 return False
767
767
768 @property
768 @property
769 def ip_addresses(self):
769 def ip_addresses(self):
770 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
770 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
771 return [x.ip_addr for x in ret]
771 return [x.ip_addr for x in ret]
772
772
773 @property
773 @property
774 def username_and_name(self):
774 def username_and_name(self):
775 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
775 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
776
776
777 @property
777 @property
778 def username_or_name_or_email(self):
778 def username_or_name_or_email(self):
779 full_name = self.full_name if self.full_name is not ' ' else None
779 full_name = self.full_name if self.full_name is not ' ' else None
780 return self.username or full_name or self.email
780 return self.username or full_name or self.email
781
781
782 @property
782 @property
783 def full_name(self):
783 def full_name(self):
784 return '%s %s' % (self.first_name, self.last_name)
784 return '%s %s' % (self.first_name, self.last_name)
785
785
786 @property
786 @property
787 def full_name_or_username(self):
787 def full_name_or_username(self):
788 return ('%s %s' % (self.first_name, self.last_name)
788 return ('%s %s' % (self.first_name, self.last_name)
789 if (self.first_name and self.last_name) else self.username)
789 if (self.first_name and self.last_name) else self.username)
790
790
791 @property
791 @property
792 def full_contact(self):
792 def full_contact(self):
793 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
793 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
794
794
795 @property
795 @property
796 def short_contact(self):
796 def short_contact(self):
797 return '%s %s' % (self.first_name, self.last_name)
797 return '%s %s' % (self.first_name, self.last_name)
798
798
799 @property
799 @property
800 def is_admin(self):
800 def is_admin(self):
801 return self.admin
801 return self.admin
802
802
803 def AuthUser(self, **kwargs):
803 def AuthUser(self, **kwargs):
804 """
804 """
805 Returns instance of AuthUser for this user
805 Returns instance of AuthUser for this user
806 """
806 """
807 from rhodecode.lib.auth import AuthUser
807 from rhodecode.lib.auth import AuthUser
808 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
808 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
809
809
810 @hybrid_property
810 @hybrid_property
811 def user_data(self):
811 def user_data(self):
812 if not self._user_data:
812 if not self._user_data:
813 return {}
813 return {}
814
814
815 try:
815 try:
816 return json.loads(self._user_data)
816 return json.loads(self._user_data)
817 except TypeError:
817 except TypeError:
818 return {}
818 return {}
819
819
820 @user_data.setter
820 @user_data.setter
821 def user_data(self, val):
821 def user_data(self, val):
822 if not isinstance(val, dict):
822 if not isinstance(val, dict):
823 raise Exception('user_data must be dict, got %s' % type(val))
823 raise Exception('user_data must be dict, got %s' % type(val))
824 try:
824 try:
825 self._user_data = json.dumps(val)
825 self._user_data = json.dumps(val)
826 except Exception:
826 except Exception:
827 log.error(traceback.format_exc())
827 log.error(traceback.format_exc())
828
828
829 @classmethod
829 @classmethod
830 def get_by_username(cls, username, case_insensitive=False,
830 def get_by_username(cls, username, case_insensitive=False,
831 cache=False, identity_cache=False):
831 cache=False, identity_cache=False):
832 session = Session()
832 session = Session()
833
833
834 if case_insensitive:
834 if case_insensitive:
835 q = cls.query().filter(
835 q = cls.query().filter(
836 func.lower(cls.username) == func.lower(username))
836 func.lower(cls.username) == func.lower(username))
837 else:
837 else:
838 q = cls.query().filter(cls.username == username)
838 q = cls.query().filter(cls.username == username)
839
839
840 if cache:
840 if cache:
841 if identity_cache:
841 if identity_cache:
842 val = cls.identity_cache(session, 'username', username)
842 val = cls.identity_cache(session, 'username', username)
843 if val:
843 if val:
844 return val
844 return val
845 else:
845 else:
846 cache_key = "get_user_by_name_%s" % _hash_key(username)
846 cache_key = "get_user_by_name_%s" % _hash_key(username)
847 q = q.options(
847 q = q.options(
848 FromCache("sql_cache_short", cache_key))
848 FromCache("sql_cache_short", cache_key))
849
849
850 return q.scalar()
850 return q.scalar()
851
851
852 @classmethod
852 @classmethod
853 def get_by_auth_token(cls, auth_token, cache=False):
853 def get_by_auth_token(cls, auth_token, cache=False):
854 q = UserApiKeys.query()\
854 q = UserApiKeys.query()\
855 .filter(UserApiKeys.api_key == auth_token)\
855 .filter(UserApiKeys.api_key == auth_token)\
856 .filter(or_(UserApiKeys.expires == -1,
856 .filter(or_(UserApiKeys.expires == -1,
857 UserApiKeys.expires >= time.time()))
857 UserApiKeys.expires >= time.time()))
858 if cache:
858 if cache:
859 q = q.options(
859 q = q.options(
860 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
860 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
861
861
862 match = q.first()
862 match = q.first()
863 if match:
863 if match:
864 return match.user
864 return match.user
865
865
866 @classmethod
866 @classmethod
867 def get_by_email(cls, email, case_insensitive=False, cache=False):
867 def get_by_email(cls, email, case_insensitive=False, cache=False):
868
868
869 if case_insensitive:
869 if case_insensitive:
870 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
870 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
871
871
872 else:
872 else:
873 q = cls.query().filter(cls.email == email)
873 q = cls.query().filter(cls.email == email)
874
874
875 email_key = _hash_key(email)
875 email_key = _hash_key(email)
876 if cache:
876 if cache:
877 q = q.options(
877 q = q.options(
878 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
878 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
879
879
880 ret = q.scalar()
880 ret = q.scalar()
881 if ret is None:
881 if ret is None:
882 q = UserEmailMap.query()
882 q = UserEmailMap.query()
883 # try fetching in alternate email map
883 # try fetching in alternate email map
884 if case_insensitive:
884 if case_insensitive:
885 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
885 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
886 else:
886 else:
887 q = q.filter(UserEmailMap.email == email)
887 q = q.filter(UserEmailMap.email == email)
888 q = q.options(joinedload(UserEmailMap.user))
888 q = q.options(joinedload(UserEmailMap.user))
889 if cache:
889 if cache:
890 q = q.options(
890 q = q.options(
891 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
891 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
892 ret = getattr(q.scalar(), 'user', None)
892 ret = getattr(q.scalar(), 'user', None)
893
893
894 return ret
894 return ret
895
895
896 @classmethod
896 @classmethod
897 def get_from_cs_author(cls, author):
897 def get_from_cs_author(cls, author):
898 """
898 """
899 Tries to get User objects out of commit author string
899 Tries to get User objects out of commit author string
900
900
901 :param author:
901 :param author:
902 """
902 """
903 from rhodecode.lib.helpers import email, author_name
903 from rhodecode.lib.helpers import email, author_name
904 # Valid email in the attribute passed, see if they're in the system
904 # Valid email in the attribute passed, see if they're in the system
905 _email = email(author)
905 _email = email(author)
906 if _email:
906 if _email:
907 user = cls.get_by_email(_email, case_insensitive=True)
907 user = cls.get_by_email(_email, case_insensitive=True)
908 if user:
908 if user:
909 return user
909 return user
910 # Maybe we can match by username?
910 # Maybe we can match by username?
911 _author = author_name(author)
911 _author = author_name(author)
912 user = cls.get_by_username(_author, case_insensitive=True)
912 user = cls.get_by_username(_author, case_insensitive=True)
913 if user:
913 if user:
914 return user
914 return user
915
915
916 def update_userdata(self, **kwargs):
916 def update_userdata(self, **kwargs):
917 usr = self
917 usr = self
918 old = usr.user_data
918 old = usr.user_data
919 old.update(**kwargs)
919 old.update(**kwargs)
920 usr.user_data = old
920 usr.user_data = old
921 Session().add(usr)
921 Session().add(usr)
922 log.debug('updated userdata with ', kwargs)
922 log.debug('updated userdata with ', kwargs)
923
923
924 def update_lastlogin(self):
924 def update_lastlogin(self):
925 """Update user lastlogin"""
925 """Update user lastlogin"""
926 self.last_login = datetime.datetime.now()
926 self.last_login = datetime.datetime.now()
927 Session().add(self)
927 Session().add(self)
928 log.debug('updated user %s lastlogin', self.username)
928 log.debug('updated user %s lastlogin', self.username)
929
929
930 def update_lastactivity(self):
930 def update_lastactivity(self):
931 """Update user lastactivity"""
931 """Update user lastactivity"""
932 self.last_activity = datetime.datetime.now()
932 self.last_activity = datetime.datetime.now()
933 Session().add(self)
933 Session().add(self)
934 log.debug('updated user `%s` last activity', self.username)
934 log.debug('updated user `%s` last activity', self.username)
935
935
936 def update_password(self, new_password):
936 def update_password(self, new_password):
937 from rhodecode.lib.auth import get_crypt_password
937 from rhodecode.lib.auth import get_crypt_password
938
938
939 self.password = get_crypt_password(new_password)
939 self.password = get_crypt_password(new_password)
940 Session().add(self)
940 Session().add(self)
941
941
942 @classmethod
942 @classmethod
943 def get_first_super_admin(cls):
943 def get_first_super_admin(cls):
944 user = User.query().filter(User.admin == true()).first()
944 user = User.query().filter(User.admin == true()).first()
945 if user is None:
945 if user is None:
946 raise Exception('FATAL: Missing administrative account!')
946 raise Exception('FATAL: Missing administrative account!')
947 return user
947 return user
948
948
949 @classmethod
949 @classmethod
950 def get_all_super_admins(cls):
950 def get_all_super_admins(cls):
951 """
951 """
952 Returns all admin accounts sorted by username
952 Returns all admin accounts sorted by username
953 """
953 """
954 return User.query().filter(User.admin == true())\
954 return User.query().filter(User.admin == true())\
955 .order_by(User.username.asc()).all()
955 .order_by(User.username.asc()).all()
956
956
957 @classmethod
957 @classmethod
958 def get_default_user(cls, cache=False, refresh=False):
958 def get_default_user(cls, cache=False, refresh=False):
959 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
959 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
960 if user is None:
960 if user is None:
961 raise Exception('FATAL: Missing default account!')
961 raise Exception('FATAL: Missing default account!')
962 if refresh:
962 if refresh:
963 # The default user might be based on outdated state which
963 # The default user might be based on outdated state which
964 # has been loaded from the cache.
964 # has been loaded from the cache.
965 # A call to refresh() ensures that the
965 # A call to refresh() ensures that the
966 # latest state from the database is used.
966 # latest state from the database is used.
967 Session().refresh(user)
967 Session().refresh(user)
968 return user
968 return user
969
969
970 def _get_default_perms(self, user, suffix=''):
970 def _get_default_perms(self, user, suffix=''):
971 from rhodecode.model.permission import PermissionModel
971 from rhodecode.model.permission import PermissionModel
972 return PermissionModel().get_default_perms(user.user_perms, suffix)
972 return PermissionModel().get_default_perms(user.user_perms, suffix)
973
973
974 def get_default_perms(self, suffix=''):
974 def get_default_perms(self, suffix=''):
975 return self._get_default_perms(self, suffix)
975 return self._get_default_perms(self, suffix)
976
976
977 def get_api_data(self, include_secrets=False, details='full'):
977 def get_api_data(self, include_secrets=False, details='full'):
978 """
978 """
979 Common function for generating user related data for API
979 Common function for generating user related data for API
980
980
981 :param include_secrets: By default secrets in the API data will be replaced
981 :param include_secrets: By default secrets in the API data will be replaced
982 by a placeholder value to prevent exposing this data by accident. In case
982 by a placeholder value to prevent exposing this data by accident. In case
983 this data shall be exposed, set this flag to ``True``.
983 this data shall be exposed, set this flag to ``True``.
984
984
985 :param details: details can be 'basic|full' basic gives only a subset of
985 :param details: details can be 'basic|full' basic gives only a subset of
986 the available user information that includes user_id, name and emails.
986 the available user information that includes user_id, name and emails.
987 """
987 """
988 user = self
988 user = self
989 user_data = self.user_data
989 user_data = self.user_data
990 data = {
990 data = {
991 'user_id': user.user_id,
991 'user_id': user.user_id,
992 'username': user.username,
992 'username': user.username,
993 'firstname': user.name,
993 'firstname': user.name,
994 'lastname': user.lastname,
994 'lastname': user.lastname,
995 'email': user.email,
995 'email': user.email,
996 'emails': user.emails,
996 'emails': user.emails,
997 }
997 }
998 if details == 'basic':
998 if details == 'basic':
999 return data
999 return data
1000
1000
1001 auth_token_length = 40
1001 auth_token_length = 40
1002 auth_token_replacement = '*' * auth_token_length
1002 auth_token_replacement = '*' * auth_token_length
1003
1003
1004 extras = {
1004 extras = {
1005 'auth_tokens': [auth_token_replacement],
1005 'auth_tokens': [auth_token_replacement],
1006 'active': user.active,
1006 'active': user.active,
1007 'admin': user.admin,
1007 'admin': user.admin,
1008 'extern_type': user.extern_type,
1008 'extern_type': user.extern_type,
1009 'extern_name': user.extern_name,
1009 'extern_name': user.extern_name,
1010 'last_login': user.last_login,
1010 'last_login': user.last_login,
1011 'last_activity': user.last_activity,
1011 'last_activity': user.last_activity,
1012 'ip_addresses': user.ip_addresses,
1012 'ip_addresses': user.ip_addresses,
1013 'language': user_data.get('language')
1013 'language': user_data.get('language')
1014 }
1014 }
1015 data.update(extras)
1015 data.update(extras)
1016
1016
1017 if include_secrets:
1017 if include_secrets:
1018 data['auth_tokens'] = user.auth_tokens
1018 data['auth_tokens'] = user.auth_tokens
1019 return data
1019 return data
1020
1020
1021 def __json__(self):
1021 def __json__(self):
1022 data = {
1022 data = {
1023 'full_name': self.full_name,
1023 'full_name': self.full_name,
1024 'full_name_or_username': self.full_name_or_username,
1024 'full_name_or_username': self.full_name_or_username,
1025 'short_contact': self.short_contact,
1025 'short_contact': self.short_contact,
1026 'full_contact': self.full_contact,
1026 'full_contact': self.full_contact,
1027 }
1027 }
1028 data.update(self.get_api_data())
1028 data.update(self.get_api_data())
1029 return data
1029 return data
1030
1030
1031
1031
1032 class UserApiKeys(Base, BaseModel):
1032 class UserApiKeys(Base, BaseModel):
1033 __tablename__ = 'user_api_keys'
1033 __tablename__ = 'user_api_keys'
1034 __table_args__ = (
1034 __table_args__ = (
1035 Index('uak_api_key_idx', 'api_key', unique=True),
1035 Index('uak_api_key_idx', 'api_key', unique=True),
1036 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1036 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1039 )
1039 )
1040 __mapper_args__ = {}
1040 __mapper_args__ = {}
1041
1041
1042 # ApiKey role
1042 # ApiKey role
1043 ROLE_ALL = 'token_role_all'
1043 ROLE_ALL = 'token_role_all'
1044 ROLE_HTTP = 'token_role_http'
1044 ROLE_HTTP = 'token_role_http'
1045 ROLE_VCS = 'token_role_vcs'
1045 ROLE_VCS = 'token_role_vcs'
1046 ROLE_API = 'token_role_api'
1046 ROLE_API = 'token_role_api'
1047 ROLE_FEED = 'token_role_feed'
1047 ROLE_FEED = 'token_role_feed'
1048 ROLE_PASSWORD_RESET = 'token_password_reset'
1048 ROLE_PASSWORD_RESET = 'token_password_reset'
1049
1049
1050 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1050 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1051
1051
1052 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1052 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1054 api_key = Column("api_key", String(255), nullable=False, unique=True)
1054 api_key = Column("api_key", String(255), nullable=False, unique=True)
1055 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1055 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1056 expires = Column('expires', Float(53), nullable=False)
1056 expires = Column('expires', Float(53), nullable=False)
1057 role = Column('role', String(255), nullable=True)
1057 role = Column('role', String(255), nullable=True)
1058 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1058 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1059
1059
1060 # scope columns
1060 # scope columns
1061 repo_id = Column(
1061 repo_id = Column(
1062 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1062 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1063 nullable=True, unique=None, default=None)
1063 nullable=True, unique=None, default=None)
1064 repo = relationship('Repository', lazy='joined')
1064 repo = relationship('Repository', lazy='joined')
1065
1065
1066 repo_group_id = Column(
1066 repo_group_id = Column(
1067 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1067 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1068 nullable=True, unique=None, default=None)
1068 nullable=True, unique=None, default=None)
1069 repo_group = relationship('RepoGroup', lazy='joined')
1069 repo_group = relationship('RepoGroup', lazy='joined')
1070
1070
1071 user = relationship('User', lazy='joined')
1071 user = relationship('User', lazy='joined')
1072
1072
1073 def __unicode__(self):
1073 def __unicode__(self):
1074 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1074 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1075
1075
1076 def __json__(self):
1076 def __json__(self):
1077 data = {
1077 data = {
1078 'auth_token': self.api_key,
1078 'auth_token': self.api_key,
1079 'role': self.role,
1079 'role': self.role,
1080 'scope': self.scope_humanized,
1080 'scope': self.scope_humanized,
1081 'expired': self.expired
1081 'expired': self.expired
1082 }
1082 }
1083 return data
1083 return data
1084
1084
1085 def get_api_data(self, include_secrets=False):
1085 def get_api_data(self, include_secrets=False):
1086 data = self.__json__()
1086 data = self.__json__()
1087 if include_secrets:
1087 if include_secrets:
1088 return data
1088 return data
1089 else:
1089 else:
1090 data['auth_token'] = self.token_obfuscated
1090 data['auth_token'] = self.token_obfuscated
1091 return data
1091 return data
1092
1092
1093 @hybrid_property
1093 @hybrid_property
1094 def description_safe(self):
1094 def description_safe(self):
1095 from rhodecode.lib import helpers as h
1095 from rhodecode.lib import helpers as h
1096 return h.escape(self.description)
1096 return h.escape(self.description)
1097
1097
1098 @property
1098 @property
1099 def expired(self):
1099 def expired(self):
1100 if self.expires == -1:
1100 if self.expires == -1:
1101 return False
1101 return False
1102 return time.time() > self.expires
1102 return time.time() > self.expires
1103
1103
1104 @classmethod
1104 @classmethod
1105 def _get_role_name(cls, role):
1105 def _get_role_name(cls, role):
1106 return {
1106 return {
1107 cls.ROLE_ALL: _('all'),
1107 cls.ROLE_ALL: _('all'),
1108 cls.ROLE_HTTP: _('http/web interface'),
1108 cls.ROLE_HTTP: _('http/web interface'),
1109 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1109 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1110 cls.ROLE_API: _('api calls'),
1110 cls.ROLE_API: _('api calls'),
1111 cls.ROLE_FEED: _('feed access'),
1111 cls.ROLE_FEED: _('feed access'),
1112 }.get(role, role)
1112 }.get(role, role)
1113
1113
1114 @property
1114 @property
1115 def role_humanized(self):
1115 def role_humanized(self):
1116 return self._get_role_name(self.role)
1116 return self._get_role_name(self.role)
1117
1117
1118 def _get_scope(self):
1118 def _get_scope(self):
1119 if self.repo:
1119 if self.repo:
1120 return repr(self.repo)
1120 return repr(self.repo)
1121 if self.repo_group:
1121 if self.repo_group:
1122 return repr(self.repo_group) + ' (recursive)'
1122 return repr(self.repo_group) + ' (recursive)'
1123 return 'global'
1123 return 'global'
1124
1124
1125 @property
1125 @property
1126 def scope_humanized(self):
1126 def scope_humanized(self):
1127 return self._get_scope()
1127 return self._get_scope()
1128
1128
1129 @property
1129 @property
1130 def token_obfuscated(self):
1130 def token_obfuscated(self):
1131 if self.api_key:
1131 if self.api_key:
1132 return self.api_key[:4] + "****"
1132 return self.api_key[:4] + "****"
1133
1133
1134
1134
1135 class UserEmailMap(Base, BaseModel):
1135 class UserEmailMap(Base, BaseModel):
1136 __tablename__ = 'user_email_map'
1136 __tablename__ = 'user_email_map'
1137 __table_args__ = (
1137 __table_args__ = (
1138 Index('uem_email_idx', 'email'),
1138 Index('uem_email_idx', 'email'),
1139 UniqueConstraint('email'),
1139 UniqueConstraint('email'),
1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1141 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1141 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1142 )
1142 )
1143 __mapper_args__ = {}
1143 __mapper_args__ = {}
1144
1144
1145 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1145 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1147 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1147 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1148 user = relationship('User', lazy='joined')
1148 user = relationship('User', lazy='joined')
1149
1149
1150 @validates('_email')
1150 @validates('_email')
1151 def validate_email(self, key, email):
1151 def validate_email(self, key, email):
1152 # check if this email is not main one
1152 # check if this email is not main one
1153 main_email = Session().query(User).filter(User.email == email).scalar()
1153 main_email = Session().query(User).filter(User.email == email).scalar()
1154 if main_email is not None:
1154 if main_email is not None:
1155 raise AttributeError('email %s is present is user table' % email)
1155 raise AttributeError('email %s is present is user table' % email)
1156 return email
1156 return email
1157
1157
1158 @hybrid_property
1158 @hybrid_property
1159 def email(self):
1159 def email(self):
1160 return self._email
1160 return self._email
1161
1161
1162 @email.setter
1162 @email.setter
1163 def email(self, val):
1163 def email(self, val):
1164 self._email = val.lower() if val else None
1164 self._email = val.lower() if val else None
1165
1165
1166
1166
1167 class UserIpMap(Base, BaseModel):
1167 class UserIpMap(Base, BaseModel):
1168 __tablename__ = 'user_ip_map'
1168 __tablename__ = 'user_ip_map'
1169 __table_args__ = (
1169 __table_args__ = (
1170 UniqueConstraint('user_id', 'ip_addr'),
1170 UniqueConstraint('user_id', 'ip_addr'),
1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1172 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1172 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1173 )
1173 )
1174 __mapper_args__ = {}
1174 __mapper_args__ = {}
1175
1175
1176 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1176 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1177 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1177 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1178 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1178 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1179 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1179 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1180 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1180 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1181 user = relationship('User', lazy='joined')
1181 user = relationship('User', lazy='joined')
1182
1182
1183 @hybrid_property
1183 @hybrid_property
1184 def description_safe(self):
1184 def description_safe(self):
1185 from rhodecode.lib import helpers as h
1185 from rhodecode.lib import helpers as h
1186 return h.escape(self.description)
1186 return h.escape(self.description)
1187
1187
1188 @classmethod
1188 @classmethod
1189 def _get_ip_range(cls, ip_addr):
1189 def _get_ip_range(cls, ip_addr):
1190 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1190 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1191 return [str(net.network_address), str(net.broadcast_address)]
1191 return [str(net.network_address), str(net.broadcast_address)]
1192
1192
1193 def __json__(self):
1193 def __json__(self):
1194 return {
1194 return {
1195 'ip_addr': self.ip_addr,
1195 'ip_addr': self.ip_addr,
1196 'ip_range': self._get_ip_range(self.ip_addr),
1196 'ip_range': self._get_ip_range(self.ip_addr),
1197 }
1197 }
1198
1198
1199 def __unicode__(self):
1199 def __unicode__(self):
1200 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1200 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1201 self.user_id, self.ip_addr)
1201 self.user_id, self.ip_addr)
1202
1202
1203
1203
1204 class UserSshKeys(Base, BaseModel):
1204 class UserSshKeys(Base, BaseModel):
1205 __tablename__ = 'user_ssh_keys'
1205 __tablename__ = 'user_ssh_keys'
1206 __table_args__ = (
1206 __table_args__ = (
1207 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1207 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1208
1208
1209 UniqueConstraint('ssh_key_fingerprint'),
1209 UniqueConstraint('ssh_key_fingerprint'),
1210
1210
1211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1213 )
1213 )
1214 __mapper_args__ = {}
1214 __mapper_args__ = {}
1215
1215
1216 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1216 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1217 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1217 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1218 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1218 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1219
1219
1220 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1220 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1221
1221
1222 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1222 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1223 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1223 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1224 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1224 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1225
1225
1226 user = relationship('User', lazy='joined')
1226 user = relationship('User', lazy='joined')
1227
1227
1228 def __json__(self):
1228 def __json__(self):
1229 data = {
1229 data = {
1230 'ssh_fingerprint': self.ssh_key_fingerprint,
1230 'ssh_fingerprint': self.ssh_key_fingerprint,
1231 'description': self.description,
1231 'description': self.description,
1232 'created_on': self.created_on
1232 'created_on': self.created_on
1233 }
1233 }
1234 return data
1234 return data
1235
1235
1236 def get_api_data(self):
1236 def get_api_data(self):
1237 data = self.__json__()
1237 data = self.__json__()
1238 return data
1238 return data
1239
1239
1240
1240
1241 class UserLog(Base, BaseModel):
1241 class UserLog(Base, BaseModel):
1242 __tablename__ = 'user_logs'
1242 __tablename__ = 'user_logs'
1243 __table_args__ = (
1243 __table_args__ = (
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1246 )
1246 )
1247 VERSION_1 = 'v1'
1247 VERSION_1 = 'v1'
1248 VERSION_2 = 'v2'
1248 VERSION_2 = 'v2'
1249 VERSIONS = [VERSION_1, VERSION_2]
1249 VERSIONS = [VERSION_1, VERSION_2]
1250
1250
1251 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1251 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1253 username = Column("username", String(255), nullable=True, unique=None, default=None)
1253 username = Column("username", String(255), nullable=True, unique=None, default=None)
1254 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1254 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1255 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1255 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1256 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1256 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1257 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1257 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1258 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1258 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1259
1259
1260 version = Column("version", String(255), nullable=True, default=VERSION_1)
1260 version = Column("version", String(255), nullable=True, default=VERSION_1)
1261 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1261 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1262 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1262 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1263
1263
1264 def __unicode__(self):
1264 def __unicode__(self):
1265 return u"<%s('id:%s:%s')>" % (
1265 return u"<%s('id:%s:%s')>" % (
1266 self.__class__.__name__, self.repository_name, self.action)
1266 self.__class__.__name__, self.repository_name, self.action)
1267
1267
1268 def __json__(self):
1268 def __json__(self):
1269 return {
1269 return {
1270 'user_id': self.user_id,
1270 'user_id': self.user_id,
1271 'username': self.username,
1271 'username': self.username,
1272 'repository_id': self.repository_id,
1272 'repository_id': self.repository_id,
1273 'repository_name': self.repository_name,
1273 'repository_name': self.repository_name,
1274 'user_ip': self.user_ip,
1274 'user_ip': self.user_ip,
1275 'action_date': self.action_date,
1275 'action_date': self.action_date,
1276 'action': self.action,
1276 'action': self.action,
1277 }
1277 }
1278
1278
1279 @hybrid_property
1279 @hybrid_property
1280 def entry_id(self):
1280 def entry_id(self):
1281 return self.user_log_id
1281 return self.user_log_id
1282
1282
1283 @property
1283 @property
1284 def action_as_day(self):
1284 def action_as_day(self):
1285 return datetime.date(*self.action_date.timetuple()[:3])
1285 return datetime.date(*self.action_date.timetuple()[:3])
1286
1286
1287 user = relationship('User')
1287 user = relationship('User')
1288 repository = relationship('Repository', cascade='')
1288 repository = relationship('Repository', cascade='')
1289
1289
1290
1290
1291 class UserGroup(Base, BaseModel):
1291 class UserGroup(Base, BaseModel):
1292 __tablename__ = 'users_groups'
1292 __tablename__ = 'users_groups'
1293 __table_args__ = (
1293 __table_args__ = (
1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1295 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1295 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1296 )
1296 )
1297
1297
1298 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1298 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1299 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1299 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1300 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1300 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1301 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1301 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1302 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1302 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1304 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1304 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1305 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1305 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1306
1306
1307 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1307 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1308 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1308 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1309 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1309 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1310 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1310 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1311 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1311 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1312 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1312 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1313
1313
1314 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1314 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1315 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1315 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1316
1316
1317 @classmethod
1317 @classmethod
1318 def _load_group_data(cls, column):
1318 def _load_group_data(cls, column):
1319 if not column:
1319 if not column:
1320 return {}
1320 return {}
1321
1321
1322 try:
1322 try:
1323 return json.loads(column) or {}
1323 return json.loads(column) or {}
1324 except TypeError:
1324 except TypeError:
1325 return {}
1325 return {}
1326
1326
1327 @hybrid_property
1327 @hybrid_property
1328 def description_safe(self):
1328 def description_safe(self):
1329 from rhodecode.lib import helpers as h
1329 from rhodecode.lib import helpers as h
1330 return h.escape(self.user_group_description)
1330 return h.escape(self.user_group_description)
1331
1331
1332 @hybrid_property
1332 @hybrid_property
1333 def group_data(self):
1333 def group_data(self):
1334 return self._load_group_data(self._group_data)
1334 return self._load_group_data(self._group_data)
1335
1335
1336 @group_data.expression
1336 @group_data.expression
1337 def group_data(self, **kwargs):
1337 def group_data(self, **kwargs):
1338 return self._group_data
1338 return self._group_data
1339
1339
1340 @group_data.setter
1340 @group_data.setter
1341 def group_data(self, val):
1341 def group_data(self, val):
1342 try:
1342 try:
1343 self._group_data = json.dumps(val)
1343 self._group_data = json.dumps(val)
1344 except Exception:
1344 except Exception:
1345 log.error(traceback.format_exc())
1345 log.error(traceback.format_exc())
1346
1346
1347 @classmethod
1347 @classmethod
1348 def _load_sync(cls, group_data):
1348 def _load_sync(cls, group_data):
1349 if group_data:
1349 if group_data:
1350 return group_data.get('extern_type')
1350 return group_data.get('extern_type')
1351
1351
1352 @property
1352 @property
1353 def sync(self):
1353 def sync(self):
1354 return self._load_sync(self.group_data)
1354 return self._load_sync(self.group_data)
1355
1355
1356 def __unicode__(self):
1356 def __unicode__(self):
1357 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1357 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1358 self.users_group_id,
1358 self.users_group_id,
1359 self.users_group_name)
1359 self.users_group_name)
1360
1360
1361 @classmethod
1361 @classmethod
1362 def get_by_group_name(cls, group_name, cache=False,
1362 def get_by_group_name(cls, group_name, cache=False,
1363 case_insensitive=False):
1363 case_insensitive=False):
1364 if case_insensitive:
1364 if case_insensitive:
1365 q = cls.query().filter(func.lower(cls.users_group_name) ==
1365 q = cls.query().filter(func.lower(cls.users_group_name) ==
1366 func.lower(group_name))
1366 func.lower(group_name))
1367
1367
1368 else:
1368 else:
1369 q = cls.query().filter(cls.users_group_name == group_name)
1369 q = cls.query().filter(cls.users_group_name == group_name)
1370 if cache:
1370 if cache:
1371 q = q.options(
1371 q = q.options(
1372 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1372 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1373 return q.scalar()
1373 return q.scalar()
1374
1374
1375 @classmethod
1375 @classmethod
1376 def get(cls, user_group_id, cache=False):
1376 def get(cls, user_group_id, cache=False):
1377 if not user_group_id:
1377 if not user_group_id:
1378 return
1378 return
1379
1379
1380 user_group = cls.query()
1380 user_group = cls.query()
1381 if cache:
1381 if cache:
1382 user_group = user_group.options(
1382 user_group = user_group.options(
1383 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1383 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1384 return user_group.get(user_group_id)
1384 return user_group.get(user_group_id)
1385
1385
1386 def permissions(self, with_admins=True, with_owner=True):
1386 def permissions(self, with_admins=True, with_owner=True):
1387 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1387 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1388 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1388 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1389 joinedload(UserUserGroupToPerm.user),
1389 joinedload(UserUserGroupToPerm.user),
1390 joinedload(UserUserGroupToPerm.permission),)
1390 joinedload(UserUserGroupToPerm.permission),)
1391
1391
1392 # get owners and admins and permissions. We do a trick of re-writing
1392 # get owners and admins and permissions. We do a trick of re-writing
1393 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1393 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1394 # has a global reference and changing one object propagates to all
1394 # has a global reference and changing one object propagates to all
1395 # others. This means if admin is also an owner admin_row that change
1395 # others. This means if admin is also an owner admin_row that change
1396 # would propagate to both objects
1396 # would propagate to both objects
1397 perm_rows = []
1397 perm_rows = []
1398 for _usr in q.all():
1398 for _usr in q.all():
1399 usr = AttributeDict(_usr.user.get_dict())
1399 usr = AttributeDict(_usr.user.get_dict())
1400 usr.permission = _usr.permission.permission_name
1400 usr.permission = _usr.permission.permission_name
1401 perm_rows.append(usr)
1401 perm_rows.append(usr)
1402
1402
1403 # filter the perm rows by 'default' first and then sort them by
1403 # filter the perm rows by 'default' first and then sort them by
1404 # admin,write,read,none permissions sorted again alphabetically in
1404 # admin,write,read,none permissions sorted again alphabetically in
1405 # each group
1405 # each group
1406 perm_rows = sorted(perm_rows, key=display_user_sort)
1406 perm_rows = sorted(perm_rows, key=display_user_sort)
1407
1407
1408 _admin_perm = 'usergroup.admin'
1408 _admin_perm = 'usergroup.admin'
1409 owner_row = []
1409 owner_row = []
1410 if with_owner:
1410 if with_owner:
1411 usr = AttributeDict(self.user.get_dict())
1411 usr = AttributeDict(self.user.get_dict())
1412 usr.owner_row = True
1412 usr.owner_row = True
1413 usr.permission = _admin_perm
1413 usr.permission = _admin_perm
1414 owner_row.append(usr)
1414 owner_row.append(usr)
1415
1415
1416 super_admin_rows = []
1416 super_admin_rows = []
1417 if with_admins:
1417 if with_admins:
1418 for usr in User.get_all_super_admins():
1418 for usr in User.get_all_super_admins():
1419 # if this admin is also owner, don't double the record
1419 # if this admin is also owner, don't double the record
1420 if usr.user_id == owner_row[0].user_id:
1420 if usr.user_id == owner_row[0].user_id:
1421 owner_row[0].admin_row = True
1421 owner_row[0].admin_row = True
1422 else:
1422 else:
1423 usr = AttributeDict(usr.get_dict())
1423 usr = AttributeDict(usr.get_dict())
1424 usr.admin_row = True
1424 usr.admin_row = True
1425 usr.permission = _admin_perm
1425 usr.permission = _admin_perm
1426 super_admin_rows.append(usr)
1426 super_admin_rows.append(usr)
1427
1427
1428 return super_admin_rows + owner_row + perm_rows
1428 return super_admin_rows + owner_row + perm_rows
1429
1429
1430 def permission_user_groups(self):
1430 def permission_user_groups(self):
1431 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1431 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1432 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1432 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1433 joinedload(UserGroupUserGroupToPerm.target_user_group),
1433 joinedload(UserGroupUserGroupToPerm.target_user_group),
1434 joinedload(UserGroupUserGroupToPerm.permission),)
1434 joinedload(UserGroupUserGroupToPerm.permission),)
1435
1435
1436 perm_rows = []
1436 perm_rows = []
1437 for _user_group in q.all():
1437 for _user_group in q.all():
1438 usr = AttributeDict(_user_group.user_group.get_dict())
1438 usr = AttributeDict(_user_group.user_group.get_dict())
1439 usr.permission = _user_group.permission.permission_name
1439 usr.permission = _user_group.permission.permission_name
1440 perm_rows.append(usr)
1440 perm_rows.append(usr)
1441
1441
1442 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1442 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1443 return perm_rows
1443 return perm_rows
1444
1444
1445 def _get_default_perms(self, user_group, suffix=''):
1445 def _get_default_perms(self, user_group, suffix=''):
1446 from rhodecode.model.permission import PermissionModel
1446 from rhodecode.model.permission import PermissionModel
1447 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1447 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1448
1448
1449 def get_default_perms(self, suffix=''):
1449 def get_default_perms(self, suffix=''):
1450 return self._get_default_perms(self, suffix)
1450 return self._get_default_perms(self, suffix)
1451
1451
1452 def get_api_data(self, with_group_members=True, include_secrets=False):
1452 def get_api_data(self, with_group_members=True, include_secrets=False):
1453 """
1453 """
1454 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1454 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1455 basically forwarded.
1455 basically forwarded.
1456
1456
1457 """
1457 """
1458 user_group = self
1458 user_group = self
1459 data = {
1459 data = {
1460 'users_group_id': user_group.users_group_id,
1460 'users_group_id': user_group.users_group_id,
1461 'group_name': user_group.users_group_name,
1461 'group_name': user_group.users_group_name,
1462 'group_description': user_group.user_group_description,
1462 'group_description': user_group.user_group_description,
1463 'active': user_group.users_group_active,
1463 'active': user_group.users_group_active,
1464 'owner': user_group.user.username,
1464 'owner': user_group.user.username,
1465 'sync': user_group.sync,
1465 'sync': user_group.sync,
1466 'owner_email': user_group.user.email,
1466 'owner_email': user_group.user.email,
1467 }
1467 }
1468
1468
1469 if with_group_members:
1469 if with_group_members:
1470 users = []
1470 users = []
1471 for user in user_group.members:
1471 for user in user_group.members:
1472 user = user.user
1472 user = user.user
1473 users.append(user.get_api_data(include_secrets=include_secrets))
1473 users.append(user.get_api_data(include_secrets=include_secrets))
1474 data['users'] = users
1474 data['users'] = users
1475
1475
1476 return data
1476 return data
1477
1477
1478
1478
1479 class UserGroupMember(Base, BaseModel):
1479 class UserGroupMember(Base, BaseModel):
1480 __tablename__ = 'users_groups_members'
1480 __tablename__ = 'users_groups_members'
1481 __table_args__ = (
1481 __table_args__ = (
1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1484 )
1484 )
1485
1485
1486 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1486 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1487 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1487 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1488 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1488 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1489
1489
1490 user = relationship('User', lazy='joined')
1490 user = relationship('User', lazy='joined')
1491 users_group = relationship('UserGroup')
1491 users_group = relationship('UserGroup')
1492
1492
1493 def __init__(self, gr_id='', u_id=''):
1493 def __init__(self, gr_id='', u_id=''):
1494 self.users_group_id = gr_id
1494 self.users_group_id = gr_id
1495 self.user_id = u_id
1495 self.user_id = u_id
1496
1496
1497
1497
1498 class RepositoryField(Base, BaseModel):
1498 class RepositoryField(Base, BaseModel):
1499 __tablename__ = 'repositories_fields'
1499 __tablename__ = 'repositories_fields'
1500 __table_args__ = (
1500 __table_args__ = (
1501 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1501 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1504 )
1504 )
1505 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1505 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1506
1506
1507 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1507 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1508 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1508 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1509 field_key = Column("field_key", String(250))
1509 field_key = Column("field_key", String(250))
1510 field_label = Column("field_label", String(1024), nullable=False)
1510 field_label = Column("field_label", String(1024), nullable=False)
1511 field_value = Column("field_value", String(10000), nullable=False)
1511 field_value = Column("field_value", String(10000), nullable=False)
1512 field_desc = Column("field_desc", String(1024), nullable=False)
1512 field_desc = Column("field_desc", String(1024), nullable=False)
1513 field_type = Column("field_type", String(255), nullable=False, unique=None)
1513 field_type = Column("field_type", String(255), nullable=False, unique=None)
1514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1515
1515
1516 repository = relationship('Repository')
1516 repository = relationship('Repository')
1517
1517
1518 @property
1518 @property
1519 def field_key_prefixed(self):
1519 def field_key_prefixed(self):
1520 return 'ex_%s' % self.field_key
1520 return 'ex_%s' % self.field_key
1521
1521
1522 @classmethod
1522 @classmethod
1523 def un_prefix_key(cls, key):
1523 def un_prefix_key(cls, key):
1524 if key.startswith(cls.PREFIX):
1524 if key.startswith(cls.PREFIX):
1525 return key[len(cls.PREFIX):]
1525 return key[len(cls.PREFIX):]
1526 return key
1526 return key
1527
1527
1528 @classmethod
1528 @classmethod
1529 def get_by_key_name(cls, key, repo):
1529 def get_by_key_name(cls, key, repo):
1530 row = cls.query()\
1530 row = cls.query()\
1531 .filter(cls.repository == repo)\
1531 .filter(cls.repository == repo)\
1532 .filter(cls.field_key == key).scalar()
1532 .filter(cls.field_key == key).scalar()
1533 return row
1533 return row
1534
1534
1535
1535
1536 class Repository(Base, BaseModel):
1536 class Repository(Base, BaseModel):
1537 __tablename__ = 'repositories'
1537 __tablename__ = 'repositories'
1538 __table_args__ = (
1538 __table_args__ = (
1539 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1539 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1540 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1540 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1541 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1541 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1542 )
1542 )
1543 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1543 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1544 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1544 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1545 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1545 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1546
1546
1547 STATE_CREATED = 'repo_state_created'
1547 STATE_CREATED = 'repo_state_created'
1548 STATE_PENDING = 'repo_state_pending'
1548 STATE_PENDING = 'repo_state_pending'
1549 STATE_ERROR = 'repo_state_error'
1549 STATE_ERROR = 'repo_state_error'
1550
1550
1551 LOCK_AUTOMATIC = 'lock_auto'
1551 LOCK_AUTOMATIC = 'lock_auto'
1552 LOCK_API = 'lock_api'
1552 LOCK_API = 'lock_api'
1553 LOCK_WEB = 'lock_web'
1553 LOCK_WEB = 'lock_web'
1554 LOCK_PULL = 'lock_pull'
1554 LOCK_PULL = 'lock_pull'
1555
1555
1556 NAME_SEP = URL_SEP
1556 NAME_SEP = URL_SEP
1557
1557
1558 repo_id = Column(
1558 repo_id = Column(
1559 "repo_id", Integer(), nullable=False, unique=True, default=None,
1559 "repo_id", Integer(), nullable=False, unique=True, default=None,
1560 primary_key=True)
1560 primary_key=True)
1561 _repo_name = Column(
1561 _repo_name = Column(
1562 "repo_name", Text(), nullable=False, default=None)
1562 "repo_name", Text(), nullable=False, default=None)
1563 _repo_name_hash = Column(
1563 _repo_name_hash = Column(
1564 "repo_name_hash", String(255), nullable=False, unique=True)
1564 "repo_name_hash", String(255), nullable=False, unique=True)
1565 repo_state = Column("repo_state", String(255), nullable=True)
1565 repo_state = Column("repo_state", String(255), nullable=True)
1566
1566
1567 clone_uri = Column(
1567 clone_uri = Column(
1568 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1568 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1569 default=None)
1569 default=None)
1570 push_uri = Column(
1570 push_uri = Column(
1571 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1571 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1572 default=None)
1572 default=None)
1573 repo_type = Column(
1573 repo_type = Column(
1574 "repo_type", String(255), nullable=False, unique=False, default=None)
1574 "repo_type", String(255), nullable=False, unique=False, default=None)
1575 user_id = Column(
1575 user_id = Column(
1576 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1576 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1577 unique=False, default=None)
1577 unique=False, default=None)
1578 private = Column(
1578 private = Column(
1579 "private", Boolean(), nullable=True, unique=None, default=None)
1579 "private", Boolean(), nullable=True, unique=None, default=None)
1580 enable_statistics = Column(
1580 enable_statistics = Column(
1581 "statistics", Boolean(), nullable=True, unique=None, default=True)
1581 "statistics", Boolean(), nullable=True, unique=None, default=True)
1582 enable_downloads = Column(
1582 enable_downloads = Column(
1583 "downloads", Boolean(), nullable=True, unique=None, default=True)
1583 "downloads", Boolean(), nullable=True, unique=None, default=True)
1584 description = Column(
1584 description = Column(
1585 "description", String(10000), nullable=True, unique=None, default=None)
1585 "description", String(10000), nullable=True, unique=None, default=None)
1586 created_on = Column(
1586 created_on = Column(
1587 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1587 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1588 default=datetime.datetime.now)
1588 default=datetime.datetime.now)
1589 updated_on = Column(
1589 updated_on = Column(
1590 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1590 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1591 default=datetime.datetime.now)
1591 default=datetime.datetime.now)
1592 _landing_revision = Column(
1592 _landing_revision = Column(
1593 "landing_revision", String(255), nullable=False, unique=False,
1593 "landing_revision", String(255), nullable=False, unique=False,
1594 default=None)
1594 default=None)
1595 enable_locking = Column(
1595 enable_locking = Column(
1596 "enable_locking", Boolean(), nullable=False, unique=None,
1596 "enable_locking", Boolean(), nullable=False, unique=None,
1597 default=False)
1597 default=False)
1598 _locked = Column(
1598 _locked = Column(
1599 "locked", String(255), nullable=True, unique=False, default=None)
1599 "locked", String(255), nullable=True, unique=False, default=None)
1600 _changeset_cache = Column(
1600 _changeset_cache = Column(
1601 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1601 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1602
1602
1603 fork_id = Column(
1603 fork_id = Column(
1604 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1604 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1605 nullable=True, unique=False, default=None)
1605 nullable=True, unique=False, default=None)
1606 group_id = Column(
1606 group_id = Column(
1607 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1607 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1608 unique=False, default=None)
1608 unique=False, default=None)
1609
1609
1610 user = relationship('User', lazy='joined')
1610 user = relationship('User', lazy='joined')
1611 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1611 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1612 group = relationship('RepoGroup', lazy='joined')
1612 group = relationship('RepoGroup', lazy='joined')
1613 repo_to_perm = relationship(
1613 repo_to_perm = relationship(
1614 'UserRepoToPerm', cascade='all',
1614 'UserRepoToPerm', cascade='all',
1615 order_by='UserRepoToPerm.repo_to_perm_id')
1615 order_by='UserRepoToPerm.repo_to_perm_id')
1616 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1616 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1617 stats = relationship('Statistics', cascade='all', uselist=False)
1617 stats = relationship('Statistics', cascade='all', uselist=False)
1618
1618
1619 followers = relationship(
1619 followers = relationship(
1620 'UserFollowing',
1620 'UserFollowing',
1621 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1621 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1622 cascade='all')
1622 cascade='all')
1623 extra_fields = relationship(
1623 extra_fields = relationship(
1624 'RepositoryField', cascade="all, delete, delete-orphan")
1624 'RepositoryField', cascade="all, delete, delete-orphan")
1625 logs = relationship('UserLog')
1625 logs = relationship('UserLog')
1626 comments = relationship(
1626 comments = relationship(
1627 'ChangesetComment', cascade="all, delete, delete-orphan")
1627 'ChangesetComment', cascade="all, delete, delete-orphan")
1628 pull_requests_source = relationship(
1628 pull_requests_source = relationship(
1629 'PullRequest',
1629 'PullRequest',
1630 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1630 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1631 cascade="all, delete, delete-orphan")
1631 cascade="all, delete, delete-orphan")
1632 pull_requests_target = relationship(
1632 pull_requests_target = relationship(
1633 'PullRequest',
1633 'PullRequest',
1634 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1634 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1635 cascade="all, delete, delete-orphan")
1635 cascade="all, delete, delete-orphan")
1636 ui = relationship('RepoRhodeCodeUi', cascade="all")
1636 ui = relationship('RepoRhodeCodeUi', cascade="all")
1637 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1637 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1638 integrations = relationship('Integration',
1638 integrations = relationship('Integration',
1639 cascade="all, delete, delete-orphan")
1639 cascade="all, delete, delete-orphan")
1640
1640
1641 scoped_tokens = relationship('UserApiKeys', cascade="all")
1641 scoped_tokens = relationship('UserApiKeys', cascade="all")
1642
1642
1643 def __unicode__(self):
1643 def __unicode__(self):
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1645 safe_unicode(self.repo_name))
1645 safe_unicode(self.repo_name))
1646
1646
1647 @hybrid_property
1647 @hybrid_property
1648 def description_safe(self):
1648 def description_safe(self):
1649 from rhodecode.lib import helpers as h
1649 from rhodecode.lib import helpers as h
1650 return h.escape(self.description)
1650 return h.escape(self.description)
1651
1651
1652 @hybrid_property
1652 @hybrid_property
1653 def landing_rev(self):
1653 def landing_rev(self):
1654 # always should return [rev_type, rev]
1654 # always should return [rev_type, rev]
1655 if self._landing_revision:
1655 if self._landing_revision:
1656 _rev_info = self._landing_revision.split(':')
1656 _rev_info = self._landing_revision.split(':')
1657 if len(_rev_info) < 2:
1657 if len(_rev_info) < 2:
1658 _rev_info.insert(0, 'rev')
1658 _rev_info.insert(0, 'rev')
1659 return [_rev_info[0], _rev_info[1]]
1659 return [_rev_info[0], _rev_info[1]]
1660 return [None, None]
1660 return [None, None]
1661
1661
1662 @landing_rev.setter
1662 @landing_rev.setter
1663 def landing_rev(self, val):
1663 def landing_rev(self, val):
1664 if ':' not in val:
1664 if ':' not in val:
1665 raise ValueError('value must be delimited with `:` and consist '
1665 raise ValueError('value must be delimited with `:` and consist '
1666 'of <rev_type>:<rev>, got %s instead' % val)
1666 'of <rev_type>:<rev>, got %s instead' % val)
1667 self._landing_revision = val
1667 self._landing_revision = val
1668
1668
1669 @hybrid_property
1669 @hybrid_property
1670 def locked(self):
1670 def locked(self):
1671 if self._locked:
1671 if self._locked:
1672 user_id, timelocked, reason = self._locked.split(':')
1672 user_id, timelocked, reason = self._locked.split(':')
1673 lock_values = int(user_id), timelocked, reason
1673 lock_values = int(user_id), timelocked, reason
1674 else:
1674 else:
1675 lock_values = [None, None, None]
1675 lock_values = [None, None, None]
1676 return lock_values
1676 return lock_values
1677
1677
1678 @locked.setter
1678 @locked.setter
1679 def locked(self, val):
1679 def locked(self, val):
1680 if val and isinstance(val, (list, tuple)):
1680 if val and isinstance(val, (list, tuple)):
1681 self._locked = ':'.join(map(str, val))
1681 self._locked = ':'.join(map(str, val))
1682 else:
1682 else:
1683 self._locked = None
1683 self._locked = None
1684
1684
1685 @hybrid_property
1685 @hybrid_property
1686 def changeset_cache(self):
1686 def changeset_cache(self):
1687 from rhodecode.lib.vcs.backends.base import EmptyCommit
1687 from rhodecode.lib.vcs.backends.base import EmptyCommit
1688 dummy = EmptyCommit().__json__()
1688 dummy = EmptyCommit().__json__()
1689 if not self._changeset_cache:
1689 if not self._changeset_cache:
1690 return dummy
1690 return dummy
1691 try:
1691 try:
1692 return json.loads(self._changeset_cache)
1692 return json.loads(self._changeset_cache)
1693 except TypeError:
1693 except TypeError:
1694 return dummy
1694 return dummy
1695 except Exception:
1695 except Exception:
1696 log.error(traceback.format_exc())
1696 log.error(traceback.format_exc())
1697 return dummy
1697 return dummy
1698
1698
1699 @changeset_cache.setter
1699 @changeset_cache.setter
1700 def changeset_cache(self, val):
1700 def changeset_cache(self, val):
1701 try:
1701 try:
1702 self._changeset_cache = json.dumps(val)
1702 self._changeset_cache = json.dumps(val)
1703 except Exception:
1703 except Exception:
1704 log.error(traceback.format_exc())
1704 log.error(traceback.format_exc())
1705
1705
1706 @hybrid_property
1706 @hybrid_property
1707 def repo_name(self):
1707 def repo_name(self):
1708 return self._repo_name
1708 return self._repo_name
1709
1709
1710 @repo_name.setter
1710 @repo_name.setter
1711 def repo_name(self, value):
1711 def repo_name(self, value):
1712 self._repo_name = value
1712 self._repo_name = value
1713 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1713 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1714
1714
1715 @classmethod
1715 @classmethod
1716 def normalize_repo_name(cls, repo_name):
1716 def normalize_repo_name(cls, repo_name):
1717 """
1717 """
1718 Normalizes os specific repo_name to the format internally stored inside
1718 Normalizes os specific repo_name to the format internally stored inside
1719 database using URL_SEP
1719 database using URL_SEP
1720
1720
1721 :param cls:
1721 :param cls:
1722 :param repo_name:
1722 :param repo_name:
1723 """
1723 """
1724 return cls.NAME_SEP.join(repo_name.split(os.sep))
1724 return cls.NAME_SEP.join(repo_name.split(os.sep))
1725
1725
1726 @classmethod
1726 @classmethod
1727 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1727 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1728 session = Session()
1728 session = Session()
1729 q = session.query(cls).filter(cls.repo_name == repo_name)
1729 q = session.query(cls).filter(cls.repo_name == repo_name)
1730
1730
1731 if cache:
1731 if cache:
1732 if identity_cache:
1732 if identity_cache:
1733 val = cls.identity_cache(session, 'repo_name', repo_name)
1733 val = cls.identity_cache(session, 'repo_name', repo_name)
1734 if val:
1734 if val:
1735 return val
1735 return val
1736 else:
1736 else:
1737 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1737 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1738 q = q.options(
1738 q = q.options(
1739 FromCache("sql_cache_short", cache_key))
1739 FromCache("sql_cache_short", cache_key))
1740
1740
1741 return q.scalar()
1741 return q.scalar()
1742
1742
1743 @classmethod
1743 @classmethod
1744 def get_by_id_or_repo_name(cls, repoid):
1744 def get_by_id_or_repo_name(cls, repoid):
1745 if isinstance(repoid, (int, long)):
1745 if isinstance(repoid, (int, long)):
1746 try:
1746 try:
1747 repo = cls.get(repoid)
1747 repo = cls.get(repoid)
1748 except ValueError:
1748 except ValueError:
1749 repo = None
1749 repo = None
1750 else:
1750 else:
1751 repo = cls.get_by_repo_name(repoid)
1751 repo = cls.get_by_repo_name(repoid)
1752 return repo
1752 return repo
1753
1753
1754 @classmethod
1754 @classmethod
1755 def get_by_full_path(cls, repo_full_path):
1755 def get_by_full_path(cls, repo_full_path):
1756 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1756 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1757 repo_name = cls.normalize_repo_name(repo_name)
1757 repo_name = cls.normalize_repo_name(repo_name)
1758 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1758 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1759
1759
1760 @classmethod
1760 @classmethod
1761 def get_repo_forks(cls, repo_id):
1761 def get_repo_forks(cls, repo_id):
1762 return cls.query().filter(Repository.fork_id == repo_id)
1762 return cls.query().filter(Repository.fork_id == repo_id)
1763
1763
1764 @classmethod
1764 @classmethod
1765 def base_path(cls):
1765 def base_path(cls):
1766 """
1766 """
1767 Returns base path when all repos are stored
1767 Returns base path when all repos are stored
1768
1768
1769 :param cls:
1769 :param cls:
1770 """
1770 """
1771 q = Session().query(RhodeCodeUi)\
1771 q = Session().query(RhodeCodeUi)\
1772 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1772 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1773 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1773 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1774 return q.one().ui_value
1774 return q.one().ui_value
1775
1775
1776 @classmethod
1776 @classmethod
1777 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1777 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1778 case_insensitive=True):
1778 case_insensitive=True):
1779 q = Repository.query()
1779 q = Repository.query()
1780
1780
1781 if not isinstance(user_id, Optional):
1781 if not isinstance(user_id, Optional):
1782 q = q.filter(Repository.user_id == user_id)
1782 q = q.filter(Repository.user_id == user_id)
1783
1783
1784 if not isinstance(group_id, Optional):
1784 if not isinstance(group_id, Optional):
1785 q = q.filter(Repository.group_id == group_id)
1785 q = q.filter(Repository.group_id == group_id)
1786
1786
1787 if case_insensitive:
1787 if case_insensitive:
1788 q = q.order_by(func.lower(Repository.repo_name))
1788 q = q.order_by(func.lower(Repository.repo_name))
1789 else:
1789 else:
1790 q = q.order_by(Repository.repo_name)
1790 q = q.order_by(Repository.repo_name)
1791 return q.all()
1791 return q.all()
1792
1792
1793 @property
1793 @property
1794 def forks(self):
1794 def forks(self):
1795 """
1795 """
1796 Return forks of this repo
1796 Return forks of this repo
1797 """
1797 """
1798 return Repository.get_repo_forks(self.repo_id)
1798 return Repository.get_repo_forks(self.repo_id)
1799
1799
1800 @property
1800 @property
1801 def parent(self):
1801 def parent(self):
1802 """
1802 """
1803 Returns fork parent
1803 Returns fork parent
1804 """
1804 """
1805 return self.fork
1805 return self.fork
1806
1806
1807 @property
1807 @property
1808 def just_name(self):
1808 def just_name(self):
1809 return self.repo_name.split(self.NAME_SEP)[-1]
1809 return self.repo_name.split(self.NAME_SEP)[-1]
1810
1810
1811 @property
1811 @property
1812 def groups_with_parents(self):
1812 def groups_with_parents(self):
1813 groups = []
1813 groups = []
1814 if self.group is None:
1814 if self.group is None:
1815 return groups
1815 return groups
1816
1816
1817 cur_gr = self.group
1817 cur_gr = self.group
1818 groups.insert(0, cur_gr)
1818 groups.insert(0, cur_gr)
1819 while 1:
1819 while 1:
1820 gr = getattr(cur_gr, 'parent_group', None)
1820 gr = getattr(cur_gr, 'parent_group', None)
1821 cur_gr = cur_gr.parent_group
1821 cur_gr = cur_gr.parent_group
1822 if gr is None:
1822 if gr is None:
1823 break
1823 break
1824 groups.insert(0, gr)
1824 groups.insert(0, gr)
1825
1825
1826 return groups
1826 return groups
1827
1827
1828 @property
1828 @property
1829 def groups_and_repo(self):
1829 def groups_and_repo(self):
1830 return self.groups_with_parents, self
1830 return self.groups_with_parents, self
1831
1831
1832 @LazyProperty
1832 @LazyProperty
1833 def repo_path(self):
1833 def repo_path(self):
1834 """
1834 """
1835 Returns base full path for that repository means where it actually
1835 Returns base full path for that repository means where it actually
1836 exists on a filesystem
1836 exists on a filesystem
1837 """
1837 """
1838 q = Session().query(RhodeCodeUi).filter(
1838 q = Session().query(RhodeCodeUi).filter(
1839 RhodeCodeUi.ui_key == self.NAME_SEP)
1839 RhodeCodeUi.ui_key == self.NAME_SEP)
1840 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1840 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1841 return q.one().ui_value
1841 return q.one().ui_value
1842
1842
1843 @property
1843 @property
1844 def repo_full_path(self):
1844 def repo_full_path(self):
1845 p = [self.repo_path]
1845 p = [self.repo_path]
1846 # we need to split the name by / since this is how we store the
1846 # we need to split the name by / since this is how we store the
1847 # names in the database, but that eventually needs to be converted
1847 # names in the database, but that eventually needs to be converted
1848 # into a valid system path
1848 # into a valid system path
1849 p += self.repo_name.split(self.NAME_SEP)
1849 p += self.repo_name.split(self.NAME_SEP)
1850 return os.path.join(*map(safe_unicode, p))
1850 return os.path.join(*map(safe_unicode, p))
1851
1851
1852 @property
1852 @property
1853 def cache_keys(self):
1853 def cache_keys(self):
1854 """
1854 """
1855 Returns associated cache keys for that repo
1855 Returns associated cache keys for that repo
1856 """
1856 """
1857 return CacheKey.query()\
1857 return CacheKey.query()\
1858 .filter(CacheKey.cache_args == self.repo_name)\
1858 .filter(CacheKey.cache_args == self.repo_name)\
1859 .order_by(CacheKey.cache_key)\
1859 .order_by(CacheKey.cache_key)\
1860 .all()
1860 .all()
1861
1861
1862 @property
1862 @property
1863 def cached_diffs_relative_dir(self):
1863 def cached_diffs_relative_dir(self):
1864 """
1864 """
1865 Return a relative to the repository store path of cached diffs
1865 Return a relative to the repository store path of cached diffs
1866 used for safe display for users, who shouldn't know the absolute store
1866 used for safe display for users, who shouldn't know the absolute store
1867 path
1867 path
1868 """
1868 """
1869 return os.path.join(
1869 return os.path.join(
1870 os.path.dirname(self.repo_name),
1870 os.path.dirname(self.repo_name),
1871 self.cached_diffs_dir.split(os.path.sep)[-1])
1871 self.cached_diffs_dir.split(os.path.sep)[-1])
1872
1872
1873 @property
1873 @property
1874 def cached_diffs_dir(self):
1874 def cached_diffs_dir(self):
1875 path = self.repo_full_path
1875 path = self.repo_full_path
1876 return os.path.join(
1876 return os.path.join(
1877 os.path.dirname(path),
1877 os.path.dirname(path),
1878 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1878 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1879
1879
1880 def cached_diffs(self):
1880 def cached_diffs(self):
1881 diff_cache_dir = self.cached_diffs_dir
1881 diff_cache_dir = self.cached_diffs_dir
1882 if os.path.isdir(diff_cache_dir):
1882 if os.path.isdir(diff_cache_dir):
1883 return os.listdir(diff_cache_dir)
1883 return os.listdir(diff_cache_dir)
1884 return []
1884 return []
1885
1885
1886 def get_new_name(self, repo_name):
1886 def get_new_name(self, repo_name):
1887 """
1887 """
1888 returns new full repository name based on assigned group and new new
1888 returns new full repository name based on assigned group and new new
1889
1889
1890 :param group_name:
1890 :param group_name:
1891 """
1891 """
1892 path_prefix = self.group.full_path_splitted if self.group else []
1892 path_prefix = self.group.full_path_splitted if self.group else []
1893 return self.NAME_SEP.join(path_prefix + [repo_name])
1893 return self.NAME_SEP.join(path_prefix + [repo_name])
1894
1894
1895 @property
1895 @property
1896 def _config(self):
1896 def _config(self):
1897 """
1897 """
1898 Returns db based config object.
1898 Returns db based config object.
1899 """
1899 """
1900 from rhodecode.lib.utils import make_db_config
1900 from rhodecode.lib.utils import make_db_config
1901 return make_db_config(clear_session=False, repo=self)
1901 return make_db_config(clear_session=False, repo=self)
1902
1902
1903 def permissions(self, with_admins=True, with_owner=True):
1903 def permissions(self, with_admins=True, with_owner=True):
1904 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1904 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1905 q = q.options(joinedload(UserRepoToPerm.repository),
1905 q = q.options(joinedload(UserRepoToPerm.repository),
1906 joinedload(UserRepoToPerm.user),
1906 joinedload(UserRepoToPerm.user),
1907 joinedload(UserRepoToPerm.permission),)
1907 joinedload(UserRepoToPerm.permission),)
1908
1908
1909 # get owners and admins and permissions. We do a trick of re-writing
1909 # get owners and admins and permissions. We do a trick of re-writing
1910 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1910 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1911 # has a global reference and changing one object propagates to all
1911 # has a global reference and changing one object propagates to all
1912 # others. This means if admin is also an owner admin_row that change
1912 # others. This means if admin is also an owner admin_row that change
1913 # would propagate to both objects
1913 # would propagate to both objects
1914 perm_rows = []
1914 perm_rows = []
1915 for _usr in q.all():
1915 for _usr in q.all():
1916 usr = AttributeDict(_usr.user.get_dict())
1916 usr = AttributeDict(_usr.user.get_dict())
1917 usr.permission = _usr.permission.permission_name
1917 usr.permission = _usr.permission.permission_name
1918 usr.permission_id = _usr.repo_to_perm_id
1918 usr.permission_id = _usr.repo_to_perm_id
1919 perm_rows.append(usr)
1919 perm_rows.append(usr)
1920
1920
1921 # filter the perm rows by 'default' first and then sort them by
1921 # filter the perm rows by 'default' first and then sort them by
1922 # admin,write,read,none permissions sorted again alphabetically in
1922 # admin,write,read,none permissions sorted again alphabetically in
1923 # each group
1923 # each group
1924 perm_rows = sorted(perm_rows, key=display_user_sort)
1924 perm_rows = sorted(perm_rows, key=display_user_sort)
1925
1925
1926 _admin_perm = 'repository.admin'
1926 _admin_perm = 'repository.admin'
1927 owner_row = []
1927 owner_row = []
1928 if with_owner:
1928 if with_owner:
1929 usr = AttributeDict(self.user.get_dict())
1929 usr = AttributeDict(self.user.get_dict())
1930 usr.owner_row = True
1930 usr.owner_row = True
1931 usr.permission = _admin_perm
1931 usr.permission = _admin_perm
1932 usr.permission_id = None
1932 usr.permission_id = None
1933 owner_row.append(usr)
1933 owner_row.append(usr)
1934
1934
1935 super_admin_rows = []
1935 super_admin_rows = []
1936 if with_admins:
1936 if with_admins:
1937 for usr in User.get_all_super_admins():
1937 for usr in User.get_all_super_admins():
1938 # if this admin is also owner, don't double the record
1938 # if this admin is also owner, don't double the record
1939 if usr.user_id == owner_row[0].user_id:
1939 if usr.user_id == owner_row[0].user_id:
1940 owner_row[0].admin_row = True
1940 owner_row[0].admin_row = True
1941 else:
1941 else:
1942 usr = AttributeDict(usr.get_dict())
1942 usr = AttributeDict(usr.get_dict())
1943 usr.admin_row = True
1943 usr.admin_row = True
1944 usr.permission = _admin_perm
1944 usr.permission = _admin_perm
1945 usr.permission_id = None
1945 usr.permission_id = None
1946 super_admin_rows.append(usr)
1946 super_admin_rows.append(usr)
1947
1947
1948 return super_admin_rows + owner_row + perm_rows
1948 return super_admin_rows + owner_row + perm_rows
1949
1949
1950 def permission_user_groups(self):
1950 def permission_user_groups(self):
1951 q = UserGroupRepoToPerm.query().filter(
1951 q = UserGroupRepoToPerm.query().filter(
1952 UserGroupRepoToPerm.repository == self)
1952 UserGroupRepoToPerm.repository == self)
1953 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1953 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1954 joinedload(UserGroupRepoToPerm.users_group),
1954 joinedload(UserGroupRepoToPerm.users_group),
1955 joinedload(UserGroupRepoToPerm.permission),)
1955 joinedload(UserGroupRepoToPerm.permission),)
1956
1956
1957 perm_rows = []
1957 perm_rows = []
1958 for _user_group in q.all():
1958 for _user_group in q.all():
1959 usr = AttributeDict(_user_group.users_group.get_dict())
1959 usr = AttributeDict(_user_group.users_group.get_dict())
1960 usr.permission = _user_group.permission.permission_name
1960 usr.permission = _user_group.permission.permission_name
1961 perm_rows.append(usr)
1961 perm_rows.append(usr)
1962
1962
1963 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1963 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1964 return perm_rows
1964 return perm_rows
1965
1965
1966 def get_api_data(self, include_secrets=False):
1966 def get_api_data(self, include_secrets=False):
1967 """
1967 """
1968 Common function for generating repo api data
1968 Common function for generating repo api data
1969
1969
1970 :param include_secrets: See :meth:`User.get_api_data`.
1970 :param include_secrets: See :meth:`User.get_api_data`.
1971
1971
1972 """
1972 """
1973 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1973 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1974 # move this methods on models level.
1974 # move this methods on models level.
1975 from rhodecode.model.settings import SettingsModel
1975 from rhodecode.model.settings import SettingsModel
1976 from rhodecode.model.repo import RepoModel
1976 from rhodecode.model.repo import RepoModel
1977
1977
1978 repo = self
1978 repo = self
1979 _user_id, _time, _reason = self.locked
1979 _user_id, _time, _reason = self.locked
1980
1980
1981 data = {
1981 data = {
1982 'repo_id': repo.repo_id,
1982 'repo_id': repo.repo_id,
1983 'repo_name': repo.repo_name,
1983 'repo_name': repo.repo_name,
1984 'repo_type': repo.repo_type,
1984 'repo_type': repo.repo_type,
1985 'clone_uri': repo.clone_uri or '',
1985 'clone_uri': repo.clone_uri or '',
1986 'push_uri': repo.push_uri or '',
1986 'push_uri': repo.push_uri or '',
1987 'url': RepoModel().get_url(self),
1987 'url': RepoModel().get_url(self),
1988 'private': repo.private,
1988 'private': repo.private,
1989 'created_on': repo.created_on,
1989 'created_on': repo.created_on,
1990 'description': repo.description_safe,
1990 'description': repo.description_safe,
1991 'landing_rev': repo.landing_rev,
1991 'landing_rev': repo.landing_rev,
1992 'owner': repo.user.username,
1992 'owner': repo.user.username,
1993 'fork_of': repo.fork.repo_name if repo.fork else None,
1993 'fork_of': repo.fork.repo_name if repo.fork else None,
1994 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1994 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1995 'enable_statistics': repo.enable_statistics,
1995 'enable_statistics': repo.enable_statistics,
1996 'enable_locking': repo.enable_locking,
1996 'enable_locking': repo.enable_locking,
1997 'enable_downloads': repo.enable_downloads,
1997 'enable_downloads': repo.enable_downloads,
1998 'last_changeset': repo.changeset_cache,
1998 'last_changeset': repo.changeset_cache,
1999 'locked_by': User.get(_user_id).get_api_data(
1999 'locked_by': User.get(_user_id).get_api_data(
2000 include_secrets=include_secrets) if _user_id else None,
2000 include_secrets=include_secrets) if _user_id else None,
2001 'locked_date': time_to_datetime(_time) if _time else None,
2001 'locked_date': time_to_datetime(_time) if _time else None,
2002 'lock_reason': _reason if _reason else None,
2002 'lock_reason': _reason if _reason else None,
2003 }
2003 }
2004
2004
2005 # TODO: mikhail: should be per-repo settings here
2005 # TODO: mikhail: should be per-repo settings here
2006 rc_config = SettingsModel().get_all_settings()
2006 rc_config = SettingsModel().get_all_settings()
2007 repository_fields = str2bool(
2007 repository_fields = str2bool(
2008 rc_config.get('rhodecode_repository_fields'))
2008 rc_config.get('rhodecode_repository_fields'))
2009 if repository_fields:
2009 if repository_fields:
2010 for f in self.extra_fields:
2010 for f in self.extra_fields:
2011 data[f.field_key_prefixed] = f.field_value
2011 data[f.field_key_prefixed] = f.field_value
2012
2012
2013 return data
2013 return data
2014
2014
2015 @classmethod
2015 @classmethod
2016 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2016 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2017 if not lock_time:
2017 if not lock_time:
2018 lock_time = time.time()
2018 lock_time = time.time()
2019 if not lock_reason:
2019 if not lock_reason:
2020 lock_reason = cls.LOCK_AUTOMATIC
2020 lock_reason = cls.LOCK_AUTOMATIC
2021 repo.locked = [user_id, lock_time, lock_reason]
2021 repo.locked = [user_id, lock_time, lock_reason]
2022 Session().add(repo)
2022 Session().add(repo)
2023 Session().commit()
2023 Session().commit()
2024
2024
2025 @classmethod
2025 @classmethod
2026 def unlock(cls, repo):
2026 def unlock(cls, repo):
2027 repo.locked = None
2027 repo.locked = None
2028 Session().add(repo)
2028 Session().add(repo)
2029 Session().commit()
2029 Session().commit()
2030
2030
2031 @classmethod
2031 @classmethod
2032 def getlock(cls, repo):
2032 def getlock(cls, repo):
2033 return repo.locked
2033 return repo.locked
2034
2034
2035 def is_user_lock(self, user_id):
2035 def is_user_lock(self, user_id):
2036 if self.lock[0]:
2036 if self.lock[0]:
2037 lock_user_id = safe_int(self.lock[0])
2037 lock_user_id = safe_int(self.lock[0])
2038 user_id = safe_int(user_id)
2038 user_id = safe_int(user_id)
2039 # both are ints, and they are equal
2039 # both are ints, and they are equal
2040 return all([lock_user_id, user_id]) and lock_user_id == user_id
2040 return all([lock_user_id, user_id]) and lock_user_id == user_id
2041
2041
2042 return False
2042 return False
2043
2043
2044 def get_locking_state(self, action, user_id, only_when_enabled=True):
2044 def get_locking_state(self, action, user_id, only_when_enabled=True):
2045 """
2045 """
2046 Checks locking on this repository, if locking is enabled and lock is
2046 Checks locking on this repository, if locking is enabled and lock is
2047 present returns a tuple of make_lock, locked, locked_by.
2047 present returns a tuple of make_lock, locked, locked_by.
2048 make_lock can have 3 states None (do nothing) True, make lock
2048 make_lock can have 3 states None (do nothing) True, make lock
2049 False release lock, This value is later propagated to hooks, which
2049 False release lock, This value is later propagated to hooks, which
2050 do the locking. Think about this as signals passed to hooks what to do.
2050 do the locking. Think about this as signals passed to hooks what to do.
2051
2051
2052 """
2052 """
2053 # TODO: johbo: This is part of the business logic and should be moved
2053 # TODO: johbo: This is part of the business logic and should be moved
2054 # into the RepositoryModel.
2054 # into the RepositoryModel.
2055
2055
2056 if action not in ('push', 'pull'):
2056 if action not in ('push', 'pull'):
2057 raise ValueError("Invalid action value: %s" % repr(action))
2057 raise ValueError("Invalid action value: %s" % repr(action))
2058
2058
2059 # defines if locked error should be thrown to user
2059 # defines if locked error should be thrown to user
2060 currently_locked = False
2060 currently_locked = False
2061 # defines if new lock should be made, tri-state
2061 # defines if new lock should be made, tri-state
2062 make_lock = None
2062 make_lock = None
2063 repo = self
2063 repo = self
2064 user = User.get(user_id)
2064 user = User.get(user_id)
2065
2065
2066 lock_info = repo.locked
2066 lock_info = repo.locked
2067
2067
2068 if repo and (repo.enable_locking or not only_when_enabled):
2068 if repo and (repo.enable_locking or not only_when_enabled):
2069 if action == 'push':
2069 if action == 'push':
2070 # check if it's already locked !, if it is compare users
2070 # check if it's already locked !, if it is compare users
2071 locked_by_user_id = lock_info[0]
2071 locked_by_user_id = lock_info[0]
2072 if user.user_id == locked_by_user_id:
2072 if user.user_id == locked_by_user_id:
2073 log.debug(
2073 log.debug(
2074 'Got `push` action from user %s, now unlocking', user)
2074 'Got `push` action from user %s, now unlocking', user)
2075 # unlock if we have push from user who locked
2075 # unlock if we have push from user who locked
2076 make_lock = False
2076 make_lock = False
2077 else:
2077 else:
2078 # we're not the same user who locked, ban with
2078 # we're not the same user who locked, ban with
2079 # code defined in settings (default is 423 HTTP Locked) !
2079 # code defined in settings (default is 423 HTTP Locked) !
2080 log.debug('Repo %s is currently locked by %s', repo, user)
2080 log.debug('Repo %s is currently locked by %s', repo, user)
2081 currently_locked = True
2081 currently_locked = True
2082 elif action == 'pull':
2082 elif action == 'pull':
2083 # [0] user [1] date
2083 # [0] user [1] date
2084 if lock_info[0] and lock_info[1]:
2084 if lock_info[0] and lock_info[1]:
2085 log.debug('Repo %s is currently locked by %s', repo, user)
2085 log.debug('Repo %s is currently locked by %s', repo, user)
2086 currently_locked = True
2086 currently_locked = True
2087 else:
2087 else:
2088 log.debug('Setting lock on repo %s by %s', repo, user)
2088 log.debug('Setting lock on repo %s by %s', repo, user)
2089 make_lock = True
2089 make_lock = True
2090
2090
2091 else:
2091 else:
2092 log.debug('Repository %s do not have locking enabled', repo)
2092 log.debug('Repository %s do not have locking enabled', repo)
2093
2093
2094 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2094 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2095 make_lock, currently_locked, lock_info)
2095 make_lock, currently_locked, lock_info)
2096
2096
2097 from rhodecode.lib.auth import HasRepoPermissionAny
2097 from rhodecode.lib.auth import HasRepoPermissionAny
2098 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2098 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2099 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2099 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2100 # if we don't have at least write permission we cannot make a lock
2100 # if we don't have at least write permission we cannot make a lock
2101 log.debug('lock state reset back to FALSE due to lack '
2101 log.debug('lock state reset back to FALSE due to lack '
2102 'of at least read permission')
2102 'of at least read permission')
2103 make_lock = False
2103 make_lock = False
2104
2104
2105 return make_lock, currently_locked, lock_info
2105 return make_lock, currently_locked, lock_info
2106
2106
2107 @property
2107 @property
2108 def last_db_change(self):
2108 def last_db_change(self):
2109 return self.updated_on
2109 return self.updated_on
2110
2110
2111 @property
2111 @property
2112 def clone_uri_hidden(self):
2112 def clone_uri_hidden(self):
2113 clone_uri = self.clone_uri
2113 clone_uri = self.clone_uri
2114 if clone_uri:
2114 if clone_uri:
2115 import urlobject
2115 import urlobject
2116 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2116 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2117 if url_obj.password:
2117 if url_obj.password:
2118 clone_uri = url_obj.with_password('*****')
2118 clone_uri = url_obj.with_password('*****')
2119 return clone_uri
2119 return clone_uri
2120
2120
2121 @property
2121 @property
2122 def push_uri_hidden(self):
2122 def push_uri_hidden(self):
2123 push_uri = self.push_uri
2123 push_uri = self.push_uri
2124 if push_uri:
2124 if push_uri:
2125 import urlobject
2125 import urlobject
2126 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2126 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2127 if url_obj.password:
2127 if url_obj.password:
2128 push_uri = url_obj.with_password('*****')
2128 push_uri = url_obj.with_password('*****')
2129 return push_uri
2129 return push_uri
2130
2130
2131 def clone_url(self, **override):
2131 def clone_url(self, **override):
2132 from rhodecode.model.settings import SettingsModel
2132 from rhodecode.model.settings import SettingsModel
2133
2133
2134 uri_tmpl = None
2134 uri_tmpl = None
2135 if 'with_id' in override:
2135 if 'with_id' in override:
2136 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2136 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2137 del override['with_id']
2137 del override['with_id']
2138
2138
2139 if 'uri_tmpl' in override:
2139 if 'uri_tmpl' in override:
2140 uri_tmpl = override['uri_tmpl']
2140 uri_tmpl = override['uri_tmpl']
2141 del override['uri_tmpl']
2141 del override['uri_tmpl']
2142
2142
2143 ssh = False
2143 ssh = False
2144 if 'ssh' in override:
2144 if 'ssh' in override:
2145 ssh = True
2145 ssh = True
2146 del override['ssh']
2146 del override['ssh']
2147
2147
2148 # we didn't override our tmpl from **overrides
2148 # we didn't override our tmpl from **overrides
2149 if not uri_tmpl:
2149 if not uri_tmpl:
2150 rc_config = SettingsModel().get_all_settings(cache=True)
2150 rc_config = SettingsModel().get_all_settings(cache=True)
2151 if ssh:
2151 if ssh:
2152 uri_tmpl = rc_config.get(
2152 uri_tmpl = rc_config.get(
2153 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2153 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2154 else:
2154 else:
2155 uri_tmpl = rc_config.get(
2155 uri_tmpl = rc_config.get(
2156 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2156 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2157
2157
2158 request = get_current_request()
2158 request = get_current_request()
2159 return get_clone_url(request=request,
2159 return get_clone_url(request=request,
2160 uri_tmpl=uri_tmpl,
2160 uri_tmpl=uri_tmpl,
2161 repo_name=self.repo_name,
2161 repo_name=self.repo_name,
2162 repo_id=self.repo_id, **override)
2162 repo_id=self.repo_id, **override)
2163
2163
2164 def set_state(self, state):
2164 def set_state(self, state):
2165 self.repo_state = state
2165 self.repo_state = state
2166 Session().add(self)
2166 Session().add(self)
2167 #==========================================================================
2167 #==========================================================================
2168 # SCM PROPERTIES
2168 # SCM PROPERTIES
2169 #==========================================================================
2169 #==========================================================================
2170
2170
2171 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2171 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2172 return get_commit_safe(
2172 return get_commit_safe(
2173 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2173 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2174
2174
2175 def get_changeset(self, rev=None, pre_load=None):
2175 def get_changeset(self, rev=None, pre_load=None):
2176 warnings.warn("Use get_commit", DeprecationWarning)
2176 warnings.warn("Use get_commit", DeprecationWarning)
2177 commit_id = None
2177 commit_id = None
2178 commit_idx = None
2178 commit_idx = None
2179 if isinstance(rev, basestring):
2179 if isinstance(rev, basestring):
2180 commit_id = rev
2180 commit_id = rev
2181 else:
2181 else:
2182 commit_idx = rev
2182 commit_idx = rev
2183 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2183 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2184 pre_load=pre_load)
2184 pre_load=pre_load)
2185
2185
2186 def get_landing_commit(self):
2186 def get_landing_commit(self):
2187 """
2187 """
2188 Returns landing commit, or if that doesn't exist returns the tip
2188 Returns landing commit, or if that doesn't exist returns the tip
2189 """
2189 """
2190 _rev_type, _rev = self.landing_rev
2190 _rev_type, _rev = self.landing_rev
2191 commit = self.get_commit(_rev)
2191 commit = self.get_commit(_rev)
2192 if isinstance(commit, EmptyCommit):
2192 if isinstance(commit, EmptyCommit):
2193 return self.get_commit()
2193 return self.get_commit()
2194 return commit
2194 return commit
2195
2195
2196 def update_commit_cache(self, cs_cache=None, config=None):
2196 def update_commit_cache(self, cs_cache=None, config=None):
2197 """
2197 """
2198 Update cache of last changeset for repository, keys should be::
2198 Update cache of last changeset for repository, keys should be::
2199
2199
2200 short_id
2200 short_id
2201 raw_id
2201 raw_id
2202 revision
2202 revision
2203 parents
2203 parents
2204 message
2204 message
2205 date
2205 date
2206 author
2206 author
2207
2207
2208 :param cs_cache:
2208 :param cs_cache:
2209 """
2209 """
2210 from rhodecode.lib.vcs.backends.base import BaseChangeset
2210 from rhodecode.lib.vcs.backends.base import BaseChangeset
2211 if cs_cache is None:
2211 if cs_cache is None:
2212 # use no-cache version here
2212 # use no-cache version here
2213 scm_repo = self.scm_instance(cache=False, config=config)
2213 scm_repo = self.scm_instance(cache=False, config=config)
2214 if scm_repo:
2214 if scm_repo:
2215 cs_cache = scm_repo.get_commit(
2215 cs_cache = scm_repo.get_commit(
2216 pre_load=["author", "date", "message", "parents"])
2216 pre_load=["author", "date", "message", "parents"])
2217 else:
2217 else:
2218 cs_cache = EmptyCommit()
2218 cs_cache = EmptyCommit()
2219
2219
2220 if isinstance(cs_cache, BaseChangeset):
2220 if isinstance(cs_cache, BaseChangeset):
2221 cs_cache = cs_cache.__json__()
2221 cs_cache = cs_cache.__json__()
2222
2222
2223 def is_outdated(new_cs_cache):
2223 def is_outdated(new_cs_cache):
2224 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2224 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2225 new_cs_cache['revision'] != self.changeset_cache['revision']):
2225 new_cs_cache['revision'] != self.changeset_cache['revision']):
2226 return True
2226 return True
2227 return False
2227 return False
2228
2228
2229 # check if we have maybe already latest cached revision
2229 # check if we have maybe already latest cached revision
2230 if is_outdated(cs_cache) or not self.changeset_cache:
2230 if is_outdated(cs_cache) or not self.changeset_cache:
2231 _default = datetime.datetime.fromtimestamp(0)
2231 _default = datetime.datetime.fromtimestamp(0)
2232 last_change = cs_cache.get('date') or _default
2232 last_change = cs_cache.get('date') or _default
2233 log.debug('updated repo %s with new cs cache %s',
2233 log.debug('updated repo %s with new cs cache %s',
2234 self.repo_name, cs_cache)
2234 self.repo_name, cs_cache)
2235 self.updated_on = last_change
2235 self.updated_on = last_change
2236 self.changeset_cache = cs_cache
2236 self.changeset_cache = cs_cache
2237 Session().add(self)
2237 Session().add(self)
2238 Session().commit()
2238 Session().commit()
2239 else:
2239 else:
2240 log.debug('Skipping update_commit_cache for repo:`%s` '
2240 log.debug('Skipping update_commit_cache for repo:`%s` '
2241 'commit already with latest changes', self.repo_name)
2241 'commit already with latest changes', self.repo_name)
2242
2242
2243 @property
2243 @property
2244 def tip(self):
2244 def tip(self):
2245 return self.get_commit('tip')
2245 return self.get_commit('tip')
2246
2246
2247 @property
2247 @property
2248 def author(self):
2248 def author(self):
2249 return self.tip.author
2249 return self.tip.author
2250
2250
2251 @property
2251 @property
2252 def last_change(self):
2252 def last_change(self):
2253 return self.scm_instance().last_change
2253 return self.scm_instance().last_change
2254
2254
2255 def get_comments(self, revisions=None):
2255 def get_comments(self, revisions=None):
2256 """
2256 """
2257 Returns comments for this repository grouped by revisions
2257 Returns comments for this repository grouped by revisions
2258
2258
2259 :param revisions: filter query by revisions only
2259 :param revisions: filter query by revisions only
2260 """
2260 """
2261 cmts = ChangesetComment.query()\
2261 cmts = ChangesetComment.query()\
2262 .filter(ChangesetComment.repo == self)
2262 .filter(ChangesetComment.repo == self)
2263 if revisions:
2263 if revisions:
2264 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2264 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2265 grouped = collections.defaultdict(list)
2265 grouped = collections.defaultdict(list)
2266 for cmt in cmts.all():
2266 for cmt in cmts.all():
2267 grouped[cmt.revision].append(cmt)
2267 grouped[cmt.revision].append(cmt)
2268 return grouped
2268 return grouped
2269
2269
2270 def statuses(self, revisions=None):
2270 def statuses(self, revisions=None):
2271 """
2271 """
2272 Returns statuses for this repository
2272 Returns statuses for this repository
2273
2273
2274 :param revisions: list of revisions to get statuses for
2274 :param revisions: list of revisions to get statuses for
2275 """
2275 """
2276 statuses = ChangesetStatus.query()\
2276 statuses = ChangesetStatus.query()\
2277 .filter(ChangesetStatus.repo == self)\
2277 .filter(ChangesetStatus.repo == self)\
2278 .filter(ChangesetStatus.version == 0)
2278 .filter(ChangesetStatus.version == 0)
2279
2279
2280 if revisions:
2280 if revisions:
2281 # Try doing the filtering in chunks to avoid hitting limits
2281 # Try doing the filtering in chunks to avoid hitting limits
2282 size = 500
2282 size = 500
2283 status_results = []
2283 status_results = []
2284 for chunk in xrange(0, len(revisions), size):
2284 for chunk in xrange(0, len(revisions), size):
2285 status_results += statuses.filter(
2285 status_results += statuses.filter(
2286 ChangesetStatus.revision.in_(
2286 ChangesetStatus.revision.in_(
2287 revisions[chunk: chunk+size])
2287 revisions[chunk: chunk+size])
2288 ).all()
2288 ).all()
2289 else:
2289 else:
2290 status_results = statuses.all()
2290 status_results = statuses.all()
2291
2291
2292 grouped = {}
2292 grouped = {}
2293
2293
2294 # maybe we have open new pullrequest without a status?
2294 # maybe we have open new pullrequest without a status?
2295 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2295 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2296 status_lbl = ChangesetStatus.get_status_lbl(stat)
2296 status_lbl = ChangesetStatus.get_status_lbl(stat)
2297 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2297 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2298 for rev in pr.revisions:
2298 for rev in pr.revisions:
2299 pr_id = pr.pull_request_id
2299 pr_id = pr.pull_request_id
2300 pr_repo = pr.target_repo.repo_name
2300 pr_repo = pr.target_repo.repo_name
2301 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2301 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2302
2302
2303 for stat in status_results:
2303 for stat in status_results:
2304 pr_id = pr_repo = None
2304 pr_id = pr_repo = None
2305 if stat.pull_request:
2305 if stat.pull_request:
2306 pr_id = stat.pull_request.pull_request_id
2306 pr_id = stat.pull_request.pull_request_id
2307 pr_repo = stat.pull_request.target_repo.repo_name
2307 pr_repo = stat.pull_request.target_repo.repo_name
2308 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2308 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2309 pr_id, pr_repo]
2309 pr_id, pr_repo]
2310 return grouped
2310 return grouped
2311
2311
2312 # ==========================================================================
2312 # ==========================================================================
2313 # SCM CACHE INSTANCE
2313 # SCM CACHE INSTANCE
2314 # ==========================================================================
2314 # ==========================================================================
2315
2315
2316 def scm_instance(self, **kwargs):
2316 def scm_instance(self, **kwargs):
2317 import rhodecode
2317 import rhodecode
2318
2318
2319 # Passing a config will not hit the cache currently only used
2319 # Passing a config will not hit the cache currently only used
2320 # for repo2dbmapper
2320 # for repo2dbmapper
2321 config = kwargs.pop('config', None)
2321 config = kwargs.pop('config', None)
2322 cache = kwargs.pop('cache', None)
2322 cache = kwargs.pop('cache', None)
2323 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2323 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2324 # if cache is NOT defined use default global, else we have a full
2324 # if cache is NOT defined use default global, else we have a full
2325 # control over cache behaviour
2325 # control over cache behaviour
2326 if cache is None and full_cache and not config:
2326 if cache is None and full_cache and not config:
2327 return self._get_instance_cached()
2327 return self._get_instance_cached()
2328 return self._get_instance(cache=bool(cache), config=config)
2328 return self._get_instance(cache=bool(cache), config=config)
2329
2329
2330 def _get_instance_cached(self):
2330 def _get_instance_cached(self):
2331 return self._get_instance()
2331 return self._get_instance()
2332
2332
2333 def _get_instance(self, cache=True, config=None):
2333 def _get_instance(self, cache=True, config=None):
2334 config = config or self._config
2334 config = config or self._config
2335 custom_wire = {
2335 custom_wire = {
2336 'cache': cache # controls the vcs.remote cache
2336 'cache': cache # controls the vcs.remote cache
2337 }
2337 }
2338 repo = get_vcs_instance(
2338 repo = get_vcs_instance(
2339 repo_path=safe_str(self.repo_full_path),
2339 repo_path=safe_str(self.repo_full_path),
2340 config=config,
2340 config=config,
2341 with_wire=custom_wire,
2341 with_wire=custom_wire,
2342 create=False,
2342 create=False,
2343 _vcs_alias=self.repo_type)
2343 _vcs_alias=self.repo_type)
2344
2344
2345 return repo
2345 return repo
2346
2346
2347 def __json__(self):
2347 def __json__(self):
2348 return {'landing_rev': self.landing_rev}
2348 return {'landing_rev': self.landing_rev}
2349
2349
2350 def get_dict(self):
2350 def get_dict(self):
2351
2351
2352 # Since we transformed `repo_name` to a hybrid property, we need to
2352 # Since we transformed `repo_name` to a hybrid property, we need to
2353 # keep compatibility with the code which uses `repo_name` field.
2353 # keep compatibility with the code which uses `repo_name` field.
2354
2354
2355 result = super(Repository, self).get_dict()
2355 result = super(Repository, self).get_dict()
2356 result['repo_name'] = result.pop('_repo_name', None)
2356 result['repo_name'] = result.pop('_repo_name', None)
2357 return result
2357 return result
2358
2358
2359
2359
2360 class RepoGroup(Base, BaseModel):
2360 class RepoGroup(Base, BaseModel):
2361 __tablename__ = 'groups'
2361 __tablename__ = 'groups'
2362 __table_args__ = (
2362 __table_args__ = (
2363 UniqueConstraint('group_name', 'group_parent_id'),
2363 UniqueConstraint('group_name', 'group_parent_id'),
2364 CheckConstraint('group_id != group_parent_id'),
2364 CheckConstraint('group_id != group_parent_id'),
2365 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2365 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2366 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2366 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2367 )
2367 )
2368 __mapper_args__ = {'order_by': 'group_name'}
2368 __mapper_args__ = {'order_by': 'group_name'}
2369
2369
2370 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2370 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2371
2371
2372 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2372 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2373 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2373 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2374 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2374 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2375 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2375 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2376 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2376 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2377 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2377 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2378 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2378 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2379 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2379 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2380 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2380 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2381
2381
2382 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2382 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2383 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2383 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2384 parent_group = relationship('RepoGroup', remote_side=group_id)
2384 parent_group = relationship('RepoGroup', remote_side=group_id)
2385 user = relationship('User')
2385 user = relationship('User')
2386 integrations = relationship('Integration',
2386 integrations = relationship('Integration',
2387 cascade="all, delete, delete-orphan")
2387 cascade="all, delete, delete-orphan")
2388
2388
2389 def __init__(self, group_name='', parent_group=None):
2389 def __init__(self, group_name='', parent_group=None):
2390 self.group_name = group_name
2390 self.group_name = group_name
2391 self.parent_group = parent_group
2391 self.parent_group = parent_group
2392
2392
2393 def __unicode__(self):
2393 def __unicode__(self):
2394 return u"<%s('id:%s:%s')>" % (
2394 return u"<%s('id:%s:%s')>" % (
2395 self.__class__.__name__, self.group_id, self.group_name)
2395 self.__class__.__name__, self.group_id, self.group_name)
2396
2396
2397 @hybrid_property
2397 @hybrid_property
2398 def description_safe(self):
2398 def description_safe(self):
2399 from rhodecode.lib import helpers as h
2399 from rhodecode.lib import helpers as h
2400 return h.escape(self.group_description)
2400 return h.escape(self.group_description)
2401
2401
2402 @classmethod
2402 @classmethod
2403 def _generate_choice(cls, repo_group):
2403 def _generate_choice(cls, repo_group):
2404 from webhelpers.html import literal as _literal
2404 from webhelpers.html import literal as _literal
2405 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2405 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2406 return repo_group.group_id, _name(repo_group.full_path_splitted)
2406 return repo_group.group_id, _name(repo_group.full_path_splitted)
2407
2407
2408 @classmethod
2408 @classmethod
2409 def groups_choices(cls, groups=None, show_empty_group=True):
2409 def groups_choices(cls, groups=None, show_empty_group=True):
2410 if not groups:
2410 if not groups:
2411 groups = cls.query().all()
2411 groups = cls.query().all()
2412
2412
2413 repo_groups = []
2413 repo_groups = []
2414 if show_empty_group:
2414 if show_empty_group:
2415 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2415 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2416
2416
2417 repo_groups.extend([cls._generate_choice(x) for x in groups])
2417 repo_groups.extend([cls._generate_choice(x) for x in groups])
2418
2418
2419 repo_groups = sorted(
2419 repo_groups = sorted(
2420 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2420 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2421 return repo_groups
2421 return repo_groups
2422
2422
2423 @classmethod
2423 @classmethod
2424 def url_sep(cls):
2424 def url_sep(cls):
2425 return URL_SEP
2425 return URL_SEP
2426
2426
2427 @classmethod
2427 @classmethod
2428 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2428 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2429 if case_insensitive:
2429 if case_insensitive:
2430 gr = cls.query().filter(func.lower(cls.group_name)
2430 gr = cls.query().filter(func.lower(cls.group_name)
2431 == func.lower(group_name))
2431 == func.lower(group_name))
2432 else:
2432 else:
2433 gr = cls.query().filter(cls.group_name == group_name)
2433 gr = cls.query().filter(cls.group_name == group_name)
2434 if cache:
2434 if cache:
2435 name_key = _hash_key(group_name)
2435 name_key = _hash_key(group_name)
2436 gr = gr.options(
2436 gr = gr.options(
2437 FromCache("sql_cache_short", "get_group_%s" % name_key))
2437 FromCache("sql_cache_short", "get_group_%s" % name_key))
2438 return gr.scalar()
2438 return gr.scalar()
2439
2439
2440 @classmethod
2440 @classmethod
2441 def get_user_personal_repo_group(cls, user_id):
2441 def get_user_personal_repo_group(cls, user_id):
2442 user = User.get(user_id)
2442 user = User.get(user_id)
2443 if user.username == User.DEFAULT_USER:
2443 if user.username == User.DEFAULT_USER:
2444 return None
2444 return None
2445
2445
2446 return cls.query()\
2446 return cls.query()\
2447 .filter(cls.personal == true()) \
2447 .filter(cls.personal == true()) \
2448 .filter(cls.user == user).scalar()
2448 .filter(cls.user == user).scalar()
2449
2449
2450 @classmethod
2450 @classmethod
2451 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2451 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2452 case_insensitive=True):
2452 case_insensitive=True):
2453 q = RepoGroup.query()
2453 q = RepoGroup.query()
2454
2454
2455 if not isinstance(user_id, Optional):
2455 if not isinstance(user_id, Optional):
2456 q = q.filter(RepoGroup.user_id == user_id)
2456 q = q.filter(RepoGroup.user_id == user_id)
2457
2457
2458 if not isinstance(group_id, Optional):
2458 if not isinstance(group_id, Optional):
2459 q = q.filter(RepoGroup.group_parent_id == group_id)
2459 q = q.filter(RepoGroup.group_parent_id == group_id)
2460
2460
2461 if case_insensitive:
2461 if case_insensitive:
2462 q = q.order_by(func.lower(RepoGroup.group_name))
2462 q = q.order_by(func.lower(RepoGroup.group_name))
2463 else:
2463 else:
2464 q = q.order_by(RepoGroup.group_name)
2464 q = q.order_by(RepoGroup.group_name)
2465 return q.all()
2465 return q.all()
2466
2466
2467 @property
2467 @property
2468 def parents(self):
2468 def parents(self):
2469 parents_recursion_limit = 10
2469 parents_recursion_limit = 10
2470 groups = []
2470 groups = []
2471 if self.parent_group is None:
2471 if self.parent_group is None:
2472 return groups
2472 return groups
2473 cur_gr = self.parent_group
2473 cur_gr = self.parent_group
2474 groups.insert(0, cur_gr)
2474 groups.insert(0, cur_gr)
2475 cnt = 0
2475 cnt = 0
2476 while 1:
2476 while 1:
2477 cnt += 1
2477 cnt += 1
2478 gr = getattr(cur_gr, 'parent_group', None)
2478 gr = getattr(cur_gr, 'parent_group', None)
2479 cur_gr = cur_gr.parent_group
2479 cur_gr = cur_gr.parent_group
2480 if gr is None:
2480 if gr is None:
2481 break
2481 break
2482 if cnt == parents_recursion_limit:
2482 if cnt == parents_recursion_limit:
2483 # this will prevent accidental infinit loops
2483 # this will prevent accidental infinit loops
2484 log.error('more than %s parents found for group %s, stopping '
2484 log.error('more than %s parents found for group %s, stopping '
2485 'recursive parent fetching', parents_recursion_limit, self)
2485 'recursive parent fetching', parents_recursion_limit, self)
2486 break
2486 break
2487
2487
2488 groups.insert(0, gr)
2488 groups.insert(0, gr)
2489 return groups
2489 return groups
2490
2490
2491 @property
2491 @property
2492 def last_db_change(self):
2492 def last_db_change(self):
2493 return self.updated_on
2493 return self.updated_on
2494
2494
2495 @property
2495 @property
2496 def children(self):
2496 def children(self):
2497 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2497 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2498
2498
2499 @property
2499 @property
2500 def name(self):
2500 def name(self):
2501 return self.group_name.split(RepoGroup.url_sep())[-1]
2501 return self.group_name.split(RepoGroup.url_sep())[-1]
2502
2502
2503 @property
2503 @property
2504 def full_path(self):
2504 def full_path(self):
2505 return self.group_name
2505 return self.group_name
2506
2506
2507 @property
2507 @property
2508 def full_path_splitted(self):
2508 def full_path_splitted(self):
2509 return self.group_name.split(RepoGroup.url_sep())
2509 return self.group_name.split(RepoGroup.url_sep())
2510
2510
2511 @property
2511 @property
2512 def repositories(self):
2512 def repositories(self):
2513 return Repository.query()\
2513 return Repository.query()\
2514 .filter(Repository.group == self)\
2514 .filter(Repository.group == self)\
2515 .order_by(Repository.repo_name)
2515 .order_by(Repository.repo_name)
2516
2516
2517 @property
2517 @property
2518 def repositories_recursive_count(self):
2518 def repositories_recursive_count(self):
2519 cnt = self.repositories.count()
2519 cnt = self.repositories.count()
2520
2520
2521 def children_count(group):
2521 def children_count(group):
2522 cnt = 0
2522 cnt = 0
2523 for child in group.children:
2523 for child in group.children:
2524 cnt += child.repositories.count()
2524 cnt += child.repositories.count()
2525 cnt += children_count(child)
2525 cnt += children_count(child)
2526 return cnt
2526 return cnt
2527
2527
2528 return cnt + children_count(self)
2528 return cnt + children_count(self)
2529
2529
2530 def _recursive_objects(self, include_repos=True):
2530 def _recursive_objects(self, include_repos=True):
2531 all_ = []
2531 all_ = []
2532
2532
2533 def _get_members(root_gr):
2533 def _get_members(root_gr):
2534 if include_repos:
2534 if include_repos:
2535 for r in root_gr.repositories:
2535 for r in root_gr.repositories:
2536 all_.append(r)
2536 all_.append(r)
2537 childs = root_gr.children.all()
2537 childs = root_gr.children.all()
2538 if childs:
2538 if childs:
2539 for gr in childs:
2539 for gr in childs:
2540 all_.append(gr)
2540 all_.append(gr)
2541 _get_members(gr)
2541 _get_members(gr)
2542
2542
2543 _get_members(self)
2543 _get_members(self)
2544 return [self] + all_
2544 return [self] + all_
2545
2545
2546 def recursive_groups_and_repos(self):
2546 def recursive_groups_and_repos(self):
2547 """
2547 """
2548 Recursive return all groups, with repositories in those groups
2548 Recursive return all groups, with repositories in those groups
2549 """
2549 """
2550 return self._recursive_objects()
2550 return self._recursive_objects()
2551
2551
2552 def recursive_groups(self):
2552 def recursive_groups(self):
2553 """
2553 """
2554 Returns all children groups for this group including children of children
2554 Returns all children groups for this group including children of children
2555 """
2555 """
2556 return self._recursive_objects(include_repos=False)
2556 return self._recursive_objects(include_repos=False)
2557
2557
2558 def get_new_name(self, group_name):
2558 def get_new_name(self, group_name):
2559 """
2559 """
2560 returns new full group name based on parent and new name
2560 returns new full group name based on parent and new name
2561
2561
2562 :param group_name:
2562 :param group_name:
2563 """
2563 """
2564 path_prefix = (self.parent_group.full_path_splitted if
2564 path_prefix = (self.parent_group.full_path_splitted if
2565 self.parent_group else [])
2565 self.parent_group else [])
2566 return RepoGroup.url_sep().join(path_prefix + [group_name])
2566 return RepoGroup.url_sep().join(path_prefix + [group_name])
2567
2567
2568 def permissions(self, with_admins=True, with_owner=True):
2568 def permissions(self, with_admins=True, with_owner=True):
2569 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2569 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2570 q = q.options(joinedload(UserRepoGroupToPerm.group),
2570 q = q.options(joinedload(UserRepoGroupToPerm.group),
2571 joinedload(UserRepoGroupToPerm.user),
2571 joinedload(UserRepoGroupToPerm.user),
2572 joinedload(UserRepoGroupToPerm.permission),)
2572 joinedload(UserRepoGroupToPerm.permission),)
2573
2573
2574 # get owners and admins and permissions. We do a trick of re-writing
2574 # get owners and admins and permissions. We do a trick of re-writing
2575 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2575 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2576 # has a global reference and changing one object propagates to all
2576 # has a global reference and changing one object propagates to all
2577 # others. This means if admin is also an owner admin_row that change
2577 # others. This means if admin is also an owner admin_row that change
2578 # would propagate to both objects
2578 # would propagate to both objects
2579 perm_rows = []
2579 perm_rows = []
2580 for _usr in q.all():
2580 for _usr in q.all():
2581 usr = AttributeDict(_usr.user.get_dict())
2581 usr = AttributeDict(_usr.user.get_dict())
2582 usr.permission = _usr.permission.permission_name
2582 usr.permission = _usr.permission.permission_name
2583 perm_rows.append(usr)
2583 perm_rows.append(usr)
2584
2584
2585 # filter the perm rows by 'default' first and then sort them by
2585 # filter the perm rows by 'default' first and then sort them by
2586 # admin,write,read,none permissions sorted again alphabetically in
2586 # admin,write,read,none permissions sorted again alphabetically in
2587 # each group
2587 # each group
2588 perm_rows = sorted(perm_rows, key=display_user_sort)
2588 perm_rows = sorted(perm_rows, key=display_user_sort)
2589
2589
2590 _admin_perm = 'group.admin'
2590 _admin_perm = 'group.admin'
2591 owner_row = []
2591 owner_row = []
2592 if with_owner:
2592 if with_owner:
2593 usr = AttributeDict(self.user.get_dict())
2593 usr = AttributeDict(self.user.get_dict())
2594 usr.owner_row = True
2594 usr.owner_row = True
2595 usr.permission = _admin_perm
2595 usr.permission = _admin_perm
2596 owner_row.append(usr)
2596 owner_row.append(usr)
2597
2597
2598 super_admin_rows = []
2598 super_admin_rows = []
2599 if with_admins:
2599 if with_admins:
2600 for usr in User.get_all_super_admins():
2600 for usr in User.get_all_super_admins():
2601 # if this admin is also owner, don't double the record
2601 # if this admin is also owner, don't double the record
2602 if usr.user_id == owner_row[0].user_id:
2602 if usr.user_id == owner_row[0].user_id:
2603 owner_row[0].admin_row = True
2603 owner_row[0].admin_row = True
2604 else:
2604 else:
2605 usr = AttributeDict(usr.get_dict())
2605 usr = AttributeDict(usr.get_dict())
2606 usr.admin_row = True
2606 usr.admin_row = True
2607 usr.permission = _admin_perm
2607 usr.permission = _admin_perm
2608 super_admin_rows.append(usr)
2608 super_admin_rows.append(usr)
2609
2609
2610 return super_admin_rows + owner_row + perm_rows
2610 return super_admin_rows + owner_row + perm_rows
2611
2611
2612 def permission_user_groups(self):
2612 def permission_user_groups(self):
2613 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2613 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2614 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2614 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2615 joinedload(UserGroupRepoGroupToPerm.users_group),
2615 joinedload(UserGroupRepoGroupToPerm.users_group),
2616 joinedload(UserGroupRepoGroupToPerm.permission),)
2616 joinedload(UserGroupRepoGroupToPerm.permission),)
2617
2617
2618 perm_rows = []
2618 perm_rows = []
2619 for _user_group in q.all():
2619 for _user_group in q.all():
2620 usr = AttributeDict(_user_group.users_group.get_dict())
2620 usr = AttributeDict(_user_group.users_group.get_dict())
2621 usr.permission = _user_group.permission.permission_name
2621 usr.permission = _user_group.permission.permission_name
2622 perm_rows.append(usr)
2622 perm_rows.append(usr)
2623
2623
2624 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2624 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2625 return perm_rows
2625 return perm_rows
2626
2626
2627 def get_api_data(self):
2627 def get_api_data(self):
2628 """
2628 """
2629 Common function for generating api data
2629 Common function for generating api data
2630
2630
2631 """
2631 """
2632 group = self
2632 group = self
2633 data = {
2633 data = {
2634 'group_id': group.group_id,
2634 'group_id': group.group_id,
2635 'group_name': group.group_name,
2635 'group_name': group.group_name,
2636 'group_description': group.description_safe,
2636 'group_description': group.description_safe,
2637 'parent_group': group.parent_group.group_name if group.parent_group else None,
2637 'parent_group': group.parent_group.group_name if group.parent_group else None,
2638 'repositories': [x.repo_name for x in group.repositories],
2638 'repositories': [x.repo_name for x in group.repositories],
2639 'owner': group.user.username,
2639 'owner': group.user.username,
2640 }
2640 }
2641 return data
2641 return data
2642
2642
2643
2643
2644 class Permission(Base, BaseModel):
2644 class Permission(Base, BaseModel):
2645 __tablename__ = 'permissions'
2645 __tablename__ = 'permissions'
2646 __table_args__ = (
2646 __table_args__ = (
2647 Index('p_perm_name_idx', 'permission_name'),
2647 Index('p_perm_name_idx', 'permission_name'),
2648 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2648 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2649 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2649 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2650 )
2650 )
2651 PERMS = [
2651 PERMS = [
2652 ('hg.admin', _('RhodeCode Super Administrator')),
2652 ('hg.admin', _('RhodeCode Super Administrator')),
2653
2653
2654 ('repository.none', _('Repository no access')),
2654 ('repository.none', _('Repository no access')),
2655 ('repository.read', _('Repository read access')),
2655 ('repository.read', _('Repository read access')),
2656 ('repository.write', _('Repository write access')),
2656 ('repository.write', _('Repository write access')),
2657 ('repository.admin', _('Repository admin access')),
2657 ('repository.admin', _('Repository admin access')),
2658
2658
2659 ('group.none', _('Repository group no access')),
2659 ('group.none', _('Repository group no access')),
2660 ('group.read', _('Repository group read access')),
2660 ('group.read', _('Repository group read access')),
2661 ('group.write', _('Repository group write access')),
2661 ('group.write', _('Repository group write access')),
2662 ('group.admin', _('Repository group admin access')),
2662 ('group.admin', _('Repository group admin access')),
2663
2663
2664 ('usergroup.none', _('User group no access')),
2664 ('usergroup.none', _('User group no access')),
2665 ('usergroup.read', _('User group read access')),
2665 ('usergroup.read', _('User group read access')),
2666 ('usergroup.write', _('User group write access')),
2666 ('usergroup.write', _('User group write access')),
2667 ('usergroup.admin', _('User group admin access')),
2667 ('usergroup.admin', _('User group admin access')),
2668
2668
2669 ('branch.none', _('Branch no permissions')),
2669 ('branch.none', _('Branch no permissions')),
2670 ('branch.merge', _('Branch access by web merge')),
2670 ('branch.merge', _('Branch access by web merge')),
2671 ('branch.push', _('Branch access by push')),
2671 ('branch.push', _('Branch access by push')),
2672 ('branch.push_force', _('Branch access by push with force')),
2672 ('branch.push_force', _('Branch access by push with force')),
2673
2673
2674 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2674 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2675 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2675 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2676
2676
2677 ('hg.usergroup.create.false', _('User Group creation disabled')),
2677 ('hg.usergroup.create.false', _('User Group creation disabled')),
2678 ('hg.usergroup.create.true', _('User Group creation enabled')),
2678 ('hg.usergroup.create.true', _('User Group creation enabled')),
2679
2679
2680 ('hg.create.none', _('Repository creation disabled')),
2680 ('hg.create.none', _('Repository creation disabled')),
2681 ('hg.create.repository', _('Repository creation enabled')),
2681 ('hg.create.repository', _('Repository creation enabled')),
2682 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2682 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2683 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2683 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2684
2684
2685 ('hg.fork.none', _('Repository forking disabled')),
2685 ('hg.fork.none', _('Repository forking disabled')),
2686 ('hg.fork.repository', _('Repository forking enabled')),
2686 ('hg.fork.repository', _('Repository forking enabled')),
2687
2687
2688 ('hg.register.none', _('Registration disabled')),
2688 ('hg.register.none', _('Registration disabled')),
2689 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2689 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2690 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2690 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2691
2691
2692 ('hg.password_reset.enabled', _('Password reset enabled')),
2692 ('hg.password_reset.enabled', _('Password reset enabled')),
2693 ('hg.password_reset.hidden', _('Password reset hidden')),
2693 ('hg.password_reset.hidden', _('Password reset hidden')),
2694 ('hg.password_reset.disabled', _('Password reset disabled')),
2694 ('hg.password_reset.disabled', _('Password reset disabled')),
2695
2695
2696 ('hg.extern_activate.manual', _('Manual activation of external account')),
2696 ('hg.extern_activate.manual', _('Manual activation of external account')),
2697 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2697 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2698
2698
2699 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2699 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2700 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2700 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2701 ]
2701 ]
2702
2702
2703 # definition of system default permissions for DEFAULT user, created on
2703 # definition of system default permissions for DEFAULT user, created on
2704 # system setup
2704 # system setup
2705 DEFAULT_USER_PERMISSIONS = [
2705 DEFAULT_USER_PERMISSIONS = [
2706 # object perms
2706 # object perms
2707 'repository.read',
2707 'repository.read',
2708 'group.read',
2708 'group.read',
2709 'usergroup.read',
2709 'usergroup.read',
2710 # branch
2710 # branch
2711 'branch.push',
2711 'branch.push',
2712 # global
2712 # global
2713 'hg.create.repository',
2713 'hg.create.repository',
2714 'hg.repogroup.create.false',
2714 'hg.repogroup.create.false',
2715 'hg.usergroup.create.false',
2715 'hg.usergroup.create.false',
2716 'hg.create.write_on_repogroup.true',
2716 'hg.create.write_on_repogroup.true',
2717 'hg.fork.repository',
2717 'hg.fork.repository',
2718 'hg.register.manual_activate',
2718 'hg.register.manual_activate',
2719 'hg.password_reset.enabled',
2719 'hg.password_reset.enabled',
2720 'hg.extern_activate.auto',
2720 'hg.extern_activate.auto',
2721 'hg.inherit_default_perms.true',
2721 'hg.inherit_default_perms.true',
2722 ]
2722 ]
2723
2723
2724 # defines which permissions are more important higher the more important
2724 # defines which permissions are more important higher the more important
2725 # Weight defines which permissions are more important.
2725 # Weight defines which permissions are more important.
2726 # The higher number the more important.
2726 # The higher number the more important.
2727 PERM_WEIGHTS = {
2727 PERM_WEIGHTS = {
2728 'repository.none': 0,
2728 'repository.none': 0,
2729 'repository.read': 1,
2729 'repository.read': 1,
2730 'repository.write': 3,
2730 'repository.write': 3,
2731 'repository.admin': 4,
2731 'repository.admin': 4,
2732
2732
2733 'group.none': 0,
2733 'group.none': 0,
2734 'group.read': 1,
2734 'group.read': 1,
2735 'group.write': 3,
2735 'group.write': 3,
2736 'group.admin': 4,
2736 'group.admin': 4,
2737
2737
2738 'usergroup.none': 0,
2738 'usergroup.none': 0,
2739 'usergroup.read': 1,
2739 'usergroup.read': 1,
2740 'usergroup.write': 3,
2740 'usergroup.write': 3,
2741 'usergroup.admin': 4,
2741 'usergroup.admin': 4,
2742
2742
2743 'branch.none': 0,
2743 'branch.none': 0,
2744 'branch.merge': 1,
2744 'branch.merge': 1,
2745 'branch.push': 3,
2745 'branch.push': 3,
2746 'branch.push_force': 4,
2746 'branch.push_force': 4,
2747
2747
2748 'hg.repogroup.create.false': 0,
2748 'hg.repogroup.create.false': 0,
2749 'hg.repogroup.create.true': 1,
2749 'hg.repogroup.create.true': 1,
2750
2750
2751 'hg.usergroup.create.false': 0,
2751 'hg.usergroup.create.false': 0,
2752 'hg.usergroup.create.true': 1,
2752 'hg.usergroup.create.true': 1,
2753
2753
2754 'hg.fork.none': 0,
2754 'hg.fork.none': 0,
2755 'hg.fork.repository': 1,
2755 'hg.fork.repository': 1,
2756 'hg.create.none': 0,
2756 'hg.create.none': 0,
2757 'hg.create.repository': 1
2757 'hg.create.repository': 1
2758 }
2758 }
2759
2759
2760 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2760 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2761 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2761 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2762 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2762 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2763
2763
2764 def __unicode__(self):
2764 def __unicode__(self):
2765 return u"<%s('%s:%s')>" % (
2765 return u"<%s('%s:%s')>" % (
2766 self.__class__.__name__, self.permission_id, self.permission_name
2766 self.__class__.__name__, self.permission_id, self.permission_name
2767 )
2767 )
2768
2768
2769 @classmethod
2769 @classmethod
2770 def get_by_key(cls, key):
2770 def get_by_key(cls, key):
2771 return cls.query().filter(cls.permission_name == key).scalar()
2771 return cls.query().filter(cls.permission_name == key).scalar()
2772
2772
2773 @classmethod
2773 @classmethod
2774 def get_default_repo_perms(cls, user_id, repo_id=None):
2774 def get_default_repo_perms(cls, user_id, repo_id=None):
2775 q = Session().query(UserRepoToPerm, Repository, Permission)\
2775 q = Session().query(UserRepoToPerm, Repository, Permission)\
2776 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2776 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2777 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2777 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2778 .filter(UserRepoToPerm.user_id == user_id)
2778 .filter(UserRepoToPerm.user_id == user_id)
2779 if repo_id:
2779 if repo_id:
2780 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2780 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2781 return q.all()
2781 return q.all()
2782
2782
2783 @classmethod
2783 @classmethod
2784 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2784 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2785 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2785 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2786 .join(
2786 .join(
2787 Permission,
2787 Permission,
2788 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2788 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2789 .join(
2789 .join(
2790 Repository,
2790 Repository,
2791 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2791 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2792 .join(
2792 .join(
2793 UserGroup,
2793 UserGroup,
2794 UserGroupRepoToPerm.users_group_id ==
2794 UserGroupRepoToPerm.users_group_id ==
2795 UserGroup.users_group_id)\
2795 UserGroup.users_group_id)\
2796 .join(
2796 .join(
2797 UserGroupMember,
2797 UserGroupMember,
2798 UserGroupRepoToPerm.users_group_id ==
2798 UserGroupRepoToPerm.users_group_id ==
2799 UserGroupMember.users_group_id)\
2799 UserGroupMember.users_group_id)\
2800 .filter(
2800 .filter(
2801 UserGroupMember.user_id == user_id,
2801 UserGroupMember.user_id == user_id,
2802 UserGroup.users_group_active == true())
2802 UserGroup.users_group_active == true())
2803 if repo_id:
2803 if repo_id:
2804 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2804 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2805 return q.all()
2805 return q.all()
2806
2806
2807 @classmethod
2807 @classmethod
2808 def get_default_group_perms(cls, user_id, repo_group_id=None):
2808 def get_default_group_perms(cls, user_id, repo_group_id=None):
2809 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2809 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2810 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2810 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2811 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2811 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2812 .filter(UserRepoGroupToPerm.user_id == user_id)
2812 .filter(UserRepoGroupToPerm.user_id == user_id)
2813 if repo_group_id:
2813 if repo_group_id:
2814 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2814 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2815 return q.all()
2815 return q.all()
2816
2816
2817 @classmethod
2817 @classmethod
2818 def get_default_group_perms_from_user_group(
2818 def get_default_group_perms_from_user_group(
2819 cls, user_id, repo_group_id=None):
2819 cls, user_id, repo_group_id=None):
2820 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2820 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2821 .join(
2821 .join(
2822 Permission,
2822 Permission,
2823 UserGroupRepoGroupToPerm.permission_id ==
2823 UserGroupRepoGroupToPerm.permission_id ==
2824 Permission.permission_id)\
2824 Permission.permission_id)\
2825 .join(
2825 .join(
2826 RepoGroup,
2826 RepoGroup,
2827 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2827 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2828 .join(
2828 .join(
2829 UserGroup,
2829 UserGroup,
2830 UserGroupRepoGroupToPerm.users_group_id ==
2830 UserGroupRepoGroupToPerm.users_group_id ==
2831 UserGroup.users_group_id)\
2831 UserGroup.users_group_id)\
2832 .join(
2832 .join(
2833 UserGroupMember,
2833 UserGroupMember,
2834 UserGroupRepoGroupToPerm.users_group_id ==
2834 UserGroupRepoGroupToPerm.users_group_id ==
2835 UserGroupMember.users_group_id)\
2835 UserGroupMember.users_group_id)\
2836 .filter(
2836 .filter(
2837 UserGroupMember.user_id == user_id,
2837 UserGroupMember.user_id == user_id,
2838 UserGroup.users_group_active == true())
2838 UserGroup.users_group_active == true())
2839 if repo_group_id:
2839 if repo_group_id:
2840 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2840 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2841 return q.all()
2841 return q.all()
2842
2842
2843 @classmethod
2843 @classmethod
2844 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2844 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2845 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2845 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2846 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2846 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2847 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2847 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2848 .filter(UserUserGroupToPerm.user_id == user_id)
2848 .filter(UserUserGroupToPerm.user_id == user_id)
2849 if user_group_id:
2849 if user_group_id:
2850 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2850 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2851 return q.all()
2851 return q.all()
2852
2852
2853 @classmethod
2853 @classmethod
2854 def get_default_user_group_perms_from_user_group(
2854 def get_default_user_group_perms_from_user_group(
2855 cls, user_id, user_group_id=None):
2855 cls, user_id, user_group_id=None):
2856 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2856 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2857 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2857 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2858 .join(
2858 .join(
2859 Permission,
2859 Permission,
2860 UserGroupUserGroupToPerm.permission_id ==
2860 UserGroupUserGroupToPerm.permission_id ==
2861 Permission.permission_id)\
2861 Permission.permission_id)\
2862 .join(
2862 .join(
2863 TargetUserGroup,
2863 TargetUserGroup,
2864 UserGroupUserGroupToPerm.target_user_group_id ==
2864 UserGroupUserGroupToPerm.target_user_group_id ==
2865 TargetUserGroup.users_group_id)\
2865 TargetUserGroup.users_group_id)\
2866 .join(
2866 .join(
2867 UserGroup,
2867 UserGroup,
2868 UserGroupUserGroupToPerm.user_group_id ==
2868 UserGroupUserGroupToPerm.user_group_id ==
2869 UserGroup.users_group_id)\
2869 UserGroup.users_group_id)\
2870 .join(
2870 .join(
2871 UserGroupMember,
2871 UserGroupMember,
2872 UserGroupUserGroupToPerm.user_group_id ==
2872 UserGroupUserGroupToPerm.user_group_id ==
2873 UserGroupMember.users_group_id)\
2873 UserGroupMember.users_group_id)\
2874 .filter(
2874 .filter(
2875 UserGroupMember.user_id == user_id,
2875 UserGroupMember.user_id == user_id,
2876 UserGroup.users_group_active == true())
2876 UserGroup.users_group_active == true())
2877 if user_group_id:
2877 if user_group_id:
2878 q = q.filter(
2878 q = q.filter(
2879 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2879 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2880
2880
2881 return q.all()
2881 return q.all()
2882
2882
2883
2883
2884 class UserRepoToPerm(Base, BaseModel):
2884 class UserRepoToPerm(Base, BaseModel):
2885 __tablename__ = 'repo_to_perm'
2885 __tablename__ = 'repo_to_perm'
2886 __table_args__ = (
2886 __table_args__ = (
2887 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2887 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2888 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2888 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2889 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2889 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2890 )
2890 )
2891 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2891 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2892 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2892 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2893 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2893 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2894 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2894 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2895
2895
2896 user = relationship('User')
2896 user = relationship('User')
2897 repository = relationship('Repository')
2897 repository = relationship('Repository')
2898 permission = relationship('Permission')
2898 permission = relationship('Permission')
2899
2899
2900 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
2900 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
2901
2901
2902 @classmethod
2902 @classmethod
2903 def create(cls, user, repository, permission):
2903 def create(cls, user, repository, permission):
2904 n = cls()
2904 n = cls()
2905 n.user = user
2905 n.user = user
2906 n.repository = repository
2906 n.repository = repository
2907 n.permission = permission
2907 n.permission = permission
2908 Session().add(n)
2908 Session().add(n)
2909 return n
2909 return n
2910
2910
2911 def __unicode__(self):
2911 def __unicode__(self):
2912 return u'<%s => %s >' % (self.user, self.repository)
2912 return u'<%s => %s >' % (self.user, self.repository)
2913
2913
2914
2914
2915 class UserUserGroupToPerm(Base, BaseModel):
2915 class UserUserGroupToPerm(Base, BaseModel):
2916 __tablename__ = 'user_user_group_to_perm'
2916 __tablename__ = 'user_user_group_to_perm'
2917 __table_args__ = (
2917 __table_args__ = (
2918 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2918 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2920 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2920 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2921 )
2921 )
2922 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2922 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2923 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2923 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2924 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2924 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2925 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2925 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2926
2926
2927 user = relationship('User')
2927 user = relationship('User')
2928 user_group = relationship('UserGroup')
2928 user_group = relationship('UserGroup')
2929 permission = relationship('Permission')
2929 permission = relationship('Permission')
2930
2930
2931 @classmethod
2931 @classmethod
2932 def create(cls, user, user_group, permission):
2932 def create(cls, user, user_group, permission):
2933 n = cls()
2933 n = cls()
2934 n.user = user
2934 n.user = user
2935 n.user_group = user_group
2935 n.user_group = user_group
2936 n.permission = permission
2936 n.permission = permission
2937 Session().add(n)
2937 Session().add(n)
2938 return n
2938 return n
2939
2939
2940 def __unicode__(self):
2940 def __unicode__(self):
2941 return u'<%s => %s >' % (self.user, self.user_group)
2941 return u'<%s => %s >' % (self.user, self.user_group)
2942
2942
2943
2943
2944 class UserToPerm(Base, BaseModel):
2944 class UserToPerm(Base, BaseModel):
2945 __tablename__ = 'user_to_perm'
2945 __tablename__ = 'user_to_perm'
2946 __table_args__ = (
2946 __table_args__ = (
2947 UniqueConstraint('user_id', 'permission_id'),
2947 UniqueConstraint('user_id', 'permission_id'),
2948 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2948 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2949 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2949 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2950 )
2950 )
2951 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2951 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2952 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2952 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2953 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2953 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2954
2954
2955 user = relationship('User')
2955 user = relationship('User')
2956 permission = relationship('Permission', lazy='joined')
2956 permission = relationship('Permission', lazy='joined')
2957
2957
2958 def __unicode__(self):
2958 def __unicode__(self):
2959 return u'<%s => %s >' % (self.user, self.permission)
2959 return u'<%s => %s >' % (self.user, self.permission)
2960
2960
2961
2961
2962 class UserGroupRepoToPerm(Base, BaseModel):
2962 class UserGroupRepoToPerm(Base, BaseModel):
2963 __tablename__ = 'users_group_repo_to_perm'
2963 __tablename__ = 'users_group_repo_to_perm'
2964 __table_args__ = (
2964 __table_args__ = (
2965 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2965 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2966 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2966 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2967 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2967 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2968 )
2968 )
2969 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2969 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2970 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2970 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2971 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2971 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2972 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2972 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2973
2973
2974 users_group = relationship('UserGroup')
2974 users_group = relationship('UserGroup')
2975 permission = relationship('Permission')
2975 permission = relationship('Permission')
2976 repository = relationship('Repository')
2976 repository = relationship('Repository')
2977
2977
2978 @classmethod
2978 @classmethod
2979 def create(cls, users_group, repository, permission):
2979 def create(cls, users_group, repository, permission):
2980 n = cls()
2980 n = cls()
2981 n.users_group = users_group
2981 n.users_group = users_group
2982 n.repository = repository
2982 n.repository = repository
2983 n.permission = permission
2983 n.permission = permission
2984 Session().add(n)
2984 Session().add(n)
2985 return n
2985 return n
2986
2986
2987 def __unicode__(self):
2987 def __unicode__(self):
2988 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2988 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2989
2989
2990
2990
2991 class UserGroupUserGroupToPerm(Base, BaseModel):
2991 class UserGroupUserGroupToPerm(Base, BaseModel):
2992 __tablename__ = 'user_group_user_group_to_perm'
2992 __tablename__ = 'user_group_user_group_to_perm'
2993 __table_args__ = (
2993 __table_args__ = (
2994 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2994 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2995 CheckConstraint('target_user_group_id != user_group_id'),
2995 CheckConstraint('target_user_group_id != user_group_id'),
2996 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2996 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2997 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2997 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2998 )
2998 )
2999 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2999 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3000 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3000 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3001 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3001 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3002 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3002 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3003
3003
3004 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3004 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3005 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3005 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3006 permission = relationship('Permission')
3006 permission = relationship('Permission')
3007
3007
3008 @classmethod
3008 @classmethod
3009 def create(cls, target_user_group, user_group, permission):
3009 def create(cls, target_user_group, user_group, permission):
3010 n = cls()
3010 n = cls()
3011 n.target_user_group = target_user_group
3011 n.target_user_group = target_user_group
3012 n.user_group = user_group
3012 n.user_group = user_group
3013 n.permission = permission
3013 n.permission = permission
3014 Session().add(n)
3014 Session().add(n)
3015 return n
3015 return n
3016
3016
3017 def __unicode__(self):
3017 def __unicode__(self):
3018 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3018 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3019
3019
3020
3020
3021 class UserGroupToPerm(Base, BaseModel):
3021 class UserGroupToPerm(Base, BaseModel):
3022 __tablename__ = 'users_group_to_perm'
3022 __tablename__ = 'users_group_to_perm'
3023 __table_args__ = (
3023 __table_args__ = (
3024 UniqueConstraint('users_group_id', 'permission_id',),
3024 UniqueConstraint('users_group_id', 'permission_id',),
3025 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3025 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3026 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3026 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3027 )
3027 )
3028 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3028 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3029 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3029 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3030 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3030 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3031
3031
3032 users_group = relationship('UserGroup')
3032 users_group = relationship('UserGroup')
3033 permission = relationship('Permission')
3033 permission = relationship('Permission')
3034
3034
3035
3035
3036 class UserRepoGroupToPerm(Base, BaseModel):
3036 class UserRepoGroupToPerm(Base, BaseModel):
3037 __tablename__ = 'user_repo_group_to_perm'
3037 __tablename__ = 'user_repo_group_to_perm'
3038 __table_args__ = (
3038 __table_args__ = (
3039 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3039 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3040 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3040 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3041 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3041 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3042 )
3042 )
3043
3043
3044 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3044 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3046 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3046 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3047 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3047 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3048
3048
3049 user = relationship('User')
3049 user = relationship('User')
3050 group = relationship('RepoGroup')
3050 group = relationship('RepoGroup')
3051 permission = relationship('Permission')
3051 permission = relationship('Permission')
3052
3052
3053 @classmethod
3053 @classmethod
3054 def create(cls, user, repository_group, permission):
3054 def create(cls, user, repository_group, permission):
3055 n = cls()
3055 n = cls()
3056 n.user = user
3056 n.user = user
3057 n.group = repository_group
3057 n.group = repository_group
3058 n.permission = permission
3058 n.permission = permission
3059 Session().add(n)
3059 Session().add(n)
3060 return n
3060 return n
3061
3061
3062
3062
3063 class UserGroupRepoGroupToPerm(Base, BaseModel):
3063 class UserGroupRepoGroupToPerm(Base, BaseModel):
3064 __tablename__ = 'users_group_repo_group_to_perm'
3064 __tablename__ = 'users_group_repo_group_to_perm'
3065 __table_args__ = (
3065 __table_args__ = (
3066 UniqueConstraint('users_group_id', 'group_id'),
3066 UniqueConstraint('users_group_id', 'group_id'),
3067 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3067 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3068 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3068 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3069 )
3069 )
3070
3070
3071 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3071 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3072 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3072 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3073 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3073 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3074 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3074 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3075
3075
3076 users_group = relationship('UserGroup')
3076 users_group = relationship('UserGroup')
3077 permission = relationship('Permission')
3077 permission = relationship('Permission')
3078 group = relationship('RepoGroup')
3078 group = relationship('RepoGroup')
3079
3079
3080 @classmethod
3080 @classmethod
3081 def create(cls, user_group, repository_group, permission):
3081 def create(cls, user_group, repository_group, permission):
3082 n = cls()
3082 n = cls()
3083 n.users_group = user_group
3083 n.users_group = user_group
3084 n.group = repository_group
3084 n.group = repository_group
3085 n.permission = permission
3085 n.permission = permission
3086 Session().add(n)
3086 Session().add(n)
3087 return n
3087 return n
3088
3088
3089 def __unicode__(self):
3089 def __unicode__(self):
3090 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3090 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3091
3091
3092
3092
3093 class Statistics(Base, BaseModel):
3093 class Statistics(Base, BaseModel):
3094 __tablename__ = 'statistics'
3094 __tablename__ = 'statistics'
3095 __table_args__ = (
3095 __table_args__ = (
3096 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3096 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3097 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3097 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3098 )
3098 )
3099 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3099 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3100 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3100 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3101 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3101 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3102 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3102 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3103 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3103 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3104 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3104 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3105
3105
3106 repository = relationship('Repository', single_parent=True)
3106 repository = relationship('Repository', single_parent=True)
3107
3107
3108
3108
3109 class UserFollowing(Base, BaseModel):
3109 class UserFollowing(Base, BaseModel):
3110 __tablename__ = 'user_followings'
3110 __tablename__ = 'user_followings'
3111 __table_args__ = (
3111 __table_args__ = (
3112 UniqueConstraint('user_id', 'follows_repository_id'),
3112 UniqueConstraint('user_id', 'follows_repository_id'),
3113 UniqueConstraint('user_id', 'follows_user_id'),
3113 UniqueConstraint('user_id', 'follows_user_id'),
3114 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3114 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3115 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3115 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3116 )
3116 )
3117
3117
3118 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3118 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3119 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3119 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3120 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3120 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3121 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3121 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3122 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3122 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3123
3123
3124 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3124 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3125
3125
3126 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3126 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3127 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3127 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3128
3128
3129 @classmethod
3129 @classmethod
3130 def get_repo_followers(cls, repo_id):
3130 def get_repo_followers(cls, repo_id):
3131 return cls.query().filter(cls.follows_repo_id == repo_id)
3131 return cls.query().filter(cls.follows_repo_id == repo_id)
3132
3132
3133
3133
3134 class CacheKey(Base, BaseModel):
3134 class CacheKey(Base, BaseModel):
3135 __tablename__ = 'cache_invalidation'
3135 __tablename__ = 'cache_invalidation'
3136 __table_args__ = (
3136 __table_args__ = (
3137 UniqueConstraint('cache_key'),
3137 UniqueConstraint('cache_key'),
3138 Index('key_idx', 'cache_key'),
3138 Index('key_idx', 'cache_key'),
3139 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3139 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3140 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3140 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3141 )
3141 )
3142 CACHE_TYPE_ATOM = 'ATOM'
3142 CACHE_TYPE_ATOM = 'ATOM'
3143 CACHE_TYPE_RSS = 'RSS'
3143 CACHE_TYPE_RSS = 'RSS'
3144 CACHE_TYPE_README = 'README'
3144 CACHE_TYPE_README = 'README'
3145
3145
3146 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3146 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3147 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3147 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3148 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3148 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3149 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3149 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3150
3150
3151 def __init__(self, cache_key, cache_args=''):
3151 def __init__(self, cache_key, cache_args=''):
3152 self.cache_key = cache_key
3152 self.cache_key = cache_key
3153 self.cache_args = cache_args
3153 self.cache_args = cache_args
3154 self.cache_active = False
3154 self.cache_active = False
3155
3155
3156 def __unicode__(self):
3156 def __unicode__(self):
3157 return u"<%s('%s:%s[%s]')>" % (
3157 return u"<%s('%s:%s[%s]')>" % (
3158 self.__class__.__name__,
3158 self.__class__.__name__,
3159 self.cache_id, self.cache_key, self.cache_active)
3159 self.cache_id, self.cache_key, self.cache_active)
3160
3160
3161 def _cache_key_partition(self):
3161 def _cache_key_partition(self):
3162 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3162 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3163 return prefix, repo_name, suffix
3163 return prefix, repo_name, suffix
3164
3164
3165 def get_prefix(self):
3165 def get_prefix(self):
3166 """
3166 """
3167 Try to extract prefix from existing cache key. The key could consist
3167 Try to extract prefix from existing cache key. The key could consist
3168 of prefix, repo_name, suffix
3168 of prefix, repo_name, suffix
3169 """
3169 """
3170 # this returns prefix, repo_name, suffix
3170 # this returns prefix, repo_name, suffix
3171 return self._cache_key_partition()[0]
3171 return self._cache_key_partition()[0]
3172
3172
3173 def get_suffix(self):
3173 def get_suffix(self):
3174 """
3174 """
3175 get suffix that might have been used in _get_cache_key to
3175 get suffix that might have been used in _get_cache_key to
3176 generate self.cache_key. Only used for informational purposes
3176 generate self.cache_key. Only used for informational purposes
3177 in repo_edit.mako.
3177 in repo_edit.mako.
3178 """
3178 """
3179 # prefix, repo_name, suffix
3179 # prefix, repo_name, suffix
3180 return self._cache_key_partition()[2]
3180 return self._cache_key_partition()[2]
3181
3181
3182 @classmethod
3182 @classmethod
3183 def delete_all_cache(cls):
3183 def delete_all_cache(cls):
3184 """
3184 """
3185 Delete all cache keys from database.
3185 Delete all cache keys from database.
3186 Should only be run when all instances are down and all entries
3186 Should only be run when all instances are down and all entries
3187 thus stale.
3187 thus stale.
3188 """
3188 """
3189 cls.query().delete()
3189 cls.query().delete()
3190 Session().commit()
3190 Session().commit()
3191
3191
3192 @classmethod
3192 @classmethod
3193 def get_cache_key(cls, repo_name, cache_type):
3193 def get_cache_key(cls, repo_name, cache_type):
3194 """
3194 """
3195
3195
3196 Generate a cache key for this process of RhodeCode instance.
3196 Generate a cache key for this process of RhodeCode instance.
3197 Prefix most likely will be process id or maybe explicitly set
3197 Prefix most likely will be process id or maybe explicitly set
3198 instance_id from .ini file.
3198 instance_id from .ini file.
3199 """
3199 """
3200 import rhodecode
3200 import rhodecode
3201 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3201 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3202
3202
3203 repo_as_unicode = safe_unicode(repo_name)
3203 repo_as_unicode = safe_unicode(repo_name)
3204 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3204 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3205 if cache_type else repo_as_unicode
3205 if cache_type else repo_as_unicode
3206
3206
3207 return u'{}{}'.format(prefix, key)
3207 return u'{}{}'.format(prefix, key)
3208
3208
3209 @classmethod
3209 @classmethod
3210 def set_invalidate(cls, repo_name, delete=False):
3210 def set_invalidate(cls, repo_name, delete=False):
3211 """
3211 """
3212 Mark all caches of a repo as invalid in the database.
3212 Mark all caches of a repo as invalid in the database.
3213 """
3213 """
3214
3214
3215 try:
3215 try:
3216 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3216 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3217 if delete:
3217 if delete:
3218 log.debug('cache objects deleted for repo %s',
3218 log.debug('cache objects deleted for repo %s',
3219 safe_str(repo_name))
3219 safe_str(repo_name))
3220 qry.delete()
3220 qry.delete()
3221 else:
3221 else:
3222 log.debug('cache objects marked as invalid for repo %s',
3222 log.debug('cache objects marked as invalid for repo %s',
3223 safe_str(repo_name))
3223 safe_str(repo_name))
3224 qry.update({"cache_active": False})
3224 qry.update({"cache_active": False})
3225
3225
3226 Session().commit()
3226 Session().commit()
3227 except Exception:
3227 except Exception:
3228 log.exception(
3228 log.exception(
3229 'Cache key invalidation failed for repository %s',
3229 'Cache key invalidation failed for repository %s',
3230 safe_str(repo_name))
3230 safe_str(repo_name))
3231 Session().rollback()
3231 Session().rollback()
3232
3232
3233 @classmethod
3233 @classmethod
3234 def get_active_cache(cls, cache_key):
3234 def get_active_cache(cls, cache_key):
3235 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3235 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3236 if inv_obj:
3236 if inv_obj:
3237 return inv_obj
3237 return inv_obj
3238 return None
3238 return None
3239
3239
3240
3240
3241 class ChangesetComment(Base, BaseModel):
3241 class ChangesetComment(Base, BaseModel):
3242 __tablename__ = 'changeset_comments'
3242 __tablename__ = 'changeset_comments'
3243 __table_args__ = (
3243 __table_args__ = (
3244 Index('cc_revision_idx', 'revision'),
3244 Index('cc_revision_idx', 'revision'),
3245 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3245 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3246 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3246 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3247 )
3247 )
3248
3248
3249 COMMENT_OUTDATED = u'comment_outdated'
3249 COMMENT_OUTDATED = u'comment_outdated'
3250 COMMENT_TYPE_NOTE = u'note'
3250 COMMENT_TYPE_NOTE = u'note'
3251 COMMENT_TYPE_TODO = u'todo'
3251 COMMENT_TYPE_TODO = u'todo'
3252 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3252 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3253
3253
3254 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3254 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3255 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3255 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3256 revision = Column('revision', String(40), nullable=True)
3256 revision = Column('revision', String(40), nullable=True)
3257 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3257 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3258 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3258 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3259 line_no = Column('line_no', Unicode(10), nullable=True)
3259 line_no = Column('line_no', Unicode(10), nullable=True)
3260 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3260 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3261 f_path = Column('f_path', Unicode(1000), nullable=True)
3261 f_path = Column('f_path', Unicode(1000), nullable=True)
3262 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3262 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3263 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3263 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3264 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3264 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3265 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3265 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3266 renderer = Column('renderer', Unicode(64), nullable=True)
3266 renderer = Column('renderer', Unicode(64), nullable=True)
3267 display_state = Column('display_state', Unicode(128), nullable=True)
3267 display_state = Column('display_state', Unicode(128), nullable=True)
3268
3268
3269 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3269 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3270 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3270 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3271 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3271 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3272 author = relationship('User', lazy='joined')
3272 author = relationship('User', lazy='joined')
3273 repo = relationship('Repository')
3273 repo = relationship('Repository')
3274 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3274 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3275 pull_request = relationship('PullRequest', lazy='joined')
3275 pull_request = relationship('PullRequest', lazy='joined')
3276 pull_request_version = relationship('PullRequestVersion')
3276 pull_request_version = relationship('PullRequestVersion')
3277
3277
3278 @classmethod
3278 @classmethod
3279 def get_users(cls, revision=None, pull_request_id=None):
3279 def get_users(cls, revision=None, pull_request_id=None):
3280 """
3280 """
3281 Returns user associated with this ChangesetComment. ie those
3281 Returns user associated with this ChangesetComment. ie those
3282 who actually commented
3282 who actually commented
3283
3283
3284 :param cls:
3284 :param cls:
3285 :param revision:
3285 :param revision:
3286 """
3286 """
3287 q = Session().query(User)\
3287 q = Session().query(User)\
3288 .join(ChangesetComment.author)
3288 .join(ChangesetComment.author)
3289 if revision:
3289 if revision:
3290 q = q.filter(cls.revision == revision)
3290 q = q.filter(cls.revision == revision)
3291 elif pull_request_id:
3291 elif pull_request_id:
3292 q = q.filter(cls.pull_request_id == pull_request_id)
3292 q = q.filter(cls.pull_request_id == pull_request_id)
3293 return q.all()
3293 return q.all()
3294
3294
3295 @classmethod
3295 @classmethod
3296 def get_index_from_version(cls, pr_version, versions):
3296 def get_index_from_version(cls, pr_version, versions):
3297 num_versions = [x.pull_request_version_id for x in versions]
3297 num_versions = [x.pull_request_version_id for x in versions]
3298 try:
3298 try:
3299 return num_versions.index(pr_version) +1
3299 return num_versions.index(pr_version) +1
3300 except (IndexError, ValueError):
3300 except (IndexError, ValueError):
3301 return
3301 return
3302
3302
3303 @property
3303 @property
3304 def outdated(self):
3304 def outdated(self):
3305 return self.display_state == self.COMMENT_OUTDATED
3305 return self.display_state == self.COMMENT_OUTDATED
3306
3306
3307 def outdated_at_version(self, version):
3307 def outdated_at_version(self, version):
3308 """
3308 """
3309 Checks if comment is outdated for given pull request version
3309 Checks if comment is outdated for given pull request version
3310 """
3310 """
3311 return self.outdated and self.pull_request_version_id != version
3311 return self.outdated and self.pull_request_version_id != version
3312
3312
3313 def older_than_version(self, version):
3313 def older_than_version(self, version):
3314 """
3314 """
3315 Checks if comment is made from previous version than given
3315 Checks if comment is made from previous version than given
3316 """
3316 """
3317 if version is None:
3317 if version is None:
3318 return self.pull_request_version_id is not None
3318 return self.pull_request_version_id is not None
3319
3319
3320 return self.pull_request_version_id < version
3320 return self.pull_request_version_id < version
3321
3321
3322 @property
3322 @property
3323 def resolved(self):
3323 def resolved(self):
3324 return self.resolved_by[0] if self.resolved_by else None
3324 return self.resolved_by[0] if self.resolved_by else None
3325
3325
3326 @property
3326 @property
3327 def is_todo(self):
3327 def is_todo(self):
3328 return self.comment_type == self.COMMENT_TYPE_TODO
3328 return self.comment_type == self.COMMENT_TYPE_TODO
3329
3329
3330 @property
3330 @property
3331 def is_inline(self):
3331 def is_inline(self):
3332 return self.line_no and self.f_path
3332 return self.line_no and self.f_path
3333
3333
3334 def get_index_version(self, versions):
3334 def get_index_version(self, versions):
3335 return self.get_index_from_version(
3335 return self.get_index_from_version(
3336 self.pull_request_version_id, versions)
3336 self.pull_request_version_id, versions)
3337
3337
3338 def __repr__(self):
3338 def __repr__(self):
3339 if self.comment_id:
3339 if self.comment_id:
3340 return '<DB:Comment #%s>' % self.comment_id
3340 return '<DB:Comment #%s>' % self.comment_id
3341 else:
3341 else:
3342 return '<DB:Comment at %#x>' % id(self)
3342 return '<DB:Comment at %#x>' % id(self)
3343
3343
3344 def get_api_data(self):
3344 def get_api_data(self):
3345 comment = self
3345 comment = self
3346 data = {
3346 data = {
3347 'comment_id': comment.comment_id,
3347 'comment_id': comment.comment_id,
3348 'comment_type': comment.comment_type,
3348 'comment_type': comment.comment_type,
3349 'comment_text': comment.text,
3349 'comment_text': comment.text,
3350 'comment_status': comment.status_change,
3350 'comment_status': comment.status_change,
3351 'comment_f_path': comment.f_path,
3351 'comment_f_path': comment.f_path,
3352 'comment_lineno': comment.line_no,
3352 'comment_lineno': comment.line_no,
3353 'comment_author': comment.author,
3353 'comment_author': comment.author,
3354 'comment_created_on': comment.created_on
3354 'comment_created_on': comment.created_on
3355 }
3355 }
3356 return data
3356 return data
3357
3357
3358 def __json__(self):
3358 def __json__(self):
3359 data = dict()
3359 data = dict()
3360 data.update(self.get_api_data())
3360 data.update(self.get_api_data())
3361 return data
3361 return data
3362
3362
3363
3363
3364 class ChangesetStatus(Base, BaseModel):
3364 class ChangesetStatus(Base, BaseModel):
3365 __tablename__ = 'changeset_statuses'
3365 __tablename__ = 'changeset_statuses'
3366 __table_args__ = (
3366 __table_args__ = (
3367 Index('cs_revision_idx', 'revision'),
3367 Index('cs_revision_idx', 'revision'),
3368 Index('cs_version_idx', 'version'),
3368 Index('cs_version_idx', 'version'),
3369 UniqueConstraint('repo_id', 'revision', 'version'),
3369 UniqueConstraint('repo_id', 'revision', 'version'),
3370 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3370 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3371 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3371 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3372 )
3372 )
3373 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3373 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3374 STATUS_APPROVED = 'approved'
3374 STATUS_APPROVED = 'approved'
3375 STATUS_REJECTED = 'rejected'
3375 STATUS_REJECTED = 'rejected'
3376 STATUS_UNDER_REVIEW = 'under_review'
3376 STATUS_UNDER_REVIEW = 'under_review'
3377
3377
3378 STATUSES = [
3378 STATUSES = [
3379 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3379 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3380 (STATUS_APPROVED, _("Approved")),
3380 (STATUS_APPROVED, _("Approved")),
3381 (STATUS_REJECTED, _("Rejected")),
3381 (STATUS_REJECTED, _("Rejected")),
3382 (STATUS_UNDER_REVIEW, _("Under Review")),
3382 (STATUS_UNDER_REVIEW, _("Under Review")),
3383 ]
3383 ]
3384
3384
3385 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3385 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3386 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3386 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3387 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3387 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3388 revision = Column('revision', String(40), nullable=False)
3388 revision = Column('revision', String(40), nullable=False)
3389 status = Column('status', String(128), nullable=False, default=DEFAULT)
3389 status = Column('status', String(128), nullable=False, default=DEFAULT)
3390 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3390 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3391 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3391 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3392 version = Column('version', Integer(), nullable=False, default=0)
3392 version = Column('version', Integer(), nullable=False, default=0)
3393 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3393 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3394
3394
3395 author = relationship('User', lazy='joined')
3395 author = relationship('User', lazy='joined')
3396 repo = relationship('Repository')
3396 repo = relationship('Repository')
3397 comment = relationship('ChangesetComment', lazy='joined')
3397 comment = relationship('ChangesetComment', lazy='joined')
3398 pull_request = relationship('PullRequest', lazy='joined')
3398 pull_request = relationship('PullRequest', lazy='joined')
3399
3399
3400 def __unicode__(self):
3400 def __unicode__(self):
3401 return u"<%s('%s[v%s]:%s')>" % (
3401 return u"<%s('%s[v%s]:%s')>" % (
3402 self.__class__.__name__,
3402 self.__class__.__name__,
3403 self.status, self.version, self.author
3403 self.status, self.version, self.author
3404 )
3404 )
3405
3405
3406 @classmethod
3406 @classmethod
3407 def get_status_lbl(cls, value):
3407 def get_status_lbl(cls, value):
3408 return dict(cls.STATUSES).get(value)
3408 return dict(cls.STATUSES).get(value)
3409
3409
3410 @property
3410 @property
3411 def status_lbl(self):
3411 def status_lbl(self):
3412 return ChangesetStatus.get_status_lbl(self.status)
3412 return ChangesetStatus.get_status_lbl(self.status)
3413
3413
3414 def get_api_data(self):
3414 def get_api_data(self):
3415 status = self
3415 status = self
3416 data = {
3416 data = {
3417 'status_id': status.changeset_status_id,
3417 'status_id': status.changeset_status_id,
3418 'status': status.status,
3418 'status': status.status,
3419 }
3419 }
3420 return data
3420 return data
3421
3421
3422 def __json__(self):
3422 def __json__(self):
3423 data = dict()
3423 data = dict()
3424 data.update(self.get_api_data())
3424 data.update(self.get_api_data())
3425 return data
3425 return data
3426
3426
3427
3427
3428 class _PullRequestBase(BaseModel):
3428 class _PullRequestBase(BaseModel):
3429 """
3429 """
3430 Common attributes of pull request and version entries.
3430 Common attributes of pull request and version entries.
3431 """
3431 """
3432
3432
3433 # .status values
3433 # .status values
3434 STATUS_NEW = u'new'
3434 STATUS_NEW = u'new'
3435 STATUS_OPEN = u'open'
3435 STATUS_OPEN = u'open'
3436 STATUS_CLOSED = u'closed'
3436 STATUS_CLOSED = u'closed'
3437
3437
3438 title = Column('title', Unicode(255), nullable=True)
3438 title = Column('title', Unicode(255), nullable=True)
3439 description = Column(
3439 description = Column(
3440 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3440 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3441 nullable=True)
3441 nullable=True)
3442 # new/open/closed status of pull request (not approve/reject/etc)
3442 # new/open/closed status of pull request (not approve/reject/etc)
3443 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3443 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3444 created_on = Column(
3444 created_on = Column(
3445 'created_on', DateTime(timezone=False), nullable=False,
3445 'created_on', DateTime(timezone=False), nullable=False,
3446 default=datetime.datetime.now)
3446 default=datetime.datetime.now)
3447 updated_on = Column(
3447 updated_on = Column(
3448 'updated_on', DateTime(timezone=False), nullable=False,
3448 'updated_on', DateTime(timezone=False), nullable=False,
3449 default=datetime.datetime.now)
3449 default=datetime.datetime.now)
3450
3450
3451 @declared_attr
3451 @declared_attr
3452 def user_id(cls):
3452 def user_id(cls):
3453 return Column(
3453 return Column(
3454 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3454 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3455 unique=None)
3455 unique=None)
3456
3456
3457 # 500 revisions max
3457 # 500 revisions max
3458 _revisions = Column(
3458 _revisions = Column(
3459 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3459 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3460
3460
3461 @declared_attr
3461 @declared_attr
3462 def source_repo_id(cls):
3462 def source_repo_id(cls):
3463 # TODO: dan: rename column to source_repo_id
3463 # TODO: dan: rename column to source_repo_id
3464 return Column(
3464 return Column(
3465 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3465 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3466 nullable=False)
3466 nullable=False)
3467
3467
3468 source_ref = Column('org_ref', Unicode(255), nullable=False)
3468 source_ref = Column('org_ref', Unicode(255), nullable=False)
3469
3469
3470 @declared_attr
3470 @declared_attr
3471 def target_repo_id(cls):
3471 def target_repo_id(cls):
3472 # TODO: dan: rename column to target_repo_id
3472 # TODO: dan: rename column to target_repo_id
3473 return Column(
3473 return Column(
3474 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3474 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3475 nullable=False)
3475 nullable=False)
3476
3476
3477 target_ref = Column('other_ref', Unicode(255), nullable=False)
3477 target_ref = Column('other_ref', Unicode(255), nullable=False)
3478 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3478 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3479
3479
3480 # TODO: dan: rename column to last_merge_source_rev
3480 # TODO: dan: rename column to last_merge_source_rev
3481 _last_merge_source_rev = Column(
3481 _last_merge_source_rev = Column(
3482 'last_merge_org_rev', String(40), nullable=True)
3482 'last_merge_org_rev', String(40), nullable=True)
3483 # TODO: dan: rename column to last_merge_target_rev
3483 # TODO: dan: rename column to last_merge_target_rev
3484 _last_merge_target_rev = Column(
3484 _last_merge_target_rev = Column(
3485 'last_merge_other_rev', String(40), nullable=True)
3485 'last_merge_other_rev', String(40), nullable=True)
3486 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3486 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3487 merge_rev = Column('merge_rev', String(40), nullable=True)
3487 merge_rev = Column('merge_rev', String(40), nullable=True)
3488
3488
3489 reviewer_data = Column(
3489 reviewer_data = Column(
3490 'reviewer_data_json', MutationObj.as_mutable(
3490 'reviewer_data_json', MutationObj.as_mutable(
3491 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3491 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3492
3492
3493 @property
3493 @property
3494 def reviewer_data_json(self):
3494 def reviewer_data_json(self):
3495 return json.dumps(self.reviewer_data)
3495 return json.dumps(self.reviewer_data)
3496
3496
3497 @hybrid_property
3497 @hybrid_property
3498 def description_safe(self):
3498 def description_safe(self):
3499 from rhodecode.lib import helpers as h
3499 from rhodecode.lib import helpers as h
3500 return h.escape(self.description)
3500 return h.escape(self.description)
3501
3501
3502 @hybrid_property
3502 @hybrid_property
3503 def revisions(self):
3503 def revisions(self):
3504 return self._revisions.split(':') if self._revisions else []
3504 return self._revisions.split(':') if self._revisions else []
3505
3505
3506 @revisions.setter
3506 @revisions.setter
3507 def revisions(self, val):
3507 def revisions(self, val):
3508 self._revisions = ':'.join(val)
3508 self._revisions = ':'.join(val)
3509
3509
3510 @hybrid_property
3510 @hybrid_property
3511 def last_merge_status(self):
3511 def last_merge_status(self):
3512 return safe_int(self._last_merge_status)
3512 return safe_int(self._last_merge_status)
3513
3513
3514 @last_merge_status.setter
3514 @last_merge_status.setter
3515 def last_merge_status(self, val):
3515 def last_merge_status(self, val):
3516 self._last_merge_status = val
3516 self._last_merge_status = val
3517
3517
3518 @declared_attr
3518 @declared_attr
3519 def author(cls):
3519 def author(cls):
3520 return relationship('User', lazy='joined')
3520 return relationship('User', lazy='joined')
3521
3521
3522 @declared_attr
3522 @declared_attr
3523 def source_repo(cls):
3523 def source_repo(cls):
3524 return relationship(
3524 return relationship(
3525 'Repository',
3525 'Repository',
3526 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3526 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3527
3527
3528 @property
3528 @property
3529 def source_ref_parts(self):
3529 def source_ref_parts(self):
3530 return self.unicode_to_reference(self.source_ref)
3530 return self.unicode_to_reference(self.source_ref)
3531
3531
3532 @declared_attr
3532 @declared_attr
3533 def target_repo(cls):
3533 def target_repo(cls):
3534 return relationship(
3534 return relationship(
3535 'Repository',
3535 'Repository',
3536 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3536 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3537
3537
3538 @property
3538 @property
3539 def target_ref_parts(self):
3539 def target_ref_parts(self):
3540 return self.unicode_to_reference(self.target_ref)
3540 return self.unicode_to_reference(self.target_ref)
3541
3541
3542 @property
3542 @property
3543 def shadow_merge_ref(self):
3543 def shadow_merge_ref(self):
3544 return self.unicode_to_reference(self._shadow_merge_ref)
3544 return self.unicode_to_reference(self._shadow_merge_ref)
3545
3545
3546 @shadow_merge_ref.setter
3546 @shadow_merge_ref.setter
3547 def shadow_merge_ref(self, ref):
3547 def shadow_merge_ref(self, ref):
3548 self._shadow_merge_ref = self.reference_to_unicode(ref)
3548 self._shadow_merge_ref = self.reference_to_unicode(ref)
3549
3549
3550 def unicode_to_reference(self, raw):
3550 def unicode_to_reference(self, raw):
3551 """
3551 """
3552 Convert a unicode (or string) to a reference object.
3552 Convert a unicode (or string) to a reference object.
3553 If unicode evaluates to False it returns None.
3553 If unicode evaluates to False it returns None.
3554 """
3554 """
3555 if raw:
3555 if raw:
3556 refs = raw.split(':')
3556 refs = raw.split(':')
3557 return Reference(*refs)
3557 return Reference(*refs)
3558 else:
3558 else:
3559 return None
3559 return None
3560
3560
3561 def reference_to_unicode(self, ref):
3561 def reference_to_unicode(self, ref):
3562 """
3562 """
3563 Convert a reference object to unicode.
3563 Convert a reference object to unicode.
3564 If reference is None it returns None.
3564 If reference is None it returns None.
3565 """
3565 """
3566 if ref:
3566 if ref:
3567 return u':'.join(ref)
3567 return u':'.join(ref)
3568 else:
3568 else:
3569 return None
3569 return None
3570
3570
3571 def get_api_data(self, with_merge_state=True):
3571 def get_api_data(self, with_merge_state=True):
3572 from rhodecode.model.pull_request import PullRequestModel
3572 from rhodecode.model.pull_request import PullRequestModel
3573
3573
3574 pull_request = self
3574 pull_request = self
3575 if with_merge_state:
3575 if with_merge_state:
3576 merge_status = PullRequestModel().merge_status(pull_request)
3576 merge_status = PullRequestModel().merge_status(pull_request)
3577 merge_state = {
3577 merge_state = {
3578 'status': merge_status[0],
3578 'status': merge_status[0],
3579 'message': safe_unicode(merge_status[1]),
3579 'message': safe_unicode(merge_status[1]),
3580 }
3580 }
3581 else:
3581 else:
3582 merge_state = {'status': 'not_available',
3582 merge_state = {'status': 'not_available',
3583 'message': 'not_available'}
3583 'message': 'not_available'}
3584
3584
3585 merge_data = {
3585 merge_data = {
3586 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3586 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3587 'reference': (
3587 'reference': (
3588 pull_request.shadow_merge_ref._asdict()
3588 pull_request.shadow_merge_ref._asdict()
3589 if pull_request.shadow_merge_ref else None),
3589 if pull_request.shadow_merge_ref else None),
3590 }
3590 }
3591
3591
3592 data = {
3592 data = {
3593 'pull_request_id': pull_request.pull_request_id,
3593 'pull_request_id': pull_request.pull_request_id,
3594 'url': PullRequestModel().get_url(pull_request),
3594 'url': PullRequestModel().get_url(pull_request),
3595 'title': pull_request.title,
3595 'title': pull_request.title,
3596 'description': pull_request.description,
3596 'description': pull_request.description,
3597 'status': pull_request.status,
3597 'status': pull_request.status,
3598 'created_on': pull_request.created_on,
3598 'created_on': pull_request.created_on,
3599 'updated_on': pull_request.updated_on,
3599 'updated_on': pull_request.updated_on,
3600 'commit_ids': pull_request.revisions,
3600 'commit_ids': pull_request.revisions,
3601 'review_status': pull_request.calculated_review_status(),
3601 'review_status': pull_request.calculated_review_status(),
3602 'mergeable': merge_state,
3602 'mergeable': merge_state,
3603 'source': {
3603 'source': {
3604 'clone_url': pull_request.source_repo.clone_url(),
3604 'clone_url': pull_request.source_repo.clone_url(),
3605 'repository': pull_request.source_repo.repo_name,
3605 'repository': pull_request.source_repo.repo_name,
3606 'reference': {
3606 'reference': {
3607 'name': pull_request.source_ref_parts.name,
3607 'name': pull_request.source_ref_parts.name,
3608 'type': pull_request.source_ref_parts.type,
3608 'type': pull_request.source_ref_parts.type,
3609 'commit_id': pull_request.source_ref_parts.commit_id,
3609 'commit_id': pull_request.source_ref_parts.commit_id,
3610 },
3610 },
3611 },
3611 },
3612 'target': {
3612 'target': {
3613 'clone_url': pull_request.target_repo.clone_url(),
3613 'clone_url': pull_request.target_repo.clone_url(),
3614 'repository': pull_request.target_repo.repo_name,
3614 'repository': pull_request.target_repo.repo_name,
3615 'reference': {
3615 'reference': {
3616 'name': pull_request.target_ref_parts.name,
3616 'name': pull_request.target_ref_parts.name,
3617 'type': pull_request.target_ref_parts.type,
3617 'type': pull_request.target_ref_parts.type,
3618 'commit_id': pull_request.target_ref_parts.commit_id,
3618 'commit_id': pull_request.target_ref_parts.commit_id,
3619 },
3619 },
3620 },
3620 },
3621 'merge': merge_data,
3621 'merge': merge_data,
3622 'author': pull_request.author.get_api_data(include_secrets=False,
3622 'author': pull_request.author.get_api_data(include_secrets=False,
3623 details='basic'),
3623 details='basic'),
3624 'reviewers': [
3624 'reviewers': [
3625 {
3625 {
3626 'user': reviewer.get_api_data(include_secrets=False,
3626 'user': reviewer.get_api_data(include_secrets=False,
3627 details='basic'),
3627 details='basic'),
3628 'reasons': reasons,
3628 'reasons': reasons,
3629 'review_status': st[0][1].status if st else 'not_reviewed',
3629 'review_status': st[0][1].status if st else 'not_reviewed',
3630 }
3630 }
3631 for obj, reviewer, reasons, mandatory, st in
3631 for obj, reviewer, reasons, mandatory, st in
3632 pull_request.reviewers_statuses()
3632 pull_request.reviewers_statuses()
3633 ]
3633 ]
3634 }
3634 }
3635
3635
3636 return data
3636 return data
3637
3637
3638
3638
3639 class PullRequest(Base, _PullRequestBase):
3639 class PullRequest(Base, _PullRequestBase):
3640 __tablename__ = 'pull_requests'
3640 __tablename__ = 'pull_requests'
3641 __table_args__ = (
3641 __table_args__ = (
3642 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3642 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3643 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3643 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3644 )
3644 )
3645
3645
3646 pull_request_id = Column(
3646 pull_request_id = Column(
3647 'pull_request_id', Integer(), nullable=False, primary_key=True)
3647 'pull_request_id', Integer(), nullable=False, primary_key=True)
3648
3648
3649 def __repr__(self):
3649 def __repr__(self):
3650 if self.pull_request_id:
3650 if self.pull_request_id:
3651 return '<DB:PullRequest #%s>' % self.pull_request_id
3651 return '<DB:PullRequest #%s>' % self.pull_request_id
3652 else:
3652 else:
3653 return '<DB:PullRequest at %#x>' % id(self)
3653 return '<DB:PullRequest at %#x>' % id(self)
3654
3654
3655 reviewers = relationship('PullRequestReviewers',
3655 reviewers = relationship('PullRequestReviewers',
3656 cascade="all, delete, delete-orphan")
3656 cascade="all, delete, delete-orphan")
3657 statuses = relationship('ChangesetStatus',
3657 statuses = relationship('ChangesetStatus',
3658 cascade="all, delete, delete-orphan")
3658 cascade="all, delete, delete-orphan")
3659 comments = relationship('ChangesetComment',
3659 comments = relationship('ChangesetComment',
3660 cascade="all, delete, delete-orphan")
3660 cascade="all, delete, delete-orphan")
3661 versions = relationship('PullRequestVersion',
3661 versions = relationship('PullRequestVersion',
3662 cascade="all, delete, delete-orphan",
3662 cascade="all, delete, delete-orphan",
3663 lazy='dynamic')
3663 lazy='dynamic')
3664
3664
3665 @classmethod
3665 @classmethod
3666 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3666 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3667 internal_methods=None):
3667 internal_methods=None):
3668
3668
3669 class PullRequestDisplay(object):
3669 class PullRequestDisplay(object):
3670 """
3670 """
3671 Special object wrapper for showing PullRequest data via Versions
3671 Special object wrapper for showing PullRequest data via Versions
3672 It mimics PR object as close as possible. This is read only object
3672 It mimics PR object as close as possible. This is read only object
3673 just for display
3673 just for display
3674 """
3674 """
3675
3675
3676 def __init__(self, attrs, internal=None):
3676 def __init__(self, attrs, internal=None):
3677 self.attrs = attrs
3677 self.attrs = attrs
3678 # internal have priority over the given ones via attrs
3678 # internal have priority over the given ones via attrs
3679 self.internal = internal or ['versions']
3679 self.internal = internal or ['versions']
3680
3680
3681 def __getattr__(self, item):
3681 def __getattr__(self, item):
3682 if item in self.internal:
3682 if item in self.internal:
3683 return getattr(self, item)
3683 return getattr(self, item)
3684 try:
3684 try:
3685 return self.attrs[item]
3685 return self.attrs[item]
3686 except KeyError:
3686 except KeyError:
3687 raise AttributeError(
3687 raise AttributeError(
3688 '%s object has no attribute %s' % (self, item))
3688 '%s object has no attribute %s' % (self, item))
3689
3689
3690 def __repr__(self):
3690 def __repr__(self):
3691 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3691 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3692
3692
3693 def versions(self):
3693 def versions(self):
3694 return pull_request_obj.versions.order_by(
3694 return pull_request_obj.versions.order_by(
3695 PullRequestVersion.pull_request_version_id).all()
3695 PullRequestVersion.pull_request_version_id).all()
3696
3696
3697 def is_closed(self):
3697 def is_closed(self):
3698 return pull_request_obj.is_closed()
3698 return pull_request_obj.is_closed()
3699
3699
3700 @property
3700 @property
3701 def pull_request_version_id(self):
3701 def pull_request_version_id(self):
3702 return getattr(pull_request_obj, 'pull_request_version_id', None)
3702 return getattr(pull_request_obj, 'pull_request_version_id', None)
3703
3703
3704 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3704 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3705
3705
3706 attrs.author = StrictAttributeDict(
3706 attrs.author = StrictAttributeDict(
3707 pull_request_obj.author.get_api_data())
3707 pull_request_obj.author.get_api_data())
3708 if pull_request_obj.target_repo:
3708 if pull_request_obj.target_repo:
3709 attrs.target_repo = StrictAttributeDict(
3709 attrs.target_repo = StrictAttributeDict(
3710 pull_request_obj.target_repo.get_api_data())
3710 pull_request_obj.target_repo.get_api_data())
3711 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3711 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3712
3712
3713 if pull_request_obj.source_repo:
3713 if pull_request_obj.source_repo:
3714 attrs.source_repo = StrictAttributeDict(
3714 attrs.source_repo = StrictAttributeDict(
3715 pull_request_obj.source_repo.get_api_data())
3715 pull_request_obj.source_repo.get_api_data())
3716 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3716 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3717
3717
3718 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3718 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3719 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3719 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3720 attrs.revisions = pull_request_obj.revisions
3720 attrs.revisions = pull_request_obj.revisions
3721
3721
3722 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3722 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3723 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3723 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3724 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3724 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3725
3725
3726 return PullRequestDisplay(attrs, internal=internal_methods)
3726 return PullRequestDisplay(attrs, internal=internal_methods)
3727
3727
3728 def is_closed(self):
3728 def is_closed(self):
3729 return self.status == self.STATUS_CLOSED
3729 return self.status == self.STATUS_CLOSED
3730
3730
3731 def __json__(self):
3731 def __json__(self):
3732 return {
3732 return {
3733 'revisions': self.revisions,
3733 'revisions': self.revisions,
3734 }
3734 }
3735
3735
3736 def calculated_review_status(self):
3736 def calculated_review_status(self):
3737 from rhodecode.model.changeset_status import ChangesetStatusModel
3737 from rhodecode.model.changeset_status import ChangesetStatusModel
3738 return ChangesetStatusModel().calculated_review_status(self)
3738 return ChangesetStatusModel().calculated_review_status(self)
3739
3739
3740 def reviewers_statuses(self):
3740 def reviewers_statuses(self):
3741 from rhodecode.model.changeset_status import ChangesetStatusModel
3741 from rhodecode.model.changeset_status import ChangesetStatusModel
3742 return ChangesetStatusModel().reviewers_statuses(self)
3742 return ChangesetStatusModel().reviewers_statuses(self)
3743
3743
3744 @property
3744 @property
3745 def workspace_id(self):
3745 def workspace_id(self):
3746 from rhodecode.model.pull_request import PullRequestModel
3746 from rhodecode.model.pull_request import PullRequestModel
3747 return PullRequestModel()._workspace_id(self)
3747 return PullRequestModel()._workspace_id(self)
3748
3748
3749 def get_shadow_repo(self):
3749 def get_shadow_repo(self):
3750 workspace_id = self.workspace_id
3750 workspace_id = self.workspace_id
3751 vcs_obj = self.target_repo.scm_instance()
3751 vcs_obj = self.target_repo.scm_instance()
3752 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3752 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3753 workspace_id)
3753 workspace_id)
3754 return vcs_obj._get_shadow_instance(shadow_repository_path)
3754 return vcs_obj._get_shadow_instance(shadow_repository_path)
3755
3755
3756
3756
3757 class PullRequestVersion(Base, _PullRequestBase):
3757 class PullRequestVersion(Base, _PullRequestBase):
3758 __tablename__ = 'pull_request_versions'
3758 __tablename__ = 'pull_request_versions'
3759 __table_args__ = (
3759 __table_args__ = (
3760 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3760 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3761 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3761 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3762 )
3762 )
3763
3763
3764 pull_request_version_id = Column(
3764 pull_request_version_id = Column(
3765 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3765 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3766 pull_request_id = Column(
3766 pull_request_id = Column(
3767 'pull_request_id', Integer(),
3767 'pull_request_id', Integer(),
3768 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3768 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3769 pull_request = relationship('PullRequest')
3769 pull_request = relationship('PullRequest')
3770
3770
3771 def __repr__(self):
3771 def __repr__(self):
3772 if self.pull_request_version_id:
3772 if self.pull_request_version_id:
3773 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3773 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3774 else:
3774 else:
3775 return '<DB:PullRequestVersion at %#x>' % id(self)
3775 return '<DB:PullRequestVersion at %#x>' % id(self)
3776
3776
3777 @property
3777 @property
3778 def reviewers(self):
3778 def reviewers(self):
3779 return self.pull_request.reviewers
3779 return self.pull_request.reviewers
3780
3780
3781 @property
3781 @property
3782 def versions(self):
3782 def versions(self):
3783 return self.pull_request.versions
3783 return self.pull_request.versions
3784
3784
3785 def is_closed(self):
3785 def is_closed(self):
3786 # calculate from original
3786 # calculate from original
3787 return self.pull_request.status == self.STATUS_CLOSED
3787 return self.pull_request.status == self.STATUS_CLOSED
3788
3788
3789 def calculated_review_status(self):
3789 def calculated_review_status(self):
3790 return self.pull_request.calculated_review_status()
3790 return self.pull_request.calculated_review_status()
3791
3791
3792 def reviewers_statuses(self):
3792 def reviewers_statuses(self):
3793 return self.pull_request.reviewers_statuses()
3793 return self.pull_request.reviewers_statuses()
3794
3794
3795
3795
3796 class PullRequestReviewers(Base, BaseModel):
3796 class PullRequestReviewers(Base, BaseModel):
3797 __tablename__ = 'pull_request_reviewers'
3797 __tablename__ = 'pull_request_reviewers'
3798 __table_args__ = (
3798 __table_args__ = (
3799 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3799 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3800 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3800 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3801 )
3801 )
3802
3802
3803 @hybrid_property
3803 @hybrid_property
3804 def reasons(self):
3804 def reasons(self):
3805 if not self._reasons:
3805 if not self._reasons:
3806 return []
3806 return []
3807 return self._reasons
3807 return self._reasons
3808
3808
3809 @reasons.setter
3809 @reasons.setter
3810 def reasons(self, val):
3810 def reasons(self, val):
3811 val = val or []
3811 val = val or []
3812 if any(not isinstance(x, basestring) for x in val):
3812 if any(not isinstance(x, basestring) for x in val):
3813 raise Exception('invalid reasons type, must be list of strings')
3813 raise Exception('invalid reasons type, must be list of strings')
3814 self._reasons = val
3814 self._reasons = val
3815
3815
3816 pull_requests_reviewers_id = Column(
3816 pull_requests_reviewers_id = Column(
3817 'pull_requests_reviewers_id', Integer(), nullable=False,
3817 'pull_requests_reviewers_id', Integer(), nullable=False,
3818 primary_key=True)
3818 primary_key=True)
3819 pull_request_id = Column(
3819 pull_request_id = Column(
3820 "pull_request_id", Integer(),
3820 "pull_request_id", Integer(),
3821 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3821 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3822 user_id = Column(
3822 user_id = Column(
3823 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3823 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3824 _reasons = Column(
3824 _reasons = Column(
3825 'reason', MutationList.as_mutable(
3825 'reason', MutationList.as_mutable(
3826 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3826 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3827
3827
3828 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3828 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3829 user = relationship('User')
3829 user = relationship('User')
3830 pull_request = relationship('PullRequest')
3830 pull_request = relationship('PullRequest')
3831
3831
3832 rule_data = Column(
3832 rule_data = Column(
3833 'rule_data_json',
3833 'rule_data_json',
3834 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3834 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3835
3835
3836 def rule_user_group_data(self):
3836 def rule_user_group_data(self):
3837 """
3837 """
3838 Returns the voting user group rule data for this reviewer
3838 Returns the voting user group rule data for this reviewer
3839 """
3839 """
3840
3840
3841 if self.rule_data and 'vote_rule' in self.rule_data:
3841 if self.rule_data and 'vote_rule' in self.rule_data:
3842 user_group_data = {}
3842 user_group_data = {}
3843 if 'rule_user_group_entry_id' in self.rule_data:
3843 if 'rule_user_group_entry_id' in self.rule_data:
3844 # means a group with voting rules !
3844 # means a group with voting rules !
3845 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3845 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3846 user_group_data['name'] = self.rule_data['rule_name']
3846 user_group_data['name'] = self.rule_data['rule_name']
3847 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3847 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3848
3848
3849 return user_group_data
3849 return user_group_data
3850
3850
3851 def __unicode__(self):
3851 def __unicode__(self):
3852 return u"<%s('id:%s')>" % (self.__class__.__name__,
3852 return u"<%s('id:%s')>" % (self.__class__.__name__,
3853 self.pull_requests_reviewers_id)
3853 self.pull_requests_reviewers_id)
3854
3854
3855
3855
3856 class Notification(Base, BaseModel):
3856 class Notification(Base, BaseModel):
3857 __tablename__ = 'notifications'
3857 __tablename__ = 'notifications'
3858 __table_args__ = (
3858 __table_args__ = (
3859 Index('notification_type_idx', 'type'),
3859 Index('notification_type_idx', 'type'),
3860 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3860 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3861 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3861 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3862 )
3862 )
3863
3863
3864 TYPE_CHANGESET_COMMENT = u'cs_comment'
3864 TYPE_CHANGESET_COMMENT = u'cs_comment'
3865 TYPE_MESSAGE = u'message'
3865 TYPE_MESSAGE = u'message'
3866 TYPE_MENTION = u'mention'
3866 TYPE_MENTION = u'mention'
3867 TYPE_REGISTRATION = u'registration'
3867 TYPE_REGISTRATION = u'registration'
3868 TYPE_PULL_REQUEST = u'pull_request'
3868 TYPE_PULL_REQUEST = u'pull_request'
3869 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3869 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3870
3870
3871 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3871 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3872 subject = Column('subject', Unicode(512), nullable=True)
3872 subject = Column('subject', Unicode(512), nullable=True)
3873 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3873 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3874 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3874 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3875 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3875 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3876 type_ = Column('type', Unicode(255))
3876 type_ = Column('type', Unicode(255))
3877
3877
3878 created_by_user = relationship('User')
3878 created_by_user = relationship('User')
3879 notifications_to_users = relationship('UserNotification', lazy='joined',
3879 notifications_to_users = relationship('UserNotification', lazy='joined',
3880 cascade="all, delete, delete-orphan")
3880 cascade="all, delete, delete-orphan")
3881
3881
3882 @property
3882 @property
3883 def recipients(self):
3883 def recipients(self):
3884 return [x.user for x in UserNotification.query()\
3884 return [x.user for x in UserNotification.query()\
3885 .filter(UserNotification.notification == self)\
3885 .filter(UserNotification.notification == self)\
3886 .order_by(UserNotification.user_id.asc()).all()]
3886 .order_by(UserNotification.user_id.asc()).all()]
3887
3887
3888 @classmethod
3888 @classmethod
3889 def create(cls, created_by, subject, body, recipients, type_=None):
3889 def create(cls, created_by, subject, body, recipients, type_=None):
3890 if type_ is None:
3890 if type_ is None:
3891 type_ = Notification.TYPE_MESSAGE
3891 type_ = Notification.TYPE_MESSAGE
3892
3892
3893 notification = cls()
3893 notification = cls()
3894 notification.created_by_user = created_by
3894 notification.created_by_user = created_by
3895 notification.subject = subject
3895 notification.subject = subject
3896 notification.body = body
3896 notification.body = body
3897 notification.type_ = type_
3897 notification.type_ = type_
3898 notification.created_on = datetime.datetime.now()
3898 notification.created_on = datetime.datetime.now()
3899
3899
3900 for u in recipients:
3900 for u in recipients:
3901 assoc = UserNotification()
3901 assoc = UserNotification()
3902 assoc.notification = notification
3902 assoc.notification = notification
3903
3903
3904 # if created_by is inside recipients mark his notification
3904 # if created_by is inside recipients mark his notification
3905 # as read
3905 # as read
3906 if u.user_id == created_by.user_id:
3906 if u.user_id == created_by.user_id:
3907 assoc.read = True
3907 assoc.read = True
3908
3908
3909 u.notifications.append(assoc)
3909 u.notifications.append(assoc)
3910 Session().add(notification)
3910 Session().add(notification)
3911
3911
3912 return notification
3912 return notification
3913
3913
3914
3914
3915 class UserNotification(Base, BaseModel):
3915 class UserNotification(Base, BaseModel):
3916 __tablename__ = 'user_to_notification'
3916 __tablename__ = 'user_to_notification'
3917 __table_args__ = (
3917 __table_args__ = (
3918 UniqueConstraint('user_id', 'notification_id'),
3918 UniqueConstraint('user_id', 'notification_id'),
3919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3920 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3920 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3921 )
3921 )
3922 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3922 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3923 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3923 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3924 read = Column('read', Boolean, default=False)
3924 read = Column('read', Boolean, default=False)
3925 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3925 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3926
3926
3927 user = relationship('User', lazy="joined")
3927 user = relationship('User', lazy="joined")
3928 notification = relationship('Notification', lazy="joined",
3928 notification = relationship('Notification', lazy="joined",
3929 order_by=lambda: Notification.created_on.desc(),)
3929 order_by=lambda: Notification.created_on.desc(),)
3930
3930
3931 def mark_as_read(self):
3931 def mark_as_read(self):
3932 self.read = True
3932 self.read = True
3933 Session().add(self)
3933 Session().add(self)
3934
3934
3935
3935
3936 class Gist(Base, BaseModel):
3936 class Gist(Base, BaseModel):
3937 __tablename__ = 'gists'
3937 __tablename__ = 'gists'
3938 __table_args__ = (
3938 __table_args__ = (
3939 Index('g_gist_access_id_idx', 'gist_access_id'),
3939 Index('g_gist_access_id_idx', 'gist_access_id'),
3940 Index('g_created_on_idx', 'created_on'),
3940 Index('g_created_on_idx', 'created_on'),
3941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3943 )
3943 )
3944 GIST_PUBLIC = u'public'
3944 GIST_PUBLIC = u'public'
3945 GIST_PRIVATE = u'private'
3945 GIST_PRIVATE = u'private'
3946 DEFAULT_FILENAME = u'gistfile1.txt'
3946 DEFAULT_FILENAME = u'gistfile1.txt'
3947
3947
3948 ACL_LEVEL_PUBLIC = u'acl_public'
3948 ACL_LEVEL_PUBLIC = u'acl_public'
3949 ACL_LEVEL_PRIVATE = u'acl_private'
3949 ACL_LEVEL_PRIVATE = u'acl_private'
3950
3950
3951 gist_id = Column('gist_id', Integer(), primary_key=True)
3951 gist_id = Column('gist_id', Integer(), primary_key=True)
3952 gist_access_id = Column('gist_access_id', Unicode(250))
3952 gist_access_id = Column('gist_access_id', Unicode(250))
3953 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3953 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3954 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3954 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3955 gist_expires = Column('gist_expires', Float(53), nullable=False)
3955 gist_expires = Column('gist_expires', Float(53), nullable=False)
3956 gist_type = Column('gist_type', Unicode(128), nullable=False)
3956 gist_type = Column('gist_type', Unicode(128), nullable=False)
3957 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3957 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3958 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3958 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3959 acl_level = Column('acl_level', Unicode(128), nullable=True)
3959 acl_level = Column('acl_level', Unicode(128), nullable=True)
3960
3960
3961 owner = relationship('User')
3961 owner = relationship('User')
3962
3962
3963 def __repr__(self):
3963 def __repr__(self):
3964 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3964 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3965
3965
3966 @hybrid_property
3966 @hybrid_property
3967 def description_safe(self):
3967 def description_safe(self):
3968 from rhodecode.lib import helpers as h
3968 from rhodecode.lib import helpers as h
3969 return h.escape(self.gist_description)
3969 return h.escape(self.gist_description)
3970
3970
3971 @classmethod
3971 @classmethod
3972 def get_or_404(cls, id_):
3972 def get_or_404(cls, id_):
3973 from pyramid.httpexceptions import HTTPNotFound
3973 from pyramid.httpexceptions import HTTPNotFound
3974
3974
3975 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3975 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3976 if not res:
3976 if not res:
3977 raise HTTPNotFound()
3977 raise HTTPNotFound()
3978 return res
3978 return res
3979
3979
3980 @classmethod
3980 @classmethod
3981 def get_by_access_id(cls, gist_access_id):
3981 def get_by_access_id(cls, gist_access_id):
3982 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3982 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3983
3983
3984 def gist_url(self):
3984 def gist_url(self):
3985 from rhodecode.model.gist import GistModel
3985 from rhodecode.model.gist import GistModel
3986 return GistModel().get_url(self)
3986 return GistModel().get_url(self)
3987
3987
3988 @classmethod
3988 @classmethod
3989 def base_path(cls):
3989 def base_path(cls):
3990 """
3990 """
3991 Returns base path when all gists are stored
3991 Returns base path when all gists are stored
3992
3992
3993 :param cls:
3993 :param cls:
3994 """
3994 """
3995 from rhodecode.model.gist import GIST_STORE_LOC
3995 from rhodecode.model.gist import GIST_STORE_LOC
3996 q = Session().query(RhodeCodeUi)\
3996 q = Session().query(RhodeCodeUi)\
3997 .filter(RhodeCodeUi.ui_key == URL_SEP)
3997 .filter(RhodeCodeUi.ui_key == URL_SEP)
3998 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3998 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3999 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3999 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4000
4000
4001 def get_api_data(self):
4001 def get_api_data(self):
4002 """
4002 """
4003 Common function for generating gist related data for API
4003 Common function for generating gist related data for API
4004 """
4004 """
4005 gist = self
4005 gist = self
4006 data = {
4006 data = {
4007 'gist_id': gist.gist_id,
4007 'gist_id': gist.gist_id,
4008 'type': gist.gist_type,
4008 'type': gist.gist_type,
4009 'access_id': gist.gist_access_id,
4009 'access_id': gist.gist_access_id,
4010 'description': gist.gist_description,
4010 'description': gist.gist_description,
4011 'url': gist.gist_url(),
4011 'url': gist.gist_url(),
4012 'expires': gist.gist_expires,
4012 'expires': gist.gist_expires,
4013 'created_on': gist.created_on,
4013 'created_on': gist.created_on,
4014 'modified_at': gist.modified_at,
4014 'modified_at': gist.modified_at,
4015 'content': None,
4015 'content': None,
4016 'acl_level': gist.acl_level,
4016 'acl_level': gist.acl_level,
4017 }
4017 }
4018 return data
4018 return data
4019
4019
4020 def __json__(self):
4020 def __json__(self):
4021 data = dict(
4021 data = dict(
4022 )
4022 )
4023 data.update(self.get_api_data())
4023 data.update(self.get_api_data())
4024 return data
4024 return data
4025 # SCM functions
4025 # SCM functions
4026
4026
4027 def scm_instance(self, **kwargs):
4027 def scm_instance(self, **kwargs):
4028 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4028 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4029 return get_vcs_instance(
4029 return get_vcs_instance(
4030 repo_path=safe_str(full_repo_path), create=False)
4030 repo_path=safe_str(full_repo_path), create=False)
4031
4031
4032
4032
4033 class ExternalIdentity(Base, BaseModel):
4033 class ExternalIdentity(Base, BaseModel):
4034 __tablename__ = 'external_identities'
4034 __tablename__ = 'external_identities'
4035 __table_args__ = (
4035 __table_args__ = (
4036 Index('local_user_id_idx', 'local_user_id'),
4036 Index('local_user_id_idx', 'local_user_id'),
4037 Index('external_id_idx', 'external_id'),
4037 Index('external_id_idx', 'external_id'),
4038 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4038 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4039 'mysql_charset': 'utf8'})
4039 'mysql_charset': 'utf8'})
4040
4040
4041 external_id = Column('external_id', Unicode(255), default=u'',
4041 external_id = Column('external_id', Unicode(255), default=u'',
4042 primary_key=True)
4042 primary_key=True)
4043 external_username = Column('external_username', Unicode(1024), default=u'')
4043 external_username = Column('external_username', Unicode(1024), default=u'')
4044 local_user_id = Column('local_user_id', Integer(),
4044 local_user_id = Column('local_user_id', Integer(),
4045 ForeignKey('users.user_id'), primary_key=True)
4045 ForeignKey('users.user_id'), primary_key=True)
4046 provider_name = Column('provider_name', Unicode(255), default=u'',
4046 provider_name = Column('provider_name', Unicode(255), default=u'',
4047 primary_key=True)
4047 primary_key=True)
4048 access_token = Column('access_token', String(1024), default=u'')
4048 access_token = Column('access_token', String(1024), default=u'')
4049 alt_token = Column('alt_token', String(1024), default=u'')
4049 alt_token = Column('alt_token', String(1024), default=u'')
4050 token_secret = Column('token_secret', String(1024), default=u'')
4050 token_secret = Column('token_secret', String(1024), default=u'')
4051
4051
4052 @classmethod
4052 @classmethod
4053 def by_external_id_and_provider(cls, external_id, provider_name,
4053 def by_external_id_and_provider(cls, external_id, provider_name,
4054 local_user_id=None):
4054 local_user_id=None):
4055 """
4055 """
4056 Returns ExternalIdentity instance based on search params
4056 Returns ExternalIdentity instance based on search params
4057
4057
4058 :param external_id:
4058 :param external_id:
4059 :param provider_name:
4059 :param provider_name:
4060 :return: ExternalIdentity
4060 :return: ExternalIdentity
4061 """
4061 """
4062 query = cls.query()
4062 query = cls.query()
4063 query = query.filter(cls.external_id == external_id)
4063 query = query.filter(cls.external_id == external_id)
4064 query = query.filter(cls.provider_name == provider_name)
4064 query = query.filter(cls.provider_name == provider_name)
4065 if local_user_id:
4065 if local_user_id:
4066 query = query.filter(cls.local_user_id == local_user_id)
4066 query = query.filter(cls.local_user_id == local_user_id)
4067 return query.first()
4067 return query.first()
4068
4068
4069 @classmethod
4069 @classmethod
4070 def user_by_external_id_and_provider(cls, external_id, provider_name):
4070 def user_by_external_id_and_provider(cls, external_id, provider_name):
4071 """
4071 """
4072 Returns User instance based on search params
4072 Returns User instance based on search params
4073
4073
4074 :param external_id:
4074 :param external_id:
4075 :param provider_name:
4075 :param provider_name:
4076 :return: User
4076 :return: User
4077 """
4077 """
4078 query = User.query()
4078 query = User.query()
4079 query = query.filter(cls.external_id == external_id)
4079 query = query.filter(cls.external_id == external_id)
4080 query = query.filter(cls.provider_name == provider_name)
4080 query = query.filter(cls.provider_name == provider_name)
4081 query = query.filter(User.user_id == cls.local_user_id)
4081 query = query.filter(User.user_id == cls.local_user_id)
4082 return query.first()
4082 return query.first()
4083
4083
4084 @classmethod
4084 @classmethod
4085 def by_local_user_id(cls, local_user_id):
4085 def by_local_user_id(cls, local_user_id):
4086 """
4086 """
4087 Returns all tokens for user
4087 Returns all tokens for user
4088
4088
4089 :param local_user_id:
4089 :param local_user_id:
4090 :return: ExternalIdentity
4090 :return: ExternalIdentity
4091 """
4091 """
4092 query = cls.query()
4092 query = cls.query()
4093 query = query.filter(cls.local_user_id == local_user_id)
4093 query = query.filter(cls.local_user_id == local_user_id)
4094 return query
4094 return query
4095
4095
4096
4096
4097 class Integration(Base, BaseModel):
4097 class Integration(Base, BaseModel):
4098 __tablename__ = 'integrations'
4098 __tablename__ = 'integrations'
4099 __table_args__ = (
4099 __table_args__ = (
4100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4101 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4101 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4102 )
4102 )
4103
4103
4104 integration_id = Column('integration_id', Integer(), primary_key=True)
4104 integration_id = Column('integration_id', Integer(), primary_key=True)
4105 integration_type = Column('integration_type', String(255))
4105 integration_type = Column('integration_type', String(255))
4106 enabled = Column('enabled', Boolean(), nullable=False)
4106 enabled = Column('enabled', Boolean(), nullable=False)
4107 name = Column('name', String(255), nullable=False)
4107 name = Column('name', String(255), nullable=False)
4108 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4108 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4109 default=False)
4109 default=False)
4110
4110
4111 settings = Column(
4111 settings = Column(
4112 'settings_json', MutationObj.as_mutable(
4112 'settings_json', MutationObj.as_mutable(
4113 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4113 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4114 repo_id = Column(
4114 repo_id = Column(
4115 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4115 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4116 nullable=True, unique=None, default=None)
4116 nullable=True, unique=None, default=None)
4117 repo = relationship('Repository', lazy='joined')
4117 repo = relationship('Repository', lazy='joined')
4118
4118
4119 repo_group_id = Column(
4119 repo_group_id = Column(
4120 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4120 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4121 nullable=True, unique=None, default=None)
4121 nullable=True, unique=None, default=None)
4122 repo_group = relationship('RepoGroup', lazy='joined')
4122 repo_group = relationship('RepoGroup', lazy='joined')
4123
4123
4124 @property
4124 @property
4125 def scope(self):
4125 def scope(self):
4126 if self.repo:
4126 if self.repo:
4127 return repr(self.repo)
4127 return repr(self.repo)
4128 if self.repo_group:
4128 if self.repo_group:
4129 if self.child_repos_only:
4129 if self.child_repos_only:
4130 return repr(self.repo_group) + ' (child repos only)'
4130 return repr(self.repo_group) + ' (child repos only)'
4131 else:
4131 else:
4132 return repr(self.repo_group) + ' (recursive)'
4132 return repr(self.repo_group) + ' (recursive)'
4133 if self.child_repos_only:
4133 if self.child_repos_only:
4134 return 'root_repos'
4134 return 'root_repos'
4135 return 'global'
4135 return 'global'
4136
4136
4137 def __repr__(self):
4137 def __repr__(self):
4138 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4138 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4139
4139
4140
4140
4141 class RepoReviewRuleUser(Base, BaseModel):
4141 class RepoReviewRuleUser(Base, BaseModel):
4142 __tablename__ = 'repo_review_rules_users'
4142 __tablename__ = 'repo_review_rules_users'
4143 __table_args__ = (
4143 __table_args__ = (
4144 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4144 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4145 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4145 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4146 )
4146 )
4147
4147
4148 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4148 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4149 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4149 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4150 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4150 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4151 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4151 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4152 user = relationship('User')
4152 user = relationship('User')
4153
4153
4154 def rule_data(self):
4154 def rule_data(self):
4155 return {
4155 return {
4156 'mandatory': self.mandatory
4156 'mandatory': self.mandatory
4157 }
4157 }
4158
4158
4159
4159
4160 class RepoReviewRuleUserGroup(Base, BaseModel):
4160 class RepoReviewRuleUserGroup(Base, BaseModel):
4161 __tablename__ = 'repo_review_rules_users_groups'
4161 __tablename__ = 'repo_review_rules_users_groups'
4162 __table_args__ = (
4162 __table_args__ = (
4163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4164 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4164 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4165 )
4165 )
4166 VOTE_RULE_ALL = -1
4166 VOTE_RULE_ALL = -1
4167
4167
4168 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4168 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4169 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4169 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4170 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4170 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4171 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4171 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4172 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4172 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4173 users_group = relationship('UserGroup')
4173 users_group = relationship('UserGroup')
4174
4174
4175 def rule_data(self):
4175 def rule_data(self):
4176 return {
4176 return {
4177 'mandatory': self.mandatory,
4177 'mandatory': self.mandatory,
4178 'vote_rule': self.vote_rule
4178 'vote_rule': self.vote_rule
4179 }
4179 }
4180
4180
4181 @property
4181 @property
4182 def vote_rule_label(self):
4182 def vote_rule_label(self):
4183 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4183 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4184 return 'all must vote'
4184 return 'all must vote'
4185 else:
4185 else:
4186 return 'min. vote {}'.format(self.vote_rule)
4186 return 'min. vote {}'.format(self.vote_rule)
4187
4187
4188
4188
4189 class RepoReviewRule(Base, BaseModel):
4189 class RepoReviewRule(Base, BaseModel):
4190 __tablename__ = 'repo_review_rules'
4190 __tablename__ = 'repo_review_rules'
4191 __table_args__ = (
4191 __table_args__ = (
4192 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4192 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4193 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4193 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4194 )
4194 )
4195
4195
4196 repo_review_rule_id = Column(
4196 repo_review_rule_id = Column(
4197 'repo_review_rule_id', Integer(), primary_key=True)
4197 'repo_review_rule_id', Integer(), primary_key=True)
4198 repo_id = Column(
4198 repo_id = Column(
4199 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4199 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4200 repo = relationship('Repository', backref='review_rules')
4200 repo = relationship('Repository', backref='review_rules')
4201
4201
4202 review_rule_name = Column('review_rule_name', String(255))
4202 review_rule_name = Column('review_rule_name', String(255))
4203 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4203 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4204 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4204 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4205 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4205 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4206
4206
4207 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4207 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4208 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4208 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4209 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4209 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4210 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4210 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4211
4211
4212 rule_users = relationship('RepoReviewRuleUser')
4212 rule_users = relationship('RepoReviewRuleUser')
4213 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4213 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4214
4214
4215 def _validate_glob(self, value):
4215 def _validate_glob(self, value):
4216 re.compile('^' + glob2re(value) + '$')
4216 re.compile('^' + glob2re(value) + '$')
4217
4217
4218 @hybrid_property
4218 @hybrid_property
4219 def source_branch_pattern(self):
4219 def source_branch_pattern(self):
4220 return self._branch_pattern or '*'
4220 return self._branch_pattern or '*'
4221
4221
4222 @source_branch_pattern.setter
4222 @source_branch_pattern.setter
4223 def source_branch_pattern(self, value):
4223 def source_branch_pattern(self, value):
4224 self._validate_glob(value)
4224 self._validate_glob(value)
4225 self._branch_pattern = value or '*'
4225 self._branch_pattern = value or '*'
4226
4226
4227 @hybrid_property
4227 @hybrid_property
4228 def target_branch_pattern(self):
4228 def target_branch_pattern(self):
4229 return self._target_branch_pattern or '*'
4229 return self._target_branch_pattern or '*'
4230
4230
4231 @target_branch_pattern.setter
4231 @target_branch_pattern.setter
4232 def target_branch_pattern(self, value):
4232 def target_branch_pattern(self, value):
4233 self._validate_glob(value)
4233 self._validate_glob(value)
4234 self._target_branch_pattern = value or '*'
4234 self._target_branch_pattern = value or '*'
4235
4235
4236 @hybrid_property
4236 @hybrid_property
4237 def file_pattern(self):
4237 def file_pattern(self):
4238 return self._file_pattern or '*'
4238 return self._file_pattern or '*'
4239
4239
4240 @file_pattern.setter
4240 @file_pattern.setter
4241 def file_pattern(self, value):
4241 def file_pattern(self, value):
4242 self._validate_glob(value)
4242 self._validate_glob(value)
4243 self._file_pattern = value or '*'
4243 self._file_pattern = value or '*'
4244
4244
4245 def matches(self, source_branch, target_branch, files_changed):
4245 def matches(self, source_branch, target_branch, files_changed):
4246 """
4246 """
4247 Check if this review rule matches a branch/files in a pull request
4247 Check if this review rule matches a branch/files in a pull request
4248
4248
4249 :param source_branch: source branch name for the commit
4249 :param source_branch: source branch name for the commit
4250 :param target_branch: target branch name for the commit
4250 :param target_branch: target branch name for the commit
4251 :param files_changed: list of file paths changed in the pull request
4251 :param files_changed: list of file paths changed in the pull request
4252 """
4252 """
4253
4253
4254 source_branch = source_branch or ''
4254 source_branch = source_branch or ''
4255 target_branch = target_branch or ''
4255 target_branch = target_branch or ''
4256 files_changed = files_changed or []
4256 files_changed = files_changed or []
4257
4257
4258 branch_matches = True
4258 branch_matches = True
4259 if source_branch or target_branch:
4259 if source_branch or target_branch:
4260 if self.source_branch_pattern == '*':
4260 if self.source_branch_pattern == '*':
4261 source_branch_match = True
4261 source_branch_match = True
4262 else:
4262 else:
4263 source_branch_regex = re.compile(
4263 source_branch_regex = re.compile(
4264 '^' + glob2re(self.source_branch_pattern) + '$')
4264 '^' + glob2re(self.source_branch_pattern) + '$')
4265 source_branch_match = bool(source_branch_regex.search(source_branch))
4265 source_branch_match = bool(source_branch_regex.search(source_branch))
4266 if self.target_branch_pattern == '*':
4266 if self.target_branch_pattern == '*':
4267 target_branch_match = True
4267 target_branch_match = True
4268 else:
4268 else:
4269 target_branch_regex = re.compile(
4269 target_branch_regex = re.compile(
4270 '^' + glob2re(self.target_branch_pattern) + '$')
4270 '^' + glob2re(self.target_branch_pattern) + '$')
4271 target_branch_match = bool(target_branch_regex.search(target_branch))
4271 target_branch_match = bool(target_branch_regex.search(target_branch))
4272
4272
4273 branch_matches = source_branch_match and target_branch_match
4273 branch_matches = source_branch_match and target_branch_match
4274
4274
4275 files_matches = True
4275 files_matches = True
4276 if self.file_pattern != '*':
4276 if self.file_pattern != '*':
4277 files_matches = False
4277 files_matches = False
4278 file_regex = re.compile(glob2re(self.file_pattern))
4278 file_regex = re.compile(glob2re(self.file_pattern))
4279 for filename in files_changed:
4279 for filename in files_changed:
4280 if file_regex.search(filename):
4280 if file_regex.search(filename):
4281 files_matches = True
4281 files_matches = True
4282 break
4282 break
4283
4283
4284 return branch_matches and files_matches
4284 return branch_matches and files_matches
4285
4285
4286 @property
4286 @property
4287 def review_users(self):
4287 def review_users(self):
4288 """ Returns the users which this rule applies to """
4288 """ Returns the users which this rule applies to """
4289
4289
4290 users = collections.OrderedDict()
4290 users = collections.OrderedDict()
4291
4291
4292 for rule_user in self.rule_users:
4292 for rule_user in self.rule_users:
4293 if rule_user.user.active:
4293 if rule_user.user.active:
4294 if rule_user.user not in users:
4294 if rule_user.user not in users:
4295 users[rule_user.user.username] = {
4295 users[rule_user.user.username] = {
4296 'user': rule_user.user,
4296 'user': rule_user.user,
4297 'source': 'user',
4297 'source': 'user',
4298 'source_data': {},
4298 'source_data': {},
4299 'data': rule_user.rule_data()
4299 'data': rule_user.rule_data()
4300 }
4300 }
4301
4301
4302 for rule_user_group in self.rule_user_groups:
4302 for rule_user_group in self.rule_user_groups:
4303 source_data = {
4303 source_data = {
4304 'user_group_id': rule_user_group.users_group.users_group_id,
4304 'user_group_id': rule_user_group.users_group.users_group_id,
4305 'name': rule_user_group.users_group.users_group_name,
4305 'name': rule_user_group.users_group.users_group_name,
4306 'members': len(rule_user_group.users_group.members)
4306 'members': len(rule_user_group.users_group.members)
4307 }
4307 }
4308 for member in rule_user_group.users_group.members:
4308 for member in rule_user_group.users_group.members:
4309 if member.user.active:
4309 if member.user.active:
4310 key = member.user.username
4310 key = member.user.username
4311 if key in users:
4311 if key in users:
4312 # skip this member as we have him already
4312 # skip this member as we have him already
4313 # this prevents from override the "first" matched
4313 # this prevents from override the "first" matched
4314 # users with duplicates in multiple groups
4314 # users with duplicates in multiple groups
4315 continue
4315 continue
4316
4316
4317 users[key] = {
4317 users[key] = {
4318 'user': member.user,
4318 'user': member.user,
4319 'source': 'user_group',
4319 'source': 'user_group',
4320 'source_data': source_data,
4320 'source_data': source_data,
4321 'data': rule_user_group.rule_data()
4321 'data': rule_user_group.rule_data()
4322 }
4322 }
4323
4323
4324 return users
4324 return users
4325
4325
4326 def user_group_vote_rule(self):
4326 def user_group_vote_rule(self):
4327 rules = []
4327 rules = []
4328 if self.rule_user_groups:
4328 if self.rule_user_groups:
4329 for user_group in self.rule_user_groups:
4329 for user_group in self.rule_user_groups:
4330 rules.append(user_group)
4330 rules.append(user_group)
4331 return rules
4331 return rules
4332
4332
4333 def __repr__(self):
4333 def __repr__(self):
4334 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4334 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4335 self.repo_review_rule_id, self.repo)
4335 self.repo_review_rule_id, self.repo)
4336
4336
4337
4337
4338 class ScheduleEntry(Base, BaseModel):
4338 class ScheduleEntry(Base, BaseModel):
4339 __tablename__ = 'schedule_entries'
4339 __tablename__ = 'schedule_entries'
4340 __table_args__ = (
4340 __table_args__ = (
4341 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4341 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4342 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4342 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4343 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4343 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4344 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4344 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4345 )
4345 )
4346 schedule_types = ['crontab', 'timedelta', 'integer']
4346 schedule_types = ['crontab', 'timedelta', 'integer']
4347 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4347 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4348
4348
4349 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4349 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4350 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4350 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4351 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4351 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4352
4352
4353 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4353 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4354 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4354 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4355
4355
4356 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4356 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4357 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4357 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4358
4358
4359 # task
4359 # task
4360 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4360 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4361 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4361 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4362 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4362 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4363 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4363 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4364
4364
4365 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4365 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4366 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4366 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4367
4367
4368 @hybrid_property
4368 @hybrid_property
4369 def schedule_type(self):
4369 def schedule_type(self):
4370 return self._schedule_type
4370 return self._schedule_type
4371
4371
4372 @schedule_type.setter
4372 @schedule_type.setter
4373 def schedule_type(self, val):
4373 def schedule_type(self, val):
4374 if val not in self.schedule_types:
4374 if val not in self.schedule_types:
4375 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4375 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4376 val, self.schedule_type))
4376 val, self.schedule_type))
4377
4377
4378 self._schedule_type = val
4378 self._schedule_type = val
4379
4379
4380 @classmethod
4380 @classmethod
4381 def get_uid(cls, obj):
4381 def get_uid(cls, obj):
4382 args = obj.task_args
4382 args = obj.task_args
4383 kwargs = obj.task_kwargs
4383 kwargs = obj.task_kwargs
4384 if isinstance(args, JsonRaw):
4384 if isinstance(args, JsonRaw):
4385 try:
4385 try:
4386 args = json.loads(args)
4386 args = json.loads(args)
4387 except ValueError:
4387 except ValueError:
4388 args = tuple()
4388 args = tuple()
4389
4389
4390 if isinstance(kwargs, JsonRaw):
4390 if isinstance(kwargs, JsonRaw):
4391 try:
4391 try:
4392 kwargs = json.loads(kwargs)
4392 kwargs = json.loads(kwargs)
4393 except ValueError:
4393 except ValueError:
4394 kwargs = dict()
4394 kwargs = dict()
4395
4395
4396 dot_notation = obj.task_dot_notation
4396 dot_notation = obj.task_dot_notation
4397 val = '.'.join(map(safe_str, [
4397 val = '.'.join(map(safe_str, [
4398 sorted(dot_notation), args, sorted(kwargs.items())]))
4398 sorted(dot_notation), args, sorted(kwargs.items())]))
4399 return hashlib.sha1(val).hexdigest()
4399 return hashlib.sha1(val).hexdigest()
4400
4400
4401 @classmethod
4401 @classmethod
4402 def get_by_schedule_name(cls, schedule_name):
4402 def get_by_schedule_name(cls, schedule_name):
4403 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4403 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4404
4404
4405 @classmethod
4405 @classmethod
4406 def get_by_schedule_id(cls, schedule_id):
4406 def get_by_schedule_id(cls, schedule_id):
4407 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4407 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4408
4408
4409 @property
4409 @property
4410 def task(self):
4410 def task(self):
4411 return self.task_dot_notation
4411 return self.task_dot_notation
4412
4412
4413 @property
4413 @property
4414 def schedule(self):
4414 def schedule(self):
4415 from rhodecode.lib.celerylib.utils import raw_2_schedule
4415 from rhodecode.lib.celerylib.utils import raw_2_schedule
4416 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4416 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4417 return schedule
4417 return schedule
4418
4418
4419 @property
4419 @property
4420 def args(self):
4420 def args(self):
4421 try:
4421 try:
4422 return list(self.task_args or [])
4422 return list(self.task_args or [])
4423 except ValueError:
4423 except ValueError:
4424 return list()
4424 return list()
4425
4425
4426 @property
4426 @property
4427 def kwargs(self):
4427 def kwargs(self):
4428 try:
4428 try:
4429 return dict(self.task_kwargs or {})
4429 return dict(self.task_kwargs or {})
4430 except ValueError:
4430 except ValueError:
4431 return dict()
4431 return dict()
4432
4432
4433 def _as_raw(self, val):
4433 def _as_raw(self, val):
4434 if hasattr(val, 'de_coerce'):
4434 if hasattr(val, 'de_coerce'):
4435 val = val.de_coerce()
4435 val = val.de_coerce()
4436 if val:
4436 if val:
4437 val = json.dumps(val)
4437 val = json.dumps(val)
4438
4438
4439 return val
4439 return val
4440
4440
4441 @property
4441 @property
4442 def schedule_definition_raw(self):
4442 def schedule_definition_raw(self):
4443 return self._as_raw(self.schedule_definition)
4443 return self._as_raw(self.schedule_definition)
4444
4444
4445 @property
4445 @property
4446 def args_raw(self):
4446 def args_raw(self):
4447 return self._as_raw(self.task_args)
4447 return self._as_raw(self.task_args)
4448
4448
4449 @property
4449 @property
4450 def kwargs_raw(self):
4450 def kwargs_raw(self):
4451 return self._as_raw(self.task_kwargs)
4451 return self._as_raw(self.task_kwargs)
4452
4452
4453 def __repr__(self):
4453 def __repr__(self):
4454 return '<DB:ScheduleEntry({}:{})>'.format(
4454 return '<DB:ScheduleEntry({}:{})>'.format(
4455 self.schedule_entry_id, self.schedule_name)
4455 self.schedule_entry_id, self.schedule_name)
4456
4456
4457
4457
4458 @event.listens_for(ScheduleEntry, 'before_update')
4458 @event.listens_for(ScheduleEntry, 'before_update')
4459 def update_task_uid(mapper, connection, target):
4459 def update_task_uid(mapper, connection, target):
4460 target.task_uid = ScheduleEntry.get_uid(target)
4460 target.task_uid = ScheduleEntry.get_uid(target)
4461
4461
4462
4462
4463 @event.listens_for(ScheduleEntry, 'before_insert')
4463 @event.listens_for(ScheduleEntry, 'before_insert')
4464 def set_task_uid(mapper, connection, target):
4464 def set_task_uid(mapper, connection, target):
4465 target.task_uid = ScheduleEntry.get_uid(target)
4465 target.task_uid = ScheduleEntry.get_uid(target)
4466
4466
4467
4467
4468 class _BaseBranchPerms(BaseModel):
4468 class _BaseBranchPerms(BaseModel):
4469 @classmethod
4469 @classmethod
4470 def compute_hash(cls, value):
4470 def compute_hash(cls, value):
4471 return md5_safe(value)
4471 return md5_safe(value)
4472
4472
4473 @hybrid_property
4473 @hybrid_property
4474 def branch_pattern(self):
4474 def branch_pattern(self):
4475 return self._branch_pattern or '*'
4475 return self._branch_pattern or '*'
4476
4476
4477 @hybrid_property
4477 @hybrid_property
4478 def branch_hash(self):
4478 def branch_hash(self):
4479 return self._branch_hash
4479 return self._branch_hash
4480
4480
4481 def _validate_glob(self, value):
4481 def _validate_glob(self, value):
4482 re.compile('^' + glob2re(value) + '$')
4482 re.compile('^' + glob2re(value) + '$')
4483
4483
4484 @branch_pattern.setter
4484 @branch_pattern.setter
4485 def branch_pattern(self, value):
4485 def branch_pattern(self, value):
4486 self._validate_glob(value)
4486 self._validate_glob(value)
4487 self._branch_pattern = value or '*'
4487 self._branch_pattern = value or '*'
4488 # set the Hash when setting the branch pattern
4488 # set the Hash when setting the branch pattern
4489 self._branch_hash = self.compute_hash(self._branch_pattern)
4489 self._branch_hash = self.compute_hash(self._branch_pattern)
4490
4490
4491 def matches(self, branch):
4491 def matches(self, branch):
4492 """
4492 """
4493 Check if this the branch matches entry
4493 Check if this the branch matches entry
4494
4494
4495 :param branch: branch name for the commit
4495 :param branch: branch name for the commit
4496 """
4496 """
4497
4497
4498 branch = branch or ''
4498 branch = branch or ''
4499
4499
4500 branch_matches = True
4500 branch_matches = True
4501 if branch:
4501 if branch:
4502 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4502 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4503 branch_matches = bool(branch_regex.search(branch))
4503 branch_matches = bool(branch_regex.search(branch))
4504
4504
4505 return branch_matches
4505 return branch_matches
4506
4506
4507
4507
4508 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4508 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4509 __tablename__ = 'user_to_repo_branch_permissions'
4509 __tablename__ = 'user_to_repo_branch_permissions'
4510 __table_args__ = (
4510 __table_args__ = (
4511 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4511 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4512 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4512 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4513 )
4513 )
4514
4514
4515 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4515 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4516
4516
4517 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4517 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4518 repo = relationship('Repository', backref='user_branch_perms')
4518 repo = relationship('Repository', backref='user_branch_perms')
4519
4519
4520 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4520 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4521 permission = relationship('Permission')
4521 permission = relationship('Permission')
4522
4522
4523 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4523 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4524 user_repo_to_perm = relationship('UserRepoToPerm')
4524 user_repo_to_perm = relationship('UserRepoToPerm')
4525
4525
4526 rule_order = Column('rule_order', Integer(), nullable=False)
4526 rule_order = Column('rule_order', Integer(), nullable=False)
4527 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4527 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4528 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4528 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4529
4529
4530 def __unicode__(self):
4530 def __unicode__(self):
4531 return u'<UserBranchPermission(%s => %r)>' % (
4531 return u'<UserBranchPermission(%s => %r)>' % (
4532 self.user_repo_to_perm, self.branch_pattern)
4532 self.user_repo_to_perm, self.branch_pattern)
4533
4533
4534
4534
4535 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4535 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4536 __tablename__ = 'user_group_to_repo_branch_permissions'
4536 __tablename__ = 'user_group_to_repo_branch_permissions'
4537 __table_args__ = (
4537 __table_args__ = (
4538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4540 )
4540 )
4541
4541
4542 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4542 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4543
4543
4544 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4544 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4545 repo = relationship('Repository', backref='user_group_branch_perms')
4545 repo = relationship('Repository', backref='user_group_branch_perms')
4546
4546
4547 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4547 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4548 permission = relationship('Permission')
4548 permission = relationship('Permission')
4549
4549
4550 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4550 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4551 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4551 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4552
4552
4553 rule_order = Column('rule_order', Integer(), nullable=False)
4553 rule_order = Column('rule_order', Integer(), nullable=False)
4554 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4554 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4555 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4555 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4556
4556
4557 def __unicode__(self):
4557 def __unicode__(self):
4558 return u'<UserBranchPermission(%s => %r)>' % (
4558 return u'<UserBranchPermission(%s => %r)>' % (
4559 self.user_group_repo_to_perm, self.branch_pattern)
4559 self.user_group_repo_to_perm, self.branch_pattern)
4560
4560
4561
4561
4562 class DbMigrateVersion(Base, BaseModel):
4562 class DbMigrateVersion(Base, BaseModel):
4563 __tablename__ = 'db_migrate_version'
4563 __tablename__ = 'db_migrate_version'
4564 __table_args__ = (
4564 __table_args__ = (
4565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4567 )
4567 )
4568 repository_id = Column('repository_id', String(250), primary_key=True)
4568 repository_id = Column('repository_id', String(250), primary_key=True)
4569 repository_path = Column('repository_path', Text)
4569 repository_path = Column('repository_path', Text)
4570 version = Column('version', Integer)
4570 version = Column('version', Integer)
4571
4571
4572
4572
4573 class DbSession(Base, BaseModel):
4573 class DbSession(Base, BaseModel):
4574 __tablename__ = 'db_session'
4574 __tablename__ = 'db_session'
4575 __table_args__ = (
4575 __table_args__ = (
4576 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4576 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4577 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4577 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4578 )
4578 )
4579
4579
4580 def __repr__(self):
4580 def __repr__(self):
4581 return '<DB:DbSession({})>'.format(self.id)
4581 return '<DB:DbSession({})>'.format(self.id)
4582
4582
4583 id = Column('id', Integer())
4583 id = Column('id', Integer())
4584 namespace = Column('namespace', String(255), primary_key=True)
4584 namespace = Column('namespace', String(255), primary_key=True)
4585 accessed = Column('accessed', DateTime, nullable=False)
4585 accessed = Column('accessed', DateTime, nullable=False)
4586 created = Column('created', DateTime, nullable=False)
4586 created = Column('created', DateTime, nullable=False)
4587 data = Column('data', PickleType, nullable=False)
4587 data = Column('data', PickleType, nullable=False)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now