##// END OF EJS Templates
python3: removed use of xrang
super-admin -
r4906:71eadd2f default
parent child Browse files
Show More

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

@@ -1,141 +1,141 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 import pytest
22 import pytest
23
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_error
24 from rhodecode.api.tests.utils import build_data, api_call, assert_error
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetRepoChangeset(object):
28 class TestGetRepoChangeset(object):
29 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
29 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
30 def test_get_repo_changeset(self, details, backend):
30 def test_get_repo_changeset(self, details, backend):
31 commit = backend.repo.get_commit(commit_idx=0)
31 commit = backend.repo.get_commit(commit_idx=0)
32 __, params = build_data(
32 __, params = build_data(
33 self.apikey, 'get_repo_changeset',
33 self.apikey, 'get_repo_changeset',
34 repoid=backend.repo_name, revision=commit.raw_id,
34 repoid=backend.repo_name, revision=commit.raw_id,
35 details=details,
35 details=details,
36 )
36 )
37 response = api_call(self.app, params)
37 response = api_call(self.app, params)
38 result = response.json['result']
38 result = response.json['result']
39 assert result['revision'] == 0
39 assert result['revision'] == 0
40 assert result['raw_id'] == commit.raw_id
40 assert result['raw_id'] == commit.raw_id
41
41
42 if details == 'full':
42 if details == 'full':
43 assert result['refs']['bookmarks'] == getattr(
43 assert result['refs']['bookmarks'] == getattr(
44 commit, 'bookmarks', [])
44 commit, 'bookmarks', [])
45 branches = [commit.branch] if commit.branch else []
45 branches = [commit.branch] if commit.branch else []
46 assert result['refs']['branches'] == branches
46 assert result['refs']['branches'] == branches
47 assert result['refs']['tags'] == commit.tags
47 assert result['refs']['tags'] == commit.tags
48
48
49 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
49 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
50 def test_get_repo_changeset_bad_type(self, details, backend):
50 def test_get_repo_changeset_bad_type(self, details, backend):
51 id_, params = build_data(
51 id_, params = build_data(
52 self.apikey, 'get_repo_changeset',
52 self.apikey, 'get_repo_changeset',
53 repoid=backend.repo_name, revision=0,
53 repoid=backend.repo_name, revision=0,
54 details=details,
54 details=details,
55 )
55 )
56 response = api_call(self.app, params)
56 response = api_call(self.app, params)
57 expected = "commit_id must be a string value got <type 'int'> instead"
57 expected = "commit_id must be a string value got <type 'int'> instead"
58 assert_error(id_, expected, given=response.body)
58 assert_error(id_, expected, given=response.body)
59
59
60 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
60 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
61 def test_get_repo_changesets(self, details, backend):
61 def test_get_repo_changesets(self, details, backend):
62 limit = 2
62 limit = 2
63 commit = backend.repo.get_commit(commit_idx=0)
63 commit = backend.repo.get_commit(commit_idx=0)
64 __, params = build_data(
64 __, params = build_data(
65 self.apikey, 'get_repo_changesets',
65 self.apikey, 'get_repo_changesets',
66 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
66 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
67 details=details,
67 details=details,
68 )
68 )
69 response = api_call(self.app, params)
69 response = api_call(self.app, params)
70 result = response.json['result']
70 result = response.json['result']
71 assert result
71 assert result
72 assert len(result) == limit
72 assert len(result) == limit
73 for x in xrange(limit):
73 for x in range(limit):
74 assert result[x]['revision'] == x
74 assert result[x]['revision'] == x
75
75
76 if details == 'full':
76 if details == 'full':
77 for x in xrange(limit):
77 for x in range(limit):
78 assert 'bookmarks' in result[x]['refs']
78 assert 'bookmarks' in result[x]['refs']
79 assert 'branches' in result[x]['refs']
79 assert 'branches' in result[x]['refs']
80 assert 'tags' in result[x]['refs']
80 assert 'tags' in result[x]['refs']
81
81
82 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
82 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
83 @pytest.mark.parametrize("start_rev, expected_revision", [
83 @pytest.mark.parametrize("start_rev, expected_revision", [
84 ("0", 0),
84 ("0", 0),
85 ("10", 10),
85 ("10", 10),
86 ("20", 20),
86 ("20", 20),
87 ])
87 ])
88 @pytest.mark.backends("hg", "git")
88 @pytest.mark.backends("hg", "git")
89 def test_get_repo_changesets_commit_range(
89 def test_get_repo_changesets_commit_range(
90 self, details, backend, start_rev, expected_revision):
90 self, details, backend, start_rev, expected_revision):
91 limit = 10
91 limit = 10
92 __, params = build_data(
92 __, params = build_data(
93 self.apikey, 'get_repo_changesets',
93 self.apikey, 'get_repo_changesets',
94 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
94 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
95 details=details,
95 details=details,
96 )
96 )
97 response = api_call(self.app, params)
97 response = api_call(self.app, params)
98 result = response.json['result']
98 result = response.json['result']
99 assert result
99 assert result
100 assert len(result) == limit
100 assert len(result) == limit
101 for i in xrange(limit):
101 for i in range(limit):
102 assert result[i]['revision'] == int(expected_revision) + i
102 assert result[i]['revision'] == int(expected_revision) + i
103
103
104 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
104 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
105 @pytest.mark.parametrize("start_rev, expected_revision", [
105 @pytest.mark.parametrize("start_rev, expected_revision", [
106 ("0", 0),
106 ("0", 0),
107 ("10", 9),
107 ("10", 9),
108 ("20", 19),
108 ("20", 19),
109 ])
109 ])
110 def test_get_repo_changesets_commit_range_svn(
110 def test_get_repo_changesets_commit_range_svn(
111 self, details, backend_svn, start_rev, expected_revision):
111 self, details, backend_svn, start_rev, expected_revision):
112
112
113 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
113 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
114 # in our API allows to pass in a "Commit ID" as well as a
114 # in our API allows to pass in a "Commit ID" as well as a
115 # "Commit Index". In the case of Subversion it is not possible to
115 # "Commit Index". In the case of Subversion it is not possible to
116 # distinguish these cases. As a workaround we implemented this
116 # distinguish these cases. As a workaround we implemented this
117 # behavior which gives a preference to see it as a "Commit ID".
117 # behavior which gives a preference to see it as a "Commit ID".
118
118
119 limit = 10
119 limit = 10
120 __, params = build_data(
120 __, params = build_data(
121 self.apikey, 'get_repo_changesets',
121 self.apikey, 'get_repo_changesets',
122 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
122 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
123 details=details,
123 details=details,
124 )
124 )
125 response = api_call(self.app, params)
125 response = api_call(self.app, params)
126 result = response.json['result']
126 result = response.json['result']
127 assert result
127 assert result
128 assert len(result) == limit
128 assert len(result) == limit
129 for i in xrange(limit):
129 for i in range(limit):
130 assert result[i]['revision'] == int(expected_revision) + i
130 assert result[i]['revision'] == int(expected_revision) + i
131
131
132 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
132 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
133 def test_get_repo_changesets_bad_type(self, details, backend):
133 def test_get_repo_changesets_bad_type(self, details, backend):
134 id_, params = build_data(
134 id_, params = build_data(
135 self.apikey, 'get_repo_changesets',
135 self.apikey, 'get_repo_changesets',
136 repoid=backend.repo_name, start_rev=0, limit=2,
136 repoid=backend.repo_name, start_rev=0, limit=2,
137 details=details,
137 details=details,
138 )
138 )
139 response = api_call(self.app, params)
139 response = api_call(self.app, params)
140 expected = "commit_id must be a string value got <type 'int'> instead"
140 expected = "commit_id must be a string value got <type 'int'> instead"
141 assert_error(id_, expected, given=response.body)
141 assert_error(id_, expected, given=response.body)
@@ -1,156 +1,156 b''
1 from __future__ import absolute_import, division, unicode_literals
1 from __future__ import absolute_import, division, unicode_literals
2
2
3 import re
3 import re
4 import random
4 import random
5 from collections import deque
5 from collections import deque
6 from datetime import timedelta
6 from datetime import timedelta
7 from repoze.lru import lru_cache
7 from repoze.lru import lru_cache
8
8
9 from .timer import Timer
9 from .timer import Timer
10
10
11 TAG_INVALID_CHARS_RE = re.compile(
11 TAG_INVALID_CHARS_RE = re.compile(
12 r"[^\w\d_\-:/\.]",
12 r"[^\w\d_\-:/\.]",
13 #re.UNICODE
13 #re.UNICODE
14 )
14 )
15 TAG_INVALID_CHARS_SUBS = "_"
15 TAG_INVALID_CHARS_SUBS = "_"
16
16
17 # we save and expose methods called by statsd for discovery
17 # we save and expose methods called by statsd for discovery
18 buckets_dict = {
18 buckets_dict = {
19
19
20 }
20 }
21
21
22
22
23 @lru_cache(maxsize=500)
23 @lru_cache(maxsize=500)
24 def _normalize_tags_with_cache(tag_list):
24 def _normalize_tags_with_cache(tag_list):
25 return [TAG_INVALID_CHARS_RE.sub(TAG_INVALID_CHARS_SUBS, tag) for tag in tag_list]
25 return [TAG_INVALID_CHARS_RE.sub(TAG_INVALID_CHARS_SUBS, tag) for tag in tag_list]
26
26
27
27
28 def normalize_tags(tag_list):
28 def normalize_tags(tag_list):
29 # We have to turn our input tag list into a non-mutable tuple for it to
29 # We have to turn our input tag list into a non-mutable tuple for it to
30 # be hashable (and thus usable) by the @lru_cache decorator.
30 # be hashable (and thus usable) by the @lru_cache decorator.
31 return _normalize_tags_with_cache(tuple(tag_list))
31 return _normalize_tags_with_cache(tuple(tag_list))
32
32
33
33
34 class StatsClientBase(object):
34 class StatsClientBase(object):
35 """A Base class for various statsd clients."""
35 """A Base class for various statsd clients."""
36
36
37 def close(self):
37 def close(self):
38 """Used to close and clean up any underlying resources."""
38 """Used to close and clean up any underlying resources."""
39 raise NotImplementedError()
39 raise NotImplementedError()
40
40
41 def _send(self):
41 def _send(self):
42 raise NotImplementedError()
42 raise NotImplementedError()
43
43
44 def pipeline(self):
44 def pipeline(self):
45 raise NotImplementedError()
45 raise NotImplementedError()
46
46
47 def timer(self, stat, rate=1, tags=None, auto_send=True):
47 def timer(self, stat, rate=1, tags=None, auto_send=True):
48 """
48 """
49 statsd = StatsdClient.statsd
49 statsd = StatsdClient.statsd
50 with statsd.timer('bucket_name', auto_send=True) as tmr:
50 with statsd.timer('bucket_name', auto_send=True) as tmr:
51 # This block will be timed.
51 # This block will be timed.
52 for i in xrange(0, 100000):
52 for i in range(0, 100000):
53 i ** 2
53 i ** 2
54 # you can access time here...
54 # you can access time here...
55 elapsed_ms = tmr.ms
55 elapsed_ms = tmr.ms
56 """
56 """
57 return Timer(self, stat, rate, tags, auto_send=auto_send)
57 return Timer(self, stat, rate, tags, auto_send=auto_send)
58
58
59 def timing(self, stat, delta, rate=1, tags=None, use_decimals=True):
59 def timing(self, stat, delta, rate=1, tags=None, use_decimals=True):
60 """
60 """
61 Send new timing information.
61 Send new timing information.
62
62
63 `delta` can be either a number of milliseconds or a timedelta.
63 `delta` can be either a number of milliseconds or a timedelta.
64 """
64 """
65 if isinstance(delta, timedelta):
65 if isinstance(delta, timedelta):
66 # Convert timedelta to number of milliseconds.
66 # Convert timedelta to number of milliseconds.
67 delta = delta.total_seconds() * 1000.
67 delta = delta.total_seconds() * 1000.
68 if use_decimals:
68 if use_decimals:
69 fmt = '%0.6f|ms'
69 fmt = '%0.6f|ms'
70 else:
70 else:
71 fmt = '%s|ms'
71 fmt = '%s|ms'
72 self._send_stat(stat, fmt % delta, rate, tags)
72 self._send_stat(stat, fmt % delta, rate, tags)
73
73
74 def incr(self, stat, count=1, rate=1, tags=None):
74 def incr(self, stat, count=1, rate=1, tags=None):
75 """Increment a stat by `count`."""
75 """Increment a stat by `count`."""
76 self._send_stat(stat, '%s|c' % count, rate, tags)
76 self._send_stat(stat, '%s|c' % count, rate, tags)
77
77
78 def decr(self, stat, count=1, rate=1, tags=None):
78 def decr(self, stat, count=1, rate=1, tags=None):
79 """Decrement a stat by `count`."""
79 """Decrement a stat by `count`."""
80 self.incr(stat, -count, rate, tags)
80 self.incr(stat, -count, rate, tags)
81
81
82 def gauge(self, stat, value, rate=1, delta=False, tags=None):
82 def gauge(self, stat, value, rate=1, delta=False, tags=None):
83 """Set a gauge value."""
83 """Set a gauge value."""
84 if value < 0 and not delta:
84 if value < 0 and not delta:
85 if rate < 1:
85 if rate < 1:
86 if random.random() > rate:
86 if random.random() > rate:
87 return
87 return
88 with self.pipeline() as pipe:
88 with self.pipeline() as pipe:
89 pipe._send_stat(stat, '0|g', 1)
89 pipe._send_stat(stat, '0|g', 1)
90 pipe._send_stat(stat, '%s|g' % value, 1)
90 pipe._send_stat(stat, '%s|g' % value, 1)
91 else:
91 else:
92 prefix = '+' if delta and value >= 0 else ''
92 prefix = '+' if delta and value >= 0 else ''
93 self._send_stat(stat, '%s%s|g' % (prefix, value), rate, tags)
93 self._send_stat(stat, '%s%s|g' % (prefix, value), rate, tags)
94
94
95 def set(self, stat, value, rate=1):
95 def set(self, stat, value, rate=1):
96 """Set a set value."""
96 """Set a set value."""
97 self._send_stat(stat, '%s|s' % value, rate)
97 self._send_stat(stat, '%s|s' % value, rate)
98
98
99 def histogram(self, stat, value, rate=1, tags=None):
99 def histogram(self, stat, value, rate=1, tags=None):
100 """Set a histogram"""
100 """Set a histogram"""
101 self._send_stat(stat, '%s|h' % value, rate, tags)
101 self._send_stat(stat, '%s|h' % value, rate, tags)
102
102
103 def _send_stat(self, stat, value, rate, tags=None):
103 def _send_stat(self, stat, value, rate, tags=None):
104 self._after(self._prepare(stat, value, rate, tags))
104 self._after(self._prepare(stat, value, rate, tags))
105
105
106 def _prepare(self, stat, value, rate, tags=None):
106 def _prepare(self, stat, value, rate, tags=None):
107 global buckets_dict
107 global buckets_dict
108 buckets_dict[stat] = 1
108 buckets_dict[stat] = 1
109
109
110 if rate < 1:
110 if rate < 1:
111 if random.random() > rate:
111 if random.random() > rate:
112 return
112 return
113 value = '%s|@%s' % (value, rate)
113 value = '%s|@%s' % (value, rate)
114
114
115 if self._prefix:
115 if self._prefix:
116 stat = '%s.%s' % (self._prefix, stat)
116 stat = '%s.%s' % (self._prefix, stat)
117
117
118 res = '%s:%s%s' % (
118 res = '%s:%s%s' % (
119 stat,
119 stat,
120 value,
120 value,
121 ("|#" + ",".join(normalize_tags(tags))) if tags else "",
121 ("|#" + ",".join(normalize_tags(tags))) if tags else "",
122 )
122 )
123 return res
123 return res
124
124
125 def _after(self, data):
125 def _after(self, data):
126 if data:
126 if data:
127 self._send(data)
127 self._send(data)
128
128
129
129
130 class PipelineBase(StatsClientBase):
130 class PipelineBase(StatsClientBase):
131
131
132 def __init__(self, client):
132 def __init__(self, client):
133 self._client = client
133 self._client = client
134 self._prefix = client._prefix
134 self._prefix = client._prefix
135 self._stats = deque()
135 self._stats = deque()
136
136
137 def _send(self):
137 def _send(self):
138 raise NotImplementedError()
138 raise NotImplementedError()
139
139
140 def _after(self, data):
140 def _after(self, data):
141 if data is not None:
141 if data is not None:
142 self._stats.append(data)
142 self._stats.append(data)
143
143
144 def __enter__(self):
144 def __enter__(self):
145 return self
145 return self
146
146
147 def __exit__(self, typ, value, tb):
147 def __exit__(self, typ, value, tb):
148 self.send()
148 self.send()
149
149
150 def send(self):
150 def send(self):
151 if not self._stats:
151 if not self._stats:
152 return
152 return
153 self._send()
153 self._send()
154
154
155 def pipeline(self):
155 def pipeline(self):
156 return self.__class__(self)
156 return self.__class__(self)
@@ -1,4333 +1,4333 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 # pragma: no cover
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 # pragma: no cover
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 from pyramid import compat
52 from pyramid import compat
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 range(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, compat.string_types):
2113 if isinstance(rev, compat.string_types):
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 commit cache %s',
2167 log.debug('updated repo %s with new commit 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 range(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 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2298 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2299 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2299 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2300 )
2300 )
2301 __mapper_args__ = {'order_by': 'group_name'}
2301 __mapper_args__ = {'order_by': 'group_name'}
2302
2302
2303 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2303 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2304
2304
2305 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2305 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2306 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2306 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2307 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2307 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2308 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2308 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2309 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2309 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2310 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2310 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2311 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2311 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2312 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2312 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2313 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2313 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2314
2314
2315 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2315 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2316 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2316 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2317 parent_group = relationship('RepoGroup', remote_side=group_id)
2317 parent_group = relationship('RepoGroup', remote_side=group_id)
2318 user = relationship('User')
2318 user = relationship('User')
2319 integrations = relationship('Integration',
2319 integrations = relationship('Integration',
2320 cascade="all, delete, delete-orphan")
2320 cascade="all, delete, delete-orphan")
2321
2321
2322 def __init__(self, group_name='', parent_group=None):
2322 def __init__(self, group_name='', parent_group=None):
2323 self.group_name = group_name
2323 self.group_name = group_name
2324 self.parent_group = parent_group
2324 self.parent_group = parent_group
2325
2325
2326 def __unicode__(self):
2326 def __unicode__(self):
2327 return u"<%s('id:%s:%s')>" % (
2327 return u"<%s('id:%s:%s')>" % (
2328 self.__class__.__name__, self.group_id, self.group_name)
2328 self.__class__.__name__, self.group_id, self.group_name)
2329
2329
2330 @hybrid_property
2330 @hybrid_property
2331 def description_safe(self):
2331 def description_safe(self):
2332 from rhodecode.lib import helpers as h
2332 from rhodecode.lib import helpers as h
2333 return h.escape(self.group_description)
2333 return h.escape(self.group_description)
2334
2334
2335 @classmethod
2335 @classmethod
2336 def _generate_choice(cls, repo_group):
2336 def _generate_choice(cls, repo_group):
2337 from webhelpers2.html import literal as _literal
2337 from webhelpers2.html import literal as _literal
2338 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2338 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2339 return repo_group.group_id, _name(repo_group.full_path_splitted)
2339 return repo_group.group_id, _name(repo_group.full_path_splitted)
2340
2340
2341 @classmethod
2341 @classmethod
2342 def groups_choices(cls, groups=None, show_empty_group=True):
2342 def groups_choices(cls, groups=None, show_empty_group=True):
2343 if not groups:
2343 if not groups:
2344 groups = cls.query().all()
2344 groups = cls.query().all()
2345
2345
2346 repo_groups = []
2346 repo_groups = []
2347 if show_empty_group:
2347 if show_empty_group:
2348 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2348 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2349
2349
2350 repo_groups.extend([cls._generate_choice(x) for x in groups])
2350 repo_groups.extend([cls._generate_choice(x) for x in groups])
2351
2351
2352 repo_groups = sorted(
2352 repo_groups = sorted(
2353 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2353 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2354 return repo_groups
2354 return repo_groups
2355
2355
2356 @classmethod
2356 @classmethod
2357 def url_sep(cls):
2357 def url_sep(cls):
2358 return URL_SEP
2358 return URL_SEP
2359
2359
2360 @classmethod
2360 @classmethod
2361 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2361 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2362 if case_insensitive:
2362 if case_insensitive:
2363 gr = cls.query().filter(func.lower(cls.group_name)
2363 gr = cls.query().filter(func.lower(cls.group_name)
2364 == func.lower(group_name))
2364 == func.lower(group_name))
2365 else:
2365 else:
2366 gr = cls.query().filter(cls.group_name == group_name)
2366 gr = cls.query().filter(cls.group_name == group_name)
2367 if cache:
2367 if cache:
2368 name_key = _hash_key(group_name)
2368 name_key = _hash_key(group_name)
2369 gr = gr.options(
2369 gr = gr.options(
2370 FromCache("sql_cache_short", "get_group_%s" % name_key))
2370 FromCache("sql_cache_short", "get_group_%s" % name_key))
2371 return gr.scalar()
2371 return gr.scalar()
2372
2372
2373 @classmethod
2373 @classmethod
2374 def get_user_personal_repo_group(cls, user_id):
2374 def get_user_personal_repo_group(cls, user_id):
2375 user = User.get(user_id)
2375 user = User.get(user_id)
2376 if user.username == User.DEFAULT_USER:
2376 if user.username == User.DEFAULT_USER:
2377 return None
2377 return None
2378
2378
2379 return cls.query()\
2379 return cls.query()\
2380 .filter(cls.personal == true()) \
2380 .filter(cls.personal == true()) \
2381 .filter(cls.user == user).scalar()
2381 .filter(cls.user == user).scalar()
2382
2382
2383 @classmethod
2383 @classmethod
2384 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2384 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2385 case_insensitive=True):
2385 case_insensitive=True):
2386 q = RepoGroup.query()
2386 q = RepoGroup.query()
2387
2387
2388 if not isinstance(user_id, Optional):
2388 if not isinstance(user_id, Optional):
2389 q = q.filter(RepoGroup.user_id == user_id)
2389 q = q.filter(RepoGroup.user_id == user_id)
2390
2390
2391 if not isinstance(group_id, Optional):
2391 if not isinstance(group_id, Optional):
2392 q = q.filter(RepoGroup.group_parent_id == group_id)
2392 q = q.filter(RepoGroup.group_parent_id == group_id)
2393
2393
2394 if case_insensitive:
2394 if case_insensitive:
2395 q = q.order_by(func.lower(RepoGroup.group_name))
2395 q = q.order_by(func.lower(RepoGroup.group_name))
2396 else:
2396 else:
2397 q = q.order_by(RepoGroup.group_name)
2397 q = q.order_by(RepoGroup.group_name)
2398 return q.all()
2398 return q.all()
2399
2399
2400 @property
2400 @property
2401 def parents(self):
2401 def parents(self):
2402 parents_recursion_limit = 10
2402 parents_recursion_limit = 10
2403 groups = []
2403 groups = []
2404 if self.parent_group is None:
2404 if self.parent_group is None:
2405 return groups
2405 return groups
2406 cur_gr = self.parent_group
2406 cur_gr = self.parent_group
2407 groups.insert(0, cur_gr)
2407 groups.insert(0, cur_gr)
2408 cnt = 0
2408 cnt = 0
2409 while 1:
2409 while 1:
2410 cnt += 1
2410 cnt += 1
2411 gr = getattr(cur_gr, 'parent_group', None)
2411 gr = getattr(cur_gr, 'parent_group', None)
2412 cur_gr = cur_gr.parent_group
2412 cur_gr = cur_gr.parent_group
2413 if gr is None:
2413 if gr is None:
2414 break
2414 break
2415 if cnt == parents_recursion_limit:
2415 if cnt == parents_recursion_limit:
2416 # this will prevent accidental infinit loops
2416 # this will prevent accidental infinit loops
2417 log.error('more than %s parents found for group %s, stopping '
2417 log.error('more than %s parents found for group %s, stopping '
2418 'recursive parent fetching', parents_recursion_limit, self)
2418 'recursive parent fetching', parents_recursion_limit, self)
2419 break
2419 break
2420
2420
2421 groups.insert(0, gr)
2421 groups.insert(0, gr)
2422 return groups
2422 return groups
2423
2423
2424 @property
2424 @property
2425 def last_db_change(self):
2425 def last_db_change(self):
2426 return self.updated_on
2426 return self.updated_on
2427
2427
2428 @property
2428 @property
2429 def children(self):
2429 def children(self):
2430 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2430 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2431
2431
2432 @property
2432 @property
2433 def name(self):
2433 def name(self):
2434 return self.group_name.split(RepoGroup.url_sep())[-1]
2434 return self.group_name.split(RepoGroup.url_sep())[-1]
2435
2435
2436 @property
2436 @property
2437 def full_path(self):
2437 def full_path(self):
2438 return self.group_name
2438 return self.group_name
2439
2439
2440 @property
2440 @property
2441 def full_path_splitted(self):
2441 def full_path_splitted(self):
2442 return self.group_name.split(RepoGroup.url_sep())
2442 return self.group_name.split(RepoGroup.url_sep())
2443
2443
2444 @property
2444 @property
2445 def repositories(self):
2445 def repositories(self):
2446 return Repository.query()\
2446 return Repository.query()\
2447 .filter(Repository.group == self)\
2447 .filter(Repository.group == self)\
2448 .order_by(Repository.repo_name)
2448 .order_by(Repository.repo_name)
2449
2449
2450 @property
2450 @property
2451 def repositories_recursive_count(self):
2451 def repositories_recursive_count(self):
2452 cnt = self.repositories.count()
2452 cnt = self.repositories.count()
2453
2453
2454 def children_count(group):
2454 def children_count(group):
2455 cnt = 0
2455 cnt = 0
2456 for child in group.children:
2456 for child in group.children:
2457 cnt += child.repositories.count()
2457 cnt += child.repositories.count()
2458 cnt += children_count(child)
2458 cnt += children_count(child)
2459 return cnt
2459 return cnt
2460
2460
2461 return cnt + children_count(self)
2461 return cnt + children_count(self)
2462
2462
2463 def _recursive_objects(self, include_repos=True):
2463 def _recursive_objects(self, include_repos=True):
2464 all_ = []
2464 all_ = []
2465
2465
2466 def _get_members(root_gr):
2466 def _get_members(root_gr):
2467 if include_repos:
2467 if include_repos:
2468 for r in root_gr.repositories:
2468 for r in root_gr.repositories:
2469 all_.append(r)
2469 all_.append(r)
2470 childs = root_gr.children.all()
2470 childs = root_gr.children.all()
2471 if childs:
2471 if childs:
2472 for gr in childs:
2472 for gr in childs:
2473 all_.append(gr)
2473 all_.append(gr)
2474 _get_members(gr)
2474 _get_members(gr)
2475
2475
2476 _get_members(self)
2476 _get_members(self)
2477 return [self] + all_
2477 return [self] + all_
2478
2478
2479 def recursive_groups_and_repos(self):
2479 def recursive_groups_and_repos(self):
2480 """
2480 """
2481 Recursive return all groups, with repositories in those groups
2481 Recursive return all groups, with repositories in those groups
2482 """
2482 """
2483 return self._recursive_objects()
2483 return self._recursive_objects()
2484
2484
2485 def recursive_groups(self):
2485 def recursive_groups(self):
2486 """
2486 """
2487 Returns all children groups for this group including children of children
2487 Returns all children groups for this group including children of children
2488 """
2488 """
2489 return self._recursive_objects(include_repos=False)
2489 return self._recursive_objects(include_repos=False)
2490
2490
2491 def get_new_name(self, group_name):
2491 def get_new_name(self, group_name):
2492 """
2492 """
2493 returns new full group name based on parent and new name
2493 returns new full group name based on parent and new name
2494
2494
2495 :param group_name:
2495 :param group_name:
2496 """
2496 """
2497 path_prefix = (self.parent_group.full_path_splitted if
2497 path_prefix = (self.parent_group.full_path_splitted if
2498 self.parent_group else [])
2498 self.parent_group else [])
2499 return RepoGroup.url_sep().join(path_prefix + [group_name])
2499 return RepoGroup.url_sep().join(path_prefix + [group_name])
2500
2500
2501 def permissions(self, with_admins=True, with_owner=True):
2501 def permissions(self, with_admins=True, with_owner=True):
2502 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2502 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2503 q = q.options(joinedload(UserRepoGroupToPerm.group),
2503 q = q.options(joinedload(UserRepoGroupToPerm.group),
2504 joinedload(UserRepoGroupToPerm.user),
2504 joinedload(UserRepoGroupToPerm.user),
2505 joinedload(UserRepoGroupToPerm.permission),)
2505 joinedload(UserRepoGroupToPerm.permission),)
2506
2506
2507 # get owners and admins and permissions. We do a trick of re-writing
2507 # get owners and admins and permissions. We do a trick of re-writing
2508 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2508 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2509 # has a global reference and changing one object propagates to all
2509 # has a global reference and changing one object propagates to all
2510 # others. This means if admin is also an owner admin_row that change
2510 # others. This means if admin is also an owner admin_row that change
2511 # would propagate to both objects
2511 # would propagate to both objects
2512 perm_rows = []
2512 perm_rows = []
2513 for _usr in q.all():
2513 for _usr in q.all():
2514 usr = AttributeDict(_usr.user.get_dict())
2514 usr = AttributeDict(_usr.user.get_dict())
2515 usr.permission = _usr.permission.permission_name
2515 usr.permission = _usr.permission.permission_name
2516 perm_rows.append(usr)
2516 perm_rows.append(usr)
2517
2517
2518 # filter the perm rows by 'default' first and then sort them by
2518 # filter the perm rows by 'default' first and then sort them by
2519 # admin,write,read,none permissions sorted again alphabetically in
2519 # admin,write,read,none permissions sorted again alphabetically in
2520 # each group
2520 # each group
2521 perm_rows = sorted(perm_rows, key=display_user_sort)
2521 perm_rows = sorted(perm_rows, key=display_user_sort)
2522
2522
2523 _admin_perm = 'group.admin'
2523 _admin_perm = 'group.admin'
2524 owner_row = []
2524 owner_row = []
2525 if with_owner:
2525 if with_owner:
2526 usr = AttributeDict(self.user.get_dict())
2526 usr = AttributeDict(self.user.get_dict())
2527 usr.owner_row = True
2527 usr.owner_row = True
2528 usr.permission = _admin_perm
2528 usr.permission = _admin_perm
2529 owner_row.append(usr)
2529 owner_row.append(usr)
2530
2530
2531 super_admin_rows = []
2531 super_admin_rows = []
2532 if with_admins:
2532 if with_admins:
2533 for usr in User.get_all_super_admins():
2533 for usr in User.get_all_super_admins():
2534 # if this admin is also owner, don't double the record
2534 # if this admin is also owner, don't double the record
2535 if usr.user_id == owner_row[0].user_id:
2535 if usr.user_id == owner_row[0].user_id:
2536 owner_row[0].admin_row = True
2536 owner_row[0].admin_row = True
2537 else:
2537 else:
2538 usr = AttributeDict(usr.get_dict())
2538 usr = AttributeDict(usr.get_dict())
2539 usr.admin_row = True
2539 usr.admin_row = True
2540 usr.permission = _admin_perm
2540 usr.permission = _admin_perm
2541 super_admin_rows.append(usr)
2541 super_admin_rows.append(usr)
2542
2542
2543 return super_admin_rows + owner_row + perm_rows
2543 return super_admin_rows + owner_row + perm_rows
2544
2544
2545 def permission_user_groups(self):
2545 def permission_user_groups(self):
2546 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2546 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2547 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2547 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2548 joinedload(UserGroupRepoGroupToPerm.users_group),
2548 joinedload(UserGroupRepoGroupToPerm.users_group),
2549 joinedload(UserGroupRepoGroupToPerm.permission),)
2549 joinedload(UserGroupRepoGroupToPerm.permission),)
2550
2550
2551 perm_rows = []
2551 perm_rows = []
2552 for _user_group in q.all():
2552 for _user_group in q.all():
2553 usr = AttributeDict(_user_group.users_group.get_dict())
2553 usr = AttributeDict(_user_group.users_group.get_dict())
2554 usr.permission = _user_group.permission.permission_name
2554 usr.permission = _user_group.permission.permission_name
2555 perm_rows.append(usr)
2555 perm_rows.append(usr)
2556
2556
2557 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2557 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2558 return perm_rows
2558 return perm_rows
2559
2559
2560 def get_api_data(self):
2560 def get_api_data(self):
2561 """
2561 """
2562 Common function for generating api data
2562 Common function for generating api data
2563
2563
2564 """
2564 """
2565 group = self
2565 group = self
2566 data = {
2566 data = {
2567 'group_id': group.group_id,
2567 'group_id': group.group_id,
2568 'group_name': group.group_name,
2568 'group_name': group.group_name,
2569 'group_description': group.description_safe,
2569 'group_description': group.description_safe,
2570 'parent_group': group.parent_group.group_name if group.parent_group else None,
2570 'parent_group': group.parent_group.group_name if group.parent_group else None,
2571 'repositories': [x.repo_name for x in group.repositories],
2571 'repositories': [x.repo_name for x in group.repositories],
2572 'owner': group.user.username,
2572 'owner': group.user.username,
2573 }
2573 }
2574 return data
2574 return data
2575
2575
2576
2576
2577 class Permission(Base, BaseModel):
2577 class Permission(Base, BaseModel):
2578 __tablename__ = 'permissions'
2578 __tablename__ = 'permissions'
2579 __table_args__ = (
2579 __table_args__ = (
2580 Index('p_perm_name_idx', 'permission_name'),
2580 Index('p_perm_name_idx', 'permission_name'),
2581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2582 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2582 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2583 )
2583 )
2584 PERMS = [
2584 PERMS = [
2585 ('hg.admin', _('RhodeCode Super Administrator')),
2585 ('hg.admin', _('RhodeCode Super Administrator')),
2586
2586
2587 ('repository.none', _('Repository no access')),
2587 ('repository.none', _('Repository no access')),
2588 ('repository.read', _('Repository read access')),
2588 ('repository.read', _('Repository read access')),
2589 ('repository.write', _('Repository write access')),
2589 ('repository.write', _('Repository write access')),
2590 ('repository.admin', _('Repository admin access')),
2590 ('repository.admin', _('Repository admin access')),
2591
2591
2592 ('group.none', _('Repository group no access')),
2592 ('group.none', _('Repository group no access')),
2593 ('group.read', _('Repository group read access')),
2593 ('group.read', _('Repository group read access')),
2594 ('group.write', _('Repository group write access')),
2594 ('group.write', _('Repository group write access')),
2595 ('group.admin', _('Repository group admin access')),
2595 ('group.admin', _('Repository group admin access')),
2596
2596
2597 ('usergroup.none', _('User group no access')),
2597 ('usergroup.none', _('User group no access')),
2598 ('usergroup.read', _('User group read access')),
2598 ('usergroup.read', _('User group read access')),
2599 ('usergroup.write', _('User group write access')),
2599 ('usergroup.write', _('User group write access')),
2600 ('usergroup.admin', _('User group admin access')),
2600 ('usergroup.admin', _('User group admin access')),
2601
2601
2602 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2602 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2603 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2603 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2604
2604
2605 ('hg.usergroup.create.false', _('User Group creation disabled')),
2605 ('hg.usergroup.create.false', _('User Group creation disabled')),
2606 ('hg.usergroup.create.true', _('User Group creation enabled')),
2606 ('hg.usergroup.create.true', _('User Group creation enabled')),
2607
2607
2608 ('hg.create.none', _('Repository creation disabled')),
2608 ('hg.create.none', _('Repository creation disabled')),
2609 ('hg.create.repository', _('Repository creation enabled')),
2609 ('hg.create.repository', _('Repository creation enabled')),
2610 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2610 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2611 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2611 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2612
2612
2613 ('hg.fork.none', _('Repository forking disabled')),
2613 ('hg.fork.none', _('Repository forking disabled')),
2614 ('hg.fork.repository', _('Repository forking enabled')),
2614 ('hg.fork.repository', _('Repository forking enabled')),
2615
2615
2616 ('hg.register.none', _('Registration disabled')),
2616 ('hg.register.none', _('Registration disabled')),
2617 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2617 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2618 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2618 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2619
2619
2620 ('hg.password_reset.enabled', _('Password reset enabled')),
2620 ('hg.password_reset.enabled', _('Password reset enabled')),
2621 ('hg.password_reset.hidden', _('Password reset hidden')),
2621 ('hg.password_reset.hidden', _('Password reset hidden')),
2622 ('hg.password_reset.disabled', _('Password reset disabled')),
2622 ('hg.password_reset.disabled', _('Password reset disabled')),
2623
2623
2624 ('hg.extern_activate.manual', _('Manual activation of external account')),
2624 ('hg.extern_activate.manual', _('Manual activation of external account')),
2625 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2625 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2626
2626
2627 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2627 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2628 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2628 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2629 ]
2629 ]
2630
2630
2631 # definition of system default permissions for DEFAULT user
2631 # definition of system default permissions for DEFAULT user
2632 DEFAULT_USER_PERMISSIONS = [
2632 DEFAULT_USER_PERMISSIONS = [
2633 'repository.read',
2633 'repository.read',
2634 'group.read',
2634 'group.read',
2635 'usergroup.read',
2635 'usergroup.read',
2636 'hg.create.repository',
2636 'hg.create.repository',
2637 'hg.repogroup.create.false',
2637 'hg.repogroup.create.false',
2638 'hg.usergroup.create.false',
2638 'hg.usergroup.create.false',
2639 'hg.create.write_on_repogroup.true',
2639 'hg.create.write_on_repogroup.true',
2640 'hg.fork.repository',
2640 'hg.fork.repository',
2641 'hg.register.manual_activate',
2641 'hg.register.manual_activate',
2642 'hg.password_reset.enabled',
2642 'hg.password_reset.enabled',
2643 'hg.extern_activate.auto',
2643 'hg.extern_activate.auto',
2644 'hg.inherit_default_perms.true',
2644 'hg.inherit_default_perms.true',
2645 ]
2645 ]
2646
2646
2647 # defines which permissions are more important higher the more important
2647 # defines which permissions are more important higher the more important
2648 # Weight defines which permissions are more important.
2648 # Weight defines which permissions are more important.
2649 # The higher number the more important.
2649 # The higher number the more important.
2650 PERM_WEIGHTS = {
2650 PERM_WEIGHTS = {
2651 'repository.none': 0,
2651 'repository.none': 0,
2652 'repository.read': 1,
2652 'repository.read': 1,
2653 'repository.write': 3,
2653 'repository.write': 3,
2654 'repository.admin': 4,
2654 'repository.admin': 4,
2655
2655
2656 'group.none': 0,
2656 'group.none': 0,
2657 'group.read': 1,
2657 'group.read': 1,
2658 'group.write': 3,
2658 'group.write': 3,
2659 'group.admin': 4,
2659 'group.admin': 4,
2660
2660
2661 'usergroup.none': 0,
2661 'usergroup.none': 0,
2662 'usergroup.read': 1,
2662 'usergroup.read': 1,
2663 'usergroup.write': 3,
2663 'usergroup.write': 3,
2664 'usergroup.admin': 4,
2664 'usergroup.admin': 4,
2665
2665
2666 'hg.repogroup.create.false': 0,
2666 'hg.repogroup.create.false': 0,
2667 'hg.repogroup.create.true': 1,
2667 'hg.repogroup.create.true': 1,
2668
2668
2669 'hg.usergroup.create.false': 0,
2669 'hg.usergroup.create.false': 0,
2670 'hg.usergroup.create.true': 1,
2670 'hg.usergroup.create.true': 1,
2671
2671
2672 'hg.fork.none': 0,
2672 'hg.fork.none': 0,
2673 'hg.fork.repository': 1,
2673 'hg.fork.repository': 1,
2674 'hg.create.none': 0,
2674 'hg.create.none': 0,
2675 'hg.create.repository': 1
2675 'hg.create.repository': 1
2676 }
2676 }
2677
2677
2678 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2678 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2679 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2679 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2680 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2680 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2681
2681
2682 def __unicode__(self):
2682 def __unicode__(self):
2683 return u"<%s('%s:%s')>" % (
2683 return u"<%s('%s:%s')>" % (
2684 self.__class__.__name__, self.permission_id, self.permission_name
2684 self.__class__.__name__, self.permission_id, self.permission_name
2685 )
2685 )
2686
2686
2687 @classmethod
2687 @classmethod
2688 def get_by_key(cls, key):
2688 def get_by_key(cls, key):
2689 return cls.query().filter(cls.permission_name == key).scalar()
2689 return cls.query().filter(cls.permission_name == key).scalar()
2690
2690
2691 @classmethod
2691 @classmethod
2692 def get_default_repo_perms(cls, user_id, repo_id=None):
2692 def get_default_repo_perms(cls, user_id, repo_id=None):
2693 q = Session().query(UserRepoToPerm, Repository, Permission)\
2693 q = Session().query(UserRepoToPerm, Repository, Permission)\
2694 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2694 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2695 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2695 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2696 .filter(UserRepoToPerm.user_id == user_id)
2696 .filter(UserRepoToPerm.user_id == user_id)
2697 if repo_id:
2697 if repo_id:
2698 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2698 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2699 return q.all()
2699 return q.all()
2700
2700
2701 @classmethod
2701 @classmethod
2702 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2702 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2703 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2703 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2704 .join(
2704 .join(
2705 Permission,
2705 Permission,
2706 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2706 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2707 .join(
2707 .join(
2708 Repository,
2708 Repository,
2709 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2709 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2710 .join(
2710 .join(
2711 UserGroup,
2711 UserGroup,
2712 UserGroupRepoToPerm.users_group_id ==
2712 UserGroupRepoToPerm.users_group_id ==
2713 UserGroup.users_group_id)\
2713 UserGroup.users_group_id)\
2714 .join(
2714 .join(
2715 UserGroupMember,
2715 UserGroupMember,
2716 UserGroupRepoToPerm.users_group_id ==
2716 UserGroupRepoToPerm.users_group_id ==
2717 UserGroupMember.users_group_id)\
2717 UserGroupMember.users_group_id)\
2718 .filter(
2718 .filter(
2719 UserGroupMember.user_id == user_id,
2719 UserGroupMember.user_id == user_id,
2720 UserGroup.users_group_active == true())
2720 UserGroup.users_group_active == true())
2721 if repo_id:
2721 if repo_id:
2722 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2722 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2723 return q.all()
2723 return q.all()
2724
2724
2725 @classmethod
2725 @classmethod
2726 def get_default_group_perms(cls, user_id, repo_group_id=None):
2726 def get_default_group_perms(cls, user_id, repo_group_id=None):
2727 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2727 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2728 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2728 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2729 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2729 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2730 .filter(UserRepoGroupToPerm.user_id == user_id)
2730 .filter(UserRepoGroupToPerm.user_id == user_id)
2731 if repo_group_id:
2731 if repo_group_id:
2732 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2732 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2733 return q.all()
2733 return q.all()
2734
2734
2735 @classmethod
2735 @classmethod
2736 def get_default_group_perms_from_user_group(
2736 def get_default_group_perms_from_user_group(
2737 cls, user_id, repo_group_id=None):
2737 cls, user_id, repo_group_id=None):
2738 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2738 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2739 .join(
2739 .join(
2740 Permission,
2740 Permission,
2741 UserGroupRepoGroupToPerm.permission_id ==
2741 UserGroupRepoGroupToPerm.permission_id ==
2742 Permission.permission_id)\
2742 Permission.permission_id)\
2743 .join(
2743 .join(
2744 RepoGroup,
2744 RepoGroup,
2745 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2745 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2746 .join(
2746 .join(
2747 UserGroup,
2747 UserGroup,
2748 UserGroupRepoGroupToPerm.users_group_id ==
2748 UserGroupRepoGroupToPerm.users_group_id ==
2749 UserGroup.users_group_id)\
2749 UserGroup.users_group_id)\
2750 .join(
2750 .join(
2751 UserGroupMember,
2751 UserGroupMember,
2752 UserGroupRepoGroupToPerm.users_group_id ==
2752 UserGroupRepoGroupToPerm.users_group_id ==
2753 UserGroupMember.users_group_id)\
2753 UserGroupMember.users_group_id)\
2754 .filter(
2754 .filter(
2755 UserGroupMember.user_id == user_id,
2755 UserGroupMember.user_id == user_id,
2756 UserGroup.users_group_active == true())
2756 UserGroup.users_group_active == true())
2757 if repo_group_id:
2757 if repo_group_id:
2758 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2758 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2759 return q.all()
2759 return q.all()
2760
2760
2761 @classmethod
2761 @classmethod
2762 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2762 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2763 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2763 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2764 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2764 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2765 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2765 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2766 .filter(UserUserGroupToPerm.user_id == user_id)
2766 .filter(UserUserGroupToPerm.user_id == user_id)
2767 if user_group_id:
2767 if user_group_id:
2768 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2768 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2769 return q.all()
2769 return q.all()
2770
2770
2771 @classmethod
2771 @classmethod
2772 def get_default_user_group_perms_from_user_group(
2772 def get_default_user_group_perms_from_user_group(
2773 cls, user_id, user_group_id=None):
2773 cls, user_id, user_group_id=None):
2774 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2774 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2775 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2775 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2776 .join(
2776 .join(
2777 Permission,
2777 Permission,
2778 UserGroupUserGroupToPerm.permission_id ==
2778 UserGroupUserGroupToPerm.permission_id ==
2779 Permission.permission_id)\
2779 Permission.permission_id)\
2780 .join(
2780 .join(
2781 TargetUserGroup,
2781 TargetUserGroup,
2782 UserGroupUserGroupToPerm.target_user_group_id ==
2782 UserGroupUserGroupToPerm.target_user_group_id ==
2783 TargetUserGroup.users_group_id)\
2783 TargetUserGroup.users_group_id)\
2784 .join(
2784 .join(
2785 UserGroup,
2785 UserGroup,
2786 UserGroupUserGroupToPerm.user_group_id ==
2786 UserGroupUserGroupToPerm.user_group_id ==
2787 UserGroup.users_group_id)\
2787 UserGroup.users_group_id)\
2788 .join(
2788 .join(
2789 UserGroupMember,
2789 UserGroupMember,
2790 UserGroupUserGroupToPerm.user_group_id ==
2790 UserGroupUserGroupToPerm.user_group_id ==
2791 UserGroupMember.users_group_id)\
2791 UserGroupMember.users_group_id)\
2792 .filter(
2792 .filter(
2793 UserGroupMember.user_id == user_id,
2793 UserGroupMember.user_id == user_id,
2794 UserGroup.users_group_active == true())
2794 UserGroup.users_group_active == true())
2795 if user_group_id:
2795 if user_group_id:
2796 q = q.filter(
2796 q = q.filter(
2797 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2797 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2798
2798
2799 return q.all()
2799 return q.all()
2800
2800
2801
2801
2802 class UserRepoToPerm(Base, BaseModel):
2802 class UserRepoToPerm(Base, BaseModel):
2803 __tablename__ = 'repo_to_perm'
2803 __tablename__ = 'repo_to_perm'
2804 __table_args__ = (
2804 __table_args__ = (
2805 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2805 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2806 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2806 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2807 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2807 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2808 )
2808 )
2809 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2809 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2810 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2810 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2811 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2811 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2812 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2812 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2813
2813
2814 user = relationship('User')
2814 user = relationship('User')
2815 repository = relationship('Repository')
2815 repository = relationship('Repository')
2816 permission = relationship('Permission')
2816 permission = relationship('Permission')
2817
2817
2818 @classmethod
2818 @classmethod
2819 def create(cls, user, repository, permission):
2819 def create(cls, user, repository, permission):
2820 n = cls()
2820 n = cls()
2821 n.user = user
2821 n.user = user
2822 n.repository = repository
2822 n.repository = repository
2823 n.permission = permission
2823 n.permission = permission
2824 Session().add(n)
2824 Session().add(n)
2825 return n
2825 return n
2826
2826
2827 def __unicode__(self):
2827 def __unicode__(self):
2828 return u'<%s => %s >' % (self.user, self.repository)
2828 return u'<%s => %s >' % (self.user, self.repository)
2829
2829
2830
2830
2831 class UserUserGroupToPerm(Base, BaseModel):
2831 class UserUserGroupToPerm(Base, BaseModel):
2832 __tablename__ = 'user_user_group_to_perm'
2832 __tablename__ = 'user_user_group_to_perm'
2833 __table_args__ = (
2833 __table_args__ = (
2834 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2834 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2835 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2835 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2836 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2836 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2837 )
2837 )
2838 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2838 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_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2839 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2840 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2840 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2841 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2841 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2842
2842
2843 user = relationship('User')
2843 user = relationship('User')
2844 user_group = relationship('UserGroup')
2844 user_group = relationship('UserGroup')
2845 permission = relationship('Permission')
2845 permission = relationship('Permission')
2846
2846
2847 @classmethod
2847 @classmethod
2848 def create(cls, user, user_group, permission):
2848 def create(cls, user, user_group, permission):
2849 n = cls()
2849 n = cls()
2850 n.user = user
2850 n.user = user
2851 n.user_group = user_group
2851 n.user_group = user_group
2852 n.permission = permission
2852 n.permission = permission
2853 Session().add(n)
2853 Session().add(n)
2854 return n
2854 return n
2855
2855
2856 def __unicode__(self):
2856 def __unicode__(self):
2857 return u'<%s => %s >' % (self.user, self.user_group)
2857 return u'<%s => %s >' % (self.user, self.user_group)
2858
2858
2859
2859
2860 class UserToPerm(Base, BaseModel):
2860 class UserToPerm(Base, BaseModel):
2861 __tablename__ = 'user_to_perm'
2861 __tablename__ = 'user_to_perm'
2862 __table_args__ = (
2862 __table_args__ = (
2863 UniqueConstraint('user_id', 'permission_id'),
2863 UniqueConstraint('user_id', 'permission_id'),
2864 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2864 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2865 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2865 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2866 )
2866 )
2867 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2867 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2868 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2868 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2869 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2869 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2870
2870
2871 user = relationship('User')
2871 user = relationship('User')
2872 permission = relationship('Permission', lazy='joined')
2872 permission = relationship('Permission', lazy='joined')
2873
2873
2874 def __unicode__(self):
2874 def __unicode__(self):
2875 return u'<%s => %s >' % (self.user, self.permission)
2875 return u'<%s => %s >' % (self.user, self.permission)
2876
2876
2877
2877
2878 class UserGroupRepoToPerm(Base, BaseModel):
2878 class UserGroupRepoToPerm(Base, BaseModel):
2879 __tablename__ = 'users_group_repo_to_perm'
2879 __tablename__ = 'users_group_repo_to_perm'
2880 __table_args__ = (
2880 __table_args__ = (
2881 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2881 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2882 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2882 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2883 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2883 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2884 )
2884 )
2885 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2885 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2886 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2886 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2887 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2887 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2888 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2888 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2889
2889
2890 users_group = relationship('UserGroup')
2890 users_group = relationship('UserGroup')
2891 permission = relationship('Permission')
2891 permission = relationship('Permission')
2892 repository = relationship('Repository')
2892 repository = relationship('Repository')
2893
2893
2894 @classmethod
2894 @classmethod
2895 def create(cls, users_group, repository, permission):
2895 def create(cls, users_group, repository, permission):
2896 n = cls()
2896 n = cls()
2897 n.users_group = users_group
2897 n.users_group = users_group
2898 n.repository = repository
2898 n.repository = repository
2899 n.permission = permission
2899 n.permission = permission
2900 Session().add(n)
2900 Session().add(n)
2901 return n
2901 return n
2902
2902
2903 def __unicode__(self):
2903 def __unicode__(self):
2904 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2904 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2905
2905
2906
2906
2907 class UserGroupUserGroupToPerm(Base, BaseModel):
2907 class UserGroupUserGroupToPerm(Base, BaseModel):
2908 __tablename__ = 'user_group_user_group_to_perm'
2908 __tablename__ = 'user_group_user_group_to_perm'
2909 __table_args__ = (
2909 __table_args__ = (
2910 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2910 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2911 CheckConstraint('target_user_group_id != user_group_id'),
2911 CheckConstraint('target_user_group_id != user_group_id'),
2912 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2912 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2913 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2913 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2914 )
2914 )
2915 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)
2915 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 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2916 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2917 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2917 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2918 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2918 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2919
2919
2920 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2920 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2921 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2921 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2922 permission = relationship('Permission')
2922 permission = relationship('Permission')
2923
2923
2924 @classmethod
2924 @classmethod
2925 def create(cls, target_user_group, user_group, permission):
2925 def create(cls, target_user_group, user_group, permission):
2926 n = cls()
2926 n = cls()
2927 n.target_user_group = target_user_group
2927 n.target_user_group = target_user_group
2928 n.user_group = user_group
2928 n.user_group = user_group
2929 n.permission = permission
2929 n.permission = permission
2930 Session().add(n)
2930 Session().add(n)
2931 return n
2931 return n
2932
2932
2933 def __unicode__(self):
2933 def __unicode__(self):
2934 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2934 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2935
2935
2936
2936
2937 class UserGroupToPerm(Base, BaseModel):
2937 class UserGroupToPerm(Base, BaseModel):
2938 __tablename__ = 'users_group_to_perm'
2938 __tablename__ = 'users_group_to_perm'
2939 __table_args__ = (
2939 __table_args__ = (
2940 UniqueConstraint('users_group_id', 'permission_id',),
2940 UniqueConstraint('users_group_id', 'permission_id',),
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2943 )
2943 )
2944 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2944 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2945 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2945 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2946 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2946 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2947
2947
2948 users_group = relationship('UserGroup')
2948 users_group = relationship('UserGroup')
2949 permission = relationship('Permission')
2949 permission = relationship('Permission')
2950
2950
2951
2951
2952 class UserRepoGroupToPerm(Base, BaseModel):
2952 class UserRepoGroupToPerm(Base, BaseModel):
2953 __tablename__ = 'user_repo_group_to_perm'
2953 __tablename__ = 'user_repo_group_to_perm'
2954 __table_args__ = (
2954 __table_args__ = (
2955 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2955 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2956 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2956 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2957 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2957 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2958 )
2958 )
2959
2959
2960 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2960 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2961 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2961 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2962 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2962 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2963 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2963 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2964
2964
2965 user = relationship('User')
2965 user = relationship('User')
2966 group = relationship('RepoGroup')
2966 group = relationship('RepoGroup')
2967 permission = relationship('Permission')
2967 permission = relationship('Permission')
2968
2968
2969 @classmethod
2969 @classmethod
2970 def create(cls, user, repository_group, permission):
2970 def create(cls, user, repository_group, permission):
2971 n = cls()
2971 n = cls()
2972 n.user = user
2972 n.user = user
2973 n.group = repository_group
2973 n.group = repository_group
2974 n.permission = permission
2974 n.permission = permission
2975 Session().add(n)
2975 Session().add(n)
2976 return n
2976 return n
2977
2977
2978
2978
2979 class UserGroupRepoGroupToPerm(Base, BaseModel):
2979 class UserGroupRepoGroupToPerm(Base, BaseModel):
2980 __tablename__ = 'users_group_repo_group_to_perm'
2980 __tablename__ = 'users_group_repo_group_to_perm'
2981 __table_args__ = (
2981 __table_args__ = (
2982 UniqueConstraint('users_group_id', 'group_id'),
2982 UniqueConstraint('users_group_id', 'group_id'),
2983 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2983 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2984 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2984 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2985 )
2985 )
2986
2986
2987 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)
2987 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_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2988 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2989 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2989 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2990 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2990 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2991
2991
2992 users_group = relationship('UserGroup')
2992 users_group = relationship('UserGroup')
2993 permission = relationship('Permission')
2993 permission = relationship('Permission')
2994 group = relationship('RepoGroup')
2994 group = relationship('RepoGroup')
2995
2995
2996 @classmethod
2996 @classmethod
2997 def create(cls, user_group, repository_group, permission):
2997 def create(cls, user_group, repository_group, permission):
2998 n = cls()
2998 n = cls()
2999 n.users_group = user_group
2999 n.users_group = user_group
3000 n.group = repository_group
3000 n.group = repository_group
3001 n.permission = permission
3001 n.permission = permission
3002 Session().add(n)
3002 Session().add(n)
3003 return n
3003 return n
3004
3004
3005 def __unicode__(self):
3005 def __unicode__(self):
3006 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3006 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3007
3007
3008
3008
3009 class Statistics(Base, BaseModel):
3009 class Statistics(Base, BaseModel):
3010 __tablename__ = 'statistics'
3010 __tablename__ = 'statistics'
3011 __table_args__ = (
3011 __table_args__ = (
3012 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3012 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3013 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3013 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3014 )
3014 )
3015 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3015 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3016 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3016 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3017 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3017 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3018 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3018 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3019 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3019 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3020 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3020 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3021
3021
3022 repository = relationship('Repository', single_parent=True)
3022 repository = relationship('Repository', single_parent=True)
3023
3023
3024
3024
3025 class UserFollowing(Base, BaseModel):
3025 class UserFollowing(Base, BaseModel):
3026 __tablename__ = 'user_followings'
3026 __tablename__ = 'user_followings'
3027 __table_args__ = (
3027 __table_args__ = (
3028 UniqueConstraint('user_id', 'follows_repository_id'),
3028 UniqueConstraint('user_id', 'follows_repository_id'),
3029 UniqueConstraint('user_id', 'follows_user_id'),
3029 UniqueConstraint('user_id', 'follows_user_id'),
3030 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3030 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3031 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3031 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3032 )
3032 )
3033
3033
3034 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3034 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3035 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3035 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3036 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3036 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3037 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3037 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3038 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3038 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3039
3039
3040 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3040 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3041
3041
3042 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3042 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3043 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3043 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3044
3044
3045 @classmethod
3045 @classmethod
3046 def get_repo_followers(cls, repo_id):
3046 def get_repo_followers(cls, repo_id):
3047 return cls.query().filter(cls.follows_repo_id == repo_id)
3047 return cls.query().filter(cls.follows_repo_id == repo_id)
3048
3048
3049
3049
3050 class CacheKey(Base, BaseModel):
3050 class CacheKey(Base, BaseModel):
3051 __tablename__ = 'cache_invalidation'
3051 __tablename__ = 'cache_invalidation'
3052 __table_args__ = (
3052 __table_args__ = (
3053 UniqueConstraint('cache_key'),
3053 UniqueConstraint('cache_key'),
3054 Index('key_idx', 'cache_key'),
3054 Index('key_idx', 'cache_key'),
3055 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3055 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3056 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3056 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3057 )
3057 )
3058 CACHE_TYPE_ATOM = 'ATOM'
3058 CACHE_TYPE_ATOM = 'ATOM'
3059 CACHE_TYPE_RSS = 'RSS'
3059 CACHE_TYPE_RSS = 'RSS'
3060 CACHE_TYPE_README = 'README'
3060 CACHE_TYPE_README = 'README'
3061
3061
3062 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3062 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3063 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3063 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3064 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3064 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3065 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3065 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3066
3066
3067 def __init__(self, cache_key, cache_args=''):
3067 def __init__(self, cache_key, cache_args=''):
3068 self.cache_key = cache_key
3068 self.cache_key = cache_key
3069 self.cache_args = cache_args
3069 self.cache_args = cache_args
3070 self.cache_active = False
3070 self.cache_active = False
3071
3071
3072 def __unicode__(self):
3072 def __unicode__(self):
3073 return u"<%s('%s:%s[%s]')>" % (
3073 return u"<%s('%s:%s[%s]')>" % (
3074 self.__class__.__name__,
3074 self.__class__.__name__,
3075 self.cache_id, self.cache_key, self.cache_active)
3075 self.cache_id, self.cache_key, self.cache_active)
3076
3076
3077 def _cache_key_partition(self):
3077 def _cache_key_partition(self):
3078 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3078 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3079 return prefix, repo_name, suffix
3079 return prefix, repo_name, suffix
3080
3080
3081 def get_prefix(self):
3081 def get_prefix(self):
3082 """
3082 """
3083 Try to extract prefix from existing cache key. The key could consist
3083 Try to extract prefix from existing cache key. The key could consist
3084 of prefix, repo_name, suffix
3084 of prefix, repo_name, suffix
3085 """
3085 """
3086 # this returns prefix, repo_name, suffix
3086 # this returns prefix, repo_name, suffix
3087 return self._cache_key_partition()[0]
3087 return self._cache_key_partition()[0]
3088
3088
3089 def get_suffix(self):
3089 def get_suffix(self):
3090 """
3090 """
3091 get suffix that might have been used in _get_cache_key to
3091 get suffix that might have been used in _get_cache_key to
3092 generate self.cache_key. Only used for informational purposes
3092 generate self.cache_key. Only used for informational purposes
3093 in repo_edit.mako.
3093 in repo_edit.mako.
3094 """
3094 """
3095 # prefix, repo_name, suffix
3095 # prefix, repo_name, suffix
3096 return self._cache_key_partition()[2]
3096 return self._cache_key_partition()[2]
3097
3097
3098 @classmethod
3098 @classmethod
3099 def delete_all_cache(cls):
3099 def delete_all_cache(cls):
3100 """
3100 """
3101 Delete all cache keys from database.
3101 Delete all cache keys from database.
3102 Should only be run when all instances are down and all entries
3102 Should only be run when all instances are down and all entries
3103 thus stale.
3103 thus stale.
3104 """
3104 """
3105 cls.query().delete()
3105 cls.query().delete()
3106 Session().commit()
3106 Session().commit()
3107
3107
3108 @classmethod
3108 @classmethod
3109 def get_cache_key(cls, repo_name, cache_type):
3109 def get_cache_key(cls, repo_name, cache_type):
3110 """
3110 """
3111
3111
3112 Generate a cache key for this process of RhodeCode instance.
3112 Generate a cache key for this process of RhodeCode instance.
3113 Prefix most likely will be process id or maybe explicitly set
3113 Prefix most likely will be process id or maybe explicitly set
3114 instance_id from .ini file.
3114 instance_id from .ini file.
3115 """
3115 """
3116 import rhodecode
3116 import rhodecode
3117 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3117 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3118
3118
3119 repo_as_unicode = safe_unicode(repo_name)
3119 repo_as_unicode = safe_unicode(repo_name)
3120 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3120 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3121 if cache_type else repo_as_unicode
3121 if cache_type else repo_as_unicode
3122
3122
3123 return u'{}{}'.format(prefix, key)
3123 return u'{}{}'.format(prefix, key)
3124
3124
3125 @classmethod
3125 @classmethod
3126 def set_invalidate(cls, repo_name, delete=False):
3126 def set_invalidate(cls, repo_name, delete=False):
3127 """
3127 """
3128 Mark all caches of a repo as invalid in the database.
3128 Mark all caches of a repo as invalid in the database.
3129 """
3129 """
3130
3130
3131 try:
3131 try:
3132 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3132 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3133 if delete:
3133 if delete:
3134 log.debug('cache objects deleted for repo %s',
3134 log.debug('cache objects deleted for repo %s',
3135 safe_str(repo_name))
3135 safe_str(repo_name))
3136 qry.delete()
3136 qry.delete()
3137 else:
3137 else:
3138 log.debug('cache objects marked as invalid for repo %s',
3138 log.debug('cache objects marked as invalid for repo %s',
3139 safe_str(repo_name))
3139 safe_str(repo_name))
3140 qry.update({"cache_active": False})
3140 qry.update({"cache_active": False})
3141
3141
3142 Session().commit()
3142 Session().commit()
3143 except Exception:
3143 except Exception:
3144 log.exception(
3144 log.exception(
3145 'Cache key invalidation failed for repository %s',
3145 'Cache key invalidation failed for repository %s',
3146 safe_str(repo_name))
3146 safe_str(repo_name))
3147 Session().rollback()
3147 Session().rollback()
3148
3148
3149 @classmethod
3149 @classmethod
3150 def get_active_cache(cls, cache_key):
3150 def get_active_cache(cls, cache_key):
3151 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3151 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3152 if inv_obj:
3152 if inv_obj:
3153 return inv_obj
3153 return inv_obj
3154 return None
3154 return None
3155
3155
3156
3156
3157 class ChangesetComment(Base, BaseModel):
3157 class ChangesetComment(Base, BaseModel):
3158 __tablename__ = 'changeset_comments'
3158 __tablename__ = 'changeset_comments'
3159 __table_args__ = (
3159 __table_args__ = (
3160 Index('cc_revision_idx', 'revision'),
3160 Index('cc_revision_idx', 'revision'),
3161 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3161 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3162 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3162 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3163 )
3163 )
3164
3164
3165 COMMENT_OUTDATED = u'comment_outdated'
3165 COMMENT_OUTDATED = u'comment_outdated'
3166 COMMENT_TYPE_NOTE = u'note'
3166 COMMENT_TYPE_NOTE = u'note'
3167 COMMENT_TYPE_TODO = u'todo'
3167 COMMENT_TYPE_TODO = u'todo'
3168 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3168 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3169
3169
3170 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3170 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3171 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3171 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3172 revision = Column('revision', String(40), nullable=True)
3172 revision = Column('revision', String(40), nullable=True)
3173 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3173 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3174 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3174 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3175 line_no = Column('line_no', Unicode(10), nullable=True)
3175 line_no = Column('line_no', Unicode(10), nullable=True)
3176 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3176 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3177 f_path = Column('f_path', Unicode(1000), nullable=True)
3177 f_path = Column('f_path', Unicode(1000), nullable=True)
3178 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3178 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3179 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3179 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3180 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3180 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3181 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3181 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3182 renderer = Column('renderer', Unicode(64), nullable=True)
3182 renderer = Column('renderer', Unicode(64), nullable=True)
3183 display_state = Column('display_state', Unicode(128), nullable=True)
3183 display_state = Column('display_state', Unicode(128), nullable=True)
3184
3184
3185 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3185 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3186 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3186 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3187 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3187 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3188 author = relationship('User', lazy='joined')
3188 author = relationship('User', lazy='joined')
3189 repo = relationship('Repository')
3189 repo = relationship('Repository')
3190 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3190 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3191 pull_request = relationship('PullRequest', lazy='joined')
3191 pull_request = relationship('PullRequest', lazy='joined')
3192 pull_request_version = relationship('PullRequestVersion')
3192 pull_request_version = relationship('PullRequestVersion')
3193
3193
3194 @classmethod
3194 @classmethod
3195 def get_users(cls, revision=None, pull_request_id=None):
3195 def get_users(cls, revision=None, pull_request_id=None):
3196 """
3196 """
3197 Returns user associated with this ChangesetComment. ie those
3197 Returns user associated with this ChangesetComment. ie those
3198 who actually commented
3198 who actually commented
3199
3199
3200 :param cls:
3200 :param cls:
3201 :param revision:
3201 :param revision:
3202 """
3202 """
3203 q = Session().query(User)\
3203 q = Session().query(User)\
3204 .join(ChangesetComment.author)
3204 .join(ChangesetComment.author)
3205 if revision:
3205 if revision:
3206 q = q.filter(cls.revision == revision)
3206 q = q.filter(cls.revision == revision)
3207 elif pull_request_id:
3207 elif pull_request_id:
3208 q = q.filter(cls.pull_request_id == pull_request_id)
3208 q = q.filter(cls.pull_request_id == pull_request_id)
3209 return q.all()
3209 return q.all()
3210
3210
3211 @classmethod
3211 @classmethod
3212 def get_index_from_version(cls, pr_version, versions):
3212 def get_index_from_version(cls, pr_version, versions):
3213 num_versions = [x.pull_request_version_id for x in versions]
3213 num_versions = [x.pull_request_version_id for x in versions]
3214 try:
3214 try:
3215 return num_versions.index(pr_version) +1
3215 return num_versions.index(pr_version) +1
3216 except (IndexError, ValueError):
3216 except (IndexError, ValueError):
3217 return
3217 return
3218
3218
3219 @property
3219 @property
3220 def outdated(self):
3220 def outdated(self):
3221 return self.display_state == self.COMMENT_OUTDATED
3221 return self.display_state == self.COMMENT_OUTDATED
3222
3222
3223 def outdated_at_version(self, version):
3223 def outdated_at_version(self, version):
3224 """
3224 """
3225 Checks if comment is outdated for given pull request version
3225 Checks if comment is outdated for given pull request version
3226 """
3226 """
3227 return self.outdated and self.pull_request_version_id != version
3227 return self.outdated and self.pull_request_version_id != version
3228
3228
3229 def older_than_version(self, version):
3229 def older_than_version(self, version):
3230 """
3230 """
3231 Checks if comment is made from previous version than given
3231 Checks if comment is made from previous version than given
3232 """
3232 """
3233 if version is None:
3233 if version is None:
3234 return self.pull_request_version_id is not None
3234 return self.pull_request_version_id is not None
3235
3235
3236 return self.pull_request_version_id < version
3236 return self.pull_request_version_id < version
3237
3237
3238 @property
3238 @property
3239 def resolved(self):
3239 def resolved(self):
3240 return self.resolved_by[0] if self.resolved_by else None
3240 return self.resolved_by[0] if self.resolved_by else None
3241
3241
3242 @property
3242 @property
3243 def is_todo(self):
3243 def is_todo(self):
3244 return self.comment_type == self.COMMENT_TYPE_TODO
3244 return self.comment_type == self.COMMENT_TYPE_TODO
3245
3245
3246 @property
3246 @property
3247 def is_inline(self):
3247 def is_inline(self):
3248 return self.line_no and self.f_path
3248 return self.line_no and self.f_path
3249
3249
3250 def get_index_version(self, versions):
3250 def get_index_version(self, versions):
3251 return self.get_index_from_version(
3251 return self.get_index_from_version(
3252 self.pull_request_version_id, versions)
3252 self.pull_request_version_id, versions)
3253
3253
3254 def __repr__(self):
3254 def __repr__(self):
3255 if self.comment_id:
3255 if self.comment_id:
3256 return '<DB:Comment #%s>' % self.comment_id
3256 return '<DB:Comment #%s>' % self.comment_id
3257 else:
3257 else:
3258 return '<DB:Comment at %#x>' % id(self)
3258 return '<DB:Comment at %#x>' % id(self)
3259
3259
3260 def get_api_data(self):
3260 def get_api_data(self):
3261 comment = self
3261 comment = self
3262 data = {
3262 data = {
3263 'comment_id': comment.comment_id,
3263 'comment_id': comment.comment_id,
3264 'comment_type': comment.comment_type,
3264 'comment_type': comment.comment_type,
3265 'comment_text': comment.text,
3265 'comment_text': comment.text,
3266 'comment_status': comment.status_change,
3266 'comment_status': comment.status_change,
3267 'comment_f_path': comment.f_path,
3267 'comment_f_path': comment.f_path,
3268 'comment_lineno': comment.line_no,
3268 'comment_lineno': comment.line_no,
3269 'comment_author': comment.author,
3269 'comment_author': comment.author,
3270 'comment_created_on': comment.created_on
3270 'comment_created_on': comment.created_on
3271 }
3271 }
3272 return data
3272 return data
3273
3273
3274 def __json__(self):
3274 def __json__(self):
3275 data = dict()
3275 data = dict()
3276 data.update(self.get_api_data())
3276 data.update(self.get_api_data())
3277 return data
3277 return data
3278
3278
3279
3279
3280 class ChangesetStatus(Base, BaseModel):
3280 class ChangesetStatus(Base, BaseModel):
3281 __tablename__ = 'changeset_statuses'
3281 __tablename__ = 'changeset_statuses'
3282 __table_args__ = (
3282 __table_args__ = (
3283 Index('cs_revision_idx', 'revision'),
3283 Index('cs_revision_idx', 'revision'),
3284 Index('cs_version_idx', 'version'),
3284 Index('cs_version_idx', 'version'),
3285 UniqueConstraint('repo_id', 'revision', 'version'),
3285 UniqueConstraint('repo_id', 'revision', 'version'),
3286 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3286 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3287 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3287 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3288 )
3288 )
3289 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3289 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3290 STATUS_APPROVED = 'approved'
3290 STATUS_APPROVED = 'approved'
3291 STATUS_REJECTED = 'rejected'
3291 STATUS_REJECTED = 'rejected'
3292 STATUS_UNDER_REVIEW = 'under_review'
3292 STATUS_UNDER_REVIEW = 'under_review'
3293
3293
3294 STATUSES = [
3294 STATUSES = [
3295 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3295 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3296 (STATUS_APPROVED, _("Approved")),
3296 (STATUS_APPROVED, _("Approved")),
3297 (STATUS_REJECTED, _("Rejected")),
3297 (STATUS_REJECTED, _("Rejected")),
3298 (STATUS_UNDER_REVIEW, _("Under Review")),
3298 (STATUS_UNDER_REVIEW, _("Under Review")),
3299 ]
3299 ]
3300
3300
3301 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3301 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3302 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3302 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3303 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3304 revision = Column('revision', String(40), nullable=False)
3304 revision = Column('revision', String(40), nullable=False)
3305 status = Column('status', String(128), nullable=False, default=DEFAULT)
3305 status = Column('status', String(128), nullable=False, default=DEFAULT)
3306 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3306 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3307 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3307 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3308 version = Column('version', Integer(), nullable=False, default=0)
3308 version = Column('version', Integer(), nullable=False, default=0)
3309 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3309 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3310
3310
3311 author = relationship('User', lazy='joined')
3311 author = relationship('User', lazy='joined')
3312 repo = relationship('Repository')
3312 repo = relationship('Repository')
3313 comment = relationship('ChangesetComment', lazy='joined')
3313 comment = relationship('ChangesetComment', lazy='joined')
3314 pull_request = relationship('PullRequest', lazy='joined')
3314 pull_request = relationship('PullRequest', lazy='joined')
3315
3315
3316 def __unicode__(self):
3316 def __unicode__(self):
3317 return u"<%s('%s[v%s]:%s')>" % (
3317 return u"<%s('%s[v%s]:%s')>" % (
3318 self.__class__.__name__,
3318 self.__class__.__name__,
3319 self.status, self.version, self.author
3319 self.status, self.version, self.author
3320 )
3320 )
3321
3321
3322 @classmethod
3322 @classmethod
3323 def get_status_lbl(cls, value):
3323 def get_status_lbl(cls, value):
3324 return dict(cls.STATUSES).get(value)
3324 return dict(cls.STATUSES).get(value)
3325
3325
3326 @property
3326 @property
3327 def status_lbl(self):
3327 def status_lbl(self):
3328 return ChangesetStatus.get_status_lbl(self.status)
3328 return ChangesetStatus.get_status_lbl(self.status)
3329
3329
3330 def get_api_data(self):
3330 def get_api_data(self):
3331 status = self
3331 status = self
3332 data = {
3332 data = {
3333 'status_id': status.changeset_status_id,
3333 'status_id': status.changeset_status_id,
3334 'status': status.status,
3334 'status': status.status,
3335 }
3335 }
3336 return data
3336 return data
3337
3337
3338 def __json__(self):
3338 def __json__(self):
3339 data = dict()
3339 data = dict()
3340 data.update(self.get_api_data())
3340 data.update(self.get_api_data())
3341 return data
3341 return data
3342
3342
3343
3343
3344 class _PullRequestBase(BaseModel):
3344 class _PullRequestBase(BaseModel):
3345 """
3345 """
3346 Common attributes of pull request and version entries.
3346 Common attributes of pull request and version entries.
3347 """
3347 """
3348
3348
3349 # .status values
3349 # .status values
3350 STATUS_NEW = u'new'
3350 STATUS_NEW = u'new'
3351 STATUS_OPEN = u'open'
3351 STATUS_OPEN = u'open'
3352 STATUS_CLOSED = u'closed'
3352 STATUS_CLOSED = u'closed'
3353
3353
3354 title = Column('title', Unicode(255), nullable=True)
3354 title = Column('title', Unicode(255), nullable=True)
3355 description = Column(
3355 description = Column(
3356 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3356 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3357 nullable=True)
3357 nullable=True)
3358 # new/open/closed status of pull request (not approve/reject/etc)
3358 # new/open/closed status of pull request (not approve/reject/etc)
3359 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3359 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3360 created_on = Column(
3360 created_on = Column(
3361 'created_on', DateTime(timezone=False), nullable=False,
3361 'created_on', DateTime(timezone=False), nullable=False,
3362 default=datetime.datetime.now)
3362 default=datetime.datetime.now)
3363 updated_on = Column(
3363 updated_on = Column(
3364 'updated_on', DateTime(timezone=False), nullable=False,
3364 'updated_on', DateTime(timezone=False), nullable=False,
3365 default=datetime.datetime.now)
3365 default=datetime.datetime.now)
3366
3366
3367 @declared_attr
3367 @declared_attr
3368 def user_id(cls):
3368 def user_id(cls):
3369 return Column(
3369 return Column(
3370 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3370 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3371 unique=None)
3371 unique=None)
3372
3372
3373 # 500 revisions max
3373 # 500 revisions max
3374 _revisions = Column(
3374 _revisions = Column(
3375 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3375 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3376
3376
3377 @declared_attr
3377 @declared_attr
3378 def source_repo_id(cls):
3378 def source_repo_id(cls):
3379 # TODO: dan: rename column to source_repo_id
3379 # TODO: dan: rename column to source_repo_id
3380 return Column(
3380 return Column(
3381 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3381 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3382 nullable=False)
3382 nullable=False)
3383
3383
3384 source_ref = Column('org_ref', Unicode(255), nullable=False)
3384 source_ref = Column('org_ref', Unicode(255), nullable=False)
3385
3385
3386 @declared_attr
3386 @declared_attr
3387 def target_repo_id(cls):
3387 def target_repo_id(cls):
3388 # TODO: dan: rename column to target_repo_id
3388 # TODO: dan: rename column to target_repo_id
3389 return Column(
3389 return Column(
3390 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3390 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3391 nullable=False)
3391 nullable=False)
3392
3392
3393 target_ref = Column('other_ref', Unicode(255), nullable=False)
3393 target_ref = Column('other_ref', Unicode(255), nullable=False)
3394 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3394 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3395
3395
3396 # TODO: dan: rename column to last_merge_source_rev
3396 # TODO: dan: rename column to last_merge_source_rev
3397 _last_merge_source_rev = Column(
3397 _last_merge_source_rev = Column(
3398 'last_merge_org_rev', String(40), nullable=True)
3398 'last_merge_org_rev', String(40), nullable=True)
3399 # TODO: dan: rename column to last_merge_target_rev
3399 # TODO: dan: rename column to last_merge_target_rev
3400 _last_merge_target_rev = Column(
3400 _last_merge_target_rev = Column(
3401 'last_merge_other_rev', String(40), nullable=True)
3401 'last_merge_other_rev', String(40), nullable=True)
3402 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3402 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3403 merge_rev = Column('merge_rev', String(40), nullable=True)
3403 merge_rev = Column('merge_rev', String(40), nullable=True)
3404
3404
3405 reviewer_data = Column(
3405 reviewer_data = Column(
3406 'reviewer_data_json', MutationObj.as_mutable(
3406 'reviewer_data_json', MutationObj.as_mutable(
3407 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3407 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3408
3408
3409 @property
3409 @property
3410 def reviewer_data_json(self):
3410 def reviewer_data_json(self):
3411 return json.dumps(self.reviewer_data)
3411 return json.dumps(self.reviewer_data)
3412
3412
3413 @hybrid_property
3413 @hybrid_property
3414 def description_safe(self):
3414 def description_safe(self):
3415 from rhodecode.lib import helpers as h
3415 from rhodecode.lib import helpers as h
3416 return h.escape(self.description)
3416 return h.escape(self.description)
3417
3417
3418 @hybrid_property
3418 @hybrid_property
3419 def revisions(self):
3419 def revisions(self):
3420 return self._revisions.split(':') if self._revisions else []
3420 return self._revisions.split(':') if self._revisions else []
3421
3421
3422 @revisions.setter
3422 @revisions.setter
3423 def revisions(self, val):
3423 def revisions(self, val):
3424 self._revisions = ':'.join(val)
3424 self._revisions = ':'.join(val)
3425
3425
3426 @hybrid_property
3426 @hybrid_property
3427 def last_merge_status(self):
3427 def last_merge_status(self):
3428 return safe_int(self._last_merge_status)
3428 return safe_int(self._last_merge_status)
3429
3429
3430 @last_merge_status.setter
3430 @last_merge_status.setter
3431 def last_merge_status(self, val):
3431 def last_merge_status(self, val):
3432 self._last_merge_status = val
3432 self._last_merge_status = val
3433
3433
3434 @declared_attr
3434 @declared_attr
3435 def author(cls):
3435 def author(cls):
3436 return relationship('User', lazy='joined')
3436 return relationship('User', lazy='joined')
3437
3437
3438 @declared_attr
3438 @declared_attr
3439 def source_repo(cls):
3439 def source_repo(cls):
3440 return relationship(
3440 return relationship(
3441 'Repository',
3441 'Repository',
3442 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3442 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3443
3443
3444 @property
3444 @property
3445 def source_ref_parts(self):
3445 def source_ref_parts(self):
3446 return self.unicode_to_reference(self.source_ref)
3446 return self.unicode_to_reference(self.source_ref)
3447
3447
3448 @declared_attr
3448 @declared_attr
3449 def target_repo(cls):
3449 def target_repo(cls):
3450 return relationship(
3450 return relationship(
3451 'Repository',
3451 'Repository',
3452 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3452 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3453
3453
3454 @property
3454 @property
3455 def target_ref_parts(self):
3455 def target_ref_parts(self):
3456 return self.unicode_to_reference(self.target_ref)
3456 return self.unicode_to_reference(self.target_ref)
3457
3457
3458 @property
3458 @property
3459 def shadow_merge_ref(self):
3459 def shadow_merge_ref(self):
3460 return self.unicode_to_reference(self._shadow_merge_ref)
3460 return self.unicode_to_reference(self._shadow_merge_ref)
3461
3461
3462 @shadow_merge_ref.setter
3462 @shadow_merge_ref.setter
3463 def shadow_merge_ref(self, ref):
3463 def shadow_merge_ref(self, ref):
3464 self._shadow_merge_ref = self.reference_to_unicode(ref)
3464 self._shadow_merge_ref = self.reference_to_unicode(ref)
3465
3465
3466 def unicode_to_reference(self, raw):
3466 def unicode_to_reference(self, raw):
3467 """
3467 """
3468 Convert a unicode (or string) to a reference object.
3468 Convert a unicode (or string) to a reference object.
3469 If unicode evaluates to False it returns None.
3469 If unicode evaluates to False it returns None.
3470 """
3470 """
3471 if raw:
3471 if raw:
3472 refs = raw.split(':')
3472 refs = raw.split(':')
3473 return Reference(*refs)
3473 return Reference(*refs)
3474 else:
3474 else:
3475 return None
3475 return None
3476
3476
3477 def reference_to_unicode(self, ref):
3477 def reference_to_unicode(self, ref):
3478 """
3478 """
3479 Convert a reference object to unicode.
3479 Convert a reference object to unicode.
3480 If reference is None it returns None.
3480 If reference is None it returns None.
3481 """
3481 """
3482 if ref:
3482 if ref:
3483 return u':'.join(ref)
3483 return u':'.join(ref)
3484 else:
3484 else:
3485 return None
3485 return None
3486
3486
3487 def get_api_data(self, with_merge_state=True):
3487 def get_api_data(self, with_merge_state=True):
3488 from rhodecode.model.pull_request import PullRequestModel
3488 from rhodecode.model.pull_request import PullRequestModel
3489
3489
3490 pull_request = self
3490 pull_request = self
3491 if with_merge_state:
3491 if with_merge_state:
3492 merge_status = PullRequestModel().merge_status(pull_request)
3492 merge_status = PullRequestModel().merge_status(pull_request)
3493 merge_state = {
3493 merge_state = {
3494 'status': merge_status[0],
3494 'status': merge_status[0],
3495 'message': safe_unicode(merge_status[1]),
3495 'message': safe_unicode(merge_status[1]),
3496 }
3496 }
3497 else:
3497 else:
3498 merge_state = {'status': 'not_available',
3498 merge_state = {'status': 'not_available',
3499 'message': 'not_available'}
3499 'message': 'not_available'}
3500
3500
3501 merge_data = {
3501 merge_data = {
3502 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3502 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3503 'reference': (
3503 'reference': (
3504 pull_request.shadow_merge_ref._asdict()
3504 pull_request.shadow_merge_ref._asdict()
3505 if pull_request.shadow_merge_ref else None),
3505 if pull_request.shadow_merge_ref else None),
3506 }
3506 }
3507
3507
3508 data = {
3508 data = {
3509 'pull_request_id': pull_request.pull_request_id,
3509 'pull_request_id': pull_request.pull_request_id,
3510 'url': PullRequestModel().get_url(pull_request),
3510 'url': PullRequestModel().get_url(pull_request),
3511 'title': pull_request.title,
3511 'title': pull_request.title,
3512 'description': pull_request.description,
3512 'description': pull_request.description,
3513 'status': pull_request.status,
3513 'status': pull_request.status,
3514 'created_on': pull_request.created_on,
3514 'created_on': pull_request.created_on,
3515 'updated_on': pull_request.updated_on,
3515 'updated_on': pull_request.updated_on,
3516 'commit_ids': pull_request.revisions,
3516 'commit_ids': pull_request.revisions,
3517 'review_status': pull_request.calculated_review_status(),
3517 'review_status': pull_request.calculated_review_status(),
3518 'mergeable': merge_state,
3518 'mergeable': merge_state,
3519 'source': {
3519 'source': {
3520 'clone_url': pull_request.source_repo.clone_url(),
3520 'clone_url': pull_request.source_repo.clone_url(),
3521 'repository': pull_request.source_repo.repo_name,
3521 'repository': pull_request.source_repo.repo_name,
3522 'reference': {
3522 'reference': {
3523 'name': pull_request.source_ref_parts.name,
3523 'name': pull_request.source_ref_parts.name,
3524 'type': pull_request.source_ref_parts.type,
3524 'type': pull_request.source_ref_parts.type,
3525 'commit_id': pull_request.source_ref_parts.commit_id,
3525 'commit_id': pull_request.source_ref_parts.commit_id,
3526 },
3526 },
3527 },
3527 },
3528 'target': {
3528 'target': {
3529 'clone_url': pull_request.target_repo.clone_url(),
3529 'clone_url': pull_request.target_repo.clone_url(),
3530 'repository': pull_request.target_repo.repo_name,
3530 'repository': pull_request.target_repo.repo_name,
3531 'reference': {
3531 'reference': {
3532 'name': pull_request.target_ref_parts.name,
3532 'name': pull_request.target_ref_parts.name,
3533 'type': pull_request.target_ref_parts.type,
3533 'type': pull_request.target_ref_parts.type,
3534 'commit_id': pull_request.target_ref_parts.commit_id,
3534 'commit_id': pull_request.target_ref_parts.commit_id,
3535 },
3535 },
3536 },
3536 },
3537 'merge': merge_data,
3537 'merge': merge_data,
3538 'author': pull_request.author.get_api_data(include_secrets=False,
3538 'author': pull_request.author.get_api_data(include_secrets=False,
3539 details='basic'),
3539 details='basic'),
3540 'reviewers': [
3540 'reviewers': [
3541 {
3541 {
3542 'user': reviewer.get_api_data(include_secrets=False,
3542 'user': reviewer.get_api_data(include_secrets=False,
3543 details='basic'),
3543 details='basic'),
3544 'reasons': reasons,
3544 'reasons': reasons,
3545 'review_status': st[0][1].status if st else 'not_reviewed',
3545 'review_status': st[0][1].status if st else 'not_reviewed',
3546 }
3546 }
3547 for reviewer, reasons, mandatory, st in
3547 for reviewer, reasons, mandatory, st in
3548 pull_request.reviewers_statuses()
3548 pull_request.reviewers_statuses()
3549 ]
3549 ]
3550 }
3550 }
3551
3551
3552 return data
3552 return data
3553
3553
3554
3554
3555 class PullRequest(Base, _PullRequestBase):
3555 class PullRequest(Base, _PullRequestBase):
3556 __tablename__ = 'pull_requests'
3556 __tablename__ = 'pull_requests'
3557 __table_args__ = (
3557 __table_args__ = (
3558 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3558 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3559 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3559 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3560 )
3560 )
3561
3561
3562 pull_request_id = Column(
3562 pull_request_id = Column(
3563 'pull_request_id', Integer(), nullable=False, primary_key=True)
3563 'pull_request_id', Integer(), nullable=False, primary_key=True)
3564
3564
3565 def __repr__(self):
3565 def __repr__(self):
3566 if self.pull_request_id:
3566 if self.pull_request_id:
3567 return '<DB:PullRequest #%s>' % self.pull_request_id
3567 return '<DB:PullRequest #%s>' % self.pull_request_id
3568 else:
3568 else:
3569 return '<DB:PullRequest at %#x>' % id(self)
3569 return '<DB:PullRequest at %#x>' % id(self)
3570
3570
3571 reviewers = relationship('PullRequestReviewers',
3571 reviewers = relationship('PullRequestReviewers',
3572 cascade="all, delete, delete-orphan")
3572 cascade="all, delete, delete-orphan")
3573 statuses = relationship('ChangesetStatus',
3573 statuses = relationship('ChangesetStatus',
3574 cascade="all, delete, delete-orphan")
3574 cascade="all, delete, delete-orphan")
3575 comments = relationship('ChangesetComment',
3575 comments = relationship('ChangesetComment',
3576 cascade="all, delete, delete-orphan")
3576 cascade="all, delete, delete-orphan")
3577 versions = relationship('PullRequestVersion',
3577 versions = relationship('PullRequestVersion',
3578 cascade="all, delete, delete-orphan",
3578 cascade="all, delete, delete-orphan",
3579 lazy='dynamic')
3579 lazy='dynamic')
3580
3580
3581 @classmethod
3581 @classmethod
3582 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3582 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3583 internal_methods=None):
3583 internal_methods=None):
3584
3584
3585 class PullRequestDisplay(object):
3585 class PullRequestDisplay(object):
3586 """
3586 """
3587 Special object wrapper for showing PullRequest data via Versions
3587 Special object wrapper for showing PullRequest data via Versions
3588 It mimics PR object as close as possible. This is read only object
3588 It mimics PR object as close as possible. This is read only object
3589 just for display
3589 just for display
3590 """
3590 """
3591
3591
3592 def __init__(self, attrs, internal=None):
3592 def __init__(self, attrs, internal=None):
3593 self.attrs = attrs
3593 self.attrs = attrs
3594 # internal have priority over the given ones via attrs
3594 # internal have priority over the given ones via attrs
3595 self.internal = internal or ['versions']
3595 self.internal = internal or ['versions']
3596
3596
3597 def __getattr__(self, item):
3597 def __getattr__(self, item):
3598 if item in self.internal:
3598 if item in self.internal:
3599 return getattr(self, item)
3599 return getattr(self, item)
3600 try:
3600 try:
3601 return self.attrs[item]
3601 return self.attrs[item]
3602 except KeyError:
3602 except KeyError:
3603 raise AttributeError(
3603 raise AttributeError(
3604 '%s object has no attribute %s' % (self, item))
3604 '%s object has no attribute %s' % (self, item))
3605
3605
3606 def __repr__(self):
3606 def __repr__(self):
3607 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3607 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3608
3608
3609 def versions(self):
3609 def versions(self):
3610 return pull_request_obj.versions.order_by(
3610 return pull_request_obj.versions.order_by(
3611 PullRequestVersion.pull_request_version_id).all()
3611 PullRequestVersion.pull_request_version_id).all()
3612
3612
3613 def is_closed(self):
3613 def is_closed(self):
3614 return pull_request_obj.is_closed()
3614 return pull_request_obj.is_closed()
3615
3615
3616 @property
3616 @property
3617 def pull_request_version_id(self):
3617 def pull_request_version_id(self):
3618 return getattr(pull_request_obj, 'pull_request_version_id', None)
3618 return getattr(pull_request_obj, 'pull_request_version_id', None)
3619
3619
3620 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3620 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3621
3621
3622 attrs.author = StrictAttributeDict(
3622 attrs.author = StrictAttributeDict(
3623 pull_request_obj.author.get_api_data())
3623 pull_request_obj.author.get_api_data())
3624 if pull_request_obj.target_repo:
3624 if pull_request_obj.target_repo:
3625 attrs.target_repo = StrictAttributeDict(
3625 attrs.target_repo = StrictAttributeDict(
3626 pull_request_obj.target_repo.get_api_data())
3626 pull_request_obj.target_repo.get_api_data())
3627 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3627 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3628
3628
3629 if pull_request_obj.source_repo:
3629 if pull_request_obj.source_repo:
3630 attrs.source_repo = StrictAttributeDict(
3630 attrs.source_repo = StrictAttributeDict(
3631 pull_request_obj.source_repo.get_api_data())
3631 pull_request_obj.source_repo.get_api_data())
3632 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3632 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3633
3633
3634 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3634 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3635 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3635 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3636 attrs.revisions = pull_request_obj.revisions
3636 attrs.revisions = pull_request_obj.revisions
3637
3637
3638 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3638 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3639 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3639 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3640 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3640 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3641
3641
3642 return PullRequestDisplay(attrs, internal=internal_methods)
3642 return PullRequestDisplay(attrs, internal=internal_methods)
3643
3643
3644 def is_closed(self):
3644 def is_closed(self):
3645 return self.status == self.STATUS_CLOSED
3645 return self.status == self.STATUS_CLOSED
3646
3646
3647 def __json__(self):
3647 def __json__(self):
3648 return {
3648 return {
3649 'revisions': self.revisions,
3649 'revisions': self.revisions,
3650 }
3650 }
3651
3651
3652 def calculated_review_status(self):
3652 def calculated_review_status(self):
3653 from rhodecode.model.changeset_status import ChangesetStatusModel
3653 from rhodecode.model.changeset_status import ChangesetStatusModel
3654 return ChangesetStatusModel().calculated_review_status(self)
3654 return ChangesetStatusModel().calculated_review_status(self)
3655
3655
3656 def reviewers_statuses(self):
3656 def reviewers_statuses(self):
3657 from rhodecode.model.changeset_status import ChangesetStatusModel
3657 from rhodecode.model.changeset_status import ChangesetStatusModel
3658 return ChangesetStatusModel().reviewers_statuses(self)
3658 return ChangesetStatusModel().reviewers_statuses(self)
3659
3659
3660 @property
3660 @property
3661 def workspace_id(self):
3661 def workspace_id(self):
3662 from rhodecode.model.pull_request import PullRequestModel
3662 from rhodecode.model.pull_request import PullRequestModel
3663 return PullRequestModel()._workspace_id(self)
3663 return PullRequestModel()._workspace_id(self)
3664
3664
3665 def get_shadow_repo(self):
3665 def get_shadow_repo(self):
3666 workspace_id = self.workspace_id
3666 workspace_id = self.workspace_id
3667 vcs_obj = self.target_repo.scm_instance()
3667 vcs_obj = self.target_repo.scm_instance()
3668 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3668 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3669 workspace_id)
3669 workspace_id)
3670 return vcs_obj.get_shadow_instance(shadow_repository_path)
3670 return vcs_obj.get_shadow_instance(shadow_repository_path)
3671
3671
3672
3672
3673 class PullRequestVersion(Base, _PullRequestBase):
3673 class PullRequestVersion(Base, _PullRequestBase):
3674 __tablename__ = 'pull_request_versions'
3674 __tablename__ = 'pull_request_versions'
3675 __table_args__ = (
3675 __table_args__ = (
3676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3677 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3677 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3678 )
3678 )
3679
3679
3680 pull_request_version_id = Column(
3680 pull_request_version_id = Column(
3681 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3681 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3682 pull_request_id = Column(
3682 pull_request_id = Column(
3683 'pull_request_id', Integer(),
3683 'pull_request_id', Integer(),
3684 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3684 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3685 pull_request = relationship('PullRequest')
3685 pull_request = relationship('PullRequest')
3686
3686
3687 def __repr__(self):
3687 def __repr__(self):
3688 if self.pull_request_version_id:
3688 if self.pull_request_version_id:
3689 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3689 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3690 else:
3690 else:
3691 return '<DB:PullRequestVersion at %#x>' % id(self)
3691 return '<DB:PullRequestVersion at %#x>' % id(self)
3692
3692
3693 @property
3693 @property
3694 def reviewers(self):
3694 def reviewers(self):
3695 return self.pull_request.reviewers
3695 return self.pull_request.reviewers
3696
3696
3697 @property
3697 @property
3698 def versions(self):
3698 def versions(self):
3699 return self.pull_request.versions
3699 return self.pull_request.versions
3700
3700
3701 def is_closed(self):
3701 def is_closed(self):
3702 # calculate from original
3702 # calculate from original
3703 return self.pull_request.status == self.STATUS_CLOSED
3703 return self.pull_request.status == self.STATUS_CLOSED
3704
3704
3705 def calculated_review_status(self):
3705 def calculated_review_status(self):
3706 return self.pull_request.calculated_review_status()
3706 return self.pull_request.calculated_review_status()
3707
3707
3708 def reviewers_statuses(self):
3708 def reviewers_statuses(self):
3709 return self.pull_request.reviewers_statuses()
3709 return self.pull_request.reviewers_statuses()
3710
3710
3711
3711
3712 class PullRequestReviewers(Base, BaseModel):
3712 class PullRequestReviewers(Base, BaseModel):
3713 __tablename__ = 'pull_request_reviewers'
3713 __tablename__ = 'pull_request_reviewers'
3714 __table_args__ = (
3714 __table_args__ = (
3715 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3715 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3716 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3716 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3717 )
3717 )
3718
3718
3719 @hybrid_property
3719 @hybrid_property
3720 def reasons(self):
3720 def reasons(self):
3721 if not self._reasons:
3721 if not self._reasons:
3722 return []
3722 return []
3723 return self._reasons
3723 return self._reasons
3724
3724
3725 @reasons.setter
3725 @reasons.setter
3726 def reasons(self, val):
3726 def reasons(self, val):
3727 val = val or []
3727 val = val or []
3728 if any(not isinstance(x, compat.string_types) for x in val):
3728 if any(not isinstance(x, compat.string_types) for x in val):
3729 raise Exception('invalid reasons type, must be list of strings')
3729 raise Exception('invalid reasons type, must be list of strings')
3730 self._reasons = val
3730 self._reasons = val
3731
3731
3732 pull_requests_reviewers_id = Column(
3732 pull_requests_reviewers_id = Column(
3733 'pull_requests_reviewers_id', Integer(), nullable=False,
3733 'pull_requests_reviewers_id', Integer(), nullable=False,
3734 primary_key=True)
3734 primary_key=True)
3735 pull_request_id = Column(
3735 pull_request_id = Column(
3736 "pull_request_id", Integer(),
3736 "pull_request_id", Integer(),
3737 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3737 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3738 user_id = Column(
3738 user_id = Column(
3739 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3739 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3740 _reasons = Column(
3740 _reasons = Column(
3741 'reason', MutationList.as_mutable(
3741 'reason', MutationList.as_mutable(
3742 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3742 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3743 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3743 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3744 user = relationship('User')
3744 user = relationship('User')
3745 pull_request = relationship('PullRequest')
3745 pull_request = relationship('PullRequest')
3746
3746
3747
3747
3748 class Notification(Base, BaseModel):
3748 class Notification(Base, BaseModel):
3749 __tablename__ = 'notifications'
3749 __tablename__ = 'notifications'
3750 __table_args__ = (
3750 __table_args__ = (
3751 Index('notification_type_idx', 'type'),
3751 Index('notification_type_idx', 'type'),
3752 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3752 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3753 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3753 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3754 )
3754 )
3755
3755
3756 TYPE_CHANGESET_COMMENT = u'cs_comment'
3756 TYPE_CHANGESET_COMMENT = u'cs_comment'
3757 TYPE_MESSAGE = u'message'
3757 TYPE_MESSAGE = u'message'
3758 TYPE_MENTION = u'mention'
3758 TYPE_MENTION = u'mention'
3759 TYPE_REGISTRATION = u'registration'
3759 TYPE_REGISTRATION = u'registration'
3760 TYPE_PULL_REQUEST = u'pull_request'
3760 TYPE_PULL_REQUEST = u'pull_request'
3761 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3761 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3762
3762
3763 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3763 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3764 subject = Column('subject', Unicode(512), nullable=True)
3764 subject = Column('subject', Unicode(512), nullable=True)
3765 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3765 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3766 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3766 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3767 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3767 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3768 type_ = Column('type', Unicode(255))
3768 type_ = Column('type', Unicode(255))
3769
3769
3770 created_by_user = relationship('User')
3770 created_by_user = relationship('User')
3771 notifications_to_users = relationship('UserNotification', lazy='joined',
3771 notifications_to_users = relationship('UserNotification', lazy='joined',
3772 cascade="all, delete, delete-orphan")
3772 cascade="all, delete, delete-orphan")
3773
3773
3774 @property
3774 @property
3775 def recipients(self):
3775 def recipients(self):
3776 return [x.user for x in UserNotification.query()\
3776 return [x.user for x in UserNotification.query()\
3777 .filter(UserNotification.notification == self)\
3777 .filter(UserNotification.notification == self)\
3778 .order_by(UserNotification.user_id.asc()).all()]
3778 .order_by(UserNotification.user_id.asc()).all()]
3779
3779
3780 @classmethod
3780 @classmethod
3781 def create(cls, created_by, subject, body, recipients, type_=None):
3781 def create(cls, created_by, subject, body, recipients, type_=None):
3782 if type_ is None:
3782 if type_ is None:
3783 type_ = Notification.TYPE_MESSAGE
3783 type_ = Notification.TYPE_MESSAGE
3784
3784
3785 notification = cls()
3785 notification = cls()
3786 notification.created_by_user = created_by
3786 notification.created_by_user = created_by
3787 notification.subject = subject
3787 notification.subject = subject
3788 notification.body = body
3788 notification.body = body
3789 notification.type_ = type_
3789 notification.type_ = type_
3790 notification.created_on = datetime.datetime.now()
3790 notification.created_on = datetime.datetime.now()
3791
3791
3792 for u in recipients:
3792 for u in recipients:
3793 assoc = UserNotification()
3793 assoc = UserNotification()
3794 assoc.notification = notification
3794 assoc.notification = notification
3795
3795
3796 # if created_by is inside recipients mark his notification
3796 # if created_by is inside recipients mark his notification
3797 # as read
3797 # as read
3798 if u.user_id == created_by.user_id:
3798 if u.user_id == created_by.user_id:
3799 assoc.read = True
3799 assoc.read = True
3800
3800
3801 u.notifications.append(assoc)
3801 u.notifications.append(assoc)
3802 Session().add(notification)
3802 Session().add(notification)
3803
3803
3804 return notification
3804 return notification
3805
3805
3806
3806
3807 class UserNotification(Base, BaseModel):
3807 class UserNotification(Base, BaseModel):
3808 __tablename__ = 'user_to_notification'
3808 __tablename__ = 'user_to_notification'
3809 __table_args__ = (
3809 __table_args__ = (
3810 UniqueConstraint('user_id', 'notification_id'),
3810 UniqueConstraint('user_id', 'notification_id'),
3811 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3811 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3812 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3812 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3813 )
3813 )
3814 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3814 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3815 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3815 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3816 read = Column('read', Boolean, default=False)
3816 read = Column('read', Boolean, default=False)
3817 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3817 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3818
3818
3819 user = relationship('User', lazy="joined")
3819 user = relationship('User', lazy="joined")
3820 notification = relationship('Notification', lazy="joined",
3820 notification = relationship('Notification', lazy="joined",
3821 order_by=lambda: Notification.created_on.desc(),)
3821 order_by=lambda: Notification.created_on.desc(),)
3822
3822
3823 def mark_as_read(self):
3823 def mark_as_read(self):
3824 self.read = True
3824 self.read = True
3825 Session().add(self)
3825 Session().add(self)
3826
3826
3827
3827
3828 class Gist(Base, BaseModel):
3828 class Gist(Base, BaseModel):
3829 __tablename__ = 'gists'
3829 __tablename__ = 'gists'
3830 __table_args__ = (
3830 __table_args__ = (
3831 Index('g_gist_access_id_idx', 'gist_access_id'),
3831 Index('g_gist_access_id_idx', 'gist_access_id'),
3832 Index('g_created_on_idx', 'created_on'),
3832 Index('g_created_on_idx', 'created_on'),
3833 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3833 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3834 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3834 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3835 )
3835 )
3836 GIST_PUBLIC = u'public'
3836 GIST_PUBLIC = u'public'
3837 GIST_PRIVATE = u'private'
3837 GIST_PRIVATE = u'private'
3838 DEFAULT_FILENAME = u'gistfile1.txt'
3838 DEFAULT_FILENAME = u'gistfile1.txt'
3839
3839
3840 ACL_LEVEL_PUBLIC = u'acl_public'
3840 ACL_LEVEL_PUBLIC = u'acl_public'
3841 ACL_LEVEL_PRIVATE = u'acl_private'
3841 ACL_LEVEL_PRIVATE = u'acl_private'
3842
3842
3843 gist_id = Column('gist_id', Integer(), primary_key=True)
3843 gist_id = Column('gist_id', Integer(), primary_key=True)
3844 gist_access_id = Column('gist_access_id', Unicode(250))
3844 gist_access_id = Column('gist_access_id', Unicode(250))
3845 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3845 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3846 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3846 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3847 gist_expires = Column('gist_expires', Float(53), nullable=False)
3847 gist_expires = Column('gist_expires', Float(53), nullable=False)
3848 gist_type = Column('gist_type', Unicode(128), nullable=False)
3848 gist_type = Column('gist_type', Unicode(128), nullable=False)
3849 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3849 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3850 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3850 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3851 acl_level = Column('acl_level', Unicode(128), nullable=True)
3851 acl_level = Column('acl_level', Unicode(128), nullable=True)
3852
3852
3853 owner = relationship('User')
3853 owner = relationship('User')
3854
3854
3855 def __repr__(self):
3855 def __repr__(self):
3856 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3856 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3857
3857
3858 @hybrid_property
3858 @hybrid_property
3859 def description_safe(self):
3859 def description_safe(self):
3860 from rhodecode.lib import helpers as h
3860 from rhodecode.lib import helpers as h
3861 return h.escape(self.gist_description)
3861 return h.escape(self.gist_description)
3862
3862
3863 @classmethod
3863 @classmethod
3864 def get_or_404(cls, id_):
3864 def get_or_404(cls, id_):
3865 from pyramid.httpexceptions import HTTPNotFound
3865 from pyramid.httpexceptions import HTTPNotFound
3866
3866
3867 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3867 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3868 if not res:
3868 if not res:
3869 raise HTTPNotFound()
3869 raise HTTPNotFound()
3870 return res
3870 return res
3871
3871
3872 @classmethod
3872 @classmethod
3873 def get_by_access_id(cls, gist_access_id):
3873 def get_by_access_id(cls, gist_access_id):
3874 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3874 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3875
3875
3876 def gist_url(self):
3876 def gist_url(self):
3877 from rhodecode.model.gist import GistModel
3877 from rhodecode.model.gist import GistModel
3878 return GistModel().get_url(self)
3878 return GistModel().get_url(self)
3879
3879
3880 @classmethod
3880 @classmethod
3881 def base_path(cls):
3881 def base_path(cls):
3882 """
3882 """
3883 Returns base path when all gists are stored
3883 Returns base path when all gists are stored
3884
3884
3885 :param cls:
3885 :param cls:
3886 """
3886 """
3887 from rhodecode.model.gist import GIST_STORE_LOC
3887 from rhodecode.model.gist import GIST_STORE_LOC
3888 q = Session().query(RhodeCodeUi)\
3888 q = Session().query(RhodeCodeUi)\
3889 .filter(RhodeCodeUi.ui_key == URL_SEP)
3889 .filter(RhodeCodeUi.ui_key == URL_SEP)
3890 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3890 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3891 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3891 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3892
3892
3893 def get_api_data(self):
3893 def get_api_data(self):
3894 """
3894 """
3895 Common function for generating gist related data for API
3895 Common function for generating gist related data for API
3896 """
3896 """
3897 gist = self
3897 gist = self
3898 data = {
3898 data = {
3899 'gist_id': gist.gist_id,
3899 'gist_id': gist.gist_id,
3900 'type': gist.gist_type,
3900 'type': gist.gist_type,
3901 'access_id': gist.gist_access_id,
3901 'access_id': gist.gist_access_id,
3902 'description': gist.gist_description,
3902 'description': gist.gist_description,
3903 'url': gist.gist_url(),
3903 'url': gist.gist_url(),
3904 'expires': gist.gist_expires,
3904 'expires': gist.gist_expires,
3905 'created_on': gist.created_on,
3905 'created_on': gist.created_on,
3906 'modified_at': gist.modified_at,
3906 'modified_at': gist.modified_at,
3907 'content': None,
3907 'content': None,
3908 'acl_level': gist.acl_level,
3908 'acl_level': gist.acl_level,
3909 }
3909 }
3910 return data
3910 return data
3911
3911
3912 def __json__(self):
3912 def __json__(self):
3913 data = dict(
3913 data = dict(
3914 )
3914 )
3915 data.update(self.get_api_data())
3915 data.update(self.get_api_data())
3916 return data
3916 return data
3917 # SCM functions
3917 # SCM functions
3918
3918
3919 def scm_instance(self, **kwargs):
3919 def scm_instance(self, **kwargs):
3920 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3920 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3921 return get_vcs_instance(
3921 return get_vcs_instance(
3922 repo_path=safe_str(full_repo_path), create=False)
3922 repo_path=safe_str(full_repo_path), create=False)
3923
3923
3924
3924
3925 class ExternalIdentity(Base, BaseModel):
3925 class ExternalIdentity(Base, BaseModel):
3926 __tablename__ = 'external_identities'
3926 __tablename__ = 'external_identities'
3927 __table_args__ = (
3927 __table_args__ = (
3928 Index('local_user_id_idx', 'local_user_id'),
3928 Index('local_user_id_idx', 'local_user_id'),
3929 Index('external_id_idx', 'external_id'),
3929 Index('external_id_idx', 'external_id'),
3930 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3930 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3931 'mysql_charset': 'utf8'})
3931 'mysql_charset': 'utf8'})
3932
3932
3933 external_id = Column('external_id', Unicode(255), default=u'',
3933 external_id = Column('external_id', Unicode(255), default=u'',
3934 primary_key=True)
3934 primary_key=True)
3935 external_username = Column('external_username', Unicode(1024), default=u'')
3935 external_username = Column('external_username', Unicode(1024), default=u'')
3936 local_user_id = Column('local_user_id', Integer(),
3936 local_user_id = Column('local_user_id', Integer(),
3937 ForeignKey('users.user_id'), primary_key=True)
3937 ForeignKey('users.user_id'), primary_key=True)
3938 provider_name = Column('provider_name', Unicode(255), default=u'',
3938 provider_name = Column('provider_name', Unicode(255), default=u'',
3939 primary_key=True)
3939 primary_key=True)
3940 access_token = Column('access_token', String(1024), default=u'')
3940 access_token = Column('access_token', String(1024), default=u'')
3941 alt_token = Column('alt_token', String(1024), default=u'')
3941 alt_token = Column('alt_token', String(1024), default=u'')
3942 token_secret = Column('token_secret', String(1024), default=u'')
3942 token_secret = Column('token_secret', String(1024), default=u'')
3943
3943
3944 @classmethod
3944 @classmethod
3945 def by_external_id_and_provider(cls, external_id, provider_name,
3945 def by_external_id_and_provider(cls, external_id, provider_name,
3946 local_user_id=None):
3946 local_user_id=None):
3947 """
3947 """
3948 Returns ExternalIdentity instance based on search params
3948 Returns ExternalIdentity instance based on search params
3949
3949
3950 :param external_id:
3950 :param external_id:
3951 :param provider_name:
3951 :param provider_name:
3952 :return: ExternalIdentity
3952 :return: ExternalIdentity
3953 """
3953 """
3954 query = cls.query()
3954 query = cls.query()
3955 query = query.filter(cls.external_id == external_id)
3955 query = query.filter(cls.external_id == external_id)
3956 query = query.filter(cls.provider_name == provider_name)
3956 query = query.filter(cls.provider_name == provider_name)
3957 if local_user_id:
3957 if local_user_id:
3958 query = query.filter(cls.local_user_id == local_user_id)
3958 query = query.filter(cls.local_user_id == local_user_id)
3959 return query.first()
3959 return query.first()
3960
3960
3961 @classmethod
3961 @classmethod
3962 def user_by_external_id_and_provider(cls, external_id, provider_name):
3962 def user_by_external_id_and_provider(cls, external_id, provider_name):
3963 """
3963 """
3964 Returns User instance based on search params
3964 Returns User instance based on search params
3965
3965
3966 :param external_id:
3966 :param external_id:
3967 :param provider_name:
3967 :param provider_name:
3968 :return: User
3968 :return: User
3969 """
3969 """
3970 query = User.query()
3970 query = User.query()
3971 query = query.filter(cls.external_id == external_id)
3971 query = query.filter(cls.external_id == external_id)
3972 query = query.filter(cls.provider_name == provider_name)
3972 query = query.filter(cls.provider_name == provider_name)
3973 query = query.filter(User.user_id == cls.local_user_id)
3973 query = query.filter(User.user_id == cls.local_user_id)
3974 return query.first()
3974 return query.first()
3975
3975
3976 @classmethod
3976 @classmethod
3977 def by_local_user_id(cls, local_user_id):
3977 def by_local_user_id(cls, local_user_id):
3978 """
3978 """
3979 Returns all tokens for user
3979 Returns all tokens for user
3980
3980
3981 :param local_user_id:
3981 :param local_user_id:
3982 :return: ExternalIdentity
3982 :return: ExternalIdentity
3983 """
3983 """
3984 query = cls.query()
3984 query = cls.query()
3985 query = query.filter(cls.local_user_id == local_user_id)
3985 query = query.filter(cls.local_user_id == local_user_id)
3986 return query
3986 return query
3987
3987
3988
3988
3989 class Integration(Base, BaseModel):
3989 class Integration(Base, BaseModel):
3990 __tablename__ = 'integrations'
3990 __tablename__ = 'integrations'
3991 __table_args__ = (
3991 __table_args__ = (
3992 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3992 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3993 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3993 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3994 )
3994 )
3995
3995
3996 integration_id = Column('integration_id', Integer(), primary_key=True)
3996 integration_id = Column('integration_id', Integer(), primary_key=True)
3997 integration_type = Column('integration_type', String(255))
3997 integration_type = Column('integration_type', String(255))
3998 enabled = Column('enabled', Boolean(), nullable=False)
3998 enabled = Column('enabled', Boolean(), nullable=False)
3999 name = Column('name', String(255), nullable=False)
3999 name = Column('name', String(255), nullable=False)
4000 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4000 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4001 default=False)
4001 default=False)
4002
4002
4003 settings = Column(
4003 settings = Column(
4004 'settings_json', MutationObj.as_mutable(
4004 'settings_json', MutationObj.as_mutable(
4005 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4005 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4006 repo_id = Column(
4006 repo_id = Column(
4007 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4007 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4008 nullable=True, unique=None, default=None)
4008 nullable=True, unique=None, default=None)
4009 repo = relationship('Repository', lazy='joined')
4009 repo = relationship('Repository', lazy='joined')
4010
4010
4011 repo_group_id = Column(
4011 repo_group_id = Column(
4012 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4012 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4013 nullable=True, unique=None, default=None)
4013 nullable=True, unique=None, default=None)
4014 repo_group = relationship('RepoGroup', lazy='joined')
4014 repo_group = relationship('RepoGroup', lazy='joined')
4015
4015
4016 @property
4016 @property
4017 def scope(self):
4017 def scope(self):
4018 if self.repo:
4018 if self.repo:
4019 return repr(self.repo)
4019 return repr(self.repo)
4020 if self.repo_group:
4020 if self.repo_group:
4021 if self.child_repos_only:
4021 if self.child_repos_only:
4022 return repr(self.repo_group) + ' (child repos only)'
4022 return repr(self.repo_group) + ' (child repos only)'
4023 else:
4023 else:
4024 return repr(self.repo_group) + ' (recursive)'
4024 return repr(self.repo_group) + ' (recursive)'
4025 if self.child_repos_only:
4025 if self.child_repos_only:
4026 return 'root_repos'
4026 return 'root_repos'
4027 return 'global'
4027 return 'global'
4028
4028
4029 def __repr__(self):
4029 def __repr__(self):
4030 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4030 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4031
4031
4032
4032
4033 class RepoReviewRuleUser(Base, BaseModel):
4033 class RepoReviewRuleUser(Base, BaseModel):
4034 __tablename__ = 'repo_review_rules_users'
4034 __tablename__ = 'repo_review_rules_users'
4035 __table_args__ = (
4035 __table_args__ = (
4036 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4036 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4037 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4037 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4038 )
4038 )
4039 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4039 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4040 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4040 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4041 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4041 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4042 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4042 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4043 user = relationship('User')
4043 user = relationship('User')
4044
4044
4045 def rule_data(self):
4045 def rule_data(self):
4046 return {
4046 return {
4047 'mandatory': self.mandatory
4047 'mandatory': self.mandatory
4048 }
4048 }
4049
4049
4050
4050
4051 class RepoReviewRuleUserGroup(Base, BaseModel):
4051 class RepoReviewRuleUserGroup(Base, BaseModel):
4052 __tablename__ = 'repo_review_rules_users_groups'
4052 __tablename__ = 'repo_review_rules_users_groups'
4053 __table_args__ = (
4053 __table_args__ = (
4054 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4054 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4055 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4055 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4056 )
4056 )
4057 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4057 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4058 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4058 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4059 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4059 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4060 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4060 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4061 users_group = relationship('UserGroup')
4061 users_group = relationship('UserGroup')
4062
4062
4063 def rule_data(self):
4063 def rule_data(self):
4064 return {
4064 return {
4065 'mandatory': self.mandatory
4065 'mandatory': self.mandatory
4066 }
4066 }
4067
4067
4068
4068
4069 class RepoReviewRule(Base, BaseModel):
4069 class RepoReviewRule(Base, BaseModel):
4070 __tablename__ = 'repo_review_rules'
4070 __tablename__ = 'repo_review_rules'
4071 __table_args__ = (
4071 __table_args__ = (
4072 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4072 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4073 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4073 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4074 )
4074 )
4075
4075
4076 repo_review_rule_id = Column(
4076 repo_review_rule_id = Column(
4077 'repo_review_rule_id', Integer(), primary_key=True)
4077 'repo_review_rule_id', Integer(), primary_key=True)
4078 repo_id = Column(
4078 repo_id = Column(
4079 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4079 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4080 repo = relationship('Repository', backref='review_rules')
4080 repo = relationship('Repository', backref='review_rules')
4081
4081
4082 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4082 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4083 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4083 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4084
4084
4085 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4085 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4086 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4086 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4087 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4087 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4088 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4088 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4089
4089
4090 rule_users = relationship('RepoReviewRuleUser')
4090 rule_users = relationship('RepoReviewRuleUser')
4091 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4091 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4092
4092
4093 @hybrid_property
4093 @hybrid_property
4094 def branch_pattern(self):
4094 def branch_pattern(self):
4095 return self._branch_pattern or '*'
4095 return self._branch_pattern or '*'
4096
4096
4097 def _validate_glob(self, value):
4097 def _validate_glob(self, value):
4098 re.compile('^' + glob2re(value) + '$')
4098 re.compile('^' + glob2re(value) + '$')
4099
4099
4100 @branch_pattern.setter
4100 @branch_pattern.setter
4101 def branch_pattern(self, value):
4101 def branch_pattern(self, value):
4102 self._validate_glob(value)
4102 self._validate_glob(value)
4103 self._branch_pattern = value or '*'
4103 self._branch_pattern = value or '*'
4104
4104
4105 @hybrid_property
4105 @hybrid_property
4106 def file_pattern(self):
4106 def file_pattern(self):
4107 return self._file_pattern or '*'
4107 return self._file_pattern or '*'
4108
4108
4109 @file_pattern.setter
4109 @file_pattern.setter
4110 def file_pattern(self, value):
4110 def file_pattern(self, value):
4111 self._validate_glob(value)
4111 self._validate_glob(value)
4112 self._file_pattern = value or '*'
4112 self._file_pattern = value or '*'
4113
4113
4114 def matches(self, branch, files_changed):
4114 def matches(self, branch, files_changed):
4115 """
4115 """
4116 Check if this review rule matches a branch/files in a pull request
4116 Check if this review rule matches a branch/files in a pull request
4117
4117
4118 :param branch: branch name for the commit
4118 :param branch: branch name for the commit
4119 :param files_changed: list of file paths changed in the pull request
4119 :param files_changed: list of file paths changed in the pull request
4120 """
4120 """
4121
4121
4122 branch = branch or ''
4122 branch = branch or ''
4123 files_changed = files_changed or []
4123 files_changed = files_changed or []
4124
4124
4125 branch_matches = True
4125 branch_matches = True
4126 if branch:
4126 if branch:
4127 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4127 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4128 branch_matches = bool(branch_regex.search(branch))
4128 branch_matches = bool(branch_regex.search(branch))
4129
4129
4130 files_matches = True
4130 files_matches = True
4131 if self.file_pattern != '*':
4131 if self.file_pattern != '*':
4132 files_matches = False
4132 files_matches = False
4133 file_regex = re.compile(glob2re(self.file_pattern))
4133 file_regex = re.compile(glob2re(self.file_pattern))
4134 for filename in files_changed:
4134 for filename in files_changed:
4135 if file_regex.search(filename):
4135 if file_regex.search(filename):
4136 files_matches = True
4136 files_matches = True
4137 break
4137 break
4138
4138
4139 return branch_matches and files_matches
4139 return branch_matches and files_matches
4140
4140
4141 @property
4141 @property
4142 def review_users(self):
4142 def review_users(self):
4143 """ Returns the users which this rule applies to """
4143 """ Returns the users which this rule applies to """
4144
4144
4145 users = collections.OrderedDict()
4145 users = collections.OrderedDict()
4146
4146
4147 for rule_user in self.rule_users:
4147 for rule_user in self.rule_users:
4148 if rule_user.user.active:
4148 if rule_user.user.active:
4149 if rule_user.user not in users:
4149 if rule_user.user not in users:
4150 users[rule_user.user.username] = {
4150 users[rule_user.user.username] = {
4151 'user': rule_user.user,
4151 'user': rule_user.user,
4152 'source': 'user',
4152 'source': 'user',
4153 'source_data': {},
4153 'source_data': {},
4154 'data': rule_user.rule_data()
4154 'data': rule_user.rule_data()
4155 }
4155 }
4156
4156
4157 for rule_user_group in self.rule_user_groups:
4157 for rule_user_group in self.rule_user_groups:
4158 source_data = {
4158 source_data = {
4159 'name': rule_user_group.users_group.users_group_name,
4159 'name': rule_user_group.users_group.users_group_name,
4160 'members': len(rule_user_group.users_group.members)
4160 'members': len(rule_user_group.users_group.members)
4161 }
4161 }
4162 for member in rule_user_group.users_group.members:
4162 for member in rule_user_group.users_group.members:
4163 if member.user.active:
4163 if member.user.active:
4164 users[member.user.username] = {
4164 users[member.user.username] = {
4165 'user': member.user,
4165 'user': member.user,
4166 'source': 'user_group',
4166 'source': 'user_group',
4167 'source_data': source_data,
4167 'source_data': source_data,
4168 'data': rule_user_group.rule_data()
4168 'data': rule_user_group.rule_data()
4169 }
4169 }
4170
4170
4171 return users
4171 return users
4172
4172
4173 def __repr__(self):
4173 def __repr__(self):
4174 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4174 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4175 self.repo_review_rule_id, self.repo)
4175 self.repo_review_rule_id, self.repo)
4176
4176
4177
4177
4178 class ScheduleEntry(Base, BaseModel):
4178 class ScheduleEntry(Base, BaseModel):
4179 __tablename__ = 'schedule_entries'
4179 __tablename__ = 'schedule_entries'
4180 __table_args__ = (
4180 __table_args__ = (
4181 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4181 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4182 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4182 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4183 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4183 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4184 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4184 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4185 )
4185 )
4186 schedule_types = ['crontab', 'timedelta', 'integer']
4186 schedule_types = ['crontab', 'timedelta', 'integer']
4187 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4187 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4188
4188
4189 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4189 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4190 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4190 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4191 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4191 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4192
4192
4193 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4193 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4194 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4194 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4195
4195
4196 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4196 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4197 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4197 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4198
4198
4199 # task
4199 # task
4200 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4200 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4201 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4201 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4202 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4202 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4203 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4203 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4204
4204
4205 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4205 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4206 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4206 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4207
4207
4208 @hybrid_property
4208 @hybrid_property
4209 def schedule_type(self):
4209 def schedule_type(self):
4210 return self._schedule_type
4210 return self._schedule_type
4211
4211
4212 @schedule_type.setter
4212 @schedule_type.setter
4213 def schedule_type(self, val):
4213 def schedule_type(self, val):
4214 if val not in self.schedule_types:
4214 if val not in self.schedule_types:
4215 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4215 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4216 val, self.schedule_type))
4216 val, self.schedule_type))
4217
4217
4218 self._schedule_type = val
4218 self._schedule_type = val
4219
4219
4220 @classmethod
4220 @classmethod
4221 def get_uid(cls, obj):
4221 def get_uid(cls, obj):
4222 args = obj.task_args
4222 args = obj.task_args
4223 kwargs = obj.task_kwargs
4223 kwargs = obj.task_kwargs
4224 if isinstance(args, JsonRaw):
4224 if isinstance(args, JsonRaw):
4225 try:
4225 try:
4226 args = json.loads(args)
4226 args = json.loads(args)
4227 except ValueError:
4227 except ValueError:
4228 args = tuple()
4228 args = tuple()
4229
4229
4230 if isinstance(kwargs, JsonRaw):
4230 if isinstance(kwargs, JsonRaw):
4231 try:
4231 try:
4232 kwargs = json.loads(kwargs)
4232 kwargs = json.loads(kwargs)
4233 except ValueError:
4233 except ValueError:
4234 kwargs = dict()
4234 kwargs = dict()
4235
4235
4236 dot_notation = obj.task_dot_notation
4236 dot_notation = obj.task_dot_notation
4237 val = '.'.join(map(safe_str, [
4237 val = '.'.join(map(safe_str, [
4238 sorted(dot_notation), args, sorted(kwargs.items())]))
4238 sorted(dot_notation), args, sorted(kwargs.items())]))
4239 return hashlib.sha1(val).hexdigest()
4239 return hashlib.sha1(val).hexdigest()
4240
4240
4241 @classmethod
4241 @classmethod
4242 def get_by_schedule_name(cls, schedule_name):
4242 def get_by_schedule_name(cls, schedule_name):
4243 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4243 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4244
4244
4245 @classmethod
4245 @classmethod
4246 def get_by_schedule_id(cls, schedule_id):
4246 def get_by_schedule_id(cls, schedule_id):
4247 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4247 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4248
4248
4249 @property
4249 @property
4250 def task(self):
4250 def task(self):
4251 return self.task_dot_notation
4251 return self.task_dot_notation
4252
4252
4253 @property
4253 @property
4254 def schedule(self):
4254 def schedule(self):
4255 from rhodecode.lib.celerylib.utils import raw_2_schedule
4255 from rhodecode.lib.celerylib.utils import raw_2_schedule
4256 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4256 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4257 return schedule
4257 return schedule
4258
4258
4259 @property
4259 @property
4260 def args(self):
4260 def args(self):
4261 try:
4261 try:
4262 return list(self.task_args or [])
4262 return list(self.task_args or [])
4263 except ValueError:
4263 except ValueError:
4264 return list()
4264 return list()
4265
4265
4266 @property
4266 @property
4267 def kwargs(self):
4267 def kwargs(self):
4268 try:
4268 try:
4269 return dict(self.task_kwargs or {})
4269 return dict(self.task_kwargs or {})
4270 except ValueError:
4270 except ValueError:
4271 return dict()
4271 return dict()
4272
4272
4273 def _as_raw(self, val):
4273 def _as_raw(self, val):
4274 if hasattr(val, 'de_coerce'):
4274 if hasattr(val, 'de_coerce'):
4275 val = val.de_coerce()
4275 val = val.de_coerce()
4276 if val:
4276 if val:
4277 val = json.dumps(val)
4277 val = json.dumps(val)
4278
4278
4279 return val
4279 return val
4280
4280
4281 @property
4281 @property
4282 def schedule_definition_raw(self):
4282 def schedule_definition_raw(self):
4283 return self._as_raw(self.schedule_definition)
4283 return self._as_raw(self.schedule_definition)
4284
4284
4285 @property
4285 @property
4286 def args_raw(self):
4286 def args_raw(self):
4287 return self._as_raw(self.task_args)
4287 return self._as_raw(self.task_args)
4288
4288
4289 @property
4289 @property
4290 def kwargs_raw(self):
4290 def kwargs_raw(self):
4291 return self._as_raw(self.task_kwargs)
4291 return self._as_raw(self.task_kwargs)
4292
4292
4293 def __repr__(self):
4293 def __repr__(self):
4294 return '<DB:ScheduleEntry({}:{})>'.format(
4294 return '<DB:ScheduleEntry({}:{})>'.format(
4295 self.schedule_entry_id, self.schedule_name)
4295 self.schedule_entry_id, self.schedule_name)
4296
4296
4297
4297
4298 @event.listens_for(ScheduleEntry, 'before_update')
4298 @event.listens_for(ScheduleEntry, 'before_update')
4299 def update_task_uid(mapper, connection, target):
4299 def update_task_uid(mapper, connection, target):
4300 target.task_uid = ScheduleEntry.get_uid(target)
4300 target.task_uid = ScheduleEntry.get_uid(target)
4301
4301
4302
4302
4303 @event.listens_for(ScheduleEntry, 'before_insert')
4303 @event.listens_for(ScheduleEntry, 'before_insert')
4304 def set_task_uid(mapper, connection, target):
4304 def set_task_uid(mapper, connection, target):
4305 target.task_uid = ScheduleEntry.get_uid(target)
4305 target.task_uid = ScheduleEntry.get_uid(target)
4306
4306
4307
4307
4308 class DbMigrateVersion(Base, BaseModel):
4308 class DbMigrateVersion(Base, BaseModel):
4309 __tablename__ = 'db_migrate_version'
4309 __tablename__ = 'db_migrate_version'
4310 __table_args__ = (
4310 __table_args__ = (
4311 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4311 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4312 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4312 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4313 )
4313 )
4314 repository_id = Column('repository_id', String(250), primary_key=True)
4314 repository_id = Column('repository_id', String(250), primary_key=True)
4315 repository_path = Column('repository_path', Text)
4315 repository_path = Column('repository_path', Text)
4316 version = Column('version', Integer)
4316 version = Column('version', Integer)
4317
4317
4318
4318
4319 class DbSession(Base, BaseModel):
4319 class DbSession(Base, BaseModel):
4320 __tablename__ = 'db_session'
4320 __tablename__ = 'db_session'
4321 __table_args__ = (
4321 __table_args__ = (
4322 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4322 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4323 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4323 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4324 )
4324 )
4325
4325
4326 def __repr__(self):
4326 def __repr__(self):
4327 return '<DB:DbSession({})>'.format(self.id)
4327 return '<DB:DbSession({})>'.format(self.id)
4328
4328
4329 id = Column('id', Integer())
4329 id = Column('id', Integer())
4330 namespace = Column('namespace', String(255), primary_key=True)
4330 namespace = Column('namespace', String(255), primary_key=True)
4331 accessed = Column('accessed', DateTime, nullable=False)
4331 accessed = Column('accessed', DateTime, nullable=False)
4332 created = Column('created', DateTime, nullable=False)
4332 created = Column('created', DateTime, nullable=False)
4333 data = Column('data', PickleType, nullable=False)
4333 data = Column('data', PickleType, nullable=False)
@@ -1,4586 +1,4586 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 # pragma: no cover
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 # pragma: no cover
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 from pyramid import compat
52 from pyramid import compat
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 range(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, compat.string_types):
2179 if isinstance(rev, compat.string_types):
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 commit cache %s',
2233 log.debug('updated repo %s with new commit 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 range(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 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2364 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2365 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2365 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2366 )
2366 )
2367 __mapper_args__ = {'order_by': 'group_name'}
2367 __mapper_args__ = {'order_by': 'group_name'}
2368
2368
2369 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2369 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2370
2370
2371 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2371 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2372 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2372 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2373 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2373 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2374 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2374 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2375 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2375 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2376 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2376 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2377 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2377 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2378 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2378 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2379 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2379 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2380
2380
2381 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2381 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2382 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2382 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2383 parent_group = relationship('RepoGroup', remote_side=group_id)
2383 parent_group = relationship('RepoGroup', remote_side=group_id)
2384 user = relationship('User')
2384 user = relationship('User')
2385 integrations = relationship('Integration',
2385 integrations = relationship('Integration',
2386 cascade="all, delete, delete-orphan")
2386 cascade="all, delete, delete-orphan")
2387
2387
2388 def __init__(self, group_name='', parent_group=None):
2388 def __init__(self, group_name='', parent_group=None):
2389 self.group_name = group_name
2389 self.group_name = group_name
2390 self.parent_group = parent_group
2390 self.parent_group = parent_group
2391
2391
2392 def __unicode__(self):
2392 def __unicode__(self):
2393 return u"<%s('id:%s:%s')>" % (
2393 return u"<%s('id:%s:%s')>" % (
2394 self.__class__.__name__, self.group_id, self.group_name)
2394 self.__class__.__name__, self.group_id, self.group_name)
2395
2395
2396 @hybrid_property
2396 @hybrid_property
2397 def description_safe(self):
2397 def description_safe(self):
2398 from rhodecode.lib import helpers as h
2398 from rhodecode.lib import helpers as h
2399 return h.escape(self.group_description)
2399 return h.escape(self.group_description)
2400
2400
2401 @classmethod
2401 @classmethod
2402 def _generate_choice(cls, repo_group):
2402 def _generate_choice(cls, repo_group):
2403 from webhelpers2.html import literal as _literal
2403 from webhelpers2.html import literal as _literal
2404 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2404 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2405 return repo_group.group_id, _name(repo_group.full_path_splitted)
2405 return repo_group.group_id, _name(repo_group.full_path_splitted)
2406
2406
2407 @classmethod
2407 @classmethod
2408 def groups_choices(cls, groups=None, show_empty_group=True):
2408 def groups_choices(cls, groups=None, show_empty_group=True):
2409 if not groups:
2409 if not groups:
2410 groups = cls.query().all()
2410 groups = cls.query().all()
2411
2411
2412 repo_groups = []
2412 repo_groups = []
2413 if show_empty_group:
2413 if show_empty_group:
2414 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2414 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2415
2415
2416 repo_groups.extend([cls._generate_choice(x) for x in groups])
2416 repo_groups.extend([cls._generate_choice(x) for x in groups])
2417
2417
2418 repo_groups = sorted(
2418 repo_groups = sorted(
2419 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2419 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2420 return repo_groups
2420 return repo_groups
2421
2421
2422 @classmethod
2422 @classmethod
2423 def url_sep(cls):
2423 def url_sep(cls):
2424 return URL_SEP
2424 return URL_SEP
2425
2425
2426 @classmethod
2426 @classmethod
2427 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2427 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2428 if case_insensitive:
2428 if case_insensitive:
2429 gr = cls.query().filter(func.lower(cls.group_name)
2429 gr = cls.query().filter(func.lower(cls.group_name)
2430 == func.lower(group_name))
2430 == func.lower(group_name))
2431 else:
2431 else:
2432 gr = cls.query().filter(cls.group_name == group_name)
2432 gr = cls.query().filter(cls.group_name == group_name)
2433 if cache:
2433 if cache:
2434 name_key = _hash_key(group_name)
2434 name_key = _hash_key(group_name)
2435 gr = gr.options(
2435 gr = gr.options(
2436 FromCache("sql_cache_short", "get_group_%s" % name_key))
2436 FromCache("sql_cache_short", "get_group_%s" % name_key))
2437 return gr.scalar()
2437 return gr.scalar()
2438
2438
2439 @classmethod
2439 @classmethod
2440 def get_user_personal_repo_group(cls, user_id):
2440 def get_user_personal_repo_group(cls, user_id):
2441 user = User.get(user_id)
2441 user = User.get(user_id)
2442 if user.username == User.DEFAULT_USER:
2442 if user.username == User.DEFAULT_USER:
2443 return None
2443 return None
2444
2444
2445 return cls.query()\
2445 return cls.query()\
2446 .filter(cls.personal == true()) \
2446 .filter(cls.personal == true()) \
2447 .filter(cls.user == user).scalar()
2447 .filter(cls.user == user).scalar()
2448
2448
2449 @classmethod
2449 @classmethod
2450 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2450 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2451 case_insensitive=True):
2451 case_insensitive=True):
2452 q = RepoGroup.query()
2452 q = RepoGroup.query()
2453
2453
2454 if not isinstance(user_id, Optional):
2454 if not isinstance(user_id, Optional):
2455 q = q.filter(RepoGroup.user_id == user_id)
2455 q = q.filter(RepoGroup.user_id == user_id)
2456
2456
2457 if not isinstance(group_id, Optional):
2457 if not isinstance(group_id, Optional):
2458 q = q.filter(RepoGroup.group_parent_id == group_id)
2458 q = q.filter(RepoGroup.group_parent_id == group_id)
2459
2459
2460 if case_insensitive:
2460 if case_insensitive:
2461 q = q.order_by(func.lower(RepoGroup.group_name))
2461 q = q.order_by(func.lower(RepoGroup.group_name))
2462 else:
2462 else:
2463 q = q.order_by(RepoGroup.group_name)
2463 q = q.order_by(RepoGroup.group_name)
2464 return q.all()
2464 return q.all()
2465
2465
2466 @property
2466 @property
2467 def parents(self):
2467 def parents(self):
2468 parents_recursion_limit = 10
2468 parents_recursion_limit = 10
2469 groups = []
2469 groups = []
2470 if self.parent_group is None:
2470 if self.parent_group is None:
2471 return groups
2471 return groups
2472 cur_gr = self.parent_group
2472 cur_gr = self.parent_group
2473 groups.insert(0, cur_gr)
2473 groups.insert(0, cur_gr)
2474 cnt = 0
2474 cnt = 0
2475 while 1:
2475 while 1:
2476 cnt += 1
2476 cnt += 1
2477 gr = getattr(cur_gr, 'parent_group', None)
2477 gr = getattr(cur_gr, 'parent_group', None)
2478 cur_gr = cur_gr.parent_group
2478 cur_gr = cur_gr.parent_group
2479 if gr is None:
2479 if gr is None:
2480 break
2480 break
2481 if cnt == parents_recursion_limit:
2481 if cnt == parents_recursion_limit:
2482 # this will prevent accidental infinit loops
2482 # this will prevent accidental infinit loops
2483 log.error('more than %s parents found for group %s, stopping '
2483 log.error('more than %s parents found for group %s, stopping '
2484 'recursive parent fetching', parents_recursion_limit, self)
2484 'recursive parent fetching', parents_recursion_limit, self)
2485 break
2485 break
2486
2486
2487 groups.insert(0, gr)
2487 groups.insert(0, gr)
2488 return groups
2488 return groups
2489
2489
2490 @property
2490 @property
2491 def last_db_change(self):
2491 def last_db_change(self):
2492 return self.updated_on
2492 return self.updated_on
2493
2493
2494 @property
2494 @property
2495 def children(self):
2495 def children(self):
2496 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2496 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2497
2497
2498 @property
2498 @property
2499 def name(self):
2499 def name(self):
2500 return self.group_name.split(RepoGroup.url_sep())[-1]
2500 return self.group_name.split(RepoGroup.url_sep())[-1]
2501
2501
2502 @property
2502 @property
2503 def full_path(self):
2503 def full_path(self):
2504 return self.group_name
2504 return self.group_name
2505
2505
2506 @property
2506 @property
2507 def full_path_splitted(self):
2507 def full_path_splitted(self):
2508 return self.group_name.split(RepoGroup.url_sep())
2508 return self.group_name.split(RepoGroup.url_sep())
2509
2509
2510 @property
2510 @property
2511 def repositories(self):
2511 def repositories(self):
2512 return Repository.query()\
2512 return Repository.query()\
2513 .filter(Repository.group == self)\
2513 .filter(Repository.group == self)\
2514 .order_by(Repository.repo_name)
2514 .order_by(Repository.repo_name)
2515
2515
2516 @property
2516 @property
2517 def repositories_recursive_count(self):
2517 def repositories_recursive_count(self):
2518 cnt = self.repositories.count()
2518 cnt = self.repositories.count()
2519
2519
2520 def children_count(group):
2520 def children_count(group):
2521 cnt = 0
2521 cnt = 0
2522 for child in group.children:
2522 for child in group.children:
2523 cnt += child.repositories.count()
2523 cnt += child.repositories.count()
2524 cnt += children_count(child)
2524 cnt += children_count(child)
2525 return cnt
2525 return cnt
2526
2526
2527 return cnt + children_count(self)
2527 return cnt + children_count(self)
2528
2528
2529 def _recursive_objects(self, include_repos=True):
2529 def _recursive_objects(self, include_repos=True):
2530 all_ = []
2530 all_ = []
2531
2531
2532 def _get_members(root_gr):
2532 def _get_members(root_gr):
2533 if include_repos:
2533 if include_repos:
2534 for r in root_gr.repositories:
2534 for r in root_gr.repositories:
2535 all_.append(r)
2535 all_.append(r)
2536 childs = root_gr.children.all()
2536 childs = root_gr.children.all()
2537 if childs:
2537 if childs:
2538 for gr in childs:
2538 for gr in childs:
2539 all_.append(gr)
2539 all_.append(gr)
2540 _get_members(gr)
2540 _get_members(gr)
2541
2541
2542 _get_members(self)
2542 _get_members(self)
2543 return [self] + all_
2543 return [self] + all_
2544
2544
2545 def recursive_groups_and_repos(self):
2545 def recursive_groups_and_repos(self):
2546 """
2546 """
2547 Recursive return all groups, with repositories in those groups
2547 Recursive return all groups, with repositories in those groups
2548 """
2548 """
2549 return self._recursive_objects()
2549 return self._recursive_objects()
2550
2550
2551 def recursive_groups(self):
2551 def recursive_groups(self):
2552 """
2552 """
2553 Returns all children groups for this group including children of children
2553 Returns all children groups for this group including children of children
2554 """
2554 """
2555 return self._recursive_objects(include_repos=False)
2555 return self._recursive_objects(include_repos=False)
2556
2556
2557 def get_new_name(self, group_name):
2557 def get_new_name(self, group_name):
2558 """
2558 """
2559 returns new full group name based on parent and new name
2559 returns new full group name based on parent and new name
2560
2560
2561 :param group_name:
2561 :param group_name:
2562 """
2562 """
2563 path_prefix = (self.parent_group.full_path_splitted if
2563 path_prefix = (self.parent_group.full_path_splitted if
2564 self.parent_group else [])
2564 self.parent_group else [])
2565 return RepoGroup.url_sep().join(path_prefix + [group_name])
2565 return RepoGroup.url_sep().join(path_prefix + [group_name])
2566
2566
2567 def permissions(self, with_admins=True, with_owner=True):
2567 def permissions(self, with_admins=True, with_owner=True):
2568 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2568 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2569 q = q.options(joinedload(UserRepoGroupToPerm.group),
2569 q = q.options(joinedload(UserRepoGroupToPerm.group),
2570 joinedload(UserRepoGroupToPerm.user),
2570 joinedload(UserRepoGroupToPerm.user),
2571 joinedload(UserRepoGroupToPerm.permission),)
2571 joinedload(UserRepoGroupToPerm.permission),)
2572
2572
2573 # get owners and admins and permissions. We do a trick of re-writing
2573 # get owners and admins and permissions. We do a trick of re-writing
2574 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2574 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2575 # has a global reference and changing one object propagates to all
2575 # has a global reference and changing one object propagates to all
2576 # others. This means if admin is also an owner admin_row that change
2576 # others. This means if admin is also an owner admin_row that change
2577 # would propagate to both objects
2577 # would propagate to both objects
2578 perm_rows = []
2578 perm_rows = []
2579 for _usr in q.all():
2579 for _usr in q.all():
2580 usr = AttributeDict(_usr.user.get_dict())
2580 usr = AttributeDict(_usr.user.get_dict())
2581 usr.permission = _usr.permission.permission_name
2581 usr.permission = _usr.permission.permission_name
2582 perm_rows.append(usr)
2582 perm_rows.append(usr)
2583
2583
2584 # filter the perm rows by 'default' first and then sort them by
2584 # filter the perm rows by 'default' first and then sort them by
2585 # admin,write,read,none permissions sorted again alphabetically in
2585 # admin,write,read,none permissions sorted again alphabetically in
2586 # each group
2586 # each group
2587 perm_rows = sorted(perm_rows, key=display_user_sort)
2587 perm_rows = sorted(perm_rows, key=display_user_sort)
2588
2588
2589 _admin_perm = 'group.admin'
2589 _admin_perm = 'group.admin'
2590 owner_row = []
2590 owner_row = []
2591 if with_owner:
2591 if with_owner:
2592 usr = AttributeDict(self.user.get_dict())
2592 usr = AttributeDict(self.user.get_dict())
2593 usr.owner_row = True
2593 usr.owner_row = True
2594 usr.permission = _admin_perm
2594 usr.permission = _admin_perm
2595 owner_row.append(usr)
2595 owner_row.append(usr)
2596
2596
2597 super_admin_rows = []
2597 super_admin_rows = []
2598 if with_admins:
2598 if with_admins:
2599 for usr in User.get_all_super_admins():
2599 for usr in User.get_all_super_admins():
2600 # if this admin is also owner, don't double the record
2600 # if this admin is also owner, don't double the record
2601 if usr.user_id == owner_row[0].user_id:
2601 if usr.user_id == owner_row[0].user_id:
2602 owner_row[0].admin_row = True
2602 owner_row[0].admin_row = True
2603 else:
2603 else:
2604 usr = AttributeDict(usr.get_dict())
2604 usr = AttributeDict(usr.get_dict())
2605 usr.admin_row = True
2605 usr.admin_row = True
2606 usr.permission = _admin_perm
2606 usr.permission = _admin_perm
2607 super_admin_rows.append(usr)
2607 super_admin_rows.append(usr)
2608
2608
2609 return super_admin_rows + owner_row + perm_rows
2609 return super_admin_rows + owner_row + perm_rows
2610
2610
2611 def permission_user_groups(self):
2611 def permission_user_groups(self):
2612 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2612 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2613 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2613 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2614 joinedload(UserGroupRepoGroupToPerm.users_group),
2614 joinedload(UserGroupRepoGroupToPerm.users_group),
2615 joinedload(UserGroupRepoGroupToPerm.permission),)
2615 joinedload(UserGroupRepoGroupToPerm.permission),)
2616
2616
2617 perm_rows = []
2617 perm_rows = []
2618 for _user_group in q.all():
2618 for _user_group in q.all():
2619 usr = AttributeDict(_user_group.users_group.get_dict())
2619 usr = AttributeDict(_user_group.users_group.get_dict())
2620 usr.permission = _user_group.permission.permission_name
2620 usr.permission = _user_group.permission.permission_name
2621 perm_rows.append(usr)
2621 perm_rows.append(usr)
2622
2622
2623 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2623 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2624 return perm_rows
2624 return perm_rows
2625
2625
2626 def get_api_data(self):
2626 def get_api_data(self):
2627 """
2627 """
2628 Common function for generating api data
2628 Common function for generating api data
2629
2629
2630 """
2630 """
2631 group = self
2631 group = self
2632 data = {
2632 data = {
2633 'group_id': group.group_id,
2633 'group_id': group.group_id,
2634 'group_name': group.group_name,
2634 'group_name': group.group_name,
2635 'group_description': group.description_safe,
2635 'group_description': group.description_safe,
2636 'parent_group': group.parent_group.group_name if group.parent_group else None,
2636 'parent_group': group.parent_group.group_name if group.parent_group else None,
2637 'repositories': [x.repo_name for x in group.repositories],
2637 'repositories': [x.repo_name for x in group.repositories],
2638 'owner': group.user.username,
2638 'owner': group.user.username,
2639 }
2639 }
2640 return data
2640 return data
2641
2641
2642
2642
2643 class Permission(Base, BaseModel):
2643 class Permission(Base, BaseModel):
2644 __tablename__ = 'permissions'
2644 __tablename__ = 'permissions'
2645 __table_args__ = (
2645 __table_args__ = (
2646 Index('p_perm_name_idx', 'permission_name'),
2646 Index('p_perm_name_idx', 'permission_name'),
2647 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2647 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2648 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2648 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2649 )
2649 )
2650 PERMS = [
2650 PERMS = [
2651 ('hg.admin', _('RhodeCode Super Administrator')),
2651 ('hg.admin', _('RhodeCode Super Administrator')),
2652
2652
2653 ('repository.none', _('Repository no access')),
2653 ('repository.none', _('Repository no access')),
2654 ('repository.read', _('Repository read access')),
2654 ('repository.read', _('Repository read access')),
2655 ('repository.write', _('Repository write access')),
2655 ('repository.write', _('Repository write access')),
2656 ('repository.admin', _('Repository admin access')),
2656 ('repository.admin', _('Repository admin access')),
2657
2657
2658 ('group.none', _('Repository group no access')),
2658 ('group.none', _('Repository group no access')),
2659 ('group.read', _('Repository group read access')),
2659 ('group.read', _('Repository group read access')),
2660 ('group.write', _('Repository group write access')),
2660 ('group.write', _('Repository group write access')),
2661 ('group.admin', _('Repository group admin access')),
2661 ('group.admin', _('Repository group admin access')),
2662
2662
2663 ('usergroup.none', _('User group no access')),
2663 ('usergroup.none', _('User group no access')),
2664 ('usergroup.read', _('User group read access')),
2664 ('usergroup.read', _('User group read access')),
2665 ('usergroup.write', _('User group write access')),
2665 ('usergroup.write', _('User group write access')),
2666 ('usergroup.admin', _('User group admin access')),
2666 ('usergroup.admin', _('User group admin access')),
2667
2667
2668 ('branch.none', _('Branch no permissions')),
2668 ('branch.none', _('Branch no permissions')),
2669 ('branch.merge', _('Branch access by web merge')),
2669 ('branch.merge', _('Branch access by web merge')),
2670 ('branch.push', _('Branch access by push')),
2670 ('branch.push', _('Branch access by push')),
2671 ('branch.push_force', _('Branch access by push with force')),
2671 ('branch.push_force', _('Branch access by push with force')),
2672
2672
2673 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2673 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2674 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2674 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2675
2675
2676 ('hg.usergroup.create.false', _('User Group creation disabled')),
2676 ('hg.usergroup.create.false', _('User Group creation disabled')),
2677 ('hg.usergroup.create.true', _('User Group creation enabled')),
2677 ('hg.usergroup.create.true', _('User Group creation enabled')),
2678
2678
2679 ('hg.create.none', _('Repository creation disabled')),
2679 ('hg.create.none', _('Repository creation disabled')),
2680 ('hg.create.repository', _('Repository creation enabled')),
2680 ('hg.create.repository', _('Repository creation enabled')),
2681 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2681 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2682 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2682 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2683
2683
2684 ('hg.fork.none', _('Repository forking disabled')),
2684 ('hg.fork.none', _('Repository forking disabled')),
2685 ('hg.fork.repository', _('Repository forking enabled')),
2685 ('hg.fork.repository', _('Repository forking enabled')),
2686
2686
2687 ('hg.register.none', _('Registration disabled')),
2687 ('hg.register.none', _('Registration disabled')),
2688 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2688 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2689 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2689 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2690
2690
2691 ('hg.password_reset.enabled', _('Password reset enabled')),
2691 ('hg.password_reset.enabled', _('Password reset enabled')),
2692 ('hg.password_reset.hidden', _('Password reset hidden')),
2692 ('hg.password_reset.hidden', _('Password reset hidden')),
2693 ('hg.password_reset.disabled', _('Password reset disabled')),
2693 ('hg.password_reset.disabled', _('Password reset disabled')),
2694
2694
2695 ('hg.extern_activate.manual', _('Manual activation of external account')),
2695 ('hg.extern_activate.manual', _('Manual activation of external account')),
2696 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2696 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2697
2697
2698 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2698 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2699 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2699 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2700 ]
2700 ]
2701
2701
2702 # definition of system default permissions for DEFAULT user, created on
2702 # definition of system default permissions for DEFAULT user, created on
2703 # system setup
2703 # system setup
2704 DEFAULT_USER_PERMISSIONS = [
2704 DEFAULT_USER_PERMISSIONS = [
2705 # object perms
2705 # object perms
2706 'repository.read',
2706 'repository.read',
2707 'group.read',
2707 'group.read',
2708 'usergroup.read',
2708 'usergroup.read',
2709 # branch
2709 # branch
2710 'branch.push',
2710 'branch.push',
2711 # global
2711 # global
2712 'hg.create.repository',
2712 'hg.create.repository',
2713 'hg.repogroup.create.false',
2713 'hg.repogroup.create.false',
2714 'hg.usergroup.create.false',
2714 'hg.usergroup.create.false',
2715 'hg.create.write_on_repogroup.true',
2715 'hg.create.write_on_repogroup.true',
2716 'hg.fork.repository',
2716 'hg.fork.repository',
2717 'hg.register.manual_activate',
2717 'hg.register.manual_activate',
2718 'hg.password_reset.enabled',
2718 'hg.password_reset.enabled',
2719 'hg.extern_activate.auto',
2719 'hg.extern_activate.auto',
2720 'hg.inherit_default_perms.true',
2720 'hg.inherit_default_perms.true',
2721 ]
2721 ]
2722
2722
2723 # defines which permissions are more important higher the more important
2723 # defines which permissions are more important higher the more important
2724 # Weight defines which permissions are more important.
2724 # Weight defines which permissions are more important.
2725 # The higher number the more important.
2725 # The higher number the more important.
2726 PERM_WEIGHTS = {
2726 PERM_WEIGHTS = {
2727 'repository.none': 0,
2727 'repository.none': 0,
2728 'repository.read': 1,
2728 'repository.read': 1,
2729 'repository.write': 3,
2729 'repository.write': 3,
2730 'repository.admin': 4,
2730 'repository.admin': 4,
2731
2731
2732 'group.none': 0,
2732 'group.none': 0,
2733 'group.read': 1,
2733 'group.read': 1,
2734 'group.write': 3,
2734 'group.write': 3,
2735 'group.admin': 4,
2735 'group.admin': 4,
2736
2736
2737 'usergroup.none': 0,
2737 'usergroup.none': 0,
2738 'usergroup.read': 1,
2738 'usergroup.read': 1,
2739 'usergroup.write': 3,
2739 'usergroup.write': 3,
2740 'usergroup.admin': 4,
2740 'usergroup.admin': 4,
2741
2741
2742 'branch.none': 0,
2742 'branch.none': 0,
2743 'branch.merge': 1,
2743 'branch.merge': 1,
2744 'branch.push': 3,
2744 'branch.push': 3,
2745 'branch.push_force': 4,
2745 'branch.push_force': 4,
2746
2746
2747 'hg.repogroup.create.false': 0,
2747 'hg.repogroup.create.false': 0,
2748 'hg.repogroup.create.true': 1,
2748 'hg.repogroup.create.true': 1,
2749
2749
2750 'hg.usergroup.create.false': 0,
2750 'hg.usergroup.create.false': 0,
2751 'hg.usergroup.create.true': 1,
2751 'hg.usergroup.create.true': 1,
2752
2752
2753 'hg.fork.none': 0,
2753 'hg.fork.none': 0,
2754 'hg.fork.repository': 1,
2754 'hg.fork.repository': 1,
2755 'hg.create.none': 0,
2755 'hg.create.none': 0,
2756 'hg.create.repository': 1
2756 'hg.create.repository': 1
2757 }
2757 }
2758
2758
2759 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2759 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2760 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2760 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2761 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2761 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2762
2762
2763 def __unicode__(self):
2763 def __unicode__(self):
2764 return u"<%s('%s:%s')>" % (
2764 return u"<%s('%s:%s')>" % (
2765 self.__class__.__name__, self.permission_id, self.permission_name
2765 self.__class__.__name__, self.permission_id, self.permission_name
2766 )
2766 )
2767
2767
2768 @classmethod
2768 @classmethod
2769 def get_by_key(cls, key):
2769 def get_by_key(cls, key):
2770 return cls.query().filter(cls.permission_name == key).scalar()
2770 return cls.query().filter(cls.permission_name == key).scalar()
2771
2771
2772 @classmethod
2772 @classmethod
2773 def get_default_repo_perms(cls, user_id, repo_id=None):
2773 def get_default_repo_perms(cls, user_id, repo_id=None):
2774 q = Session().query(UserRepoToPerm, Repository, Permission)\
2774 q = Session().query(UserRepoToPerm, Repository, Permission)\
2775 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2775 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2776 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2776 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2777 .filter(UserRepoToPerm.user_id == user_id)
2777 .filter(UserRepoToPerm.user_id == user_id)
2778 if repo_id:
2778 if repo_id:
2779 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2779 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2780 return q.all()
2780 return q.all()
2781
2781
2782 @classmethod
2782 @classmethod
2783 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2783 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2784 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2784 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2785 .join(
2785 .join(
2786 Permission,
2786 Permission,
2787 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2787 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2788 .join(
2788 .join(
2789 Repository,
2789 Repository,
2790 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2790 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2791 .join(
2791 .join(
2792 UserGroup,
2792 UserGroup,
2793 UserGroupRepoToPerm.users_group_id ==
2793 UserGroupRepoToPerm.users_group_id ==
2794 UserGroup.users_group_id)\
2794 UserGroup.users_group_id)\
2795 .join(
2795 .join(
2796 UserGroupMember,
2796 UserGroupMember,
2797 UserGroupRepoToPerm.users_group_id ==
2797 UserGroupRepoToPerm.users_group_id ==
2798 UserGroupMember.users_group_id)\
2798 UserGroupMember.users_group_id)\
2799 .filter(
2799 .filter(
2800 UserGroupMember.user_id == user_id,
2800 UserGroupMember.user_id == user_id,
2801 UserGroup.users_group_active == true())
2801 UserGroup.users_group_active == true())
2802 if repo_id:
2802 if repo_id:
2803 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2803 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2804 return q.all()
2804 return q.all()
2805
2805
2806 @classmethod
2806 @classmethod
2807 def get_default_group_perms(cls, user_id, repo_group_id=None):
2807 def get_default_group_perms(cls, user_id, repo_group_id=None):
2808 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2808 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2809 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2809 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2810 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2810 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2811 .filter(UserRepoGroupToPerm.user_id == user_id)
2811 .filter(UserRepoGroupToPerm.user_id == user_id)
2812 if repo_group_id:
2812 if repo_group_id:
2813 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2813 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2814 return q.all()
2814 return q.all()
2815
2815
2816 @classmethod
2816 @classmethod
2817 def get_default_group_perms_from_user_group(
2817 def get_default_group_perms_from_user_group(
2818 cls, user_id, repo_group_id=None):
2818 cls, user_id, repo_group_id=None):
2819 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2819 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2820 .join(
2820 .join(
2821 Permission,
2821 Permission,
2822 UserGroupRepoGroupToPerm.permission_id ==
2822 UserGroupRepoGroupToPerm.permission_id ==
2823 Permission.permission_id)\
2823 Permission.permission_id)\
2824 .join(
2824 .join(
2825 RepoGroup,
2825 RepoGroup,
2826 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2826 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2827 .join(
2827 .join(
2828 UserGroup,
2828 UserGroup,
2829 UserGroupRepoGroupToPerm.users_group_id ==
2829 UserGroupRepoGroupToPerm.users_group_id ==
2830 UserGroup.users_group_id)\
2830 UserGroup.users_group_id)\
2831 .join(
2831 .join(
2832 UserGroupMember,
2832 UserGroupMember,
2833 UserGroupRepoGroupToPerm.users_group_id ==
2833 UserGroupRepoGroupToPerm.users_group_id ==
2834 UserGroupMember.users_group_id)\
2834 UserGroupMember.users_group_id)\
2835 .filter(
2835 .filter(
2836 UserGroupMember.user_id == user_id,
2836 UserGroupMember.user_id == user_id,
2837 UserGroup.users_group_active == true())
2837 UserGroup.users_group_active == true())
2838 if repo_group_id:
2838 if repo_group_id:
2839 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2839 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2840 return q.all()
2840 return q.all()
2841
2841
2842 @classmethod
2842 @classmethod
2843 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2843 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2844 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2844 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2845 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2845 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2846 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2846 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2847 .filter(UserUserGroupToPerm.user_id == user_id)
2847 .filter(UserUserGroupToPerm.user_id == user_id)
2848 if user_group_id:
2848 if user_group_id:
2849 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2849 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2850 return q.all()
2850 return q.all()
2851
2851
2852 @classmethod
2852 @classmethod
2853 def get_default_user_group_perms_from_user_group(
2853 def get_default_user_group_perms_from_user_group(
2854 cls, user_id, user_group_id=None):
2854 cls, user_id, user_group_id=None):
2855 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2855 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2856 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2856 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2857 .join(
2857 .join(
2858 Permission,
2858 Permission,
2859 UserGroupUserGroupToPerm.permission_id ==
2859 UserGroupUserGroupToPerm.permission_id ==
2860 Permission.permission_id)\
2860 Permission.permission_id)\
2861 .join(
2861 .join(
2862 TargetUserGroup,
2862 TargetUserGroup,
2863 UserGroupUserGroupToPerm.target_user_group_id ==
2863 UserGroupUserGroupToPerm.target_user_group_id ==
2864 TargetUserGroup.users_group_id)\
2864 TargetUserGroup.users_group_id)\
2865 .join(
2865 .join(
2866 UserGroup,
2866 UserGroup,
2867 UserGroupUserGroupToPerm.user_group_id ==
2867 UserGroupUserGroupToPerm.user_group_id ==
2868 UserGroup.users_group_id)\
2868 UserGroup.users_group_id)\
2869 .join(
2869 .join(
2870 UserGroupMember,
2870 UserGroupMember,
2871 UserGroupUserGroupToPerm.user_group_id ==
2871 UserGroupUserGroupToPerm.user_group_id ==
2872 UserGroupMember.users_group_id)\
2872 UserGroupMember.users_group_id)\
2873 .filter(
2873 .filter(
2874 UserGroupMember.user_id == user_id,
2874 UserGroupMember.user_id == user_id,
2875 UserGroup.users_group_active == true())
2875 UserGroup.users_group_active == true())
2876 if user_group_id:
2876 if user_group_id:
2877 q = q.filter(
2877 q = q.filter(
2878 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2878 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2879
2879
2880 return q.all()
2880 return q.all()
2881
2881
2882
2882
2883 class UserRepoToPerm(Base, BaseModel):
2883 class UserRepoToPerm(Base, BaseModel):
2884 __tablename__ = 'repo_to_perm'
2884 __tablename__ = 'repo_to_perm'
2885 __table_args__ = (
2885 __table_args__ = (
2886 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2886 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2887 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2887 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2888 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2888 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2889 )
2889 )
2890 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2890 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2891 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2891 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2892 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2892 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2893 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2893 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2894
2894
2895 user = relationship('User')
2895 user = relationship('User')
2896 repository = relationship('Repository')
2896 repository = relationship('Repository')
2897 permission = relationship('Permission')
2897 permission = relationship('Permission')
2898
2898
2899 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
2899 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
2900
2900
2901 @classmethod
2901 @classmethod
2902 def create(cls, user, repository, permission):
2902 def create(cls, user, repository, permission):
2903 n = cls()
2903 n = cls()
2904 n.user = user
2904 n.user = user
2905 n.repository = repository
2905 n.repository = repository
2906 n.permission = permission
2906 n.permission = permission
2907 Session().add(n)
2907 Session().add(n)
2908 return n
2908 return n
2909
2909
2910 def __unicode__(self):
2910 def __unicode__(self):
2911 return u'<%s => %s >' % (self.user, self.repository)
2911 return u'<%s => %s >' % (self.user, self.repository)
2912
2912
2913
2913
2914 class UserUserGroupToPerm(Base, BaseModel):
2914 class UserUserGroupToPerm(Base, BaseModel):
2915 __tablename__ = 'user_user_group_to_perm'
2915 __tablename__ = 'user_user_group_to_perm'
2916 __table_args__ = (
2916 __table_args__ = (
2917 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2917 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2918 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2918 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2919 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2919 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2920 )
2920 )
2921 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2921 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_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2922 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2923 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2923 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2924 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2924 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2925
2925
2926 user = relationship('User')
2926 user = relationship('User')
2927 user_group = relationship('UserGroup')
2927 user_group = relationship('UserGroup')
2928 permission = relationship('Permission')
2928 permission = relationship('Permission')
2929
2929
2930 @classmethod
2930 @classmethod
2931 def create(cls, user, user_group, permission):
2931 def create(cls, user, user_group, permission):
2932 n = cls()
2932 n = cls()
2933 n.user = user
2933 n.user = user
2934 n.user_group = user_group
2934 n.user_group = user_group
2935 n.permission = permission
2935 n.permission = permission
2936 Session().add(n)
2936 Session().add(n)
2937 return n
2937 return n
2938
2938
2939 def __unicode__(self):
2939 def __unicode__(self):
2940 return u'<%s => %s >' % (self.user, self.user_group)
2940 return u'<%s => %s >' % (self.user, self.user_group)
2941
2941
2942
2942
2943 class UserToPerm(Base, BaseModel):
2943 class UserToPerm(Base, BaseModel):
2944 __tablename__ = 'user_to_perm'
2944 __tablename__ = 'user_to_perm'
2945 __table_args__ = (
2945 __table_args__ = (
2946 UniqueConstraint('user_id', 'permission_id'),
2946 UniqueConstraint('user_id', 'permission_id'),
2947 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2947 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2948 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2948 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2949 )
2949 )
2950 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2950 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2951 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2951 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2952 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2952 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2953
2953
2954 user = relationship('User')
2954 user = relationship('User')
2955 permission = relationship('Permission', lazy='joined')
2955 permission = relationship('Permission', lazy='joined')
2956
2956
2957 def __unicode__(self):
2957 def __unicode__(self):
2958 return u'<%s => %s >' % (self.user, self.permission)
2958 return u'<%s => %s >' % (self.user, self.permission)
2959
2959
2960
2960
2961 class UserGroupRepoToPerm(Base, BaseModel):
2961 class UserGroupRepoToPerm(Base, BaseModel):
2962 __tablename__ = 'users_group_repo_to_perm'
2962 __tablename__ = 'users_group_repo_to_perm'
2963 __table_args__ = (
2963 __table_args__ = (
2964 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2964 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2965 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2965 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2966 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2966 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2967 )
2967 )
2968 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2968 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2969 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2969 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2970 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2970 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2971 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2971 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2972
2972
2973 users_group = relationship('UserGroup')
2973 users_group = relationship('UserGroup')
2974 permission = relationship('Permission')
2974 permission = relationship('Permission')
2975 repository = relationship('Repository')
2975 repository = relationship('Repository')
2976
2976
2977 @classmethod
2977 @classmethod
2978 def create(cls, users_group, repository, permission):
2978 def create(cls, users_group, repository, permission):
2979 n = cls()
2979 n = cls()
2980 n.users_group = users_group
2980 n.users_group = users_group
2981 n.repository = repository
2981 n.repository = repository
2982 n.permission = permission
2982 n.permission = permission
2983 Session().add(n)
2983 Session().add(n)
2984 return n
2984 return n
2985
2985
2986 def __unicode__(self):
2986 def __unicode__(self):
2987 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2987 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2988
2988
2989
2989
2990 class UserGroupUserGroupToPerm(Base, BaseModel):
2990 class UserGroupUserGroupToPerm(Base, BaseModel):
2991 __tablename__ = 'user_group_user_group_to_perm'
2991 __tablename__ = 'user_group_user_group_to_perm'
2992 __table_args__ = (
2992 __table_args__ = (
2993 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2993 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2994 CheckConstraint('target_user_group_id != user_group_id'),
2994 CheckConstraint('target_user_group_id != user_group_id'),
2995 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2995 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2996 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2996 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2997 )
2997 )
2998 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)
2998 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 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2999 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3000 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3000 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3001 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3001 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3002
3002
3003 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3003 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3004 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3004 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3005 permission = relationship('Permission')
3005 permission = relationship('Permission')
3006
3006
3007 @classmethod
3007 @classmethod
3008 def create(cls, target_user_group, user_group, permission):
3008 def create(cls, target_user_group, user_group, permission):
3009 n = cls()
3009 n = cls()
3010 n.target_user_group = target_user_group
3010 n.target_user_group = target_user_group
3011 n.user_group = user_group
3011 n.user_group = user_group
3012 n.permission = permission
3012 n.permission = permission
3013 Session().add(n)
3013 Session().add(n)
3014 return n
3014 return n
3015
3015
3016 def __unicode__(self):
3016 def __unicode__(self):
3017 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3017 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3018
3018
3019
3019
3020 class UserGroupToPerm(Base, BaseModel):
3020 class UserGroupToPerm(Base, BaseModel):
3021 __tablename__ = 'users_group_to_perm'
3021 __tablename__ = 'users_group_to_perm'
3022 __table_args__ = (
3022 __table_args__ = (
3023 UniqueConstraint('users_group_id', 'permission_id',),
3023 UniqueConstraint('users_group_id', 'permission_id',),
3024 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3024 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3025 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3025 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3026 )
3026 )
3027 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3027 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3028 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3028 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3029 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3029 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3030
3030
3031 users_group = relationship('UserGroup')
3031 users_group = relationship('UserGroup')
3032 permission = relationship('Permission')
3032 permission = relationship('Permission')
3033
3033
3034
3034
3035 class UserRepoGroupToPerm(Base, BaseModel):
3035 class UserRepoGroupToPerm(Base, BaseModel):
3036 __tablename__ = 'user_repo_group_to_perm'
3036 __tablename__ = 'user_repo_group_to_perm'
3037 __table_args__ = (
3037 __table_args__ = (
3038 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3038 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3039 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3039 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3040 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3040 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3041 )
3041 )
3042
3042
3043 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3043 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3044 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3044 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3045 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3045 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3046 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3046 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3047
3047
3048 user = relationship('User')
3048 user = relationship('User')
3049 group = relationship('RepoGroup')
3049 group = relationship('RepoGroup')
3050 permission = relationship('Permission')
3050 permission = relationship('Permission')
3051
3051
3052 @classmethod
3052 @classmethod
3053 def create(cls, user, repository_group, permission):
3053 def create(cls, user, repository_group, permission):
3054 n = cls()
3054 n = cls()
3055 n.user = user
3055 n.user = user
3056 n.group = repository_group
3056 n.group = repository_group
3057 n.permission = permission
3057 n.permission = permission
3058 Session().add(n)
3058 Session().add(n)
3059 return n
3059 return n
3060
3060
3061
3061
3062 class UserGroupRepoGroupToPerm(Base, BaseModel):
3062 class UserGroupRepoGroupToPerm(Base, BaseModel):
3063 __tablename__ = 'users_group_repo_group_to_perm'
3063 __tablename__ = 'users_group_repo_group_to_perm'
3064 __table_args__ = (
3064 __table_args__ = (
3065 UniqueConstraint('users_group_id', 'group_id'),
3065 UniqueConstraint('users_group_id', 'group_id'),
3066 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3066 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3067 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3067 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3068 )
3068 )
3069
3069
3070 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)
3070 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_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3071 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3072 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3072 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3073 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3073 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3074
3074
3075 users_group = relationship('UserGroup')
3075 users_group = relationship('UserGroup')
3076 permission = relationship('Permission')
3076 permission = relationship('Permission')
3077 group = relationship('RepoGroup')
3077 group = relationship('RepoGroup')
3078
3078
3079 @classmethod
3079 @classmethod
3080 def create(cls, user_group, repository_group, permission):
3080 def create(cls, user_group, repository_group, permission):
3081 n = cls()
3081 n = cls()
3082 n.users_group = user_group
3082 n.users_group = user_group
3083 n.group = repository_group
3083 n.group = repository_group
3084 n.permission = permission
3084 n.permission = permission
3085 Session().add(n)
3085 Session().add(n)
3086 return n
3086 return n
3087
3087
3088 def __unicode__(self):
3088 def __unicode__(self):
3089 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3089 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3090
3090
3091
3091
3092 class Statistics(Base, BaseModel):
3092 class Statistics(Base, BaseModel):
3093 __tablename__ = 'statistics'
3093 __tablename__ = 'statistics'
3094 __table_args__ = (
3094 __table_args__ = (
3095 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3095 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3096 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3096 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3097 )
3097 )
3098 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3098 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3099 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3099 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3100 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3100 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3101 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3101 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3102 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3102 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3103 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3103 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3104
3104
3105 repository = relationship('Repository', single_parent=True)
3105 repository = relationship('Repository', single_parent=True)
3106
3106
3107
3107
3108 class UserFollowing(Base, BaseModel):
3108 class UserFollowing(Base, BaseModel):
3109 __tablename__ = 'user_followings'
3109 __tablename__ = 'user_followings'
3110 __table_args__ = (
3110 __table_args__ = (
3111 UniqueConstraint('user_id', 'follows_repository_id'),
3111 UniqueConstraint('user_id', 'follows_repository_id'),
3112 UniqueConstraint('user_id', 'follows_user_id'),
3112 UniqueConstraint('user_id', 'follows_user_id'),
3113 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3113 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3114 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3114 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3115 )
3115 )
3116
3116
3117 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3117 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3118 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3118 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3119 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3119 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3120 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3120 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3121 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3121 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3122
3122
3123 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3123 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3124
3124
3125 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3125 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3126 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3126 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3127
3127
3128 @classmethod
3128 @classmethod
3129 def get_repo_followers(cls, repo_id):
3129 def get_repo_followers(cls, repo_id):
3130 return cls.query().filter(cls.follows_repo_id == repo_id)
3130 return cls.query().filter(cls.follows_repo_id == repo_id)
3131
3131
3132
3132
3133 class CacheKey(Base, BaseModel):
3133 class CacheKey(Base, BaseModel):
3134 __tablename__ = 'cache_invalidation'
3134 __tablename__ = 'cache_invalidation'
3135 __table_args__ = (
3135 __table_args__ = (
3136 UniqueConstraint('cache_key'),
3136 UniqueConstraint('cache_key'),
3137 Index('key_idx', 'cache_key'),
3137 Index('key_idx', 'cache_key'),
3138 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3138 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3139 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3139 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3140 )
3140 )
3141 CACHE_TYPE_ATOM = 'ATOM'
3141 CACHE_TYPE_ATOM = 'ATOM'
3142 CACHE_TYPE_RSS = 'RSS'
3142 CACHE_TYPE_RSS = 'RSS'
3143 CACHE_TYPE_README = 'README'
3143 CACHE_TYPE_README = 'README'
3144
3144
3145 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3145 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3146 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3146 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3147 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3147 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3148 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3148 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3149
3149
3150 def __init__(self, cache_key, cache_args=''):
3150 def __init__(self, cache_key, cache_args=''):
3151 self.cache_key = cache_key
3151 self.cache_key = cache_key
3152 self.cache_args = cache_args
3152 self.cache_args = cache_args
3153 self.cache_active = False
3153 self.cache_active = False
3154
3154
3155 def __unicode__(self):
3155 def __unicode__(self):
3156 return u"<%s('%s:%s[%s]')>" % (
3156 return u"<%s('%s:%s[%s]')>" % (
3157 self.__class__.__name__,
3157 self.__class__.__name__,
3158 self.cache_id, self.cache_key, self.cache_active)
3158 self.cache_id, self.cache_key, self.cache_active)
3159
3159
3160 def _cache_key_partition(self):
3160 def _cache_key_partition(self):
3161 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3161 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3162 return prefix, repo_name, suffix
3162 return prefix, repo_name, suffix
3163
3163
3164 def get_prefix(self):
3164 def get_prefix(self):
3165 """
3165 """
3166 Try to extract prefix from existing cache key. The key could consist
3166 Try to extract prefix from existing cache key. The key could consist
3167 of prefix, repo_name, suffix
3167 of prefix, repo_name, suffix
3168 """
3168 """
3169 # this returns prefix, repo_name, suffix
3169 # this returns prefix, repo_name, suffix
3170 return self._cache_key_partition()[0]
3170 return self._cache_key_partition()[0]
3171
3171
3172 def get_suffix(self):
3172 def get_suffix(self):
3173 """
3173 """
3174 get suffix that might have been used in _get_cache_key to
3174 get suffix that might have been used in _get_cache_key to
3175 generate self.cache_key. Only used for informational purposes
3175 generate self.cache_key. Only used for informational purposes
3176 in repo_edit.mako.
3176 in repo_edit.mako.
3177 """
3177 """
3178 # prefix, repo_name, suffix
3178 # prefix, repo_name, suffix
3179 return self._cache_key_partition()[2]
3179 return self._cache_key_partition()[2]
3180
3180
3181 @classmethod
3181 @classmethod
3182 def delete_all_cache(cls):
3182 def delete_all_cache(cls):
3183 """
3183 """
3184 Delete all cache keys from database.
3184 Delete all cache keys from database.
3185 Should only be run when all instances are down and all entries
3185 Should only be run when all instances are down and all entries
3186 thus stale.
3186 thus stale.
3187 """
3187 """
3188 cls.query().delete()
3188 cls.query().delete()
3189 Session().commit()
3189 Session().commit()
3190
3190
3191 @classmethod
3191 @classmethod
3192 def get_cache_key(cls, repo_name, cache_type):
3192 def get_cache_key(cls, repo_name, cache_type):
3193 """
3193 """
3194
3194
3195 Generate a cache key for this process of RhodeCode instance.
3195 Generate a cache key for this process of RhodeCode instance.
3196 Prefix most likely will be process id or maybe explicitly set
3196 Prefix most likely will be process id or maybe explicitly set
3197 instance_id from .ini file.
3197 instance_id from .ini file.
3198 """
3198 """
3199 import rhodecode
3199 import rhodecode
3200 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3200 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3201
3201
3202 repo_as_unicode = safe_unicode(repo_name)
3202 repo_as_unicode = safe_unicode(repo_name)
3203 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3203 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3204 if cache_type else repo_as_unicode
3204 if cache_type else repo_as_unicode
3205
3205
3206 return u'{}{}'.format(prefix, key)
3206 return u'{}{}'.format(prefix, key)
3207
3207
3208 @classmethod
3208 @classmethod
3209 def set_invalidate(cls, repo_name, delete=False):
3209 def set_invalidate(cls, repo_name, delete=False):
3210 """
3210 """
3211 Mark all caches of a repo as invalid in the database.
3211 Mark all caches of a repo as invalid in the database.
3212 """
3212 """
3213
3213
3214 try:
3214 try:
3215 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3215 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3216 if delete:
3216 if delete:
3217 log.debug('cache objects deleted for repo %s',
3217 log.debug('cache objects deleted for repo %s',
3218 safe_str(repo_name))
3218 safe_str(repo_name))
3219 qry.delete()
3219 qry.delete()
3220 else:
3220 else:
3221 log.debug('cache objects marked as invalid for repo %s',
3221 log.debug('cache objects marked as invalid for repo %s',
3222 safe_str(repo_name))
3222 safe_str(repo_name))
3223 qry.update({"cache_active": False})
3223 qry.update({"cache_active": False})
3224
3224
3225 Session().commit()
3225 Session().commit()
3226 except Exception:
3226 except Exception:
3227 log.exception(
3227 log.exception(
3228 'Cache key invalidation failed for repository %s',
3228 'Cache key invalidation failed for repository %s',
3229 safe_str(repo_name))
3229 safe_str(repo_name))
3230 Session().rollback()
3230 Session().rollback()
3231
3231
3232 @classmethod
3232 @classmethod
3233 def get_active_cache(cls, cache_key):
3233 def get_active_cache(cls, cache_key):
3234 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3234 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3235 if inv_obj:
3235 if inv_obj:
3236 return inv_obj
3236 return inv_obj
3237 return None
3237 return None
3238
3238
3239
3239
3240 class ChangesetComment(Base, BaseModel):
3240 class ChangesetComment(Base, BaseModel):
3241 __tablename__ = 'changeset_comments'
3241 __tablename__ = 'changeset_comments'
3242 __table_args__ = (
3242 __table_args__ = (
3243 Index('cc_revision_idx', 'revision'),
3243 Index('cc_revision_idx', 'revision'),
3244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3245 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3246 )
3246 )
3247
3247
3248 COMMENT_OUTDATED = u'comment_outdated'
3248 COMMENT_OUTDATED = u'comment_outdated'
3249 COMMENT_TYPE_NOTE = u'note'
3249 COMMENT_TYPE_NOTE = u'note'
3250 COMMENT_TYPE_TODO = u'todo'
3250 COMMENT_TYPE_TODO = u'todo'
3251 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3251 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3252
3252
3253 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3253 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3254 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3254 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3255 revision = Column('revision', String(40), nullable=True)
3255 revision = Column('revision', String(40), nullable=True)
3256 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3256 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3257 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3257 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3258 line_no = Column('line_no', Unicode(10), nullable=True)
3258 line_no = Column('line_no', Unicode(10), nullable=True)
3259 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3259 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3260 f_path = Column('f_path', Unicode(1000), nullable=True)
3260 f_path = Column('f_path', Unicode(1000), nullable=True)
3261 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3261 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3262 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3262 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3263 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3263 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3264 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3264 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3265 renderer = Column('renderer', Unicode(64), nullable=True)
3265 renderer = Column('renderer', Unicode(64), nullable=True)
3266 display_state = Column('display_state', Unicode(128), nullable=True)
3266 display_state = Column('display_state', Unicode(128), nullable=True)
3267
3267
3268 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3268 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3269 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3269 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3270 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3270 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3271 author = relationship('User', lazy='joined')
3271 author = relationship('User', lazy='joined')
3272 repo = relationship('Repository')
3272 repo = relationship('Repository')
3273 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3273 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3274 pull_request = relationship('PullRequest', lazy='joined')
3274 pull_request = relationship('PullRequest', lazy='joined')
3275 pull_request_version = relationship('PullRequestVersion')
3275 pull_request_version = relationship('PullRequestVersion')
3276
3276
3277 @classmethod
3277 @classmethod
3278 def get_users(cls, revision=None, pull_request_id=None):
3278 def get_users(cls, revision=None, pull_request_id=None):
3279 """
3279 """
3280 Returns user associated with this ChangesetComment. ie those
3280 Returns user associated with this ChangesetComment. ie those
3281 who actually commented
3281 who actually commented
3282
3282
3283 :param cls:
3283 :param cls:
3284 :param revision:
3284 :param revision:
3285 """
3285 """
3286 q = Session().query(User)\
3286 q = Session().query(User)\
3287 .join(ChangesetComment.author)
3287 .join(ChangesetComment.author)
3288 if revision:
3288 if revision:
3289 q = q.filter(cls.revision == revision)
3289 q = q.filter(cls.revision == revision)
3290 elif pull_request_id:
3290 elif pull_request_id:
3291 q = q.filter(cls.pull_request_id == pull_request_id)
3291 q = q.filter(cls.pull_request_id == pull_request_id)
3292 return q.all()
3292 return q.all()
3293
3293
3294 @classmethod
3294 @classmethod
3295 def get_index_from_version(cls, pr_version, versions):
3295 def get_index_from_version(cls, pr_version, versions):
3296 num_versions = [x.pull_request_version_id for x in versions]
3296 num_versions = [x.pull_request_version_id for x in versions]
3297 try:
3297 try:
3298 return num_versions.index(pr_version) +1
3298 return num_versions.index(pr_version) +1
3299 except (IndexError, ValueError):
3299 except (IndexError, ValueError):
3300 return
3300 return
3301
3301
3302 @property
3302 @property
3303 def outdated(self):
3303 def outdated(self):
3304 return self.display_state == self.COMMENT_OUTDATED
3304 return self.display_state == self.COMMENT_OUTDATED
3305
3305
3306 def outdated_at_version(self, version):
3306 def outdated_at_version(self, version):
3307 """
3307 """
3308 Checks if comment is outdated for given pull request version
3308 Checks if comment is outdated for given pull request version
3309 """
3309 """
3310 return self.outdated and self.pull_request_version_id != version
3310 return self.outdated and self.pull_request_version_id != version
3311
3311
3312 def older_than_version(self, version):
3312 def older_than_version(self, version):
3313 """
3313 """
3314 Checks if comment is made from previous version than given
3314 Checks if comment is made from previous version than given
3315 """
3315 """
3316 if version is None:
3316 if version is None:
3317 return self.pull_request_version_id is not None
3317 return self.pull_request_version_id is not None
3318
3318
3319 return self.pull_request_version_id < version
3319 return self.pull_request_version_id < version
3320
3320
3321 @property
3321 @property
3322 def resolved(self):
3322 def resolved(self):
3323 return self.resolved_by[0] if self.resolved_by else None
3323 return self.resolved_by[0] if self.resolved_by else None
3324
3324
3325 @property
3325 @property
3326 def is_todo(self):
3326 def is_todo(self):
3327 return self.comment_type == self.COMMENT_TYPE_TODO
3327 return self.comment_type == self.COMMENT_TYPE_TODO
3328
3328
3329 @property
3329 @property
3330 def is_inline(self):
3330 def is_inline(self):
3331 return self.line_no and self.f_path
3331 return self.line_no and self.f_path
3332
3332
3333 def get_index_version(self, versions):
3333 def get_index_version(self, versions):
3334 return self.get_index_from_version(
3334 return self.get_index_from_version(
3335 self.pull_request_version_id, versions)
3335 self.pull_request_version_id, versions)
3336
3336
3337 def __repr__(self):
3337 def __repr__(self):
3338 if self.comment_id:
3338 if self.comment_id:
3339 return '<DB:Comment #%s>' % self.comment_id
3339 return '<DB:Comment #%s>' % self.comment_id
3340 else:
3340 else:
3341 return '<DB:Comment at %#x>' % id(self)
3341 return '<DB:Comment at %#x>' % id(self)
3342
3342
3343 def get_api_data(self):
3343 def get_api_data(self):
3344 comment = self
3344 comment = self
3345 data = {
3345 data = {
3346 'comment_id': comment.comment_id,
3346 'comment_id': comment.comment_id,
3347 'comment_type': comment.comment_type,
3347 'comment_type': comment.comment_type,
3348 'comment_text': comment.text,
3348 'comment_text': comment.text,
3349 'comment_status': comment.status_change,
3349 'comment_status': comment.status_change,
3350 'comment_f_path': comment.f_path,
3350 'comment_f_path': comment.f_path,
3351 'comment_lineno': comment.line_no,
3351 'comment_lineno': comment.line_no,
3352 'comment_author': comment.author,
3352 'comment_author': comment.author,
3353 'comment_created_on': comment.created_on
3353 'comment_created_on': comment.created_on
3354 }
3354 }
3355 return data
3355 return data
3356
3356
3357 def __json__(self):
3357 def __json__(self):
3358 data = dict()
3358 data = dict()
3359 data.update(self.get_api_data())
3359 data.update(self.get_api_data())
3360 return data
3360 return data
3361
3361
3362
3362
3363 class ChangesetStatus(Base, BaseModel):
3363 class ChangesetStatus(Base, BaseModel):
3364 __tablename__ = 'changeset_statuses'
3364 __tablename__ = 'changeset_statuses'
3365 __table_args__ = (
3365 __table_args__ = (
3366 Index('cs_revision_idx', 'revision'),
3366 Index('cs_revision_idx', 'revision'),
3367 Index('cs_version_idx', 'version'),
3367 Index('cs_version_idx', 'version'),
3368 UniqueConstraint('repo_id', 'revision', 'version'),
3368 UniqueConstraint('repo_id', 'revision', 'version'),
3369 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3369 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3370 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3370 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3371 )
3371 )
3372 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3372 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3373 STATUS_APPROVED = 'approved'
3373 STATUS_APPROVED = 'approved'
3374 STATUS_REJECTED = 'rejected'
3374 STATUS_REJECTED = 'rejected'
3375 STATUS_UNDER_REVIEW = 'under_review'
3375 STATUS_UNDER_REVIEW = 'under_review'
3376
3376
3377 STATUSES = [
3377 STATUSES = [
3378 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3378 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3379 (STATUS_APPROVED, _("Approved")),
3379 (STATUS_APPROVED, _("Approved")),
3380 (STATUS_REJECTED, _("Rejected")),
3380 (STATUS_REJECTED, _("Rejected")),
3381 (STATUS_UNDER_REVIEW, _("Under Review")),
3381 (STATUS_UNDER_REVIEW, _("Under Review")),
3382 ]
3382 ]
3383
3383
3384 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3384 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3385 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3385 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3386 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3386 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3387 revision = Column('revision', String(40), nullable=False)
3387 revision = Column('revision', String(40), nullable=False)
3388 status = Column('status', String(128), nullable=False, default=DEFAULT)
3388 status = Column('status', String(128), nullable=False, default=DEFAULT)
3389 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3389 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3390 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3390 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3391 version = Column('version', Integer(), nullable=False, default=0)
3391 version = Column('version', Integer(), nullable=False, default=0)
3392 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3392 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3393
3393
3394 author = relationship('User', lazy='joined')
3394 author = relationship('User', lazy='joined')
3395 repo = relationship('Repository')
3395 repo = relationship('Repository')
3396 comment = relationship('ChangesetComment', lazy='joined')
3396 comment = relationship('ChangesetComment', lazy='joined')
3397 pull_request = relationship('PullRequest', lazy='joined')
3397 pull_request = relationship('PullRequest', lazy='joined')
3398
3398
3399 def __unicode__(self):
3399 def __unicode__(self):
3400 return u"<%s('%s[v%s]:%s')>" % (
3400 return u"<%s('%s[v%s]:%s')>" % (
3401 self.__class__.__name__,
3401 self.__class__.__name__,
3402 self.status, self.version, self.author
3402 self.status, self.version, self.author
3403 )
3403 )
3404
3404
3405 @classmethod
3405 @classmethod
3406 def get_status_lbl(cls, value):
3406 def get_status_lbl(cls, value):
3407 return dict(cls.STATUSES).get(value)
3407 return dict(cls.STATUSES).get(value)
3408
3408
3409 @property
3409 @property
3410 def status_lbl(self):
3410 def status_lbl(self):
3411 return ChangesetStatus.get_status_lbl(self.status)
3411 return ChangesetStatus.get_status_lbl(self.status)
3412
3412
3413 def get_api_data(self):
3413 def get_api_data(self):
3414 status = self
3414 status = self
3415 data = {
3415 data = {
3416 'status_id': status.changeset_status_id,
3416 'status_id': status.changeset_status_id,
3417 'status': status.status,
3417 'status': status.status,
3418 }
3418 }
3419 return data
3419 return data
3420
3420
3421 def __json__(self):
3421 def __json__(self):
3422 data = dict()
3422 data = dict()
3423 data.update(self.get_api_data())
3423 data.update(self.get_api_data())
3424 return data
3424 return data
3425
3425
3426
3426
3427 class _PullRequestBase(BaseModel):
3427 class _PullRequestBase(BaseModel):
3428 """
3428 """
3429 Common attributes of pull request and version entries.
3429 Common attributes of pull request and version entries.
3430 """
3430 """
3431
3431
3432 # .status values
3432 # .status values
3433 STATUS_NEW = u'new'
3433 STATUS_NEW = u'new'
3434 STATUS_OPEN = u'open'
3434 STATUS_OPEN = u'open'
3435 STATUS_CLOSED = u'closed'
3435 STATUS_CLOSED = u'closed'
3436
3436
3437 title = Column('title', Unicode(255), nullable=True)
3437 title = Column('title', Unicode(255), nullable=True)
3438 description = Column(
3438 description = Column(
3439 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3439 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3440 nullable=True)
3440 nullable=True)
3441 # new/open/closed status of pull request (not approve/reject/etc)
3441 # new/open/closed status of pull request (not approve/reject/etc)
3442 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3442 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3443 created_on = Column(
3443 created_on = Column(
3444 'created_on', DateTime(timezone=False), nullable=False,
3444 'created_on', DateTime(timezone=False), nullable=False,
3445 default=datetime.datetime.now)
3445 default=datetime.datetime.now)
3446 updated_on = Column(
3446 updated_on = Column(
3447 'updated_on', DateTime(timezone=False), nullable=False,
3447 'updated_on', DateTime(timezone=False), nullable=False,
3448 default=datetime.datetime.now)
3448 default=datetime.datetime.now)
3449
3449
3450 @declared_attr
3450 @declared_attr
3451 def user_id(cls):
3451 def user_id(cls):
3452 return Column(
3452 return Column(
3453 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3453 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3454 unique=None)
3454 unique=None)
3455
3455
3456 # 500 revisions max
3456 # 500 revisions max
3457 _revisions = Column(
3457 _revisions = Column(
3458 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3458 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3459
3459
3460 @declared_attr
3460 @declared_attr
3461 def source_repo_id(cls):
3461 def source_repo_id(cls):
3462 # TODO: dan: rename column to source_repo_id
3462 # TODO: dan: rename column to source_repo_id
3463 return Column(
3463 return Column(
3464 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3464 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3465 nullable=False)
3465 nullable=False)
3466
3466
3467 source_ref = Column('org_ref', Unicode(255), nullable=False)
3467 source_ref = Column('org_ref', Unicode(255), nullable=False)
3468
3468
3469 @declared_attr
3469 @declared_attr
3470 def target_repo_id(cls):
3470 def target_repo_id(cls):
3471 # TODO: dan: rename column to target_repo_id
3471 # TODO: dan: rename column to target_repo_id
3472 return Column(
3472 return Column(
3473 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3473 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3474 nullable=False)
3474 nullable=False)
3475
3475
3476 target_ref = Column('other_ref', Unicode(255), nullable=False)
3476 target_ref = Column('other_ref', Unicode(255), nullable=False)
3477 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3477 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3478
3478
3479 # TODO: dan: rename column to last_merge_source_rev
3479 # TODO: dan: rename column to last_merge_source_rev
3480 _last_merge_source_rev = Column(
3480 _last_merge_source_rev = Column(
3481 'last_merge_org_rev', String(40), nullable=True)
3481 'last_merge_org_rev', String(40), nullable=True)
3482 # TODO: dan: rename column to last_merge_target_rev
3482 # TODO: dan: rename column to last_merge_target_rev
3483 _last_merge_target_rev = Column(
3483 _last_merge_target_rev = Column(
3484 'last_merge_other_rev', String(40), nullable=True)
3484 'last_merge_other_rev', String(40), nullable=True)
3485 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3485 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3486 merge_rev = Column('merge_rev', String(40), nullable=True)
3486 merge_rev = Column('merge_rev', String(40), nullable=True)
3487
3487
3488 reviewer_data = Column(
3488 reviewer_data = Column(
3489 'reviewer_data_json', MutationObj.as_mutable(
3489 'reviewer_data_json', MutationObj.as_mutable(
3490 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3490 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3491
3491
3492 @property
3492 @property
3493 def reviewer_data_json(self):
3493 def reviewer_data_json(self):
3494 return json.dumps(self.reviewer_data)
3494 return json.dumps(self.reviewer_data)
3495
3495
3496 @hybrid_property
3496 @hybrid_property
3497 def description_safe(self):
3497 def description_safe(self):
3498 from rhodecode.lib import helpers as h
3498 from rhodecode.lib import helpers as h
3499 return h.escape(self.description)
3499 return h.escape(self.description)
3500
3500
3501 @hybrid_property
3501 @hybrid_property
3502 def revisions(self):
3502 def revisions(self):
3503 return self._revisions.split(':') if self._revisions else []
3503 return self._revisions.split(':') if self._revisions else []
3504
3504
3505 @revisions.setter
3505 @revisions.setter
3506 def revisions(self, val):
3506 def revisions(self, val):
3507 self._revisions = ':'.join(val)
3507 self._revisions = ':'.join(val)
3508
3508
3509 @hybrid_property
3509 @hybrid_property
3510 def last_merge_status(self):
3510 def last_merge_status(self):
3511 return safe_int(self._last_merge_status)
3511 return safe_int(self._last_merge_status)
3512
3512
3513 @last_merge_status.setter
3513 @last_merge_status.setter
3514 def last_merge_status(self, val):
3514 def last_merge_status(self, val):
3515 self._last_merge_status = val
3515 self._last_merge_status = val
3516
3516
3517 @declared_attr
3517 @declared_attr
3518 def author(cls):
3518 def author(cls):
3519 return relationship('User', lazy='joined')
3519 return relationship('User', lazy='joined')
3520
3520
3521 @declared_attr
3521 @declared_attr
3522 def source_repo(cls):
3522 def source_repo(cls):
3523 return relationship(
3523 return relationship(
3524 'Repository',
3524 'Repository',
3525 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3525 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3526
3526
3527 @property
3527 @property
3528 def source_ref_parts(self):
3528 def source_ref_parts(self):
3529 return self.unicode_to_reference(self.source_ref)
3529 return self.unicode_to_reference(self.source_ref)
3530
3530
3531 @declared_attr
3531 @declared_attr
3532 def target_repo(cls):
3532 def target_repo(cls):
3533 return relationship(
3533 return relationship(
3534 'Repository',
3534 'Repository',
3535 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3535 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3536
3536
3537 @property
3537 @property
3538 def target_ref_parts(self):
3538 def target_ref_parts(self):
3539 return self.unicode_to_reference(self.target_ref)
3539 return self.unicode_to_reference(self.target_ref)
3540
3540
3541 @property
3541 @property
3542 def shadow_merge_ref(self):
3542 def shadow_merge_ref(self):
3543 return self.unicode_to_reference(self._shadow_merge_ref)
3543 return self.unicode_to_reference(self._shadow_merge_ref)
3544
3544
3545 @shadow_merge_ref.setter
3545 @shadow_merge_ref.setter
3546 def shadow_merge_ref(self, ref):
3546 def shadow_merge_ref(self, ref):
3547 self._shadow_merge_ref = self.reference_to_unicode(ref)
3547 self._shadow_merge_ref = self.reference_to_unicode(ref)
3548
3548
3549 def unicode_to_reference(self, raw):
3549 def unicode_to_reference(self, raw):
3550 """
3550 """
3551 Convert a unicode (or string) to a reference object.
3551 Convert a unicode (or string) to a reference object.
3552 If unicode evaluates to False it returns None.
3552 If unicode evaluates to False it returns None.
3553 """
3553 """
3554 if raw:
3554 if raw:
3555 refs = raw.split(':')
3555 refs = raw.split(':')
3556 return Reference(*refs)
3556 return Reference(*refs)
3557 else:
3557 else:
3558 return None
3558 return None
3559
3559
3560 def reference_to_unicode(self, ref):
3560 def reference_to_unicode(self, ref):
3561 """
3561 """
3562 Convert a reference object to unicode.
3562 Convert a reference object to unicode.
3563 If reference is None it returns None.
3563 If reference is None it returns None.
3564 """
3564 """
3565 if ref:
3565 if ref:
3566 return u':'.join(ref)
3566 return u':'.join(ref)
3567 else:
3567 else:
3568 return None
3568 return None
3569
3569
3570 def get_api_data(self, with_merge_state=True):
3570 def get_api_data(self, with_merge_state=True):
3571 from rhodecode.model.pull_request import PullRequestModel
3571 from rhodecode.model.pull_request import PullRequestModel
3572
3572
3573 pull_request = self
3573 pull_request = self
3574 if with_merge_state:
3574 if with_merge_state:
3575 merge_status = PullRequestModel().merge_status(pull_request)
3575 merge_status = PullRequestModel().merge_status(pull_request)
3576 merge_state = {
3576 merge_state = {
3577 'status': merge_status[0],
3577 'status': merge_status[0],
3578 'message': safe_unicode(merge_status[1]),
3578 'message': safe_unicode(merge_status[1]),
3579 }
3579 }
3580 else:
3580 else:
3581 merge_state = {'status': 'not_available',
3581 merge_state = {'status': 'not_available',
3582 'message': 'not_available'}
3582 'message': 'not_available'}
3583
3583
3584 merge_data = {
3584 merge_data = {
3585 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3585 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3586 'reference': (
3586 'reference': (
3587 pull_request.shadow_merge_ref._asdict()
3587 pull_request.shadow_merge_ref._asdict()
3588 if pull_request.shadow_merge_ref else None),
3588 if pull_request.shadow_merge_ref else None),
3589 }
3589 }
3590
3590
3591 data = {
3591 data = {
3592 'pull_request_id': pull_request.pull_request_id,
3592 'pull_request_id': pull_request.pull_request_id,
3593 'url': PullRequestModel().get_url(pull_request),
3593 'url': PullRequestModel().get_url(pull_request),
3594 'title': pull_request.title,
3594 'title': pull_request.title,
3595 'description': pull_request.description,
3595 'description': pull_request.description,
3596 'status': pull_request.status,
3596 'status': pull_request.status,
3597 'created_on': pull_request.created_on,
3597 'created_on': pull_request.created_on,
3598 'updated_on': pull_request.updated_on,
3598 'updated_on': pull_request.updated_on,
3599 'commit_ids': pull_request.revisions,
3599 'commit_ids': pull_request.revisions,
3600 'review_status': pull_request.calculated_review_status(),
3600 'review_status': pull_request.calculated_review_status(),
3601 'mergeable': merge_state,
3601 'mergeable': merge_state,
3602 'source': {
3602 'source': {
3603 'clone_url': pull_request.source_repo.clone_url(),
3603 'clone_url': pull_request.source_repo.clone_url(),
3604 'repository': pull_request.source_repo.repo_name,
3604 'repository': pull_request.source_repo.repo_name,
3605 'reference': {
3605 'reference': {
3606 'name': pull_request.source_ref_parts.name,
3606 'name': pull_request.source_ref_parts.name,
3607 'type': pull_request.source_ref_parts.type,
3607 'type': pull_request.source_ref_parts.type,
3608 'commit_id': pull_request.source_ref_parts.commit_id,
3608 'commit_id': pull_request.source_ref_parts.commit_id,
3609 },
3609 },
3610 },
3610 },
3611 'target': {
3611 'target': {
3612 'clone_url': pull_request.target_repo.clone_url(),
3612 'clone_url': pull_request.target_repo.clone_url(),
3613 'repository': pull_request.target_repo.repo_name,
3613 'repository': pull_request.target_repo.repo_name,
3614 'reference': {
3614 'reference': {
3615 'name': pull_request.target_ref_parts.name,
3615 'name': pull_request.target_ref_parts.name,
3616 'type': pull_request.target_ref_parts.type,
3616 'type': pull_request.target_ref_parts.type,
3617 'commit_id': pull_request.target_ref_parts.commit_id,
3617 'commit_id': pull_request.target_ref_parts.commit_id,
3618 },
3618 },
3619 },
3619 },
3620 'merge': merge_data,
3620 'merge': merge_data,
3621 'author': pull_request.author.get_api_data(include_secrets=False,
3621 'author': pull_request.author.get_api_data(include_secrets=False,
3622 details='basic'),
3622 details='basic'),
3623 'reviewers': [
3623 'reviewers': [
3624 {
3624 {
3625 'user': reviewer.get_api_data(include_secrets=False,
3625 'user': reviewer.get_api_data(include_secrets=False,
3626 details='basic'),
3626 details='basic'),
3627 'reasons': reasons,
3627 'reasons': reasons,
3628 'review_status': st[0][1].status if st else 'not_reviewed',
3628 'review_status': st[0][1].status if st else 'not_reviewed',
3629 }
3629 }
3630 for obj, reviewer, reasons, mandatory, st in
3630 for obj, reviewer, reasons, mandatory, st in
3631 pull_request.reviewers_statuses()
3631 pull_request.reviewers_statuses()
3632 ]
3632 ]
3633 }
3633 }
3634
3634
3635 return data
3635 return data
3636
3636
3637
3637
3638 class PullRequest(Base, _PullRequestBase):
3638 class PullRequest(Base, _PullRequestBase):
3639 __tablename__ = 'pull_requests'
3639 __tablename__ = 'pull_requests'
3640 __table_args__ = (
3640 __table_args__ = (
3641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3642 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3642 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3643 )
3643 )
3644
3644
3645 pull_request_id = Column(
3645 pull_request_id = Column(
3646 'pull_request_id', Integer(), nullable=False, primary_key=True)
3646 'pull_request_id', Integer(), nullable=False, primary_key=True)
3647
3647
3648 def __repr__(self):
3648 def __repr__(self):
3649 if self.pull_request_id:
3649 if self.pull_request_id:
3650 return '<DB:PullRequest #%s>' % self.pull_request_id
3650 return '<DB:PullRequest #%s>' % self.pull_request_id
3651 else:
3651 else:
3652 return '<DB:PullRequest at %#x>' % id(self)
3652 return '<DB:PullRequest at %#x>' % id(self)
3653
3653
3654 reviewers = relationship('PullRequestReviewers',
3654 reviewers = relationship('PullRequestReviewers',
3655 cascade="all, delete, delete-orphan")
3655 cascade="all, delete, delete-orphan")
3656 statuses = relationship('ChangesetStatus',
3656 statuses = relationship('ChangesetStatus',
3657 cascade="all, delete, delete-orphan")
3657 cascade="all, delete, delete-orphan")
3658 comments = relationship('ChangesetComment',
3658 comments = relationship('ChangesetComment',
3659 cascade="all, delete, delete-orphan")
3659 cascade="all, delete, delete-orphan")
3660 versions = relationship('PullRequestVersion',
3660 versions = relationship('PullRequestVersion',
3661 cascade="all, delete, delete-orphan",
3661 cascade="all, delete, delete-orphan",
3662 lazy='dynamic')
3662 lazy='dynamic')
3663
3663
3664 @classmethod
3664 @classmethod
3665 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3665 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3666 internal_methods=None):
3666 internal_methods=None):
3667
3667
3668 class PullRequestDisplay(object):
3668 class PullRequestDisplay(object):
3669 """
3669 """
3670 Special object wrapper for showing PullRequest data via Versions
3670 Special object wrapper for showing PullRequest data via Versions
3671 It mimics PR object as close as possible. This is read only object
3671 It mimics PR object as close as possible. This is read only object
3672 just for display
3672 just for display
3673 """
3673 """
3674
3674
3675 def __init__(self, attrs, internal=None):
3675 def __init__(self, attrs, internal=None):
3676 self.attrs = attrs
3676 self.attrs = attrs
3677 # internal have priority over the given ones via attrs
3677 # internal have priority over the given ones via attrs
3678 self.internal = internal or ['versions']
3678 self.internal = internal or ['versions']
3679
3679
3680 def __getattr__(self, item):
3680 def __getattr__(self, item):
3681 if item in self.internal:
3681 if item in self.internal:
3682 return getattr(self, item)
3682 return getattr(self, item)
3683 try:
3683 try:
3684 return self.attrs[item]
3684 return self.attrs[item]
3685 except KeyError:
3685 except KeyError:
3686 raise AttributeError(
3686 raise AttributeError(
3687 '%s object has no attribute %s' % (self, item))
3687 '%s object has no attribute %s' % (self, item))
3688
3688
3689 def __repr__(self):
3689 def __repr__(self):
3690 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3690 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3691
3691
3692 def versions(self):
3692 def versions(self):
3693 return pull_request_obj.versions.order_by(
3693 return pull_request_obj.versions.order_by(
3694 PullRequestVersion.pull_request_version_id).all()
3694 PullRequestVersion.pull_request_version_id).all()
3695
3695
3696 def is_closed(self):
3696 def is_closed(self):
3697 return pull_request_obj.is_closed()
3697 return pull_request_obj.is_closed()
3698
3698
3699 @property
3699 @property
3700 def pull_request_version_id(self):
3700 def pull_request_version_id(self):
3701 return getattr(pull_request_obj, 'pull_request_version_id', None)
3701 return getattr(pull_request_obj, 'pull_request_version_id', None)
3702
3702
3703 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3703 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3704
3704
3705 attrs.author = StrictAttributeDict(
3705 attrs.author = StrictAttributeDict(
3706 pull_request_obj.author.get_api_data())
3706 pull_request_obj.author.get_api_data())
3707 if pull_request_obj.target_repo:
3707 if pull_request_obj.target_repo:
3708 attrs.target_repo = StrictAttributeDict(
3708 attrs.target_repo = StrictAttributeDict(
3709 pull_request_obj.target_repo.get_api_data())
3709 pull_request_obj.target_repo.get_api_data())
3710 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3710 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3711
3711
3712 if pull_request_obj.source_repo:
3712 if pull_request_obj.source_repo:
3713 attrs.source_repo = StrictAttributeDict(
3713 attrs.source_repo = StrictAttributeDict(
3714 pull_request_obj.source_repo.get_api_data())
3714 pull_request_obj.source_repo.get_api_data())
3715 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3715 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3716
3716
3717 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3717 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3718 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3718 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3719 attrs.revisions = pull_request_obj.revisions
3719 attrs.revisions = pull_request_obj.revisions
3720
3720
3721 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3721 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3722 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3722 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3723 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3723 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3724
3724
3725 return PullRequestDisplay(attrs, internal=internal_methods)
3725 return PullRequestDisplay(attrs, internal=internal_methods)
3726
3726
3727 def is_closed(self):
3727 def is_closed(self):
3728 return self.status == self.STATUS_CLOSED
3728 return self.status == self.STATUS_CLOSED
3729
3729
3730 def __json__(self):
3730 def __json__(self):
3731 return {
3731 return {
3732 'revisions': self.revisions,
3732 'revisions': self.revisions,
3733 }
3733 }
3734
3734
3735 def calculated_review_status(self):
3735 def calculated_review_status(self):
3736 from rhodecode.model.changeset_status import ChangesetStatusModel
3736 from rhodecode.model.changeset_status import ChangesetStatusModel
3737 return ChangesetStatusModel().calculated_review_status(self)
3737 return ChangesetStatusModel().calculated_review_status(self)
3738
3738
3739 def reviewers_statuses(self):
3739 def reviewers_statuses(self):
3740 from rhodecode.model.changeset_status import ChangesetStatusModel
3740 from rhodecode.model.changeset_status import ChangesetStatusModel
3741 return ChangesetStatusModel().reviewers_statuses(self)
3741 return ChangesetStatusModel().reviewers_statuses(self)
3742
3742
3743 @property
3743 @property
3744 def workspace_id(self):
3744 def workspace_id(self):
3745 from rhodecode.model.pull_request import PullRequestModel
3745 from rhodecode.model.pull_request import PullRequestModel
3746 return PullRequestModel()._workspace_id(self)
3746 return PullRequestModel()._workspace_id(self)
3747
3747
3748 def get_shadow_repo(self):
3748 def get_shadow_repo(self):
3749 workspace_id = self.workspace_id
3749 workspace_id = self.workspace_id
3750 vcs_obj = self.target_repo.scm_instance()
3750 vcs_obj = self.target_repo.scm_instance()
3751 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3751 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3752 workspace_id)
3752 workspace_id)
3753 return vcs_obj.get_shadow_instance(shadow_repository_path)
3753 return vcs_obj.get_shadow_instance(shadow_repository_path)
3754
3754
3755
3755
3756 class PullRequestVersion(Base, _PullRequestBase):
3756 class PullRequestVersion(Base, _PullRequestBase):
3757 __tablename__ = 'pull_request_versions'
3757 __tablename__ = 'pull_request_versions'
3758 __table_args__ = (
3758 __table_args__ = (
3759 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3759 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3760 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3760 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3761 )
3761 )
3762
3762
3763 pull_request_version_id = Column(
3763 pull_request_version_id = Column(
3764 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3764 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3765 pull_request_id = Column(
3765 pull_request_id = Column(
3766 'pull_request_id', Integer(),
3766 'pull_request_id', Integer(),
3767 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3767 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3768 pull_request = relationship('PullRequest')
3768 pull_request = relationship('PullRequest')
3769
3769
3770 def __repr__(self):
3770 def __repr__(self):
3771 if self.pull_request_version_id:
3771 if self.pull_request_version_id:
3772 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3772 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3773 else:
3773 else:
3774 return '<DB:PullRequestVersion at %#x>' % id(self)
3774 return '<DB:PullRequestVersion at %#x>' % id(self)
3775
3775
3776 @property
3776 @property
3777 def reviewers(self):
3777 def reviewers(self):
3778 return self.pull_request.reviewers
3778 return self.pull_request.reviewers
3779
3779
3780 @property
3780 @property
3781 def versions(self):
3781 def versions(self):
3782 return self.pull_request.versions
3782 return self.pull_request.versions
3783
3783
3784 def is_closed(self):
3784 def is_closed(self):
3785 # calculate from original
3785 # calculate from original
3786 return self.pull_request.status == self.STATUS_CLOSED
3786 return self.pull_request.status == self.STATUS_CLOSED
3787
3787
3788 def calculated_review_status(self):
3788 def calculated_review_status(self):
3789 return self.pull_request.calculated_review_status()
3789 return self.pull_request.calculated_review_status()
3790
3790
3791 def reviewers_statuses(self):
3791 def reviewers_statuses(self):
3792 return self.pull_request.reviewers_statuses()
3792 return self.pull_request.reviewers_statuses()
3793
3793
3794
3794
3795 class PullRequestReviewers(Base, BaseModel):
3795 class PullRequestReviewers(Base, BaseModel):
3796 __tablename__ = 'pull_request_reviewers'
3796 __tablename__ = 'pull_request_reviewers'
3797 __table_args__ = (
3797 __table_args__ = (
3798 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3798 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3799 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3799 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3800 )
3800 )
3801
3801
3802 @hybrid_property
3802 @hybrid_property
3803 def reasons(self):
3803 def reasons(self):
3804 if not self._reasons:
3804 if not self._reasons:
3805 return []
3805 return []
3806 return self._reasons
3806 return self._reasons
3807
3807
3808 @reasons.setter
3808 @reasons.setter
3809 def reasons(self, val):
3809 def reasons(self, val):
3810 val = val or []
3810 val = val or []
3811 if any(not isinstance(x, compat.string_types) for x in val):
3811 if any(not isinstance(x, compat.string_types) for x in val):
3812 raise Exception('invalid reasons type, must be list of strings')
3812 raise Exception('invalid reasons type, must be list of strings')
3813 self._reasons = val
3813 self._reasons = val
3814
3814
3815 pull_requests_reviewers_id = Column(
3815 pull_requests_reviewers_id = Column(
3816 'pull_requests_reviewers_id', Integer(), nullable=False,
3816 'pull_requests_reviewers_id', Integer(), nullable=False,
3817 primary_key=True)
3817 primary_key=True)
3818 pull_request_id = Column(
3818 pull_request_id = Column(
3819 "pull_request_id", Integer(),
3819 "pull_request_id", Integer(),
3820 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3820 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3821 user_id = Column(
3821 user_id = Column(
3822 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3822 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3823 _reasons = Column(
3823 _reasons = Column(
3824 'reason', MutationList.as_mutable(
3824 'reason', MutationList.as_mutable(
3825 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3825 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3826
3826
3827 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3827 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3828 user = relationship('User')
3828 user = relationship('User')
3829 pull_request = relationship('PullRequest')
3829 pull_request = relationship('PullRequest')
3830
3830
3831 rule_data = Column(
3831 rule_data = Column(
3832 'rule_data_json',
3832 'rule_data_json',
3833 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3833 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3834
3834
3835 def rule_user_group_data(self):
3835 def rule_user_group_data(self):
3836 """
3836 """
3837 Returns the voting user group rule data for this reviewer
3837 Returns the voting user group rule data for this reviewer
3838 """
3838 """
3839
3839
3840 if self.rule_data and 'vote_rule' in self.rule_data:
3840 if self.rule_data and 'vote_rule' in self.rule_data:
3841 user_group_data = {}
3841 user_group_data = {}
3842 if 'rule_user_group_entry_id' in self.rule_data:
3842 if 'rule_user_group_entry_id' in self.rule_data:
3843 # means a group with voting rules !
3843 # means a group with voting rules !
3844 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3844 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3845 user_group_data['name'] = self.rule_data['rule_name']
3845 user_group_data['name'] = self.rule_data['rule_name']
3846 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3846 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3847
3847
3848 return user_group_data
3848 return user_group_data
3849
3849
3850 def __unicode__(self):
3850 def __unicode__(self):
3851 return u"<%s('id:%s')>" % (self.__class__.__name__,
3851 return u"<%s('id:%s')>" % (self.__class__.__name__,
3852 self.pull_requests_reviewers_id)
3852 self.pull_requests_reviewers_id)
3853
3853
3854
3854
3855 class Notification(Base, BaseModel):
3855 class Notification(Base, BaseModel):
3856 __tablename__ = 'notifications'
3856 __tablename__ = 'notifications'
3857 __table_args__ = (
3857 __table_args__ = (
3858 Index('notification_type_idx', 'type'),
3858 Index('notification_type_idx', 'type'),
3859 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3859 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3860 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3860 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3861 )
3861 )
3862
3862
3863 TYPE_CHANGESET_COMMENT = u'cs_comment'
3863 TYPE_CHANGESET_COMMENT = u'cs_comment'
3864 TYPE_MESSAGE = u'message'
3864 TYPE_MESSAGE = u'message'
3865 TYPE_MENTION = u'mention'
3865 TYPE_MENTION = u'mention'
3866 TYPE_REGISTRATION = u'registration'
3866 TYPE_REGISTRATION = u'registration'
3867 TYPE_PULL_REQUEST = u'pull_request'
3867 TYPE_PULL_REQUEST = u'pull_request'
3868 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3868 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3869
3869
3870 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3870 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3871 subject = Column('subject', Unicode(512), nullable=True)
3871 subject = Column('subject', Unicode(512), nullable=True)
3872 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3872 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3873 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3873 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3874 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3874 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3875 type_ = Column('type', Unicode(255))
3875 type_ = Column('type', Unicode(255))
3876
3876
3877 created_by_user = relationship('User')
3877 created_by_user = relationship('User')
3878 notifications_to_users = relationship('UserNotification', lazy='joined',
3878 notifications_to_users = relationship('UserNotification', lazy='joined',
3879 cascade="all, delete, delete-orphan")
3879 cascade="all, delete, delete-orphan")
3880
3880
3881 @property
3881 @property
3882 def recipients(self):
3882 def recipients(self):
3883 return [x.user for x in UserNotification.query()\
3883 return [x.user for x in UserNotification.query()\
3884 .filter(UserNotification.notification == self)\
3884 .filter(UserNotification.notification == self)\
3885 .order_by(UserNotification.user_id.asc()).all()]
3885 .order_by(UserNotification.user_id.asc()).all()]
3886
3886
3887 @classmethod
3887 @classmethod
3888 def create(cls, created_by, subject, body, recipients, type_=None):
3888 def create(cls, created_by, subject, body, recipients, type_=None):
3889 if type_ is None:
3889 if type_ is None:
3890 type_ = Notification.TYPE_MESSAGE
3890 type_ = Notification.TYPE_MESSAGE
3891
3891
3892 notification = cls()
3892 notification = cls()
3893 notification.created_by_user = created_by
3893 notification.created_by_user = created_by
3894 notification.subject = subject
3894 notification.subject = subject
3895 notification.body = body
3895 notification.body = body
3896 notification.type_ = type_
3896 notification.type_ = type_
3897 notification.created_on = datetime.datetime.now()
3897 notification.created_on = datetime.datetime.now()
3898
3898
3899 for u in recipients:
3899 for u in recipients:
3900 assoc = UserNotification()
3900 assoc = UserNotification()
3901 assoc.notification = notification
3901 assoc.notification = notification
3902
3902
3903 # if created_by is inside recipients mark his notification
3903 # if created_by is inside recipients mark his notification
3904 # as read
3904 # as read
3905 if u.user_id == created_by.user_id:
3905 if u.user_id == created_by.user_id:
3906 assoc.read = True
3906 assoc.read = True
3907
3907
3908 u.notifications.append(assoc)
3908 u.notifications.append(assoc)
3909 Session().add(notification)
3909 Session().add(notification)
3910
3910
3911 return notification
3911 return notification
3912
3912
3913
3913
3914 class UserNotification(Base, BaseModel):
3914 class UserNotification(Base, BaseModel):
3915 __tablename__ = 'user_to_notification'
3915 __tablename__ = 'user_to_notification'
3916 __table_args__ = (
3916 __table_args__ = (
3917 UniqueConstraint('user_id', 'notification_id'),
3917 UniqueConstraint('user_id', 'notification_id'),
3918 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3918 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3919 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3919 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3920 )
3920 )
3921 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3921 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3922 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3922 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3923 read = Column('read', Boolean, default=False)
3923 read = Column('read', Boolean, default=False)
3924 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3924 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3925
3925
3926 user = relationship('User', lazy="joined")
3926 user = relationship('User', lazy="joined")
3927 notification = relationship('Notification', lazy="joined",
3927 notification = relationship('Notification', lazy="joined",
3928 order_by=lambda: Notification.created_on.desc(),)
3928 order_by=lambda: Notification.created_on.desc(),)
3929
3929
3930 def mark_as_read(self):
3930 def mark_as_read(self):
3931 self.read = True
3931 self.read = True
3932 Session().add(self)
3932 Session().add(self)
3933
3933
3934
3934
3935 class Gist(Base, BaseModel):
3935 class Gist(Base, BaseModel):
3936 __tablename__ = 'gists'
3936 __tablename__ = 'gists'
3937 __table_args__ = (
3937 __table_args__ = (
3938 Index('g_gist_access_id_idx', 'gist_access_id'),
3938 Index('g_gist_access_id_idx', 'gist_access_id'),
3939 Index('g_created_on_idx', 'created_on'),
3939 Index('g_created_on_idx', 'created_on'),
3940 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3940 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3941 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3941 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3942 )
3942 )
3943 GIST_PUBLIC = u'public'
3943 GIST_PUBLIC = u'public'
3944 GIST_PRIVATE = u'private'
3944 GIST_PRIVATE = u'private'
3945 DEFAULT_FILENAME = u'gistfile1.txt'
3945 DEFAULT_FILENAME = u'gistfile1.txt'
3946
3946
3947 ACL_LEVEL_PUBLIC = u'acl_public'
3947 ACL_LEVEL_PUBLIC = u'acl_public'
3948 ACL_LEVEL_PRIVATE = u'acl_private'
3948 ACL_LEVEL_PRIVATE = u'acl_private'
3949
3949
3950 gist_id = Column('gist_id', Integer(), primary_key=True)
3950 gist_id = Column('gist_id', Integer(), primary_key=True)
3951 gist_access_id = Column('gist_access_id', Unicode(250))
3951 gist_access_id = Column('gist_access_id', Unicode(250))
3952 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3952 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3953 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3953 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3954 gist_expires = Column('gist_expires', Float(53), nullable=False)
3954 gist_expires = Column('gist_expires', Float(53), nullable=False)
3955 gist_type = Column('gist_type', Unicode(128), nullable=False)
3955 gist_type = Column('gist_type', Unicode(128), nullable=False)
3956 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3956 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3957 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3957 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3958 acl_level = Column('acl_level', Unicode(128), nullable=True)
3958 acl_level = Column('acl_level', Unicode(128), nullable=True)
3959
3959
3960 owner = relationship('User')
3960 owner = relationship('User')
3961
3961
3962 def __repr__(self):
3962 def __repr__(self):
3963 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3963 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3964
3964
3965 @hybrid_property
3965 @hybrid_property
3966 def description_safe(self):
3966 def description_safe(self):
3967 from rhodecode.lib import helpers as h
3967 from rhodecode.lib import helpers as h
3968 return h.escape(self.gist_description)
3968 return h.escape(self.gist_description)
3969
3969
3970 @classmethod
3970 @classmethod
3971 def get_or_404(cls, id_):
3971 def get_or_404(cls, id_):
3972 from pyramid.httpexceptions import HTTPNotFound
3972 from pyramid.httpexceptions import HTTPNotFound
3973
3973
3974 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3974 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3975 if not res:
3975 if not res:
3976 raise HTTPNotFound()
3976 raise HTTPNotFound()
3977 return res
3977 return res
3978
3978
3979 @classmethod
3979 @classmethod
3980 def get_by_access_id(cls, gist_access_id):
3980 def get_by_access_id(cls, gist_access_id):
3981 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3981 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3982
3982
3983 def gist_url(self):
3983 def gist_url(self):
3984 from rhodecode.model.gist import GistModel
3984 from rhodecode.model.gist import GistModel
3985 return GistModel().get_url(self)
3985 return GistModel().get_url(self)
3986
3986
3987 @classmethod
3987 @classmethod
3988 def base_path(cls):
3988 def base_path(cls):
3989 """
3989 """
3990 Returns base path when all gists are stored
3990 Returns base path when all gists are stored
3991
3991
3992 :param cls:
3992 :param cls:
3993 """
3993 """
3994 from rhodecode.model.gist import GIST_STORE_LOC
3994 from rhodecode.model.gist import GIST_STORE_LOC
3995 q = Session().query(RhodeCodeUi)\
3995 q = Session().query(RhodeCodeUi)\
3996 .filter(RhodeCodeUi.ui_key == URL_SEP)
3996 .filter(RhodeCodeUi.ui_key == URL_SEP)
3997 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3997 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3998 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3998 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3999
3999
4000 def get_api_data(self):
4000 def get_api_data(self):
4001 """
4001 """
4002 Common function for generating gist related data for API
4002 Common function for generating gist related data for API
4003 """
4003 """
4004 gist = self
4004 gist = self
4005 data = {
4005 data = {
4006 'gist_id': gist.gist_id,
4006 'gist_id': gist.gist_id,
4007 'type': gist.gist_type,
4007 'type': gist.gist_type,
4008 'access_id': gist.gist_access_id,
4008 'access_id': gist.gist_access_id,
4009 'description': gist.gist_description,
4009 'description': gist.gist_description,
4010 'url': gist.gist_url(),
4010 'url': gist.gist_url(),
4011 'expires': gist.gist_expires,
4011 'expires': gist.gist_expires,
4012 'created_on': gist.created_on,
4012 'created_on': gist.created_on,
4013 'modified_at': gist.modified_at,
4013 'modified_at': gist.modified_at,
4014 'content': None,
4014 'content': None,
4015 'acl_level': gist.acl_level,
4015 'acl_level': gist.acl_level,
4016 }
4016 }
4017 return data
4017 return data
4018
4018
4019 def __json__(self):
4019 def __json__(self):
4020 data = dict(
4020 data = dict(
4021 )
4021 )
4022 data.update(self.get_api_data())
4022 data.update(self.get_api_data())
4023 return data
4023 return data
4024 # SCM functions
4024 # SCM functions
4025
4025
4026 def scm_instance(self, **kwargs):
4026 def scm_instance(self, **kwargs):
4027 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4027 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4028 return get_vcs_instance(
4028 return get_vcs_instance(
4029 repo_path=safe_str(full_repo_path), create=False)
4029 repo_path=safe_str(full_repo_path), create=False)
4030
4030
4031
4031
4032 class ExternalIdentity(Base, BaseModel):
4032 class ExternalIdentity(Base, BaseModel):
4033 __tablename__ = 'external_identities'
4033 __tablename__ = 'external_identities'
4034 __table_args__ = (
4034 __table_args__ = (
4035 Index('local_user_id_idx', 'local_user_id'),
4035 Index('local_user_id_idx', 'local_user_id'),
4036 Index('external_id_idx', 'external_id'),
4036 Index('external_id_idx', 'external_id'),
4037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4038 'mysql_charset': 'utf8'})
4038 'mysql_charset': 'utf8'})
4039
4039
4040 external_id = Column('external_id', Unicode(255), default=u'',
4040 external_id = Column('external_id', Unicode(255), default=u'',
4041 primary_key=True)
4041 primary_key=True)
4042 external_username = Column('external_username', Unicode(1024), default=u'')
4042 external_username = Column('external_username', Unicode(1024), default=u'')
4043 local_user_id = Column('local_user_id', Integer(),
4043 local_user_id = Column('local_user_id', Integer(),
4044 ForeignKey('users.user_id'), primary_key=True)
4044 ForeignKey('users.user_id'), primary_key=True)
4045 provider_name = Column('provider_name', Unicode(255), default=u'',
4045 provider_name = Column('provider_name', Unicode(255), default=u'',
4046 primary_key=True)
4046 primary_key=True)
4047 access_token = Column('access_token', String(1024), default=u'')
4047 access_token = Column('access_token', String(1024), default=u'')
4048 alt_token = Column('alt_token', String(1024), default=u'')
4048 alt_token = Column('alt_token', String(1024), default=u'')
4049 token_secret = Column('token_secret', String(1024), default=u'')
4049 token_secret = Column('token_secret', String(1024), default=u'')
4050
4050
4051 @classmethod
4051 @classmethod
4052 def by_external_id_and_provider(cls, external_id, provider_name,
4052 def by_external_id_and_provider(cls, external_id, provider_name,
4053 local_user_id=None):
4053 local_user_id=None):
4054 """
4054 """
4055 Returns ExternalIdentity instance based on search params
4055 Returns ExternalIdentity instance based on search params
4056
4056
4057 :param external_id:
4057 :param external_id:
4058 :param provider_name:
4058 :param provider_name:
4059 :return: ExternalIdentity
4059 :return: ExternalIdentity
4060 """
4060 """
4061 query = cls.query()
4061 query = cls.query()
4062 query = query.filter(cls.external_id == external_id)
4062 query = query.filter(cls.external_id == external_id)
4063 query = query.filter(cls.provider_name == provider_name)
4063 query = query.filter(cls.provider_name == provider_name)
4064 if local_user_id:
4064 if local_user_id:
4065 query = query.filter(cls.local_user_id == local_user_id)
4065 query = query.filter(cls.local_user_id == local_user_id)
4066 return query.first()
4066 return query.first()
4067
4067
4068 @classmethod
4068 @classmethod
4069 def user_by_external_id_and_provider(cls, external_id, provider_name):
4069 def user_by_external_id_and_provider(cls, external_id, provider_name):
4070 """
4070 """
4071 Returns User instance based on search params
4071 Returns User instance based on search params
4072
4072
4073 :param external_id:
4073 :param external_id:
4074 :param provider_name:
4074 :param provider_name:
4075 :return: User
4075 :return: User
4076 """
4076 """
4077 query = User.query()
4077 query = User.query()
4078 query = query.filter(cls.external_id == external_id)
4078 query = query.filter(cls.external_id == external_id)
4079 query = query.filter(cls.provider_name == provider_name)
4079 query = query.filter(cls.provider_name == provider_name)
4080 query = query.filter(User.user_id == cls.local_user_id)
4080 query = query.filter(User.user_id == cls.local_user_id)
4081 return query.first()
4081 return query.first()
4082
4082
4083 @classmethod
4083 @classmethod
4084 def by_local_user_id(cls, local_user_id):
4084 def by_local_user_id(cls, local_user_id):
4085 """
4085 """
4086 Returns all tokens for user
4086 Returns all tokens for user
4087
4087
4088 :param local_user_id:
4088 :param local_user_id:
4089 :return: ExternalIdentity
4089 :return: ExternalIdentity
4090 """
4090 """
4091 query = cls.query()
4091 query = cls.query()
4092 query = query.filter(cls.local_user_id == local_user_id)
4092 query = query.filter(cls.local_user_id == local_user_id)
4093 return query
4093 return query
4094
4094
4095
4095
4096 class Integration(Base, BaseModel):
4096 class Integration(Base, BaseModel):
4097 __tablename__ = 'integrations'
4097 __tablename__ = 'integrations'
4098 __table_args__ = (
4098 __table_args__ = (
4099 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4099 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4100 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4100 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4101 )
4101 )
4102
4102
4103 integration_id = Column('integration_id', Integer(), primary_key=True)
4103 integration_id = Column('integration_id', Integer(), primary_key=True)
4104 integration_type = Column('integration_type', String(255))
4104 integration_type = Column('integration_type', String(255))
4105 enabled = Column('enabled', Boolean(), nullable=False)
4105 enabled = Column('enabled', Boolean(), nullable=False)
4106 name = Column('name', String(255), nullable=False)
4106 name = Column('name', String(255), nullable=False)
4107 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4107 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4108 default=False)
4108 default=False)
4109
4109
4110 settings = Column(
4110 settings = Column(
4111 'settings_json', MutationObj.as_mutable(
4111 'settings_json', MutationObj.as_mutable(
4112 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4112 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4113 repo_id = Column(
4113 repo_id = Column(
4114 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4114 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4115 nullable=True, unique=None, default=None)
4115 nullable=True, unique=None, default=None)
4116 repo = relationship('Repository', lazy='joined')
4116 repo = relationship('Repository', lazy='joined')
4117
4117
4118 repo_group_id = Column(
4118 repo_group_id = Column(
4119 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4119 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4120 nullable=True, unique=None, default=None)
4120 nullable=True, unique=None, default=None)
4121 repo_group = relationship('RepoGroup', lazy='joined')
4121 repo_group = relationship('RepoGroup', lazy='joined')
4122
4122
4123 @property
4123 @property
4124 def scope(self):
4124 def scope(self):
4125 if self.repo:
4125 if self.repo:
4126 return repr(self.repo)
4126 return repr(self.repo)
4127 if self.repo_group:
4127 if self.repo_group:
4128 if self.child_repos_only:
4128 if self.child_repos_only:
4129 return repr(self.repo_group) + ' (child repos only)'
4129 return repr(self.repo_group) + ' (child repos only)'
4130 else:
4130 else:
4131 return repr(self.repo_group) + ' (recursive)'
4131 return repr(self.repo_group) + ' (recursive)'
4132 if self.child_repos_only:
4132 if self.child_repos_only:
4133 return 'root_repos'
4133 return 'root_repos'
4134 return 'global'
4134 return 'global'
4135
4135
4136 def __repr__(self):
4136 def __repr__(self):
4137 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4137 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4138
4138
4139
4139
4140 class RepoReviewRuleUser(Base, BaseModel):
4140 class RepoReviewRuleUser(Base, BaseModel):
4141 __tablename__ = 'repo_review_rules_users'
4141 __tablename__ = 'repo_review_rules_users'
4142 __table_args__ = (
4142 __table_args__ = (
4143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4145 )
4145 )
4146
4146
4147 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4147 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4148 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4148 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4149 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4149 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4150 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4150 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4151 user = relationship('User')
4151 user = relationship('User')
4152
4152
4153 def rule_data(self):
4153 def rule_data(self):
4154 return {
4154 return {
4155 'mandatory': self.mandatory
4155 'mandatory': self.mandatory
4156 }
4156 }
4157
4157
4158
4158
4159 class RepoReviewRuleUserGroup(Base, BaseModel):
4159 class RepoReviewRuleUserGroup(Base, BaseModel):
4160 __tablename__ = 'repo_review_rules_users_groups'
4160 __tablename__ = 'repo_review_rules_users_groups'
4161 __table_args__ = (
4161 __table_args__ = (
4162 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4162 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4163 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4163 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4164 )
4164 )
4165 VOTE_RULE_ALL = -1
4165 VOTE_RULE_ALL = -1
4166
4166
4167 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4167 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4168 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4168 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4169 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4169 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4170 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4170 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4171 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4171 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4172 users_group = relationship('UserGroup')
4172 users_group = relationship('UserGroup')
4173
4173
4174 def rule_data(self):
4174 def rule_data(self):
4175 return {
4175 return {
4176 'mandatory': self.mandatory,
4176 'mandatory': self.mandatory,
4177 'vote_rule': self.vote_rule
4177 'vote_rule': self.vote_rule
4178 }
4178 }
4179
4179
4180 @property
4180 @property
4181 def vote_rule_label(self):
4181 def vote_rule_label(self):
4182 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4182 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4183 return 'all must vote'
4183 return 'all must vote'
4184 else:
4184 else:
4185 return 'min. vote {}'.format(self.vote_rule)
4185 return 'min. vote {}'.format(self.vote_rule)
4186
4186
4187
4187
4188 class RepoReviewRule(Base, BaseModel):
4188 class RepoReviewRule(Base, BaseModel):
4189 __tablename__ = 'repo_review_rules'
4189 __tablename__ = 'repo_review_rules'
4190 __table_args__ = (
4190 __table_args__ = (
4191 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4191 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4192 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4192 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4193 )
4193 )
4194
4194
4195 repo_review_rule_id = Column(
4195 repo_review_rule_id = Column(
4196 'repo_review_rule_id', Integer(), primary_key=True)
4196 'repo_review_rule_id', Integer(), primary_key=True)
4197 repo_id = Column(
4197 repo_id = Column(
4198 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4198 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4199 repo = relationship('Repository', backref='review_rules')
4199 repo = relationship('Repository', backref='review_rules')
4200
4200
4201 review_rule_name = Column('review_rule_name', String(255))
4201 review_rule_name = Column('review_rule_name', String(255))
4202 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4202 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4203 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4203 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4204 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4204 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4205
4205
4206 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4206 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4207 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4207 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4208 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4208 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4209 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4209 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4210
4210
4211 rule_users = relationship('RepoReviewRuleUser')
4211 rule_users = relationship('RepoReviewRuleUser')
4212 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4212 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4213
4213
4214 def _validate_glob(self, value):
4214 def _validate_glob(self, value):
4215 re.compile('^' + glob2re(value) + '$')
4215 re.compile('^' + glob2re(value) + '$')
4216
4216
4217 @hybrid_property
4217 @hybrid_property
4218 def source_branch_pattern(self):
4218 def source_branch_pattern(self):
4219 return self._branch_pattern or '*'
4219 return self._branch_pattern or '*'
4220
4220
4221 @source_branch_pattern.setter
4221 @source_branch_pattern.setter
4222 def source_branch_pattern(self, value):
4222 def source_branch_pattern(self, value):
4223 self._validate_glob(value)
4223 self._validate_glob(value)
4224 self._branch_pattern = value or '*'
4224 self._branch_pattern = value or '*'
4225
4225
4226 @hybrid_property
4226 @hybrid_property
4227 def target_branch_pattern(self):
4227 def target_branch_pattern(self):
4228 return self._target_branch_pattern or '*'
4228 return self._target_branch_pattern or '*'
4229
4229
4230 @target_branch_pattern.setter
4230 @target_branch_pattern.setter
4231 def target_branch_pattern(self, value):
4231 def target_branch_pattern(self, value):
4232 self._validate_glob(value)
4232 self._validate_glob(value)
4233 self._target_branch_pattern = value or '*'
4233 self._target_branch_pattern = value or '*'
4234
4234
4235 @hybrid_property
4235 @hybrid_property
4236 def file_pattern(self):
4236 def file_pattern(self):
4237 return self._file_pattern or '*'
4237 return self._file_pattern or '*'
4238
4238
4239 @file_pattern.setter
4239 @file_pattern.setter
4240 def file_pattern(self, value):
4240 def file_pattern(self, value):
4241 self._validate_glob(value)
4241 self._validate_glob(value)
4242 self._file_pattern = value or '*'
4242 self._file_pattern = value or '*'
4243
4243
4244 def matches(self, source_branch, target_branch, files_changed):
4244 def matches(self, source_branch, target_branch, files_changed):
4245 """
4245 """
4246 Check if this review rule matches a branch/files in a pull request
4246 Check if this review rule matches a branch/files in a pull request
4247
4247
4248 :param source_branch: source branch name for the commit
4248 :param source_branch: source branch name for the commit
4249 :param target_branch: target branch name for the commit
4249 :param target_branch: target branch name for the commit
4250 :param files_changed: list of file paths changed in the pull request
4250 :param files_changed: list of file paths changed in the pull request
4251 """
4251 """
4252
4252
4253 source_branch = source_branch or ''
4253 source_branch = source_branch or ''
4254 target_branch = target_branch or ''
4254 target_branch = target_branch or ''
4255 files_changed = files_changed or []
4255 files_changed = files_changed or []
4256
4256
4257 branch_matches = True
4257 branch_matches = True
4258 if source_branch or target_branch:
4258 if source_branch or target_branch:
4259 if self.source_branch_pattern == '*':
4259 if self.source_branch_pattern == '*':
4260 source_branch_match = True
4260 source_branch_match = True
4261 else:
4261 else:
4262 source_branch_regex = re.compile(
4262 source_branch_regex = re.compile(
4263 '^' + glob2re(self.source_branch_pattern) + '$')
4263 '^' + glob2re(self.source_branch_pattern) + '$')
4264 source_branch_match = bool(source_branch_regex.search(source_branch))
4264 source_branch_match = bool(source_branch_regex.search(source_branch))
4265 if self.target_branch_pattern == '*':
4265 if self.target_branch_pattern == '*':
4266 target_branch_match = True
4266 target_branch_match = True
4267 else:
4267 else:
4268 target_branch_regex = re.compile(
4268 target_branch_regex = re.compile(
4269 '^' + glob2re(self.target_branch_pattern) + '$')
4269 '^' + glob2re(self.target_branch_pattern) + '$')
4270 target_branch_match = bool(target_branch_regex.search(target_branch))
4270 target_branch_match = bool(target_branch_regex.search(target_branch))
4271
4271
4272 branch_matches = source_branch_match and target_branch_match
4272 branch_matches = source_branch_match and target_branch_match
4273
4273
4274 files_matches = True
4274 files_matches = True
4275 if self.file_pattern != '*':
4275 if self.file_pattern != '*':
4276 files_matches = False
4276 files_matches = False
4277 file_regex = re.compile(glob2re(self.file_pattern))
4277 file_regex = re.compile(glob2re(self.file_pattern))
4278 for filename in files_changed:
4278 for filename in files_changed:
4279 if file_regex.search(filename):
4279 if file_regex.search(filename):
4280 files_matches = True
4280 files_matches = True
4281 break
4281 break
4282
4282
4283 return branch_matches and files_matches
4283 return branch_matches and files_matches
4284
4284
4285 @property
4285 @property
4286 def review_users(self):
4286 def review_users(self):
4287 """ Returns the users which this rule applies to """
4287 """ Returns the users which this rule applies to """
4288
4288
4289 users = collections.OrderedDict()
4289 users = collections.OrderedDict()
4290
4290
4291 for rule_user in self.rule_users:
4291 for rule_user in self.rule_users:
4292 if rule_user.user.active:
4292 if rule_user.user.active:
4293 if rule_user.user not in users:
4293 if rule_user.user not in users:
4294 users[rule_user.user.username] = {
4294 users[rule_user.user.username] = {
4295 'user': rule_user.user,
4295 'user': rule_user.user,
4296 'source': 'user',
4296 'source': 'user',
4297 'source_data': {},
4297 'source_data': {},
4298 'data': rule_user.rule_data()
4298 'data': rule_user.rule_data()
4299 }
4299 }
4300
4300
4301 for rule_user_group in self.rule_user_groups:
4301 for rule_user_group in self.rule_user_groups:
4302 source_data = {
4302 source_data = {
4303 'user_group_id': rule_user_group.users_group.users_group_id,
4303 'user_group_id': rule_user_group.users_group.users_group_id,
4304 'name': rule_user_group.users_group.users_group_name,
4304 'name': rule_user_group.users_group.users_group_name,
4305 'members': len(rule_user_group.users_group.members)
4305 'members': len(rule_user_group.users_group.members)
4306 }
4306 }
4307 for member in rule_user_group.users_group.members:
4307 for member in rule_user_group.users_group.members:
4308 if member.user.active:
4308 if member.user.active:
4309 key = member.user.username
4309 key = member.user.username
4310 if key in users:
4310 if key in users:
4311 # skip this member as we have him already
4311 # skip this member as we have him already
4312 # this prevents from override the "first" matched
4312 # this prevents from override the "first" matched
4313 # users with duplicates in multiple groups
4313 # users with duplicates in multiple groups
4314 continue
4314 continue
4315
4315
4316 users[key] = {
4316 users[key] = {
4317 'user': member.user,
4317 'user': member.user,
4318 'source': 'user_group',
4318 'source': 'user_group',
4319 'source_data': source_data,
4319 'source_data': source_data,
4320 'data': rule_user_group.rule_data()
4320 'data': rule_user_group.rule_data()
4321 }
4321 }
4322
4322
4323 return users
4323 return users
4324
4324
4325 def user_group_vote_rule(self):
4325 def user_group_vote_rule(self):
4326 rules = []
4326 rules = []
4327 if self.rule_user_groups:
4327 if self.rule_user_groups:
4328 for user_group in self.rule_user_groups:
4328 for user_group in self.rule_user_groups:
4329 rules.append(user_group)
4329 rules.append(user_group)
4330 return rules
4330 return rules
4331
4331
4332 def __repr__(self):
4332 def __repr__(self):
4333 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4333 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4334 self.repo_review_rule_id, self.repo)
4334 self.repo_review_rule_id, self.repo)
4335
4335
4336
4336
4337 class ScheduleEntry(Base, BaseModel):
4337 class ScheduleEntry(Base, BaseModel):
4338 __tablename__ = 'schedule_entries'
4338 __tablename__ = 'schedule_entries'
4339 __table_args__ = (
4339 __table_args__ = (
4340 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4340 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4341 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4341 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4342 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4342 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4343 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4343 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4344 )
4344 )
4345 schedule_types = ['crontab', 'timedelta', 'integer']
4345 schedule_types = ['crontab', 'timedelta', 'integer']
4346 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4346 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4347
4347
4348 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4348 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4349 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4349 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4350 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4350 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4351
4351
4352 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4352 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4353 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4353 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4354
4354
4355 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4355 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4356 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4356 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4357
4357
4358 # task
4358 # task
4359 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4359 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4360 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4360 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4361 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4361 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4362 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4362 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4363
4363
4364 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4364 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4365 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4365 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4366
4366
4367 @hybrid_property
4367 @hybrid_property
4368 def schedule_type(self):
4368 def schedule_type(self):
4369 return self._schedule_type
4369 return self._schedule_type
4370
4370
4371 @schedule_type.setter
4371 @schedule_type.setter
4372 def schedule_type(self, val):
4372 def schedule_type(self, val):
4373 if val not in self.schedule_types:
4373 if val not in self.schedule_types:
4374 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4374 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4375 val, self.schedule_type))
4375 val, self.schedule_type))
4376
4376
4377 self._schedule_type = val
4377 self._schedule_type = val
4378
4378
4379 @classmethod
4379 @classmethod
4380 def get_uid(cls, obj):
4380 def get_uid(cls, obj):
4381 args = obj.task_args
4381 args = obj.task_args
4382 kwargs = obj.task_kwargs
4382 kwargs = obj.task_kwargs
4383 if isinstance(args, JsonRaw):
4383 if isinstance(args, JsonRaw):
4384 try:
4384 try:
4385 args = json.loads(args)
4385 args = json.loads(args)
4386 except ValueError:
4386 except ValueError:
4387 args = tuple()
4387 args = tuple()
4388
4388
4389 if isinstance(kwargs, JsonRaw):
4389 if isinstance(kwargs, JsonRaw):
4390 try:
4390 try:
4391 kwargs = json.loads(kwargs)
4391 kwargs = json.loads(kwargs)
4392 except ValueError:
4392 except ValueError:
4393 kwargs = dict()
4393 kwargs = dict()
4394
4394
4395 dot_notation = obj.task_dot_notation
4395 dot_notation = obj.task_dot_notation
4396 val = '.'.join(map(safe_str, [
4396 val = '.'.join(map(safe_str, [
4397 sorted(dot_notation), args, sorted(kwargs.items())]))
4397 sorted(dot_notation), args, sorted(kwargs.items())]))
4398 return hashlib.sha1(val).hexdigest()
4398 return hashlib.sha1(val).hexdigest()
4399
4399
4400 @classmethod
4400 @classmethod
4401 def get_by_schedule_name(cls, schedule_name):
4401 def get_by_schedule_name(cls, schedule_name):
4402 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4402 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4403
4403
4404 @classmethod
4404 @classmethod
4405 def get_by_schedule_id(cls, schedule_id):
4405 def get_by_schedule_id(cls, schedule_id):
4406 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4406 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4407
4407
4408 @property
4408 @property
4409 def task(self):
4409 def task(self):
4410 return self.task_dot_notation
4410 return self.task_dot_notation
4411
4411
4412 @property
4412 @property
4413 def schedule(self):
4413 def schedule(self):
4414 from rhodecode.lib.celerylib.utils import raw_2_schedule
4414 from rhodecode.lib.celerylib.utils import raw_2_schedule
4415 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4415 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4416 return schedule
4416 return schedule
4417
4417
4418 @property
4418 @property
4419 def args(self):
4419 def args(self):
4420 try:
4420 try:
4421 return list(self.task_args or [])
4421 return list(self.task_args or [])
4422 except ValueError:
4422 except ValueError:
4423 return list()
4423 return list()
4424
4424
4425 @property
4425 @property
4426 def kwargs(self):
4426 def kwargs(self):
4427 try:
4427 try:
4428 return dict(self.task_kwargs or {})
4428 return dict(self.task_kwargs or {})
4429 except ValueError:
4429 except ValueError:
4430 return dict()
4430 return dict()
4431
4431
4432 def _as_raw(self, val):
4432 def _as_raw(self, val):
4433 if hasattr(val, 'de_coerce'):
4433 if hasattr(val, 'de_coerce'):
4434 val = val.de_coerce()
4434 val = val.de_coerce()
4435 if val:
4435 if val:
4436 val = json.dumps(val)
4436 val = json.dumps(val)
4437
4437
4438 return val
4438 return val
4439
4439
4440 @property
4440 @property
4441 def schedule_definition_raw(self):
4441 def schedule_definition_raw(self):
4442 return self._as_raw(self.schedule_definition)
4442 return self._as_raw(self.schedule_definition)
4443
4443
4444 @property
4444 @property
4445 def args_raw(self):
4445 def args_raw(self):
4446 return self._as_raw(self.task_args)
4446 return self._as_raw(self.task_args)
4447
4447
4448 @property
4448 @property
4449 def kwargs_raw(self):
4449 def kwargs_raw(self):
4450 return self._as_raw(self.task_kwargs)
4450 return self._as_raw(self.task_kwargs)
4451
4451
4452 def __repr__(self):
4452 def __repr__(self):
4453 return '<DB:ScheduleEntry({}:{})>'.format(
4453 return '<DB:ScheduleEntry({}:{})>'.format(
4454 self.schedule_entry_id, self.schedule_name)
4454 self.schedule_entry_id, self.schedule_name)
4455
4455
4456
4456
4457 @event.listens_for(ScheduleEntry, 'before_update')
4457 @event.listens_for(ScheduleEntry, 'before_update')
4458 def update_task_uid(mapper, connection, target):
4458 def update_task_uid(mapper, connection, target):
4459 target.task_uid = ScheduleEntry.get_uid(target)
4459 target.task_uid = ScheduleEntry.get_uid(target)
4460
4460
4461
4461
4462 @event.listens_for(ScheduleEntry, 'before_insert')
4462 @event.listens_for(ScheduleEntry, 'before_insert')
4463 def set_task_uid(mapper, connection, target):
4463 def set_task_uid(mapper, connection, target):
4464 target.task_uid = ScheduleEntry.get_uid(target)
4464 target.task_uid = ScheduleEntry.get_uid(target)
4465
4465
4466
4466
4467 class _BaseBranchPerms(BaseModel):
4467 class _BaseBranchPerms(BaseModel):
4468 @classmethod
4468 @classmethod
4469 def compute_hash(cls, value):
4469 def compute_hash(cls, value):
4470 return md5_safe(value)
4470 return md5_safe(value)
4471
4471
4472 @hybrid_property
4472 @hybrid_property
4473 def branch_pattern(self):
4473 def branch_pattern(self):
4474 return self._branch_pattern or '*'
4474 return self._branch_pattern or '*'
4475
4475
4476 @hybrid_property
4476 @hybrid_property
4477 def branch_hash(self):
4477 def branch_hash(self):
4478 return self._branch_hash
4478 return self._branch_hash
4479
4479
4480 def _validate_glob(self, value):
4480 def _validate_glob(self, value):
4481 re.compile('^' + glob2re(value) + '$')
4481 re.compile('^' + glob2re(value) + '$')
4482
4482
4483 @branch_pattern.setter
4483 @branch_pattern.setter
4484 def branch_pattern(self, value):
4484 def branch_pattern(self, value):
4485 self._validate_glob(value)
4485 self._validate_glob(value)
4486 self._branch_pattern = value or '*'
4486 self._branch_pattern = value or '*'
4487 # set the Hash when setting the branch pattern
4487 # set the Hash when setting the branch pattern
4488 self._branch_hash = self.compute_hash(self._branch_pattern)
4488 self._branch_hash = self.compute_hash(self._branch_pattern)
4489
4489
4490 def matches(self, branch):
4490 def matches(self, branch):
4491 """
4491 """
4492 Check if this the branch matches entry
4492 Check if this the branch matches entry
4493
4493
4494 :param branch: branch name for the commit
4494 :param branch: branch name for the commit
4495 """
4495 """
4496
4496
4497 branch = branch or ''
4497 branch = branch or ''
4498
4498
4499 branch_matches = True
4499 branch_matches = True
4500 if branch:
4500 if branch:
4501 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4501 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4502 branch_matches = bool(branch_regex.search(branch))
4502 branch_matches = bool(branch_regex.search(branch))
4503
4503
4504 return branch_matches
4504 return branch_matches
4505
4505
4506
4506
4507 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4507 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4508 __tablename__ = 'user_to_repo_branch_permissions'
4508 __tablename__ = 'user_to_repo_branch_permissions'
4509 __table_args__ = (
4509 __table_args__ = (
4510 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4510 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4511 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4511 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4512 )
4512 )
4513
4513
4514 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4514 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4515
4515
4516 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4516 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4517 repo = relationship('Repository', backref='user_branch_perms')
4517 repo = relationship('Repository', backref='user_branch_perms')
4518
4518
4519 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4519 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4520 permission = relationship('Permission')
4520 permission = relationship('Permission')
4521
4521
4522 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4522 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 user_repo_to_perm = relationship('UserRepoToPerm')
4523 user_repo_to_perm = relationship('UserRepoToPerm')
4524
4524
4525 rule_order = Column('rule_order', Integer(), nullable=False)
4525 rule_order = Column('rule_order', Integer(), nullable=False)
4526 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4526 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4527 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4527 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4528
4528
4529 def __unicode__(self):
4529 def __unicode__(self):
4530 return u'<UserBranchPermission(%s => %r)>' % (
4530 return u'<UserBranchPermission(%s => %r)>' % (
4531 self.user_repo_to_perm, self.branch_pattern)
4531 self.user_repo_to_perm, self.branch_pattern)
4532
4532
4533
4533
4534 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4534 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4535 __tablename__ = 'user_group_to_repo_branch_permissions'
4535 __tablename__ = 'user_group_to_repo_branch_permissions'
4536 __table_args__ = (
4536 __table_args__ = (
4537 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4537 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4538 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4538 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4539 )
4539 )
4540
4540
4541 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4541 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4542
4542
4543 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4543 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4544 repo = relationship('Repository', backref='user_group_branch_perms')
4544 repo = relationship('Repository', backref='user_group_branch_perms')
4545
4545
4546 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4546 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4547 permission = relationship('Permission')
4547 permission = relationship('Permission')
4548
4548
4549 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)
4549 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 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4550 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4551
4551
4552 rule_order = Column('rule_order', Integer(), nullable=False)
4552 rule_order = Column('rule_order', Integer(), nullable=False)
4553 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4553 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4554 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4554 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4555
4555
4556 def __unicode__(self):
4556 def __unicode__(self):
4557 return u'<UserBranchPermission(%s => %r)>' % (
4557 return u'<UserBranchPermission(%s => %r)>' % (
4558 self.user_group_repo_to_perm, self.branch_pattern)
4558 self.user_group_repo_to_perm, self.branch_pattern)
4559
4559
4560
4560
4561 class DbMigrateVersion(Base, BaseModel):
4561 class DbMigrateVersion(Base, BaseModel):
4562 __tablename__ = 'db_migrate_version'
4562 __tablename__ = 'db_migrate_version'
4563 __table_args__ = (
4563 __table_args__ = (
4564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4565 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4565 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4566 )
4566 )
4567 repository_id = Column('repository_id', String(250), primary_key=True)
4567 repository_id = Column('repository_id', String(250), primary_key=True)
4568 repository_path = Column('repository_path', Text)
4568 repository_path = Column('repository_path', Text)
4569 version = Column('version', Integer)
4569 version = Column('version', Integer)
4570
4570
4571
4571
4572 class DbSession(Base, BaseModel):
4572 class DbSession(Base, BaseModel):
4573 __tablename__ = 'db_session'
4573 __tablename__ = 'db_session'
4574 __table_args__ = (
4574 __table_args__ = (
4575 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4575 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4576 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4576 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4577 )
4577 )
4578
4578
4579 def __repr__(self):
4579 def __repr__(self):
4580 return '<DB:DbSession({})>'.format(self.id)
4580 return '<DB:DbSession({})>'.format(self.id)
4581
4581
4582 id = Column('id', Integer())
4582 id = Column('id', Integer())
4583 namespace = Column('namespace', String(255), primary_key=True)
4583 namespace = Column('namespace', String(255), primary_key=True)
4584 accessed = Column('accessed', DateTime, nullable=False)
4584 accessed = Column('accessed', DateTime, nullable=False)
4585 created = Column('created', DateTime, nullable=False)
4585 created = Column('created', DateTime, nullable=False)
4586 data = Column('data', PickleType, nullable=False)
4586 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
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
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
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
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