##// END OF EJS Templates
model: trivial typo fixes
Thomas De Schampheleire -
r4919:494b04a8 default
parent child Browse files
Show More
@@ -1,189 +1,189 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 kallithea.model.changeset_status
3 kallithea.model.changeset_status
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Changeset status conttroller
6 Changeset status controller
7
7
8 This file was forked by the Kallithea project in July 2014.
8 This file was forked by the Kallithea project in July 2014.
9 Original author and date, and relevant copyright and licensing information is below:
9 Original author and date, and relevant copyright and licensing information is below:
10 :created_on: Apr 30, 2012
10 :created_on: Apr 30, 2012
11 :author: marcink
11 :author: marcink
12 :copyright: (c) 2013 RhodeCode GmbH, and others.
12 :copyright: (c) 2013 RhodeCode GmbH, and others.
13 :license: GPLv3, see LICENSE.md for more details.
13 :license: GPLv3, see LICENSE.md for more details.
14 """
14 """
15 # This program is free software: you can redistribute it and/or modify
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
18 # (at your option) any later version.
19 #
19 #
20 # This program is distributed in the hope that it will be useful,
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
23 # GNU General Public License for more details.
24 #
24 #
25 # You should have received a copy of the GNU General Public License
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27
27
28
28
29 import logging
29 import logging
30 from collections import defaultdict
30 from collections import defaultdict
31 from sqlalchemy.orm import joinedload
31 from sqlalchemy.orm import joinedload
32
32
33 from kallithea.model import BaseModel
33 from kallithea.model import BaseModel
34 from kallithea.model.db import ChangesetStatus, PullRequest
34 from kallithea.model.db import ChangesetStatus, PullRequest
35 from kallithea.lib.exceptions import StatusChangeOnClosedPullRequestError
35 from kallithea.lib.exceptions import StatusChangeOnClosedPullRequestError
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 class ChangesetStatusModel(BaseModel):
40 class ChangesetStatusModel(BaseModel):
41
41
42 cls = ChangesetStatus
42 cls = ChangesetStatus
43
43
44 def __get_changeset_status(self, changeset_status):
44 def __get_changeset_status(self, changeset_status):
45 return self._get_instance(ChangesetStatus, changeset_status)
45 return self._get_instance(ChangesetStatus, changeset_status)
46
46
47 def __get_pull_request(self, pull_request):
47 def __get_pull_request(self, pull_request):
48 return self._get_instance(PullRequest, pull_request)
48 return self._get_instance(PullRequest, pull_request)
49
49
50 def _get_status_query(self, repo, revision, pull_request,
50 def _get_status_query(self, repo, revision, pull_request,
51 with_revisions=False):
51 with_revisions=False):
52 repo = self._get_repo(repo)
52 repo = self._get_repo(repo)
53
53
54 q = ChangesetStatus.query()\
54 q = ChangesetStatus.query()\
55 .filter(ChangesetStatus.repo == repo)
55 .filter(ChangesetStatus.repo == repo)
56 if not with_revisions:
56 if not with_revisions:
57 q = q.filter(ChangesetStatus.version == 0)
57 q = q.filter(ChangesetStatus.version == 0)
58
58
59 if revision:
59 if revision:
60 q = q.filter(ChangesetStatus.revision == revision)
60 q = q.filter(ChangesetStatus.revision == revision)
61 elif pull_request:
61 elif pull_request:
62 pull_request = self.__get_pull_request(pull_request)
62 pull_request = self.__get_pull_request(pull_request)
63 q = q.filter(ChangesetStatus.pull_request == pull_request)
63 q = q.filter(ChangesetStatus.pull_request == pull_request)
64 else:
64 else:
65 raise Exception('Please specify revision or pull_request')
65 raise Exception('Please specify revision or pull_request')
66 q = q.order_by(ChangesetStatus.version.asc())
66 q = q.order_by(ChangesetStatus.version.asc())
67 return q
67 return q
68
68
69 def calculate_pull_request_result(self, pull_request):
69 def calculate_pull_request_result(self, pull_request):
70 """
70 """
71 Policy: approve if consensus. Only approve and reject counts as valid votes.
71 Policy: approve if consensus. Only approve and reject counts as valid votes.
72 """
72 """
73
73
74 # collect latest votes from all voters
74 # collect latest votes from all voters
75 cs_statuses = dict()
75 cs_statuses = dict()
76 for st in reversed(self.get_statuses(pull_request.org_repo,
76 for st in reversed(self.get_statuses(pull_request.org_repo,
77 pull_request=pull_request,
77 pull_request=pull_request,
78 with_revisions=True)):
78 with_revisions=True)):
79 cs_statuses[st.author.username] = st
79 cs_statuses[st.author.username] = st
80 # collect votes from official reviewers
80 # collect votes from official reviewers
81 pull_request_reviewers = []
81 pull_request_reviewers = []
82 pull_request_pending_reviewers = []
82 pull_request_pending_reviewers = []
83 approved_votes = 0
83 approved_votes = 0
84 for r in pull_request.reviewers:
84 for r in pull_request.reviewers:
85 st = cs_statuses.get(r.user.username)
85 st = cs_statuses.get(r.user.username)
86 if st and st.status == ChangesetStatus.STATUS_APPROVED:
86 if st and st.status == ChangesetStatus.STATUS_APPROVED:
87 approved_votes += 1
87 approved_votes += 1
88 if not st or st.status in (ChangesetStatus.STATUS_NOT_REVIEWED,
88 if not st or st.status in (ChangesetStatus.STATUS_NOT_REVIEWED,
89 ChangesetStatus.STATUS_UNDER_REVIEW):
89 ChangesetStatus.STATUS_UNDER_REVIEW):
90 st = None
90 st = None
91 pull_request_pending_reviewers.append(r.user)
91 pull_request_pending_reviewers.append(r.user)
92 pull_request_reviewers.append((r.user, st))
92 pull_request_reviewers.append((r.user, st))
93
93
94 # calculate result
94 # calculate result
95 result = ChangesetStatus.STATUS_UNDER_REVIEW
95 result = ChangesetStatus.STATUS_UNDER_REVIEW
96 if approved_votes and approved_votes == len(pull_request.reviewers):
96 if approved_votes and approved_votes == len(pull_request.reviewers):
97 result = ChangesetStatus.STATUS_APPROVED
97 result = ChangesetStatus.STATUS_APPROVED
98
98
99 return (pull_request_reviewers,
99 return (pull_request_reviewers,
100 pull_request_pending_reviewers,
100 pull_request_pending_reviewers,
101 result)
101 result)
102
102
103 def get_statuses(self, repo, revision=None, pull_request=None,
103 def get_statuses(self, repo, revision=None, pull_request=None,
104 with_revisions=False):
104 with_revisions=False):
105 q = self._get_status_query(repo, revision, pull_request,
105 q = self._get_status_query(repo, revision, pull_request,
106 with_revisions)
106 with_revisions)
107 q = q.options(joinedload('author'))
107 q = q.options(joinedload('author'))
108 return q.all()
108 return q.all()
109
109
110 def get_status(self, repo, revision=None, pull_request=None, as_str=True):
110 def get_status(self, repo, revision=None, pull_request=None, as_str=True):
111 """
111 """
112 Returns latest status of changeset for given revision or for given
112 Returns latest status of changeset for given revision or for given
113 pull request. Statuses are versioned inside a table itself and
113 pull request. Statuses are versioned inside a table itself and
114 version == 0 is always the current one
114 version == 0 is always the current one
115
115
116 :param repo:
116 :param repo:
117 :param revision: 40char hash or None
117 :param revision: 40char hash or None
118 :param pull_request: pull_request reference
118 :param pull_request: pull_request reference
119 :param as_str: return status as string not object
119 :param as_str: return status as string not object
120 """
120 """
121 q = self._get_status_query(repo, revision, pull_request)
121 q = self._get_status_query(repo, revision, pull_request)
122
122
123 # need to use first here since there can be multiple statuses
123 # need to use first here since there can be multiple statuses
124 # returned from pull_request
124 # returned from pull_request
125 status = q.first()
125 status = q.first()
126 if as_str:
126 if as_str:
127 return str(status.status) if status else ChangesetStatus.DEFAULT
127 return str(status.status) if status else ChangesetStatus.DEFAULT
128 return status
128 return status
129
129
130 def set_status(self, repo, status, user, comment, revision=None,
130 def set_status(self, repo, status, user, comment, revision=None,
131 pull_request=None, dont_allow_on_closed_pull_request=False):
131 pull_request=None, dont_allow_on_closed_pull_request=False):
132 """
132 """
133 Creates new status for changeset or updates the old ones bumping their
133 Creates new status for changeset or updates the old ones bumping their
134 version, leaving the current status at the value of 'status'.
134 version, leaving the current status at the value of 'status'.
135
135
136 :param repo:
136 :param repo:
137 :param status:
137 :param status:
138 :param user:
138 :param user:
139 :param comment:
139 :param comment:
140 :param revision:
140 :param revision:
141 :param pull_request:
141 :param pull_request:
142 :param dont_allow_on_closed_pull_request: don't allow a status change
142 :param dont_allow_on_closed_pull_request: don't allow a status change
143 if last status was for pull request and it's closed. We shouldn't
143 if last status was for pull request and it's closed. We shouldn't
144 mess around this manually
144 mess around this manually
145 """
145 """
146 repo = self._get_repo(repo)
146 repo = self._get_repo(repo)
147
147
148 q = ChangesetStatus.query()
148 q = ChangesetStatus.query()
149 if revision is not None:
149 if revision is not None:
150 assert pull_request is None
150 assert pull_request is None
151 q = q.filter(ChangesetStatus.repo == repo)
151 q = q.filter(ChangesetStatus.repo == repo)
152 q = q.filter(ChangesetStatus.revision == revision)
152 q = q.filter(ChangesetStatus.revision == revision)
153 revisions = [revision]
153 revisions = [revision]
154 else:
154 else:
155 assert pull_request is not None
155 assert pull_request is not None
156 pull_request = self.__get_pull_request(pull_request)
156 pull_request = self.__get_pull_request(pull_request)
157 repo = pull_request.org_repo
157 repo = pull_request.org_repo
158 q = q.filter(ChangesetStatus.repo == repo)
158 q = q.filter(ChangesetStatus.repo == repo)
159 q = q.filter(ChangesetStatus.revision.in_(pull_request.revisions))
159 q = q.filter(ChangesetStatus.revision.in_(pull_request.revisions))
160 revisions = pull_request.revisions
160 revisions = pull_request.revisions
161 cur_statuses = q.all()
161 cur_statuses = q.all()
162
162
163 #if statuses exists and last is associated with a closed pull request
163 #if statuses exists and last is associated with a closed pull request
164 # we need to check if we can allow this status change
164 # we need to check if we can allow this status change
165 if (dont_allow_on_closed_pull_request and cur_statuses
165 if (dont_allow_on_closed_pull_request and cur_statuses
166 and getattr(cur_statuses[0].pull_request, 'status', '')
166 and getattr(cur_statuses[0].pull_request, 'status', '')
167 == PullRequest.STATUS_CLOSED):
167 == PullRequest.STATUS_CLOSED):
168 raise StatusChangeOnClosedPullRequestError(
168 raise StatusChangeOnClosedPullRequestError(
169 'Changing status on closed pull request is not allowed'
169 'Changing status on closed pull request is not allowed'
170 )
170 )
171
171
172 #update all current statuses with older version
172 #update all current statuses with older version
173 for st in cur_statuses:
173 for st in cur_statuses:
174 st.version += 1
174 st.version += 1
175 self.sa.add(st)
175 self.sa.add(st)
176
176
177 new_statuses = []
177 new_statuses = []
178 for rev in revisions:
178 for rev in revisions:
179 new_status = ChangesetStatus()
179 new_status = ChangesetStatus()
180 new_status.version = 0 # default
180 new_status.version = 0 # default
181 new_status.author = self._get_user(user)
181 new_status.author = self._get_user(user)
182 new_status.repo = self._get_repo(repo)
182 new_status.repo = self._get_repo(repo)
183 new_status.status = status
183 new_status.status = status
184 new_status.comment = comment
184 new_status.comment = comment
185 new_status.revision = rev
185 new_status.revision = rev
186 new_status.pull_request = pull_request
186 new_status.pull_request = pull_request
187 new_statuses.append(new_status)
187 new_statuses.append(new_status)
188 self.sa.add(new_status)
188 self.sa.add(new_status)
189 return new_statuses
189 return new_statuses
@@ -1,2521 +1,2521 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.db
15 kallithea.model.db
16 ~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~
17
17
18 Database Models for Kallithea
18 Database Models for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 08, 2010
22 :created_on: Apr 08, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import os
28 import os
29 import time
29 import time
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import traceback
32 import traceback
33 import hashlib
33 import hashlib
34 import collections
34 import collections
35 import functools
35 import functools
36
36
37 from sqlalchemy import *
37 from sqlalchemy import *
38 from sqlalchemy.ext.hybrid import hybrid_property
38 from sqlalchemy.ext.hybrid import hybrid_property
39 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
39 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
40 from beaker.cache import cache_region, region_invalidate
40 from beaker.cache import cache_region, region_invalidate
41 from webob.exc import HTTPNotFound
41 from webob.exc import HTTPNotFound
42
42
43 from pylons.i18n.translation import lazy_ugettext as _
43 from pylons.i18n.translation import lazy_ugettext as _
44
44
45 from kallithea import DB_PREFIX
45 from kallithea import DB_PREFIX
46 from kallithea.lib.vcs import get_backend
46 from kallithea.lib.vcs import get_backend
47 from kallithea.lib.vcs.utils.helpers import get_scm
47 from kallithea.lib.vcs.utils.helpers import get_scm
48 from kallithea.lib.vcs.exceptions import VCSError
48 from kallithea.lib.vcs.exceptions import VCSError
49 from kallithea.lib.vcs.utils.lazy import LazyProperty
49 from kallithea.lib.vcs.utils.lazy import LazyProperty
50 from kallithea.lib.vcs.backends.base import EmptyChangeset
50 from kallithea.lib.vcs.backends.base import EmptyChangeset
51
51
52 from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
52 from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
53 safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \
53 safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \
54 get_clone_url, urlreadable
54 get_clone_url, urlreadable
55 from kallithea.lib.compat import json
55 from kallithea.lib.compat import json
56 from kallithea.lib.caching_query import FromCache
56 from kallithea.lib.caching_query import FromCache
57
57
58 from kallithea.model.meta import Base, Session
58 from kallithea.model.meta import Base, Session
59
59
60 URL_SEP = '/'
60 URL_SEP = '/'
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63 #==============================================================================
63 #==============================================================================
64 # BASE CLASSES
64 # BASE CLASSES
65 #==============================================================================
65 #==============================================================================
66
66
67 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
67 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
68
68
69
69
70 class BaseModel(object):
70 class BaseModel(object):
71 """
71 """
72 Base Model for all classess
72 Base Model for all classes
73 """
73 """
74
74
75 @classmethod
75 @classmethod
76 def _get_keys(cls):
76 def _get_keys(cls):
77 """return column names for this model """
77 """return column names for this model """
78 return class_mapper(cls).c.keys()
78 return class_mapper(cls).c.keys()
79
79
80 def get_dict(self):
80 def get_dict(self):
81 """
81 """
82 return dict with keys and values corresponding
82 return dict with keys and values corresponding
83 to this model data """
83 to this model data """
84
84
85 d = {}
85 d = {}
86 for k in self._get_keys():
86 for k in self._get_keys():
87 d[k] = getattr(self, k)
87 d[k] = getattr(self, k)
88
88
89 # also use __json__() if present to get additional fields
89 # also use __json__() if present to get additional fields
90 _json_attr = getattr(self, '__json__', None)
90 _json_attr = getattr(self, '__json__', None)
91 if _json_attr:
91 if _json_attr:
92 # update with attributes from __json__
92 # update with attributes from __json__
93 if callable(_json_attr):
93 if callable(_json_attr):
94 _json_attr = _json_attr()
94 _json_attr = _json_attr()
95 for k, val in _json_attr.iteritems():
95 for k, val in _json_attr.iteritems():
96 d[k] = val
96 d[k] = val
97 return d
97 return d
98
98
99 def get_appstruct(self):
99 def get_appstruct(self):
100 """return list with keys and values tupples corresponding
100 """return list with keys and values tuples corresponding
101 to this model data """
101 to this model data """
102
102
103 l = []
103 l = []
104 for k in self._get_keys():
104 for k in self._get_keys():
105 l.append((k, getattr(self, k),))
105 l.append((k, getattr(self, k),))
106 return l
106 return l
107
107
108 def populate_obj(self, populate_dict):
108 def populate_obj(self, populate_dict):
109 """populate model with data from given populate_dict"""
109 """populate model with data from given populate_dict"""
110
110
111 for k in self._get_keys():
111 for k in self._get_keys():
112 if k in populate_dict:
112 if k in populate_dict:
113 setattr(self, k, populate_dict[k])
113 setattr(self, k, populate_dict[k])
114
114
115 @classmethod
115 @classmethod
116 def query(cls):
116 def query(cls):
117 return Session().query(cls)
117 return Session().query(cls)
118
118
119 @classmethod
119 @classmethod
120 def get(cls, id_):
120 def get(cls, id_):
121 if id_:
121 if id_:
122 return cls.query().get(id_)
122 return cls.query().get(id_)
123
123
124 @classmethod
124 @classmethod
125 def get_or_404(cls, id_):
125 def get_or_404(cls, id_):
126 try:
126 try:
127 id_ = int(id_)
127 id_ = int(id_)
128 except (TypeError, ValueError):
128 except (TypeError, ValueError):
129 raise HTTPNotFound
129 raise HTTPNotFound
130
130
131 res = cls.query().get(id_)
131 res = cls.query().get(id_)
132 if not res:
132 if not res:
133 raise HTTPNotFound
133 raise HTTPNotFound
134 return res
134 return res
135
135
136 @classmethod
136 @classmethod
137 def getAll(cls):
137 def getAll(cls):
138 # deprecated and left for backward compatibility
138 # deprecated and left for backward compatibility
139 return cls.get_all()
139 return cls.get_all()
140
140
141 @classmethod
141 @classmethod
142 def get_all(cls):
142 def get_all(cls):
143 return cls.query().all()
143 return cls.query().all()
144
144
145 @classmethod
145 @classmethod
146 def delete(cls, id_):
146 def delete(cls, id_):
147 obj = cls.query().get(id_)
147 obj = cls.query().get(id_)
148 Session().delete(obj)
148 Session().delete(obj)
149
149
150 def __repr__(self):
150 def __repr__(self):
151 if hasattr(self, '__unicode__'):
151 if hasattr(self, '__unicode__'):
152 # python repr needs to return str
152 # python repr needs to return str
153 try:
153 try:
154 return safe_str(self.__unicode__())
154 return safe_str(self.__unicode__())
155 except UnicodeDecodeError:
155 except UnicodeDecodeError:
156 pass
156 pass
157 return '<DB:%s>' % (self.__class__.__name__)
157 return '<DB:%s>' % (self.__class__.__name__)
158
158
159
159
160 class Setting(Base, BaseModel):
160 class Setting(Base, BaseModel):
161 __tablename__ = DB_PREFIX + 'settings'
161 __tablename__ = DB_PREFIX + 'settings'
162
162
163 __table_args__ = (
163 __table_args__ = (
164 UniqueConstraint('app_settings_name'),
164 UniqueConstraint('app_settings_name'),
165 {'extend_existing': True, 'mysql_engine': 'InnoDB',
165 {'extend_existing': True, 'mysql_engine': 'InnoDB',
166 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
166 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
167 )
167 )
168
168
169 SETTINGS_TYPES = {
169 SETTINGS_TYPES = {
170 'str': safe_str,
170 'str': safe_str,
171 'int': safe_int,
171 'int': safe_int,
172 'unicode': safe_unicode,
172 'unicode': safe_unicode,
173 'bool': str2bool,
173 'bool': str2bool,
174 'list': functools.partial(aslist, sep=',')
174 'list': functools.partial(aslist, sep=',')
175 }
175 }
176 DEFAULT_UPDATE_URL = ''
176 DEFAULT_UPDATE_URL = ''
177
177
178 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
178 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
179 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
179 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
180 _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None)
180 _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None)
181 _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
181 _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
182
182
183 def __init__(self, key='', val='', type='unicode'):
183 def __init__(self, key='', val='', type='unicode'):
184 self.app_settings_name = key
184 self.app_settings_name = key
185 self.app_settings_value = val
185 self.app_settings_value = val
186 self.app_settings_type = type
186 self.app_settings_type = type
187
187
188 @validates('_app_settings_value')
188 @validates('_app_settings_value')
189 def validate_settings_value(self, key, val):
189 def validate_settings_value(self, key, val):
190 assert type(val) == unicode
190 assert type(val) == unicode
191 return val
191 return val
192
192
193 @hybrid_property
193 @hybrid_property
194 def app_settings_value(self):
194 def app_settings_value(self):
195 v = self._app_settings_value
195 v = self._app_settings_value
196 _type = self.app_settings_type
196 _type = self.app_settings_type
197 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
197 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
198 return converter(v)
198 return converter(v)
199
199
200 @app_settings_value.setter
200 @app_settings_value.setter
201 def app_settings_value(self, val):
201 def app_settings_value(self, val):
202 """
202 """
203 Setter that will always make sure we use unicode in app_settings_value
203 Setter that will always make sure we use unicode in app_settings_value
204
204
205 :param val:
205 :param val:
206 """
206 """
207 self._app_settings_value = safe_unicode(val)
207 self._app_settings_value = safe_unicode(val)
208
208
209 @hybrid_property
209 @hybrid_property
210 def app_settings_type(self):
210 def app_settings_type(self):
211 return self._app_settings_type
211 return self._app_settings_type
212
212
213 @app_settings_type.setter
213 @app_settings_type.setter
214 def app_settings_type(self, val):
214 def app_settings_type(self, val):
215 if val not in self.SETTINGS_TYPES:
215 if val not in self.SETTINGS_TYPES:
216 raise Exception('type must be one of %s got %s'
216 raise Exception('type must be one of %s got %s'
217 % (self.SETTINGS_TYPES.keys(), val))
217 % (self.SETTINGS_TYPES.keys(), val))
218 self._app_settings_type = val
218 self._app_settings_type = val
219
219
220 def __unicode__(self):
220 def __unicode__(self):
221 return u"<%s('%s:%s[%s]')>" % (
221 return u"<%s('%s:%s[%s]')>" % (
222 self.__class__.__name__,
222 self.__class__.__name__,
223 self.app_settings_name, self.app_settings_value, self.app_settings_type
223 self.app_settings_name, self.app_settings_value, self.app_settings_type
224 )
224 )
225
225
226 @classmethod
226 @classmethod
227 def get_by_name(cls, key):
227 def get_by_name(cls, key):
228 return cls.query()\
228 return cls.query()\
229 .filter(cls.app_settings_name == key).scalar()
229 .filter(cls.app_settings_name == key).scalar()
230
230
231 @classmethod
231 @classmethod
232 def get_by_name_or_create(cls, key, val='', type='unicode'):
232 def get_by_name_or_create(cls, key, val='', type='unicode'):
233 res = cls.get_by_name(key)
233 res = cls.get_by_name(key)
234 if not res:
234 if not res:
235 res = cls(key, val, type)
235 res = cls(key, val, type)
236 return res
236 return res
237
237
238 @classmethod
238 @classmethod
239 def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
239 def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
240 """
240 """
241 Creates or updates Kallithea setting. If updates are triggered, it will only
241 Creates or updates Kallithea setting. If updates are triggered, it will only
242 update parameters that are explicitly set. Optional instance will be skipped.
242 update parameters that are explicitly set. Optional instance will be skipped.
243
243
244 :param key:
244 :param key:
245 :param val:
245 :param val:
246 :param type:
246 :param type:
247 :return:
247 :return:
248 """
248 """
249 res = cls.get_by_name(key)
249 res = cls.get_by_name(key)
250 if not res:
250 if not res:
251 val = Optional.extract(val)
251 val = Optional.extract(val)
252 type = Optional.extract(type)
252 type = Optional.extract(type)
253 res = cls(key, val, type)
253 res = cls(key, val, type)
254 else:
254 else:
255 res.app_settings_name = key
255 res.app_settings_name = key
256 if not isinstance(val, Optional):
256 if not isinstance(val, Optional):
257 # update if set
257 # update if set
258 res.app_settings_value = val
258 res.app_settings_value = val
259 if not isinstance(type, Optional):
259 if not isinstance(type, Optional):
260 # update if set
260 # update if set
261 res.app_settings_type = type
261 res.app_settings_type = type
262 return res
262 return res
263
263
264 @classmethod
264 @classmethod
265 def get_app_settings(cls, cache=False):
265 def get_app_settings(cls, cache=False):
266
266
267 ret = cls.query()
267 ret = cls.query()
268
268
269 if cache:
269 if cache:
270 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
270 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
271
271
272 if not ret:
272 if not ret:
273 raise Exception('Could not get application settings !')
273 raise Exception('Could not get application settings !')
274 settings = {}
274 settings = {}
275 for each in ret:
275 for each in ret:
276 settings[each.app_settings_name] = \
276 settings[each.app_settings_name] = \
277 each.app_settings_value
277 each.app_settings_value
278
278
279 return settings
279 return settings
280
280
281 @classmethod
281 @classmethod
282 def get_auth_plugins(cls, cache=False):
282 def get_auth_plugins(cls, cache=False):
283 auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
283 auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
284 return auth_plugins
284 return auth_plugins
285
285
286 @classmethod
286 @classmethod
287 def get_auth_settings(cls, cache=False):
287 def get_auth_settings(cls, cache=False):
288 ret = cls.query()\
288 ret = cls.query()\
289 .filter(cls.app_settings_name.startswith('auth_')).all()
289 .filter(cls.app_settings_name.startswith('auth_')).all()
290 fd = {}
290 fd = {}
291 for row in ret:
291 for row in ret:
292 fd[row.app_settings_name] = row.app_settings_value
292 fd[row.app_settings_name] = row.app_settings_value
293 return fd
293 return fd
294
294
295 @classmethod
295 @classmethod
296 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
296 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
297 ret = cls.query()\
297 ret = cls.query()\
298 .filter(cls.app_settings_name.startswith('default_')).all()
298 .filter(cls.app_settings_name.startswith('default_')).all()
299 fd = {}
299 fd = {}
300 for row in ret:
300 for row in ret:
301 key = row.app_settings_name
301 key = row.app_settings_name
302 if strip_prefix:
302 if strip_prefix:
303 key = remove_prefix(key, prefix='default_')
303 key = remove_prefix(key, prefix='default_')
304 fd.update({key: row.app_settings_value})
304 fd.update({key: row.app_settings_value})
305
305
306 return fd
306 return fd
307
307
308 @classmethod
308 @classmethod
309 def get_server_info(cls):
309 def get_server_info(cls):
310 import pkg_resources
310 import pkg_resources
311 import platform
311 import platform
312 import kallithea
312 import kallithea
313 from kallithea.lib.utils import check_git_version
313 from kallithea.lib.utils import check_git_version
314 mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
314 mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
315 info = {
315 info = {
316 'modules': sorted(mods, key=lambda k: k[0].lower()),
316 'modules': sorted(mods, key=lambda k: k[0].lower()),
317 'py_version': platform.python_version(),
317 'py_version': platform.python_version(),
318 'platform': safe_unicode(platform.platform()),
318 'platform': safe_unicode(platform.platform()),
319 'kallithea_version': kallithea.__version__,
319 'kallithea_version': kallithea.__version__,
320 'git_version': safe_unicode(check_git_version()),
320 'git_version': safe_unicode(check_git_version()),
321 'git_path': kallithea.CONFIG.get('git_path')
321 'git_path': kallithea.CONFIG.get('git_path')
322 }
322 }
323 return info
323 return info
324
324
325
325
326 class Ui(Base, BaseModel):
326 class Ui(Base, BaseModel):
327 __tablename__ = DB_PREFIX + 'ui'
327 __tablename__ = DB_PREFIX + 'ui'
328 __table_args__ = (
328 __table_args__ = (
329 UniqueConstraint('ui_key'),
329 UniqueConstraint('ui_key'),
330 {'extend_existing': True, 'mysql_engine': 'InnoDB',
330 {'extend_existing': True, 'mysql_engine': 'InnoDB',
331 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
331 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
332 )
332 )
333
333
334 HOOK_UPDATE = 'changegroup.update'
334 HOOK_UPDATE = 'changegroup.update'
335 HOOK_REPO_SIZE = 'changegroup.repo_size'
335 HOOK_REPO_SIZE = 'changegroup.repo_size'
336 HOOK_PUSH = 'changegroup.push_logger'
336 HOOK_PUSH = 'changegroup.push_logger'
337 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
337 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
338 HOOK_PULL = 'outgoing.pull_logger'
338 HOOK_PULL = 'outgoing.pull_logger'
339 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
339 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
340
340
341 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
341 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
342 ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
342 ui_section = Column("ui_section", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
343 ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
343 ui_key = Column("ui_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
344 ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
344 ui_value = Column("ui_value", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
345 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
345 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
346
346
347 # def __init__(self, section='', key='', value=''):
347 # def __init__(self, section='', key='', value=''):
348 # self.ui_section = section
348 # self.ui_section = section
349 # self.ui_key = key
349 # self.ui_key = key
350 # self.ui_value = value
350 # self.ui_value = value
351
351
352 @classmethod
352 @classmethod
353 def get_by_key(cls, key):
353 def get_by_key(cls, key):
354 return cls.query().filter(cls.ui_key == key).scalar()
354 return cls.query().filter(cls.ui_key == key).scalar()
355
355
356 @classmethod
356 @classmethod
357 def get_builtin_hooks(cls):
357 def get_builtin_hooks(cls):
358 q = cls.query()
358 q = cls.query()
359 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
359 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
360 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
360 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
361 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
361 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
362 return q.all()
362 return q.all()
363
363
364 @classmethod
364 @classmethod
365 def get_custom_hooks(cls):
365 def get_custom_hooks(cls):
366 q = cls.query()
366 q = cls.query()
367 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
367 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
368 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
368 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
369 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
369 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
370 q = q.filter(cls.ui_section == 'hooks')
370 q = q.filter(cls.ui_section == 'hooks')
371 return q.all()
371 return q.all()
372
372
373 @classmethod
373 @classmethod
374 def get_repos_location(cls):
374 def get_repos_location(cls):
375 return cls.get_by_key('/').ui_value
375 return cls.get_by_key('/').ui_value
376
376
377 @classmethod
377 @classmethod
378 def create_or_update_hook(cls, key, val):
378 def create_or_update_hook(cls, key, val):
379 new_ui = cls.get_by_key(key) or cls()
379 new_ui = cls.get_by_key(key) or cls()
380 new_ui.ui_section = 'hooks'
380 new_ui.ui_section = 'hooks'
381 new_ui.ui_active = True
381 new_ui.ui_active = True
382 new_ui.ui_key = key
382 new_ui.ui_key = key
383 new_ui.ui_value = val
383 new_ui.ui_value = val
384
384
385 Session().add(new_ui)
385 Session().add(new_ui)
386
386
387 def __repr__(self):
387 def __repr__(self):
388 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
388 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
389 self.ui_key, self.ui_value)
389 self.ui_key, self.ui_value)
390
390
391
391
392 class User(Base, BaseModel):
392 class User(Base, BaseModel):
393 __tablename__ = 'users'
393 __tablename__ = 'users'
394 __table_args__ = (
394 __table_args__ = (
395 UniqueConstraint('username'), UniqueConstraint('email'),
395 UniqueConstraint('username'), UniqueConstraint('email'),
396 Index('u_username_idx', 'username'),
396 Index('u_username_idx', 'username'),
397 Index('u_email_idx', 'email'),
397 Index('u_email_idx', 'email'),
398 {'extend_existing': True, 'mysql_engine': 'InnoDB',
398 {'extend_existing': True, 'mysql_engine': 'InnoDB',
399 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
399 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
400 )
400 )
401 DEFAULT_USER = 'default'
401 DEFAULT_USER = 'default'
402 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
402 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
403
403
404 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
404 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
405 username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
405 username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
406 password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
406 password = Column("password", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
407 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
407 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
408 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
408 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
409 name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
409 name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
410 lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
410 lastname = Column("lastname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
411 _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
411 _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
412 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
412 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
413 extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
413 extern_type = Column("extern_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
414 extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
414 extern_name = Column("extern_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
415 api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
415 api_key = Column("api_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
416 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
416 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
417 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
417 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
418 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
418 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
419
419
420 user_log = relationship('UserLog')
420 user_log = relationship('UserLog')
421 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
421 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
422
422
423 repositories = relationship('Repository')
423 repositories = relationship('Repository')
424 repo_groups = relationship('RepoGroup')
424 repo_groups = relationship('RepoGroup')
425 user_groups = relationship('UserGroup')
425 user_groups = relationship('UserGroup')
426 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
426 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
427 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
427 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
428
428
429 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
429 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
430 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
430 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
431
431
432 group_member = relationship('UserGroupMember', cascade='all')
432 group_member = relationship('UserGroupMember', cascade='all')
433
433
434 notifications = relationship('UserNotification', cascade='all')
434 notifications = relationship('UserNotification', cascade='all')
435 # notifications assigned to this user
435 # notifications assigned to this user
436 user_created_notifications = relationship('Notification', cascade='all')
436 user_created_notifications = relationship('Notification', cascade='all')
437 # comments created by this user
437 # comments created by this user
438 user_comments = relationship('ChangesetComment', cascade='all')
438 user_comments = relationship('ChangesetComment', cascade='all')
439 #extra emails for this user
439 #extra emails for this user
440 user_emails = relationship('UserEmailMap', cascade='all')
440 user_emails = relationship('UserEmailMap', cascade='all')
441 #extra api keys
441 #extra api keys
442 user_api_keys = relationship('UserApiKeys', cascade='all')
442 user_api_keys = relationship('UserApiKeys', cascade='all')
443
443
444
444
445 @hybrid_property
445 @hybrid_property
446 def email(self):
446 def email(self):
447 return self._email
447 return self._email
448
448
449 @email.setter
449 @email.setter
450 def email(self, val):
450 def email(self, val):
451 self._email = val.lower() if val else None
451 self._email = val.lower() if val else None
452
452
453 @property
453 @property
454 def firstname(self):
454 def firstname(self):
455 # alias for future
455 # alias for future
456 return self.name
456 return self.name
457
457
458 @property
458 @property
459 def emails(self):
459 def emails(self):
460 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
460 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
461 return [self.email] + [x.email for x in other]
461 return [self.email] + [x.email for x in other]
462
462
463 @property
463 @property
464 def api_keys(self):
464 def api_keys(self):
465 other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
465 other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
466 return [self.api_key] + [x.api_key for x in other]
466 return [self.api_key] + [x.api_key for x in other]
467
467
468 @property
468 @property
469 def ip_addresses(self):
469 def ip_addresses(self):
470 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
470 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
471 return [x.ip_addr for x in ret]
471 return [x.ip_addr for x in ret]
472
472
473 @property
473 @property
474 def username_and_name(self):
474 def username_and_name(self):
475 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
475 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
476
476
477 @property
477 @property
478 def full_name(self):
478 def full_name(self):
479 return '%s %s' % (self.firstname, self.lastname)
479 return '%s %s' % (self.firstname, self.lastname)
480
480
481 @property
481 @property
482 def full_name_or_username(self):
482 def full_name_or_username(self):
483 return ('%s %s' % (self.firstname, self.lastname)
483 return ('%s %s' % (self.firstname, self.lastname)
484 if (self.firstname and self.lastname) else self.username)
484 if (self.firstname and self.lastname) else self.username)
485
485
486 @property
486 @property
487 def full_contact(self):
487 def full_contact(self):
488 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
488 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
489
489
490 @property
490 @property
491 def short_contact(self):
491 def short_contact(self):
492 return '%s %s' % (self.firstname, self.lastname)
492 return '%s %s' % (self.firstname, self.lastname)
493
493
494 @property
494 @property
495 def is_admin(self):
495 def is_admin(self):
496 return self.admin
496 return self.admin
497
497
498 @property
498 @property
499 def AuthUser(self):
499 def AuthUser(self):
500 """
500 """
501 Returns instance of AuthUser for this user
501 Returns instance of AuthUser for this user
502 """
502 """
503 from kallithea.lib.auth import AuthUser
503 from kallithea.lib.auth import AuthUser
504 return AuthUser(user_id=self.user_id, api_key=self.api_key,
504 return AuthUser(user_id=self.user_id, api_key=self.api_key,
505 username=self.username)
505 username=self.username)
506
506
507 @hybrid_property
507 @hybrid_property
508 def user_data(self):
508 def user_data(self):
509 if not self._user_data:
509 if not self._user_data:
510 return {}
510 return {}
511
511
512 try:
512 try:
513 return json.loads(self._user_data)
513 return json.loads(self._user_data)
514 except TypeError:
514 except TypeError:
515 return {}
515 return {}
516
516
517 @user_data.setter
517 @user_data.setter
518 def user_data(self, val):
518 def user_data(self, val):
519 try:
519 try:
520 self._user_data = json.dumps(val)
520 self._user_data = json.dumps(val)
521 except Exception:
521 except Exception:
522 log.error(traceback.format_exc())
522 log.error(traceback.format_exc())
523
523
524 def __unicode__(self):
524 def __unicode__(self):
525 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
525 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
526 self.user_id, self.username)
526 self.user_id, self.username)
527
527
528 @classmethod
528 @classmethod
529 def get_by_username(cls, username, case_insensitive=False, cache=False):
529 def get_by_username(cls, username, case_insensitive=False, cache=False):
530 if case_insensitive:
530 if case_insensitive:
531 q = cls.query().filter(cls.username.ilike(username))
531 q = cls.query().filter(cls.username.ilike(username))
532 else:
532 else:
533 q = cls.query().filter(cls.username == username)
533 q = cls.query().filter(cls.username == username)
534
534
535 if cache:
535 if cache:
536 q = q.options(FromCache(
536 q = q.options(FromCache(
537 "sql_cache_short",
537 "sql_cache_short",
538 "get_user_%s" % _hash_key(username)
538 "get_user_%s" % _hash_key(username)
539 )
539 )
540 )
540 )
541 return q.scalar()
541 return q.scalar()
542
542
543 @classmethod
543 @classmethod
544 def get_by_api_key(cls, api_key, cache=False, fallback=True):
544 def get_by_api_key(cls, api_key, cache=False, fallback=True):
545 q = cls.query().filter(cls.api_key == api_key)
545 q = cls.query().filter(cls.api_key == api_key)
546
546
547 if cache:
547 if cache:
548 q = q.options(FromCache("sql_cache_short",
548 q = q.options(FromCache("sql_cache_short",
549 "get_api_key_%s" % api_key))
549 "get_api_key_%s" % api_key))
550 res = q.scalar()
550 res = q.scalar()
551
551
552 if fallback and not res:
552 if fallback and not res:
553 #fallback to additional keys
553 #fallback to additional keys
554 _res = UserApiKeys.query()\
554 _res = UserApiKeys.query()\
555 .filter(UserApiKeys.api_key == api_key)\
555 .filter(UserApiKeys.api_key == api_key)\
556 .filter(or_(UserApiKeys.expires == -1,
556 .filter(or_(UserApiKeys.expires == -1,
557 UserApiKeys.expires >= time.time()))\
557 UserApiKeys.expires >= time.time()))\
558 .first()
558 .first()
559 if _res:
559 if _res:
560 res = _res.user
560 res = _res.user
561 return res
561 return res
562
562
563 @classmethod
563 @classmethod
564 def get_by_email(cls, email, case_insensitive=False, cache=False):
564 def get_by_email(cls, email, case_insensitive=False, cache=False):
565 if case_insensitive:
565 if case_insensitive:
566 q = cls.query().filter(cls.email.ilike(email))
566 q = cls.query().filter(cls.email.ilike(email))
567 else:
567 else:
568 q = cls.query().filter(cls.email == email)
568 q = cls.query().filter(cls.email == email)
569
569
570 if cache:
570 if cache:
571 q = q.options(FromCache("sql_cache_short",
571 q = q.options(FromCache("sql_cache_short",
572 "get_email_key_%s" % email))
572 "get_email_key_%s" % email))
573
573
574 ret = q.scalar()
574 ret = q.scalar()
575 if ret is None:
575 if ret is None:
576 q = UserEmailMap.query()
576 q = UserEmailMap.query()
577 # try fetching in alternate email map
577 # try fetching in alternate email map
578 if case_insensitive:
578 if case_insensitive:
579 q = q.filter(UserEmailMap.email.ilike(email))
579 q = q.filter(UserEmailMap.email.ilike(email))
580 else:
580 else:
581 q = q.filter(UserEmailMap.email == email)
581 q = q.filter(UserEmailMap.email == email)
582 q = q.options(joinedload(UserEmailMap.user))
582 q = q.options(joinedload(UserEmailMap.user))
583 if cache:
583 if cache:
584 q = q.options(FromCache("sql_cache_short",
584 q = q.options(FromCache("sql_cache_short",
585 "get_email_map_key_%s" % email))
585 "get_email_map_key_%s" % email))
586 ret = getattr(q.scalar(), 'user', None)
586 ret = getattr(q.scalar(), 'user', None)
587
587
588 return ret
588 return ret
589
589
590 @classmethod
590 @classmethod
591 def get_from_cs_author(cls, author):
591 def get_from_cs_author(cls, author):
592 """
592 """
593 Tries to get User objects out of commit author string
593 Tries to get User objects out of commit author string
594
594
595 :param author:
595 :param author:
596 """
596 """
597 from kallithea.lib.helpers import email, author_name
597 from kallithea.lib.helpers import email, author_name
598 # Valid email in the attribute passed, see if they're in the system
598 # Valid email in the attribute passed, see if they're in the system
599 _email = email(author)
599 _email = email(author)
600 if _email:
600 if _email:
601 user = cls.get_by_email(_email, case_insensitive=True)
601 user = cls.get_by_email(_email, case_insensitive=True)
602 if user:
602 if user:
603 return user
603 return user
604 # Maybe we can match by username?
604 # Maybe we can match by username?
605 _author = author_name(author)
605 _author = author_name(author)
606 user = cls.get_by_username(_author, case_insensitive=True)
606 user = cls.get_by_username(_author, case_insensitive=True)
607 if user:
607 if user:
608 return user
608 return user
609
609
610 def update_lastlogin(self):
610 def update_lastlogin(self):
611 """Update user lastlogin"""
611 """Update user lastlogin"""
612 self.last_login = datetime.datetime.now()
612 self.last_login = datetime.datetime.now()
613 Session().add(self)
613 Session().add(self)
614 log.debug('updated user %s lastlogin' % self.username)
614 log.debug('updated user %s lastlogin' % self.username)
615
615
616 @classmethod
616 @classmethod
617 def get_first_admin(cls):
617 def get_first_admin(cls):
618 user = User.query().filter(User.admin == True).first()
618 user = User.query().filter(User.admin == True).first()
619 if user is None:
619 if user is None:
620 raise Exception('Missing administrative account!')
620 raise Exception('Missing administrative account!')
621 return user
621 return user
622
622
623 @classmethod
623 @classmethod
624 def get_default_user(cls, cache=False):
624 def get_default_user(cls, cache=False):
625 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
625 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
626 if user is None:
626 if user is None:
627 raise Exception('Missing default account!')
627 raise Exception('Missing default account!')
628 return user
628 return user
629
629
630 def get_api_data(self, details=False):
630 def get_api_data(self, details=False):
631 """
631 """
632 Common function for generating user related data for API
632 Common function for generating user related data for API
633 """
633 """
634 user = self
634 user = self
635 data = dict(
635 data = dict(
636 user_id=user.user_id,
636 user_id=user.user_id,
637 username=user.username,
637 username=user.username,
638 firstname=user.name,
638 firstname=user.name,
639 lastname=user.lastname,
639 lastname=user.lastname,
640 email=user.email,
640 email=user.email,
641 emails=user.emails,
641 emails=user.emails,
642 active=user.active,
642 active=user.active,
643 admin=user.admin,
643 admin=user.admin,
644 )
644 )
645 if details:
645 if details:
646 data.update(dict(
646 data.update(dict(
647 extern_type=user.extern_type,
647 extern_type=user.extern_type,
648 extern_name=user.extern_name,
648 extern_name=user.extern_name,
649 api_key=user.api_key,
649 api_key=user.api_key,
650 api_keys=user.api_keys,
650 api_keys=user.api_keys,
651 last_login=user.last_login,
651 last_login=user.last_login,
652 ip_addresses=user.ip_addresses
652 ip_addresses=user.ip_addresses
653 ))
653 ))
654 return data
654 return data
655
655
656 def __json__(self):
656 def __json__(self):
657 data = dict(
657 data = dict(
658 full_name=self.full_name,
658 full_name=self.full_name,
659 full_name_or_username=self.full_name_or_username,
659 full_name_or_username=self.full_name_or_username,
660 short_contact=self.short_contact,
660 short_contact=self.short_contact,
661 full_contact=self.full_contact
661 full_contact=self.full_contact
662 )
662 )
663 data.update(self.get_api_data())
663 data.update(self.get_api_data())
664 return data
664 return data
665
665
666
666
667 class UserApiKeys(Base, BaseModel):
667 class UserApiKeys(Base, BaseModel):
668 __tablename__ = 'user_api_keys'
668 __tablename__ = 'user_api_keys'
669 __table_args__ = (
669 __table_args__ = (
670 Index('uak_api_key_idx', 'api_key'),
670 Index('uak_api_key_idx', 'api_key'),
671 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
671 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
672 UniqueConstraint('api_key'),
672 UniqueConstraint('api_key'),
673 {'extend_existing': True, 'mysql_engine': 'InnoDB',
673 {'extend_existing': True, 'mysql_engine': 'InnoDB',
674 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
674 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
675 )
675 )
676 __mapper_args__ = {}
676 __mapper_args__ = {}
677
677
678 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
678 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
679 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
679 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
680 api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True)
680 api_key = Column("api_key", String(255, convert_unicode=False), nullable=False, unique=True)
681 description = Column('description', UnicodeText(1024))
681 description = Column('description', UnicodeText(1024))
682 expires = Column('expires', Float(53), nullable=False)
682 expires = Column('expires', Float(53), nullable=False)
683 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
683 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
684
684
685 user = relationship('User')
685 user = relationship('User')
686
686
687 @property
687 @property
688 def expired(self):
688 def expired(self):
689 if self.expires == -1:
689 if self.expires == -1:
690 return False
690 return False
691 return time.time() > self.expires
691 return time.time() > self.expires
692
692
693
693
694 class UserEmailMap(Base, BaseModel):
694 class UserEmailMap(Base, BaseModel):
695 __tablename__ = 'user_email_map'
695 __tablename__ = 'user_email_map'
696 __table_args__ = (
696 __table_args__ = (
697 Index('uem_email_idx', 'email'),
697 Index('uem_email_idx', 'email'),
698 UniqueConstraint('email'),
698 UniqueConstraint('email'),
699 {'extend_existing': True, 'mysql_engine': 'InnoDB',
699 {'extend_existing': True, 'mysql_engine': 'InnoDB',
700 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
700 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
701 )
701 )
702 __mapper_args__ = {}
702 __mapper_args__ = {}
703
703
704 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
704 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
705 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
705 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
706 _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
706 _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
707 user = relationship('User')
707 user = relationship('User')
708
708
709 @validates('_email')
709 @validates('_email')
710 def validate_email(self, key, email):
710 def validate_email(self, key, email):
711 # check if this email is not main one
711 # check if this email is not main one
712 main_email = Session().query(User).filter(User.email == email).scalar()
712 main_email = Session().query(User).filter(User.email == email).scalar()
713 if main_email is not None:
713 if main_email is not None:
714 raise AttributeError('email %s is present is user table' % email)
714 raise AttributeError('email %s is present is user table' % email)
715 return email
715 return email
716
716
717 @hybrid_property
717 @hybrid_property
718 def email(self):
718 def email(self):
719 return self._email
719 return self._email
720
720
721 @email.setter
721 @email.setter
722 def email(self, val):
722 def email(self, val):
723 self._email = val.lower() if val else None
723 self._email = val.lower() if val else None
724
724
725
725
726 class UserIpMap(Base, BaseModel):
726 class UserIpMap(Base, BaseModel):
727 __tablename__ = 'user_ip_map'
727 __tablename__ = 'user_ip_map'
728 __table_args__ = (
728 __table_args__ = (
729 UniqueConstraint('user_id', 'ip_addr'),
729 UniqueConstraint('user_id', 'ip_addr'),
730 {'extend_existing': True, 'mysql_engine': 'InnoDB',
730 {'extend_existing': True, 'mysql_engine': 'InnoDB',
731 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
731 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
732 )
732 )
733 __mapper_args__ = {}
733 __mapper_args__ = {}
734
734
735 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
735 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
736 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
736 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
737 ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
737 ip_addr = Column("ip_addr", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
738 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
738 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
739 user = relationship('User')
739 user = relationship('User')
740
740
741 @classmethod
741 @classmethod
742 def _get_ip_range(cls, ip_addr):
742 def _get_ip_range(cls, ip_addr):
743 from kallithea.lib import ipaddr
743 from kallithea.lib import ipaddr
744 net = ipaddr.IPNetwork(address=ip_addr)
744 net = ipaddr.IPNetwork(address=ip_addr)
745 return [str(net.network), str(net.broadcast)]
745 return [str(net.network), str(net.broadcast)]
746
746
747 def __json__(self):
747 def __json__(self):
748 return dict(
748 return dict(
749 ip_addr=self.ip_addr,
749 ip_addr=self.ip_addr,
750 ip_range=self._get_ip_range(self.ip_addr)
750 ip_range=self._get_ip_range(self.ip_addr)
751 )
751 )
752
752
753 def __unicode__(self):
753 def __unicode__(self):
754 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
754 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
755 self.user_id, self.ip_addr)
755 self.user_id, self.ip_addr)
756
756
757 class UserLog(Base, BaseModel):
757 class UserLog(Base, BaseModel):
758 __tablename__ = 'user_logs'
758 __tablename__ = 'user_logs'
759 __table_args__ = (
759 __table_args__ = (
760 {'extend_existing': True, 'mysql_engine': 'InnoDB',
760 {'extend_existing': True, 'mysql_engine': 'InnoDB',
761 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
761 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
762 )
762 )
763 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
763 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
764 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
764 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
765 username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
765 username = Column("username", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
766 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
766 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
767 repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
767 repository_name = Column("repository_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
768 user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
768 user_ip = Column("user_ip", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
769 action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None)
769 action = Column("action", UnicodeText(1200000, convert_unicode=False), nullable=True, unique=None, default=None)
770 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
770 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
771
771
772 def __unicode__(self):
772 def __unicode__(self):
773 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
773 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
774 self.repository_name,
774 self.repository_name,
775 self.action)
775 self.action)
776
776
777 @property
777 @property
778 def action_as_day(self):
778 def action_as_day(self):
779 return datetime.date(*self.action_date.timetuple()[:3])
779 return datetime.date(*self.action_date.timetuple()[:3])
780
780
781 user = relationship('User')
781 user = relationship('User')
782 repository = relationship('Repository', cascade='')
782 repository = relationship('Repository', cascade='')
783
783
784
784
785 class UserGroup(Base, BaseModel):
785 class UserGroup(Base, BaseModel):
786 __tablename__ = 'users_groups'
786 __tablename__ = 'users_groups'
787 __table_args__ = (
787 __table_args__ = (
788 {'extend_existing': True, 'mysql_engine': 'InnoDB',
788 {'extend_existing': True, 'mysql_engine': 'InnoDB',
789 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
789 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
790 )
790 )
791
791
792 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
792 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
793 users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
793 users_group_name = Column("users_group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
794 user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
794 user_group_description = Column("user_group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
795 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
795 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
796 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
796 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
797 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
797 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
798 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
798 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
799 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
799 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
800
800
801 members = relationship('UserGroupMember', cascade="all, delete-orphan")
801 members = relationship('UserGroupMember', cascade="all, delete-orphan")
802 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
802 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
803 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
803 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
804 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
804 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
805 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
805 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
806 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
806 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
807
807
808 user = relationship('User')
808 user = relationship('User')
809
809
810 @hybrid_property
810 @hybrid_property
811 def group_data(self):
811 def group_data(self):
812 if not self._group_data:
812 if not self._group_data:
813 return {}
813 return {}
814
814
815 try:
815 try:
816 return json.loads(self._group_data)
816 return json.loads(self._group_data)
817 except TypeError:
817 except TypeError:
818 return {}
818 return {}
819
819
820 @group_data.setter
820 @group_data.setter
821 def group_data(self, val):
821 def group_data(self, val):
822 try:
822 try:
823 self._group_data = json.dumps(val)
823 self._group_data = json.dumps(val)
824 except Exception:
824 except Exception:
825 log.error(traceback.format_exc())
825 log.error(traceback.format_exc())
826
826
827 def __unicode__(self):
827 def __unicode__(self):
828 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
828 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
829 self.users_group_id,
829 self.users_group_id,
830 self.users_group_name)
830 self.users_group_name)
831
831
832 @classmethod
832 @classmethod
833 def get_by_group_name(cls, group_name, cache=False,
833 def get_by_group_name(cls, group_name, cache=False,
834 case_insensitive=False):
834 case_insensitive=False):
835 if case_insensitive:
835 if case_insensitive:
836 q = cls.query().filter(cls.users_group_name.ilike(group_name))
836 q = cls.query().filter(cls.users_group_name.ilike(group_name))
837 else:
837 else:
838 q = cls.query().filter(cls.users_group_name == group_name)
838 q = cls.query().filter(cls.users_group_name == group_name)
839 if cache:
839 if cache:
840 q = q.options(FromCache(
840 q = q.options(FromCache(
841 "sql_cache_short",
841 "sql_cache_short",
842 "get_group_%s" % _hash_key(group_name)
842 "get_group_%s" % _hash_key(group_name)
843 )
843 )
844 )
844 )
845 return q.scalar()
845 return q.scalar()
846
846
847 @classmethod
847 @classmethod
848 def get(cls, user_group_id, cache=False):
848 def get(cls, user_group_id, cache=False):
849 user_group = cls.query()
849 user_group = cls.query()
850 if cache:
850 if cache:
851 user_group = user_group.options(FromCache("sql_cache_short",
851 user_group = user_group.options(FromCache("sql_cache_short",
852 "get_users_group_%s" % user_group_id))
852 "get_users_group_%s" % user_group_id))
853 return user_group.get(user_group_id)
853 return user_group.get(user_group_id)
854
854
855 def get_api_data(self, with_members=True):
855 def get_api_data(self, with_members=True):
856 user_group = self
856 user_group = self
857
857
858 data = dict(
858 data = dict(
859 users_group_id=user_group.users_group_id,
859 users_group_id=user_group.users_group_id,
860 group_name=user_group.users_group_name,
860 group_name=user_group.users_group_name,
861 group_description=user_group.user_group_description,
861 group_description=user_group.user_group_description,
862 active=user_group.users_group_active,
862 active=user_group.users_group_active,
863 owner=user_group.user.username,
863 owner=user_group.user.username,
864 )
864 )
865 if with_members:
865 if with_members:
866 members = []
866 members = []
867 for user in user_group.members:
867 for user in user_group.members:
868 user = user.user
868 user = user.user
869 members.append(user.get_api_data())
869 members.append(user.get_api_data())
870 data['members'] = members
870 data['members'] = members
871
871
872 return data
872 return data
873
873
874
874
875 class UserGroupMember(Base, BaseModel):
875 class UserGroupMember(Base, BaseModel):
876 __tablename__ = 'users_groups_members'
876 __tablename__ = 'users_groups_members'
877 __table_args__ = (
877 __table_args__ = (
878 {'extend_existing': True, 'mysql_engine': 'InnoDB',
878 {'extend_existing': True, 'mysql_engine': 'InnoDB',
879 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
879 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
880 )
880 )
881
881
882 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
882 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
883 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
883 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
884 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
884 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
885
885
886 user = relationship('User')
886 user = relationship('User')
887 users_group = relationship('UserGroup')
887 users_group = relationship('UserGroup')
888
888
889 def __init__(self, gr_id='', u_id=''):
889 def __init__(self, gr_id='', u_id=''):
890 self.users_group_id = gr_id
890 self.users_group_id = gr_id
891 self.user_id = u_id
891 self.user_id = u_id
892
892
893
893
894 class RepositoryField(Base, BaseModel):
894 class RepositoryField(Base, BaseModel):
895 __tablename__ = 'repositories_fields'
895 __tablename__ = 'repositories_fields'
896 __table_args__ = (
896 __table_args__ = (
897 UniqueConstraint('repository_id', 'field_key'), # no-multi field
897 UniqueConstraint('repository_id', 'field_key'), # no-multi field
898 {'extend_existing': True, 'mysql_engine': 'InnoDB',
898 {'extend_existing': True, 'mysql_engine': 'InnoDB',
899 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
899 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
900 )
900 )
901 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
901 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
902
902
903 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
903 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
904 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
904 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
905 field_key = Column("field_key", String(250, convert_unicode=False))
905 field_key = Column("field_key", String(250, convert_unicode=False))
906 field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False)
906 field_label = Column("field_label", String(1024, convert_unicode=False), nullable=False)
907 field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False)
907 field_value = Column("field_value", String(10000, convert_unicode=False), nullable=False)
908 field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False)
908 field_desc = Column("field_desc", String(1024, convert_unicode=False), nullable=False)
909 field_type = Column("field_type", String(255), nullable=False, unique=None)
909 field_type = Column("field_type", String(255), nullable=False, unique=None)
910 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
910 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
911
911
912 repository = relationship('Repository')
912 repository = relationship('Repository')
913
913
914 @property
914 @property
915 def field_key_prefixed(self):
915 def field_key_prefixed(self):
916 return 'ex_%s' % self.field_key
916 return 'ex_%s' % self.field_key
917
917
918 @classmethod
918 @classmethod
919 def un_prefix_key(cls, key):
919 def un_prefix_key(cls, key):
920 if key.startswith(cls.PREFIX):
920 if key.startswith(cls.PREFIX):
921 return key[len(cls.PREFIX):]
921 return key[len(cls.PREFIX):]
922 return key
922 return key
923
923
924 @classmethod
924 @classmethod
925 def get_by_key_name(cls, key, repo):
925 def get_by_key_name(cls, key, repo):
926 row = cls.query()\
926 row = cls.query()\
927 .filter(cls.repository == repo)\
927 .filter(cls.repository == repo)\
928 .filter(cls.field_key == key).scalar()
928 .filter(cls.field_key == key).scalar()
929 return row
929 return row
930
930
931
931
932 class Repository(Base, BaseModel):
932 class Repository(Base, BaseModel):
933 __tablename__ = 'repositories'
933 __tablename__ = 'repositories'
934 __table_args__ = (
934 __table_args__ = (
935 UniqueConstraint('repo_name'),
935 UniqueConstraint('repo_name'),
936 Index('r_repo_name_idx', 'repo_name'),
936 Index('r_repo_name_idx', 'repo_name'),
937 {'extend_existing': True, 'mysql_engine': 'InnoDB',
937 {'extend_existing': True, 'mysql_engine': 'InnoDB',
938 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
938 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
939 )
939 )
940 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
940 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
941 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
941 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
942
942
943 STATE_CREATED = 'repo_state_created'
943 STATE_CREATED = 'repo_state_created'
944 STATE_PENDING = 'repo_state_pending'
944 STATE_PENDING = 'repo_state_pending'
945 STATE_ERROR = 'repo_state_error'
945 STATE_ERROR = 'repo_state_error'
946
946
947 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
947 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
948 repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
948 repo_name = Column("repo_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
949 repo_state = Column("repo_state", String(255), nullable=True)
949 repo_state = Column("repo_state", String(255), nullable=True)
950
950
951 clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
951 clone_uri = Column("clone_uri", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
952 repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
952 repo_type = Column("repo_type", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
953 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
953 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
954 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
954 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
955 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
955 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
956 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
956 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
957 description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
957 description = Column("description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
958 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
958 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
959 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
959 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
960 _landing_revision = Column("landing_revision", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
960 _landing_revision = Column("landing_revision", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
961 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
961 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
962 _locked = Column("locked", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
962 _locked = Column("locked", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
963 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
963 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
964
964
965 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
965 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
966 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
966 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
967
967
968 user = relationship('User')
968 user = relationship('User')
969 fork = relationship('Repository', remote_side=repo_id)
969 fork = relationship('Repository', remote_side=repo_id)
970 group = relationship('RepoGroup')
970 group = relationship('RepoGroup')
971 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
971 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
972 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
972 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
973 stats = relationship('Statistics', cascade='all', uselist=False)
973 stats = relationship('Statistics', cascade='all', uselist=False)
974
974
975 followers = relationship('UserFollowing',
975 followers = relationship('UserFollowing',
976 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
976 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
977 cascade='all')
977 cascade='all')
978 extra_fields = relationship('RepositoryField',
978 extra_fields = relationship('RepositoryField',
979 cascade="all, delete-orphan")
979 cascade="all, delete-orphan")
980
980
981 logs = relationship('UserLog')
981 logs = relationship('UserLog')
982 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
982 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
983
983
984 pull_requests_org = relationship('PullRequest',
984 pull_requests_org = relationship('PullRequest',
985 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
985 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
986 cascade="all, delete-orphan")
986 cascade="all, delete-orphan")
987
987
988 pull_requests_other = relationship('PullRequest',
988 pull_requests_other = relationship('PullRequest',
989 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
989 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
990 cascade="all, delete-orphan")
990 cascade="all, delete-orphan")
991
991
992 def __unicode__(self):
992 def __unicode__(self):
993 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
993 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
994 safe_unicode(self.repo_name))
994 safe_unicode(self.repo_name))
995
995
996 @hybrid_property
996 @hybrid_property
997 def landing_rev(self):
997 def landing_rev(self):
998 # always should return [rev_type, rev]
998 # always should return [rev_type, rev]
999 if self._landing_revision:
999 if self._landing_revision:
1000 _rev_info = self._landing_revision.split(':')
1000 _rev_info = self._landing_revision.split(':')
1001 if len(_rev_info) < 2:
1001 if len(_rev_info) < 2:
1002 _rev_info.insert(0, 'rev')
1002 _rev_info.insert(0, 'rev')
1003 return [_rev_info[0], _rev_info[1]]
1003 return [_rev_info[0], _rev_info[1]]
1004 return [None, None]
1004 return [None, None]
1005
1005
1006 @landing_rev.setter
1006 @landing_rev.setter
1007 def landing_rev(self, val):
1007 def landing_rev(self, val):
1008 if ':' not in val:
1008 if ':' not in val:
1009 raise ValueError('value must be delimited with `:` and consist '
1009 raise ValueError('value must be delimited with `:` and consist '
1010 'of <rev_type>:<rev>, got %s instead' % val)
1010 'of <rev_type>:<rev>, got %s instead' % val)
1011 self._landing_revision = val
1011 self._landing_revision = val
1012
1012
1013 @hybrid_property
1013 @hybrid_property
1014 def locked(self):
1014 def locked(self):
1015 # always should return [user_id, timelocked]
1015 # always should return [user_id, timelocked]
1016 if self._locked:
1016 if self._locked:
1017 _lock_info = self._locked.split(':')
1017 _lock_info = self._locked.split(':')
1018 return int(_lock_info[0]), _lock_info[1]
1018 return int(_lock_info[0]), _lock_info[1]
1019 return [None, None]
1019 return [None, None]
1020
1020
1021 @locked.setter
1021 @locked.setter
1022 def locked(self, val):
1022 def locked(self, val):
1023 if val and isinstance(val, (list, tuple)):
1023 if val and isinstance(val, (list, tuple)):
1024 self._locked = ':'.join(map(str, val))
1024 self._locked = ':'.join(map(str, val))
1025 else:
1025 else:
1026 self._locked = None
1026 self._locked = None
1027
1027
1028 @hybrid_property
1028 @hybrid_property
1029 def changeset_cache(self):
1029 def changeset_cache(self):
1030 from kallithea.lib.vcs.backends.base import EmptyChangeset
1030 from kallithea.lib.vcs.backends.base import EmptyChangeset
1031 dummy = EmptyChangeset().__json__()
1031 dummy = EmptyChangeset().__json__()
1032 if not self._changeset_cache:
1032 if not self._changeset_cache:
1033 return dummy
1033 return dummy
1034 try:
1034 try:
1035 return json.loads(self._changeset_cache)
1035 return json.loads(self._changeset_cache)
1036 except TypeError:
1036 except TypeError:
1037 return dummy
1037 return dummy
1038
1038
1039 @changeset_cache.setter
1039 @changeset_cache.setter
1040 def changeset_cache(self, val):
1040 def changeset_cache(self, val):
1041 try:
1041 try:
1042 self._changeset_cache = json.dumps(val)
1042 self._changeset_cache = json.dumps(val)
1043 except Exception:
1043 except Exception:
1044 log.error(traceback.format_exc())
1044 log.error(traceback.format_exc())
1045
1045
1046 @classmethod
1046 @classmethod
1047 def url_sep(cls):
1047 def url_sep(cls):
1048 return URL_SEP
1048 return URL_SEP
1049
1049
1050 @classmethod
1050 @classmethod
1051 def normalize_repo_name(cls, repo_name):
1051 def normalize_repo_name(cls, repo_name):
1052 """
1052 """
1053 Normalizes os specific repo_name to the format internally stored inside
1053 Normalizes os specific repo_name to the format internally stored inside
1054 dabatabase using URL_SEP
1054 database using URL_SEP
1055
1055
1056 :param cls:
1056 :param cls:
1057 :param repo_name:
1057 :param repo_name:
1058 """
1058 """
1059 return cls.url_sep().join(repo_name.split(os.sep))
1059 return cls.url_sep().join(repo_name.split(os.sep))
1060
1060
1061 @classmethod
1061 @classmethod
1062 def get_by_repo_name(cls, repo_name):
1062 def get_by_repo_name(cls, repo_name):
1063 q = Session().query(cls).filter(cls.repo_name == repo_name)
1063 q = Session().query(cls).filter(cls.repo_name == repo_name)
1064 q = q.options(joinedload(Repository.fork))\
1064 q = q.options(joinedload(Repository.fork))\
1065 .options(joinedload(Repository.user))\
1065 .options(joinedload(Repository.user))\
1066 .options(joinedload(Repository.group))
1066 .options(joinedload(Repository.group))
1067 return q.scalar()
1067 return q.scalar()
1068
1068
1069 @classmethod
1069 @classmethod
1070 def get_by_full_path(cls, repo_full_path):
1070 def get_by_full_path(cls, repo_full_path):
1071 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1071 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1072 repo_name = cls.normalize_repo_name(repo_name)
1072 repo_name = cls.normalize_repo_name(repo_name)
1073 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1073 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1074
1074
1075 @classmethod
1075 @classmethod
1076 def get_repo_forks(cls, repo_id):
1076 def get_repo_forks(cls, repo_id):
1077 return cls.query().filter(Repository.fork_id == repo_id)
1077 return cls.query().filter(Repository.fork_id == repo_id)
1078
1078
1079 @classmethod
1079 @classmethod
1080 def base_path(cls):
1080 def base_path(cls):
1081 """
1081 """
1082 Returns base path where all repos are stored
1082 Returns base path where all repos are stored
1083
1083
1084 :param cls:
1084 :param cls:
1085 """
1085 """
1086 q = Session().query(Ui)\
1086 q = Session().query(Ui)\
1087 .filter(Ui.ui_key == cls.url_sep())
1087 .filter(Ui.ui_key == cls.url_sep())
1088 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1088 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1089 return q.one().ui_value
1089 return q.one().ui_value
1090
1090
1091 @property
1091 @property
1092 def forks(self):
1092 def forks(self):
1093 """
1093 """
1094 Return forks of this repo
1094 Return forks of this repo
1095 """
1095 """
1096 return Repository.get_repo_forks(self.repo_id)
1096 return Repository.get_repo_forks(self.repo_id)
1097
1097
1098 @property
1098 @property
1099 def parent(self):
1099 def parent(self):
1100 """
1100 """
1101 Returns fork parent
1101 Returns fork parent
1102 """
1102 """
1103 return self.fork
1103 return self.fork
1104
1104
1105 @property
1105 @property
1106 def just_name(self):
1106 def just_name(self):
1107 return self.repo_name.split(Repository.url_sep())[-1]
1107 return self.repo_name.split(Repository.url_sep())[-1]
1108
1108
1109 @property
1109 @property
1110 def groups_with_parents(self):
1110 def groups_with_parents(self):
1111 groups = []
1111 groups = []
1112 if self.group is None:
1112 if self.group is None:
1113 return groups
1113 return groups
1114
1114
1115 cur_gr = self.group
1115 cur_gr = self.group
1116 groups.insert(0, cur_gr)
1116 groups.insert(0, cur_gr)
1117 while 1:
1117 while 1:
1118 gr = getattr(cur_gr, 'parent_group', None)
1118 gr = getattr(cur_gr, 'parent_group', None)
1119 cur_gr = cur_gr.parent_group
1119 cur_gr = cur_gr.parent_group
1120 if gr is None:
1120 if gr is None:
1121 break
1121 break
1122 groups.insert(0, gr)
1122 groups.insert(0, gr)
1123
1123
1124 return groups
1124 return groups
1125
1125
1126 @property
1126 @property
1127 def groups_and_repo(self):
1127 def groups_and_repo(self):
1128 return self.groups_with_parents, self.just_name, self.repo_name
1128 return self.groups_with_parents, self.just_name, self.repo_name
1129
1129
1130 @LazyProperty
1130 @LazyProperty
1131 def repo_path(self):
1131 def repo_path(self):
1132 """
1132 """
1133 Returns base full path for that repository means where it actually
1133 Returns base full path for that repository means where it actually
1134 exists on a filesystem
1134 exists on a filesystem
1135 """
1135 """
1136 q = Session().query(Ui).filter(Ui.ui_key ==
1136 q = Session().query(Ui).filter(Ui.ui_key ==
1137 Repository.url_sep())
1137 Repository.url_sep())
1138 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1138 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1139 return q.one().ui_value
1139 return q.one().ui_value
1140
1140
1141 @property
1141 @property
1142 def repo_full_path(self):
1142 def repo_full_path(self):
1143 p = [self.repo_path]
1143 p = [self.repo_path]
1144 # we need to split the name by / since this is how we store the
1144 # we need to split the name by / since this is how we store the
1145 # names in the database, but that eventually needs to be converted
1145 # names in the database, but that eventually needs to be converted
1146 # into a valid system path
1146 # into a valid system path
1147 p += self.repo_name.split(Repository.url_sep())
1147 p += self.repo_name.split(Repository.url_sep())
1148 return os.path.join(*map(safe_unicode, p))
1148 return os.path.join(*map(safe_unicode, p))
1149
1149
1150 @property
1150 @property
1151 def cache_keys(self):
1151 def cache_keys(self):
1152 """
1152 """
1153 Returns associated cache keys for that repo
1153 Returns associated cache keys for that repo
1154 """
1154 """
1155 return CacheInvalidation.query()\
1155 return CacheInvalidation.query()\
1156 .filter(CacheInvalidation.cache_args == self.repo_name)\
1156 .filter(CacheInvalidation.cache_args == self.repo_name)\
1157 .order_by(CacheInvalidation.cache_key)\
1157 .order_by(CacheInvalidation.cache_key)\
1158 .all()
1158 .all()
1159
1159
1160 def get_new_name(self, repo_name):
1160 def get_new_name(self, repo_name):
1161 """
1161 """
1162 returns new full repository name based on assigned group and new new
1162 returns new full repository name based on assigned group and new new
1163
1163
1164 :param group_name:
1164 :param group_name:
1165 """
1165 """
1166 path_prefix = self.group.full_path_splitted if self.group else []
1166 path_prefix = self.group.full_path_splitted if self.group else []
1167 return Repository.url_sep().join(path_prefix + [repo_name])
1167 return Repository.url_sep().join(path_prefix + [repo_name])
1168
1168
1169 @property
1169 @property
1170 def _ui(self):
1170 def _ui(self):
1171 """
1171 """
1172 Creates an db based ui object for this repository
1172 Creates an db based ui object for this repository
1173 """
1173 """
1174 from kallithea.lib.utils import make_ui
1174 from kallithea.lib.utils import make_ui
1175 return make_ui('db', clear_session=False)
1175 return make_ui('db', clear_session=False)
1176
1176
1177 @classmethod
1177 @classmethod
1178 def is_valid(cls, repo_name):
1178 def is_valid(cls, repo_name):
1179 """
1179 """
1180 returns True if given repo name is a valid filesystem repository
1180 returns True if given repo name is a valid filesystem repository
1181
1181
1182 :param cls:
1182 :param cls:
1183 :param repo_name:
1183 :param repo_name:
1184 """
1184 """
1185 from kallithea.lib.utils import is_valid_repo
1185 from kallithea.lib.utils import is_valid_repo
1186
1186
1187 return is_valid_repo(repo_name, cls.base_path())
1187 return is_valid_repo(repo_name, cls.base_path())
1188
1188
1189 def get_api_data(self):
1189 def get_api_data(self):
1190 """
1190 """
1191 Common function for generating repo api data
1191 Common function for generating repo api data
1192
1192
1193 """
1193 """
1194 repo = self
1194 repo = self
1195 data = dict(
1195 data = dict(
1196 repo_id=repo.repo_id,
1196 repo_id=repo.repo_id,
1197 repo_name=repo.repo_name,
1197 repo_name=repo.repo_name,
1198 repo_type=repo.repo_type,
1198 repo_type=repo.repo_type,
1199 clone_uri=repo.clone_uri,
1199 clone_uri=repo.clone_uri,
1200 private=repo.private,
1200 private=repo.private,
1201 created_on=repo.created_on,
1201 created_on=repo.created_on,
1202 description=repo.description,
1202 description=repo.description,
1203 landing_rev=repo.landing_rev,
1203 landing_rev=repo.landing_rev,
1204 owner=repo.user.username,
1204 owner=repo.user.username,
1205 fork_of=repo.fork.repo_name if repo.fork else None,
1205 fork_of=repo.fork.repo_name if repo.fork else None,
1206 enable_statistics=repo.enable_statistics,
1206 enable_statistics=repo.enable_statistics,
1207 enable_locking=repo.enable_locking,
1207 enable_locking=repo.enable_locking,
1208 enable_downloads=repo.enable_downloads,
1208 enable_downloads=repo.enable_downloads,
1209 last_changeset=repo.changeset_cache,
1209 last_changeset=repo.changeset_cache,
1210 locked_by=User.get(self.locked[0]).get_api_data() \
1210 locked_by=User.get(self.locked[0]).get_api_data() \
1211 if self.locked[0] else None,
1211 if self.locked[0] else None,
1212 locked_date=time_to_datetime(self.locked[1]) \
1212 locked_date=time_to_datetime(self.locked[1]) \
1213 if self.locked[1] else None
1213 if self.locked[1] else None
1214 )
1214 )
1215 rc_config = Setting.get_app_settings()
1215 rc_config = Setting.get_app_settings()
1216 repository_fields = str2bool(rc_config.get('repository_fields'))
1216 repository_fields = str2bool(rc_config.get('repository_fields'))
1217 if repository_fields:
1217 if repository_fields:
1218 for f in self.extra_fields:
1218 for f in self.extra_fields:
1219 data[f.field_key_prefixed] = f.field_value
1219 data[f.field_key_prefixed] = f.field_value
1220
1220
1221 return data
1221 return data
1222
1222
1223 @classmethod
1223 @classmethod
1224 def lock(cls, repo, user_id, lock_time=None):
1224 def lock(cls, repo, user_id, lock_time=None):
1225 if lock_time is not None:
1225 if lock_time is not None:
1226 lock_time = time.time()
1226 lock_time = time.time()
1227 repo.locked = [user_id, lock_time]
1227 repo.locked = [user_id, lock_time]
1228 Session().add(repo)
1228 Session().add(repo)
1229 Session().commit()
1229 Session().commit()
1230
1230
1231 @classmethod
1231 @classmethod
1232 def unlock(cls, repo):
1232 def unlock(cls, repo):
1233 repo.locked = None
1233 repo.locked = None
1234 Session().add(repo)
1234 Session().add(repo)
1235 Session().commit()
1235 Session().commit()
1236
1236
1237 @classmethod
1237 @classmethod
1238 def getlock(cls, repo):
1238 def getlock(cls, repo):
1239 return repo.locked
1239 return repo.locked
1240
1240
1241 @property
1241 @property
1242 def last_db_change(self):
1242 def last_db_change(self):
1243 return self.updated_on
1243 return self.updated_on
1244
1244
1245 @property
1245 @property
1246 def clone_uri_hidden(self):
1246 def clone_uri_hidden(self):
1247 clone_uri = self.clone_uri
1247 clone_uri = self.clone_uri
1248 if clone_uri:
1248 if clone_uri:
1249 import urlobject
1249 import urlobject
1250 url_obj = urlobject.URLObject(self.clone_uri)
1250 url_obj = urlobject.URLObject(self.clone_uri)
1251 if url_obj.password:
1251 if url_obj.password:
1252 clone_uri = url_obj.with_password('*****')
1252 clone_uri = url_obj.with_password('*****')
1253 return clone_uri
1253 return clone_uri
1254
1254
1255 def clone_url(self, **override):
1255 def clone_url(self, **override):
1256 import kallithea.lib.helpers as h
1256 import kallithea.lib.helpers as h
1257 qualified_home_url = h.canonical_url('home')
1257 qualified_home_url = h.canonical_url('home')
1258
1258
1259 uri_tmpl = None
1259 uri_tmpl = None
1260 if 'with_id' in override:
1260 if 'with_id' in override:
1261 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1261 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1262 del override['with_id']
1262 del override['with_id']
1263
1263
1264 if 'uri_tmpl' in override:
1264 if 'uri_tmpl' in override:
1265 uri_tmpl = override['uri_tmpl']
1265 uri_tmpl = override['uri_tmpl']
1266 del override['uri_tmpl']
1266 del override['uri_tmpl']
1267
1267
1268 # we didn't override our tmpl from **overrides
1268 # we didn't override our tmpl from **overrides
1269 if not uri_tmpl:
1269 if not uri_tmpl:
1270 uri_tmpl = self.DEFAULT_CLONE_URI
1270 uri_tmpl = self.DEFAULT_CLONE_URI
1271 try:
1271 try:
1272 from pylons import tmpl_context as c
1272 from pylons import tmpl_context as c
1273 uri_tmpl = c.clone_uri_tmpl
1273 uri_tmpl = c.clone_uri_tmpl
1274 except AttributeError:
1274 except AttributeError:
1275 # in any case if we call this outside of request context,
1275 # in any case if we call this outside of request context,
1276 # ie, not having tmpl_context set up
1276 # ie, not having tmpl_context set up
1277 pass
1277 pass
1278
1278
1279 return get_clone_url(uri_tmpl=uri_tmpl,
1279 return get_clone_url(uri_tmpl=uri_tmpl,
1280 qualifed_home_url=qualified_home_url,
1280 qualifed_home_url=qualified_home_url,
1281 repo_name=self.repo_name,
1281 repo_name=self.repo_name,
1282 repo_id=self.repo_id, **override)
1282 repo_id=self.repo_id, **override)
1283
1283
1284 def set_state(self, state):
1284 def set_state(self, state):
1285 self.repo_state = state
1285 self.repo_state = state
1286 Session().add(self)
1286 Session().add(self)
1287 #==========================================================================
1287 #==========================================================================
1288 # SCM PROPERTIES
1288 # SCM PROPERTIES
1289 #==========================================================================
1289 #==========================================================================
1290
1290
1291 def get_changeset(self, rev=None):
1291 def get_changeset(self, rev=None):
1292 return get_changeset_safe(self.scm_instance, rev)
1292 return get_changeset_safe(self.scm_instance, rev)
1293
1293
1294 def get_landing_changeset(self):
1294 def get_landing_changeset(self):
1295 """
1295 """
1296 Returns landing changeset, or if that doesn't exist returns the tip
1296 Returns landing changeset, or if that doesn't exist returns the tip
1297 """
1297 """
1298 _rev_type, _rev = self.landing_rev
1298 _rev_type, _rev = self.landing_rev
1299 cs = self.get_changeset(_rev)
1299 cs = self.get_changeset(_rev)
1300 if isinstance(cs, EmptyChangeset):
1300 if isinstance(cs, EmptyChangeset):
1301 return self.get_changeset()
1301 return self.get_changeset()
1302 return cs
1302 return cs
1303
1303
1304 def update_changeset_cache(self, cs_cache=None):
1304 def update_changeset_cache(self, cs_cache=None):
1305 """
1305 """
1306 Update cache of last changeset for repository, keys should be::
1306 Update cache of last changeset for repository, keys should be::
1307
1307
1308 short_id
1308 short_id
1309 raw_id
1309 raw_id
1310 revision
1310 revision
1311 message
1311 message
1312 date
1312 date
1313 author
1313 author
1314
1314
1315 :param cs_cache:
1315 :param cs_cache:
1316 """
1316 """
1317 from kallithea.lib.vcs.backends.base import BaseChangeset
1317 from kallithea.lib.vcs.backends.base import BaseChangeset
1318 if cs_cache is None:
1318 if cs_cache is None:
1319 cs_cache = EmptyChangeset()
1319 cs_cache = EmptyChangeset()
1320 # use no-cache version here
1320 # use no-cache version here
1321 scm_repo = self.scm_instance_no_cache()
1321 scm_repo = self.scm_instance_no_cache()
1322 if scm_repo:
1322 if scm_repo:
1323 cs_cache = scm_repo.get_changeset()
1323 cs_cache = scm_repo.get_changeset()
1324
1324
1325 if isinstance(cs_cache, BaseChangeset):
1325 if isinstance(cs_cache, BaseChangeset):
1326 cs_cache = cs_cache.__json__()
1326 cs_cache = cs_cache.__json__()
1327
1327
1328 if (not self.changeset_cache or cs_cache['raw_id'] != self.changeset_cache['raw_id']):
1328 if (not self.changeset_cache or cs_cache['raw_id'] != self.changeset_cache['raw_id']):
1329 _default = datetime.datetime.fromtimestamp(0)
1329 _default = datetime.datetime.fromtimestamp(0)
1330 last_change = cs_cache.get('date') or _default
1330 last_change = cs_cache.get('date') or _default
1331 log.debug('updated repo %s with new cs cache %s'
1331 log.debug('updated repo %s with new cs cache %s'
1332 % (self.repo_name, cs_cache))
1332 % (self.repo_name, cs_cache))
1333 self.updated_on = last_change
1333 self.updated_on = last_change
1334 self.changeset_cache = cs_cache
1334 self.changeset_cache = cs_cache
1335 Session().add(self)
1335 Session().add(self)
1336 Session().commit()
1336 Session().commit()
1337 else:
1337 else:
1338 log.debug('changeset_cache for %s already up to date with %s'
1338 log.debug('changeset_cache for %s already up to date with %s'
1339 % (self.repo_name, cs_cache['raw_id']))
1339 % (self.repo_name, cs_cache['raw_id']))
1340
1340
1341 @property
1341 @property
1342 def tip(self):
1342 def tip(self):
1343 return self.get_changeset('tip')
1343 return self.get_changeset('tip')
1344
1344
1345 @property
1345 @property
1346 def author(self):
1346 def author(self):
1347 return self.tip.author
1347 return self.tip.author
1348
1348
1349 @property
1349 @property
1350 def last_change(self):
1350 def last_change(self):
1351 return self.scm_instance.last_change
1351 return self.scm_instance.last_change
1352
1352
1353 def get_comments(self, revisions=None):
1353 def get_comments(self, revisions=None):
1354 """
1354 """
1355 Returns comments for this repository grouped by revisions
1355 Returns comments for this repository grouped by revisions
1356
1356
1357 :param revisions: filter query by revisions only
1357 :param revisions: filter query by revisions only
1358 """
1358 """
1359 cmts = ChangesetComment.query()\
1359 cmts = ChangesetComment.query()\
1360 .filter(ChangesetComment.repo == self)
1360 .filter(ChangesetComment.repo == self)
1361 if revisions is not None:
1361 if revisions is not None:
1362 if not revisions:
1362 if not revisions:
1363 return [] # don't use sql 'in' on empty set
1363 return [] # don't use sql 'in' on empty set
1364 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1364 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1365 grouped = collections.defaultdict(list)
1365 grouped = collections.defaultdict(list)
1366 for cmt in cmts.all():
1366 for cmt in cmts.all():
1367 grouped[cmt.revision].append(cmt)
1367 grouped[cmt.revision].append(cmt)
1368 return grouped
1368 return grouped
1369
1369
1370 def statuses(self, revisions):
1370 def statuses(self, revisions):
1371 """
1371 """
1372 Returns statuses for this repository.
1372 Returns statuses for this repository.
1373 PRs without any votes do _not_ show up as unreviewed.
1373 PRs without any votes do _not_ show up as unreviewed.
1374
1374
1375 :param revisions: list of revisions to get statuses for
1375 :param revisions: list of revisions to get statuses for
1376 """
1376 """
1377 if not revisions:
1377 if not revisions:
1378 return {}
1378 return {}
1379
1379
1380 statuses = ChangesetStatus.query()\
1380 statuses = ChangesetStatus.query()\
1381 .filter(ChangesetStatus.repo == self)\
1381 .filter(ChangesetStatus.repo == self)\
1382 .filter(ChangesetStatus.version == 0)\
1382 .filter(ChangesetStatus.version == 0)\
1383 .filter(ChangesetStatus.revision.in_(revisions))
1383 .filter(ChangesetStatus.revision.in_(revisions))
1384
1384
1385 grouped = {}
1385 grouped = {}
1386 for stat in statuses.all():
1386 for stat in statuses.all():
1387 pr_id = pr_repo = None
1387 pr_id = pr_repo = None
1388 if stat.pull_request:
1388 if stat.pull_request:
1389 pr_id = stat.pull_request.pull_request_id
1389 pr_id = stat.pull_request.pull_request_id
1390 pr_repo = stat.pull_request.other_repo.repo_name
1390 pr_repo = stat.pull_request.other_repo.repo_name
1391 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1391 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1392 pr_id, pr_repo]
1392 pr_id, pr_repo]
1393 return grouped
1393 return grouped
1394
1394
1395 def _repo_size(self):
1395 def _repo_size(self):
1396 from kallithea.lib import helpers as h
1396 from kallithea.lib import helpers as h
1397 log.debug('calculating repository size...')
1397 log.debug('calculating repository size...')
1398 return h.format_byte_size(self.scm_instance.size)
1398 return h.format_byte_size(self.scm_instance.size)
1399
1399
1400 #==========================================================================
1400 #==========================================================================
1401 # SCM CACHE INSTANCE
1401 # SCM CACHE INSTANCE
1402 #==========================================================================
1402 #==========================================================================
1403
1403
1404 def set_invalidate(self):
1404 def set_invalidate(self):
1405 """
1405 """
1406 Mark caches of this repo as invalid.
1406 Mark caches of this repo as invalid.
1407 """
1407 """
1408 CacheInvalidation.set_invalidate(self.repo_name)
1408 CacheInvalidation.set_invalidate(self.repo_name)
1409
1409
1410 def scm_instance_no_cache(self):
1410 def scm_instance_no_cache(self):
1411 return self.__get_instance()
1411 return self.__get_instance()
1412
1412
1413 @property
1413 @property
1414 def scm_instance(self):
1414 def scm_instance(self):
1415 import kallithea
1415 import kallithea
1416 full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
1416 full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
1417 if full_cache:
1417 if full_cache:
1418 return self.scm_instance_cached()
1418 return self.scm_instance_cached()
1419 return self.__get_instance()
1419 return self.__get_instance()
1420
1420
1421 def scm_instance_cached(self, valid_cache_keys=None):
1421 def scm_instance_cached(self, valid_cache_keys=None):
1422 @cache_region('long_term')
1422 @cache_region('long_term')
1423 def _c(repo_name):
1423 def _c(repo_name):
1424 return self.__get_instance()
1424 return self.__get_instance()
1425 rn = self.repo_name
1425 rn = self.repo_name
1426
1426
1427 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1427 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1428 if not valid:
1428 if not valid:
1429 log.debug('Cache for %s invalidated, getting new object' % (rn))
1429 log.debug('Cache for %s invalidated, getting new object' % (rn))
1430 region_invalidate(_c, None, rn)
1430 region_invalidate(_c, None, rn)
1431 else:
1431 else:
1432 log.debug('Getting scm_instance of %s from cache' % (rn))
1432 log.debug('Getting scm_instance of %s from cache' % (rn))
1433 return _c(rn)
1433 return _c(rn)
1434
1434
1435 def __get_instance(self):
1435 def __get_instance(self):
1436 repo_full_path = self.repo_full_path
1436 repo_full_path = self.repo_full_path
1437
1437
1438 alias = get_scm(repo_full_path)[0]
1438 alias = get_scm(repo_full_path)[0]
1439 log.debug('Creating instance of %s repository from %s'
1439 log.debug('Creating instance of %s repository from %s'
1440 % (alias, repo_full_path))
1440 % (alias, repo_full_path))
1441 backend = get_backend(alias)
1441 backend = get_backend(alias)
1442
1442
1443 if alias == 'hg':
1443 if alias == 'hg':
1444 repo = backend(safe_str(repo_full_path), create=False,
1444 repo = backend(safe_str(repo_full_path), create=False,
1445 baseui=self._ui)
1445 baseui=self._ui)
1446 else:
1446 else:
1447 repo = backend(repo_full_path, create=False)
1447 repo = backend(repo_full_path, create=False)
1448
1448
1449 return repo
1449 return repo
1450
1450
1451 def __json__(self):
1451 def __json__(self):
1452 return dict(landing_rev = self.landing_rev)
1452 return dict(landing_rev = self.landing_rev)
1453
1453
1454 class RepoGroup(Base, BaseModel):
1454 class RepoGroup(Base, BaseModel):
1455 __tablename__ = 'groups'
1455 __tablename__ = 'groups'
1456 __table_args__ = (
1456 __table_args__ = (
1457 UniqueConstraint('group_name', 'group_parent_id'),
1457 UniqueConstraint('group_name', 'group_parent_id'),
1458 CheckConstraint('group_id != group_parent_id'),
1458 CheckConstraint('group_id != group_parent_id'),
1459 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1459 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1460 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1460 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1461 )
1461 )
1462 __mapper_args__ = {'order_by': 'group_name'}
1462 __mapper_args__ = {'order_by': 'group_name'}
1463
1463
1464 SEP = ' &raquo; '
1464 SEP = ' &raquo; '
1465
1465
1466 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1466 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1467 group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
1467 group_name = Column("group_name", String(255, convert_unicode=False), nullable=False, unique=True, default=None)
1468 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1468 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1469 group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
1469 group_description = Column("group_description", String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
1470 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1470 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1471 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1471 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1472 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1472 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1473
1473
1474 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1474 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1475 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1475 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1476 parent_group = relationship('RepoGroup', remote_side=group_id)
1476 parent_group = relationship('RepoGroup', remote_side=group_id)
1477 user = relationship('User')
1477 user = relationship('User')
1478
1478
1479 def __init__(self, group_name='', parent_group=None):
1479 def __init__(self, group_name='', parent_group=None):
1480 self.group_name = group_name
1480 self.group_name = group_name
1481 self.parent_group = parent_group
1481 self.parent_group = parent_group
1482
1482
1483 def __unicode__(self):
1483 def __unicode__(self):
1484 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1484 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1485 self.group_name)
1485 self.group_name)
1486
1486
1487 @classmethod
1487 @classmethod
1488 def _generate_choice(cls, repo_group):
1488 def _generate_choice(cls, repo_group):
1489 from webhelpers.html import literal as _literal
1489 from webhelpers.html import literal as _literal
1490 _name = lambda k: _literal(cls.SEP.join(k))
1490 _name = lambda k: _literal(cls.SEP.join(k))
1491 return repo_group.group_id, _name(repo_group.full_path_splitted)
1491 return repo_group.group_id, _name(repo_group.full_path_splitted)
1492
1492
1493 @classmethod
1493 @classmethod
1494 def groups_choices(cls, groups=None, show_empty_group=True):
1494 def groups_choices(cls, groups=None, show_empty_group=True):
1495 if not groups:
1495 if not groups:
1496 groups = cls.query().all()
1496 groups = cls.query().all()
1497
1497
1498 repo_groups = []
1498 repo_groups = []
1499 if show_empty_group:
1499 if show_empty_group:
1500 repo_groups = [('-1', u'-- %s --' % _('top level'))]
1500 repo_groups = [('-1', u'-- %s --' % _('top level'))]
1501
1501
1502 repo_groups.extend([cls._generate_choice(x) for x in groups])
1502 repo_groups.extend([cls._generate_choice(x) for x in groups])
1503
1503
1504 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0])
1504 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0])
1505 return repo_groups
1505 return repo_groups
1506
1506
1507 @classmethod
1507 @classmethod
1508 def url_sep(cls):
1508 def url_sep(cls):
1509 return URL_SEP
1509 return URL_SEP
1510
1510
1511 @classmethod
1511 @classmethod
1512 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1512 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1513 if case_insensitive:
1513 if case_insensitive:
1514 gr = cls.query()\
1514 gr = cls.query()\
1515 .filter(cls.group_name.ilike(group_name))
1515 .filter(cls.group_name.ilike(group_name))
1516 else:
1516 else:
1517 gr = cls.query()\
1517 gr = cls.query()\
1518 .filter(cls.group_name == group_name)
1518 .filter(cls.group_name == group_name)
1519 if cache:
1519 if cache:
1520 gr = gr.options(FromCache(
1520 gr = gr.options(FromCache(
1521 "sql_cache_short",
1521 "sql_cache_short",
1522 "get_group_%s" % _hash_key(group_name)
1522 "get_group_%s" % _hash_key(group_name)
1523 )
1523 )
1524 )
1524 )
1525 return gr.scalar()
1525 return gr.scalar()
1526
1526
1527 @property
1527 @property
1528 def parents(self):
1528 def parents(self):
1529 parents_recursion_limit = 10
1529 parents_recursion_limit = 10
1530 groups = []
1530 groups = []
1531 if self.parent_group is None:
1531 if self.parent_group is None:
1532 return groups
1532 return groups
1533 cur_gr = self.parent_group
1533 cur_gr = self.parent_group
1534 groups.insert(0, cur_gr)
1534 groups.insert(0, cur_gr)
1535 cnt = 0
1535 cnt = 0
1536 while 1:
1536 while 1:
1537 cnt += 1
1537 cnt += 1
1538 gr = getattr(cur_gr, 'parent_group', None)
1538 gr = getattr(cur_gr, 'parent_group', None)
1539 cur_gr = cur_gr.parent_group
1539 cur_gr = cur_gr.parent_group
1540 if gr is None:
1540 if gr is None:
1541 break
1541 break
1542 if cnt == parents_recursion_limit:
1542 if cnt == parents_recursion_limit:
1543 # this will prevent accidental infinit loops
1543 # this will prevent accidental infinite loops
1544 log.error(('more than %s parents found for group %s, stopping '
1544 log.error(('more than %s parents found for group %s, stopping '
1545 'recursive parent fetching' % (parents_recursion_limit, self)))
1545 'recursive parent fetching' % (parents_recursion_limit, self)))
1546 break
1546 break
1547
1547
1548 groups.insert(0, gr)
1548 groups.insert(0, gr)
1549 return groups
1549 return groups
1550
1550
1551 @property
1551 @property
1552 def children(self):
1552 def children(self):
1553 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1553 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1554
1554
1555 @property
1555 @property
1556 def name(self):
1556 def name(self):
1557 return self.group_name.split(RepoGroup.url_sep())[-1]
1557 return self.group_name.split(RepoGroup.url_sep())[-1]
1558
1558
1559 @property
1559 @property
1560 def full_path(self):
1560 def full_path(self):
1561 return self.group_name
1561 return self.group_name
1562
1562
1563 @property
1563 @property
1564 def full_path_splitted(self):
1564 def full_path_splitted(self):
1565 return self.group_name.split(RepoGroup.url_sep())
1565 return self.group_name.split(RepoGroup.url_sep())
1566
1566
1567 @property
1567 @property
1568 def repositories(self):
1568 def repositories(self):
1569 return Repository.query()\
1569 return Repository.query()\
1570 .filter(Repository.group == self)\
1570 .filter(Repository.group == self)\
1571 .order_by(Repository.repo_name)
1571 .order_by(Repository.repo_name)
1572
1572
1573 @property
1573 @property
1574 def repositories_recursive_count(self):
1574 def repositories_recursive_count(self):
1575 cnt = self.repositories.count()
1575 cnt = self.repositories.count()
1576
1576
1577 def children_count(group):
1577 def children_count(group):
1578 cnt = 0
1578 cnt = 0
1579 for child in group.children:
1579 for child in group.children:
1580 cnt += child.repositories.count()
1580 cnt += child.repositories.count()
1581 cnt += children_count(child)
1581 cnt += children_count(child)
1582 return cnt
1582 return cnt
1583
1583
1584 return cnt + children_count(self)
1584 return cnt + children_count(self)
1585
1585
1586 def _recursive_objects(self, include_repos=True):
1586 def _recursive_objects(self, include_repos=True):
1587 all_ = []
1587 all_ = []
1588
1588
1589 def _get_members(root_gr):
1589 def _get_members(root_gr):
1590 if include_repos:
1590 if include_repos:
1591 for r in root_gr.repositories:
1591 for r in root_gr.repositories:
1592 all_.append(r)
1592 all_.append(r)
1593 childs = root_gr.children.all()
1593 childs = root_gr.children.all()
1594 if childs:
1594 if childs:
1595 for gr in childs:
1595 for gr in childs:
1596 all_.append(gr)
1596 all_.append(gr)
1597 _get_members(gr)
1597 _get_members(gr)
1598
1598
1599 _get_members(self)
1599 _get_members(self)
1600 return [self] + all_
1600 return [self] + all_
1601
1601
1602 def recursive_groups_and_repos(self):
1602 def recursive_groups_and_repos(self):
1603 """
1603 """
1604 Recursive return all groups, with repositories in those groups
1604 Recursive return all groups, with repositories in those groups
1605 """
1605 """
1606 return self._recursive_objects()
1606 return self._recursive_objects()
1607
1607
1608 def recursive_groups(self):
1608 def recursive_groups(self):
1609 """
1609 """
1610 Returns all children groups for this group including children of children
1610 Returns all children groups for this group including children of children
1611 """
1611 """
1612 return self._recursive_objects(include_repos=False)
1612 return self._recursive_objects(include_repos=False)
1613
1613
1614 def get_new_name(self, group_name):
1614 def get_new_name(self, group_name):
1615 """
1615 """
1616 returns new full group name based on parent and new name
1616 returns new full group name based on parent and new name
1617
1617
1618 :param group_name:
1618 :param group_name:
1619 """
1619 """
1620 path_prefix = (self.parent_group.full_path_splitted if
1620 path_prefix = (self.parent_group.full_path_splitted if
1621 self.parent_group else [])
1621 self.parent_group else [])
1622 return RepoGroup.url_sep().join(path_prefix + [group_name])
1622 return RepoGroup.url_sep().join(path_prefix + [group_name])
1623
1623
1624 def get_api_data(self):
1624 def get_api_data(self):
1625 """
1625 """
1626 Common function for generating api data
1626 Common function for generating api data
1627
1627
1628 """
1628 """
1629 group = self
1629 group = self
1630 data = dict(
1630 data = dict(
1631 group_id=group.group_id,
1631 group_id=group.group_id,
1632 group_name=group.group_name,
1632 group_name=group.group_name,
1633 group_description=group.group_description,
1633 group_description=group.group_description,
1634 parent_group=group.parent_group.group_name if group.parent_group else None,
1634 parent_group=group.parent_group.group_name if group.parent_group else None,
1635 repositories=[x.repo_name for x in group.repositories],
1635 repositories=[x.repo_name for x in group.repositories],
1636 owner=group.user.username
1636 owner=group.user.username
1637 )
1637 )
1638 return data
1638 return data
1639
1639
1640
1640
1641 class Permission(Base, BaseModel):
1641 class Permission(Base, BaseModel):
1642 __tablename__ = 'permissions'
1642 __tablename__ = 'permissions'
1643 __table_args__ = (
1643 __table_args__ = (
1644 Index('p_perm_name_idx', 'permission_name'),
1644 Index('p_perm_name_idx', 'permission_name'),
1645 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1645 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1646 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1646 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1647 )
1647 )
1648 PERMS = [
1648 PERMS = [
1649 ('hg.admin', _('Kallithea Administrator')),
1649 ('hg.admin', _('Kallithea Administrator')),
1650
1650
1651 ('repository.none', _('Repository no access')),
1651 ('repository.none', _('Repository no access')),
1652 ('repository.read', _('Repository read access')),
1652 ('repository.read', _('Repository read access')),
1653 ('repository.write', _('Repository write access')),
1653 ('repository.write', _('Repository write access')),
1654 ('repository.admin', _('Repository admin access')),
1654 ('repository.admin', _('Repository admin access')),
1655
1655
1656 ('group.none', _('Repository group no access')),
1656 ('group.none', _('Repository group no access')),
1657 ('group.read', _('Repository group read access')),
1657 ('group.read', _('Repository group read access')),
1658 ('group.write', _('Repository group write access')),
1658 ('group.write', _('Repository group write access')),
1659 ('group.admin', _('Repository group admin access')),
1659 ('group.admin', _('Repository group admin access')),
1660
1660
1661 ('usergroup.none', _('User group no access')),
1661 ('usergroup.none', _('User group no access')),
1662 ('usergroup.read', _('User group read access')),
1662 ('usergroup.read', _('User group read access')),
1663 ('usergroup.write', _('User group write access')),
1663 ('usergroup.write', _('User group write access')),
1664 ('usergroup.admin', _('User group admin access')),
1664 ('usergroup.admin', _('User group admin access')),
1665
1665
1666 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1666 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1667 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1667 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1668
1668
1669 ('hg.usergroup.create.false', _('User Group creation disabled')),
1669 ('hg.usergroup.create.false', _('User Group creation disabled')),
1670 ('hg.usergroup.create.true', _('User Group creation enabled')),
1670 ('hg.usergroup.create.true', _('User Group creation enabled')),
1671
1671
1672 ('hg.create.none', _('Repository creation disabled')),
1672 ('hg.create.none', _('Repository creation disabled')),
1673 ('hg.create.repository', _('Repository creation enabled')),
1673 ('hg.create.repository', _('Repository creation enabled')),
1674 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
1674 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
1675 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
1675 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
1676
1676
1677 ('hg.fork.none', _('Repository forking disabled')),
1677 ('hg.fork.none', _('Repository forking disabled')),
1678 ('hg.fork.repository', _('Repository forking enabled')),
1678 ('hg.fork.repository', _('Repository forking enabled')),
1679
1679
1680 ('hg.register.none', _('Registration disabled')),
1680 ('hg.register.none', _('Registration disabled')),
1681 ('hg.register.manual_activate', _('User Registration with manual account activation')),
1681 ('hg.register.manual_activate', _('User Registration with manual account activation')),
1682 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1682 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1683
1683
1684 ('hg.extern_activate.manual', _('Manual activation of external account')),
1684 ('hg.extern_activate.manual', _('Manual activation of external account')),
1685 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1685 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1686
1686
1687 ]
1687 ]
1688
1688
1689 #definition of system default permissions for DEFAULT user
1689 #definition of system default permissions for DEFAULT user
1690 DEFAULT_USER_PERMISSIONS = [
1690 DEFAULT_USER_PERMISSIONS = [
1691 'repository.read',
1691 'repository.read',
1692 'group.read',
1692 'group.read',
1693 'usergroup.read',
1693 'usergroup.read',
1694 'hg.create.repository',
1694 'hg.create.repository',
1695 'hg.create.write_on_repogroup.true',
1695 'hg.create.write_on_repogroup.true',
1696 'hg.fork.repository',
1696 'hg.fork.repository',
1697 'hg.register.manual_activate',
1697 'hg.register.manual_activate',
1698 'hg.extern_activate.auto',
1698 'hg.extern_activate.auto',
1699 ]
1699 ]
1700
1700
1701 # defines which permissions are more important higher the more important
1701 # defines which permissions are more important higher the more important
1702 # Weight defines which permissions are more important.
1702 # Weight defines which permissions are more important.
1703 # The higher number the more important.
1703 # The higher number the more important.
1704 PERM_WEIGHTS = {
1704 PERM_WEIGHTS = {
1705 'repository.none': 0,
1705 'repository.none': 0,
1706 'repository.read': 1,
1706 'repository.read': 1,
1707 'repository.write': 3,
1707 'repository.write': 3,
1708 'repository.admin': 4,
1708 'repository.admin': 4,
1709
1709
1710 'group.none': 0,
1710 'group.none': 0,
1711 'group.read': 1,
1711 'group.read': 1,
1712 'group.write': 3,
1712 'group.write': 3,
1713 'group.admin': 4,
1713 'group.admin': 4,
1714
1714
1715 'usergroup.none': 0,
1715 'usergroup.none': 0,
1716 'usergroup.read': 1,
1716 'usergroup.read': 1,
1717 'usergroup.write': 3,
1717 'usergroup.write': 3,
1718 'usergroup.admin': 4,
1718 'usergroup.admin': 4,
1719 'hg.repogroup.create.false': 0,
1719 'hg.repogroup.create.false': 0,
1720 'hg.repogroup.create.true': 1,
1720 'hg.repogroup.create.true': 1,
1721
1721
1722 'hg.usergroup.create.false': 0,
1722 'hg.usergroup.create.false': 0,
1723 'hg.usergroup.create.true': 1,
1723 'hg.usergroup.create.true': 1,
1724
1724
1725 'hg.fork.none': 0,
1725 'hg.fork.none': 0,
1726 'hg.fork.repository': 1,
1726 'hg.fork.repository': 1,
1727 'hg.create.none': 0,
1727 'hg.create.none': 0,
1728 'hg.create.repository': 1
1728 'hg.create.repository': 1
1729 }
1729 }
1730
1730
1731 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1731 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1732 permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
1732 permission_name = Column("permission_name", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
1733 permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
1733 permission_longname = Column("permission_longname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
1734
1734
1735 def __unicode__(self):
1735 def __unicode__(self):
1736 return u"<%s('%s:%s')>" % (
1736 return u"<%s('%s:%s')>" % (
1737 self.__class__.__name__, self.permission_id, self.permission_name
1737 self.__class__.__name__, self.permission_id, self.permission_name
1738 )
1738 )
1739
1739
1740 @classmethod
1740 @classmethod
1741 def get_by_key(cls, key):
1741 def get_by_key(cls, key):
1742 return cls.query().filter(cls.permission_name == key).scalar()
1742 return cls.query().filter(cls.permission_name == key).scalar()
1743
1743
1744 @classmethod
1744 @classmethod
1745 def get_default_perms(cls, default_user_id):
1745 def get_default_perms(cls, default_user_id):
1746 q = Session().query(UserRepoToPerm, Repository, cls)\
1746 q = Session().query(UserRepoToPerm, Repository, cls)\
1747 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1747 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1748 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1748 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1749 .filter(UserRepoToPerm.user_id == default_user_id)
1749 .filter(UserRepoToPerm.user_id == default_user_id)
1750
1750
1751 return q.all()
1751 return q.all()
1752
1752
1753 @classmethod
1753 @classmethod
1754 def get_default_group_perms(cls, default_user_id):
1754 def get_default_group_perms(cls, default_user_id):
1755 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1755 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1756 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1756 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1757 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1757 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1758 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1758 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1759
1759
1760 return q.all()
1760 return q.all()
1761
1761
1762 @classmethod
1762 @classmethod
1763 def get_default_user_group_perms(cls, default_user_id):
1763 def get_default_user_group_perms(cls, default_user_id):
1764 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1764 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1765 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1765 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1766 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1766 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1767 .filter(UserUserGroupToPerm.user_id == default_user_id)
1767 .filter(UserUserGroupToPerm.user_id == default_user_id)
1768
1768
1769 return q.all()
1769 return q.all()
1770
1770
1771
1771
1772 class UserRepoToPerm(Base, BaseModel):
1772 class UserRepoToPerm(Base, BaseModel):
1773 __tablename__ = 'repo_to_perm'
1773 __tablename__ = 'repo_to_perm'
1774 __table_args__ = (
1774 __table_args__ = (
1775 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1775 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1776 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1776 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1777 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1777 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1778 )
1778 )
1779 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1779 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1780 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1780 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1781 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1781 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1782 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1782 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1783
1783
1784 user = relationship('User')
1784 user = relationship('User')
1785 repository = relationship('Repository')
1785 repository = relationship('Repository')
1786 permission = relationship('Permission')
1786 permission = relationship('Permission')
1787
1787
1788 @classmethod
1788 @classmethod
1789 def create(cls, user, repository, permission):
1789 def create(cls, user, repository, permission):
1790 n = cls()
1790 n = cls()
1791 n.user = user
1791 n.user = user
1792 n.repository = repository
1792 n.repository = repository
1793 n.permission = permission
1793 n.permission = permission
1794 Session().add(n)
1794 Session().add(n)
1795 return n
1795 return n
1796
1796
1797 def __unicode__(self):
1797 def __unicode__(self):
1798 return u'<%s => %s >' % (self.user, self.repository)
1798 return u'<%s => %s >' % (self.user, self.repository)
1799
1799
1800
1800
1801 class UserUserGroupToPerm(Base, BaseModel):
1801 class UserUserGroupToPerm(Base, BaseModel):
1802 __tablename__ = 'user_user_group_to_perm'
1802 __tablename__ = 'user_user_group_to_perm'
1803 __table_args__ = (
1803 __table_args__ = (
1804 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1804 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1805 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1805 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1806 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1806 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1807 )
1807 )
1808 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1808 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1809 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1809 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1810 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1810 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1811 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1811 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1812
1812
1813 user = relationship('User')
1813 user = relationship('User')
1814 user_group = relationship('UserGroup')
1814 user_group = relationship('UserGroup')
1815 permission = relationship('Permission')
1815 permission = relationship('Permission')
1816
1816
1817 @classmethod
1817 @classmethod
1818 def create(cls, user, user_group, permission):
1818 def create(cls, user, user_group, permission):
1819 n = cls()
1819 n = cls()
1820 n.user = user
1820 n.user = user
1821 n.user_group = user_group
1821 n.user_group = user_group
1822 n.permission = permission
1822 n.permission = permission
1823 Session().add(n)
1823 Session().add(n)
1824 return n
1824 return n
1825
1825
1826 def __unicode__(self):
1826 def __unicode__(self):
1827 return u'<%s => %s >' % (self.user, self.user_group)
1827 return u'<%s => %s >' % (self.user, self.user_group)
1828
1828
1829
1829
1830 class UserToPerm(Base, BaseModel):
1830 class UserToPerm(Base, BaseModel):
1831 __tablename__ = 'user_to_perm'
1831 __tablename__ = 'user_to_perm'
1832 __table_args__ = (
1832 __table_args__ = (
1833 UniqueConstraint('user_id', 'permission_id'),
1833 UniqueConstraint('user_id', 'permission_id'),
1834 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1834 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1835 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1835 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1836 )
1836 )
1837 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1837 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1838 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1838 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1839 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1839 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1840
1840
1841 user = relationship('User')
1841 user = relationship('User')
1842 permission = relationship('Permission')
1842 permission = relationship('Permission')
1843
1843
1844 def __unicode__(self):
1844 def __unicode__(self):
1845 return u'<%s => %s >' % (self.user, self.permission)
1845 return u'<%s => %s >' % (self.user, self.permission)
1846
1846
1847
1847
1848 class UserGroupRepoToPerm(Base, BaseModel):
1848 class UserGroupRepoToPerm(Base, BaseModel):
1849 __tablename__ = 'users_group_repo_to_perm'
1849 __tablename__ = 'users_group_repo_to_perm'
1850 __table_args__ = (
1850 __table_args__ = (
1851 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1851 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1852 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1852 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1853 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1853 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1854 )
1854 )
1855 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1855 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1856 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1856 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1857 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1857 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1858 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1858 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1859
1859
1860 users_group = relationship('UserGroup')
1860 users_group = relationship('UserGroup')
1861 permission = relationship('Permission')
1861 permission = relationship('Permission')
1862 repository = relationship('Repository')
1862 repository = relationship('Repository')
1863
1863
1864 @classmethod
1864 @classmethod
1865 def create(cls, users_group, repository, permission):
1865 def create(cls, users_group, repository, permission):
1866 n = cls()
1866 n = cls()
1867 n.users_group = users_group
1867 n.users_group = users_group
1868 n.repository = repository
1868 n.repository = repository
1869 n.permission = permission
1869 n.permission = permission
1870 Session().add(n)
1870 Session().add(n)
1871 return n
1871 return n
1872
1872
1873 def __unicode__(self):
1873 def __unicode__(self):
1874 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1874 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1875
1875
1876
1876
1877 class UserGroupUserGroupToPerm(Base, BaseModel):
1877 class UserGroupUserGroupToPerm(Base, BaseModel):
1878 __tablename__ = 'user_group_user_group_to_perm'
1878 __tablename__ = 'user_group_user_group_to_perm'
1879 __table_args__ = (
1879 __table_args__ = (
1880 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1880 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1881 CheckConstraint('target_user_group_id != user_group_id'),
1881 CheckConstraint('target_user_group_id != user_group_id'),
1882 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1882 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1883 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1883 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1884 )
1884 )
1885 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)
1885 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)
1886 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1886 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1887 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1887 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1888 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1888 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1889
1889
1890 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1890 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1891 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1891 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1892 permission = relationship('Permission')
1892 permission = relationship('Permission')
1893
1893
1894 @classmethod
1894 @classmethod
1895 def create(cls, target_user_group, user_group, permission):
1895 def create(cls, target_user_group, user_group, permission):
1896 n = cls()
1896 n = cls()
1897 n.target_user_group = target_user_group
1897 n.target_user_group = target_user_group
1898 n.user_group = user_group
1898 n.user_group = user_group
1899 n.permission = permission
1899 n.permission = permission
1900 Session().add(n)
1900 Session().add(n)
1901 return n
1901 return n
1902
1902
1903 def __unicode__(self):
1903 def __unicode__(self):
1904 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1904 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1905
1905
1906
1906
1907 class UserGroupToPerm(Base, BaseModel):
1907 class UserGroupToPerm(Base, BaseModel):
1908 __tablename__ = 'users_group_to_perm'
1908 __tablename__ = 'users_group_to_perm'
1909 __table_args__ = (
1909 __table_args__ = (
1910 UniqueConstraint('users_group_id', 'permission_id',),
1910 UniqueConstraint('users_group_id', 'permission_id',),
1911 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1911 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1912 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1912 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1913 )
1913 )
1914 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1914 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1915 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1915 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1916 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1916 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1917
1917
1918 users_group = relationship('UserGroup')
1918 users_group = relationship('UserGroup')
1919 permission = relationship('Permission')
1919 permission = relationship('Permission')
1920
1920
1921
1921
1922 class UserRepoGroupToPerm(Base, BaseModel):
1922 class UserRepoGroupToPerm(Base, BaseModel):
1923 __tablename__ = 'user_repo_group_to_perm'
1923 __tablename__ = 'user_repo_group_to_perm'
1924 __table_args__ = (
1924 __table_args__ = (
1925 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1925 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1926 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1926 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1927 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1927 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1928 )
1928 )
1929
1929
1930 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1930 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1931 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1931 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1932 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1932 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1933 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1933 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1934
1934
1935 user = relationship('User')
1935 user = relationship('User')
1936 group = relationship('RepoGroup')
1936 group = relationship('RepoGroup')
1937 permission = relationship('Permission')
1937 permission = relationship('Permission')
1938
1938
1939 @classmethod
1939 @classmethod
1940 def create(cls, user, repository_group, permission):
1940 def create(cls, user, repository_group, permission):
1941 n = cls()
1941 n = cls()
1942 n.user = user
1942 n.user = user
1943 n.group = repository_group
1943 n.group = repository_group
1944 n.permission = permission
1944 n.permission = permission
1945 Session().add(n)
1945 Session().add(n)
1946 return n
1946 return n
1947
1947
1948
1948
1949 class UserGroupRepoGroupToPerm(Base, BaseModel):
1949 class UserGroupRepoGroupToPerm(Base, BaseModel):
1950 __tablename__ = 'users_group_repo_group_to_perm'
1950 __tablename__ = 'users_group_repo_group_to_perm'
1951 __table_args__ = (
1951 __table_args__ = (
1952 UniqueConstraint('users_group_id', 'group_id'),
1952 UniqueConstraint('users_group_id', 'group_id'),
1953 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1953 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1954 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1954 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1955 )
1955 )
1956
1956
1957 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)
1957 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)
1958 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1958 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1959 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1959 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1960 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1960 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1961
1961
1962 users_group = relationship('UserGroup')
1962 users_group = relationship('UserGroup')
1963 permission = relationship('Permission')
1963 permission = relationship('Permission')
1964 group = relationship('RepoGroup')
1964 group = relationship('RepoGroup')
1965
1965
1966 @classmethod
1966 @classmethod
1967 def create(cls, user_group, repository_group, permission):
1967 def create(cls, user_group, repository_group, permission):
1968 n = cls()
1968 n = cls()
1969 n.users_group = user_group
1969 n.users_group = user_group
1970 n.group = repository_group
1970 n.group = repository_group
1971 n.permission = permission
1971 n.permission = permission
1972 Session().add(n)
1972 Session().add(n)
1973 return n
1973 return n
1974
1974
1975
1975
1976 class Statistics(Base, BaseModel):
1976 class Statistics(Base, BaseModel):
1977 __tablename__ = 'statistics'
1977 __tablename__ = 'statistics'
1978 __table_args__ = (
1978 __table_args__ = (
1979 UniqueConstraint('repository_id'),
1979 UniqueConstraint('repository_id'),
1980 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1980 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1981 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1981 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1982 )
1982 )
1983 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1983 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1984 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1984 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1985 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1985 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1986 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1986 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1987 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1987 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1988 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1988 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1989
1989
1990 repository = relationship('Repository', single_parent=True)
1990 repository = relationship('Repository', single_parent=True)
1991
1991
1992
1992
1993 class UserFollowing(Base, BaseModel):
1993 class UserFollowing(Base, BaseModel):
1994 __tablename__ = 'user_followings'
1994 __tablename__ = 'user_followings'
1995 __table_args__ = (
1995 __table_args__ = (
1996 UniqueConstraint('user_id', 'follows_repository_id'),
1996 UniqueConstraint('user_id', 'follows_repository_id'),
1997 UniqueConstraint('user_id', 'follows_user_id'),
1997 UniqueConstraint('user_id', 'follows_user_id'),
1998 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1998 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1999 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1999 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2000 )
2000 )
2001
2001
2002 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2002 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2003 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2003 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2004 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2004 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2005 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2005 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2006 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2006 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2007
2007
2008 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2008 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2009
2009
2010 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2010 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2011 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2011 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2012
2012
2013 @classmethod
2013 @classmethod
2014 def get_repo_followers(cls, repo_id):
2014 def get_repo_followers(cls, repo_id):
2015 return cls.query().filter(cls.follows_repo_id == repo_id)
2015 return cls.query().filter(cls.follows_repo_id == repo_id)
2016
2016
2017
2017
2018 class CacheInvalidation(Base, BaseModel):
2018 class CacheInvalidation(Base, BaseModel):
2019 __tablename__ = 'cache_invalidation'
2019 __tablename__ = 'cache_invalidation'
2020 __table_args__ = (
2020 __table_args__ = (
2021 UniqueConstraint('cache_key'),
2021 UniqueConstraint('cache_key'),
2022 Index('key_idx', 'cache_key'),
2022 Index('key_idx', 'cache_key'),
2023 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2023 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2024 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2024 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2025 )
2025 )
2026 # cache_id, not used
2026 # cache_id, not used
2027 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2027 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2028 # cache_key as created by _get_cache_key
2028 # cache_key as created by _get_cache_key
2029 cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
2029 cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
2030 # cache_args is a repo_name
2030 # cache_args is a repo_name
2031 cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
2031 cache_args = Column("cache_args", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
2032 # instance sets cache_active True when it is caching,
2032 # instance sets cache_active True when it is caching,
2033 # other instances set cache_active to False to indicate that this cache is invalid
2033 # other instances set cache_active to False to indicate that this cache is invalid
2034 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2034 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2035
2035
2036 def __init__(self, cache_key, repo_name=''):
2036 def __init__(self, cache_key, repo_name=''):
2037 self.cache_key = cache_key
2037 self.cache_key = cache_key
2038 self.cache_args = repo_name
2038 self.cache_args = repo_name
2039 self.cache_active = False
2039 self.cache_active = False
2040
2040
2041 def __unicode__(self):
2041 def __unicode__(self):
2042 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
2042 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
2043 self.cache_id, self.cache_key, self.cache_active)
2043 self.cache_id, self.cache_key, self.cache_active)
2044
2044
2045 def _cache_key_partition(self):
2045 def _cache_key_partition(self):
2046 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2046 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2047 return prefix, repo_name, suffix
2047 return prefix, repo_name, suffix
2048
2048
2049 def get_prefix(self):
2049 def get_prefix(self):
2050 """
2050 """
2051 get prefix that might have been used in _get_cache_key to
2051 get prefix that might have been used in _get_cache_key to
2052 generate self.cache_key. Only used for informational purposes
2052 generate self.cache_key. Only used for informational purposes
2053 in repo_edit.html.
2053 in repo_edit.html.
2054 """
2054 """
2055 # prefix, repo_name, suffix
2055 # prefix, repo_name, suffix
2056 return self._cache_key_partition()[0]
2056 return self._cache_key_partition()[0]
2057
2057
2058 def get_suffix(self):
2058 def get_suffix(self):
2059 """
2059 """
2060 get suffix that might have been used in _get_cache_key to
2060 get suffix that might have been used in _get_cache_key to
2061 generate self.cache_key. Only used for informational purposes
2061 generate self.cache_key. Only used for informational purposes
2062 in repo_edit.html.
2062 in repo_edit.html.
2063 """
2063 """
2064 # prefix, repo_name, suffix
2064 # prefix, repo_name, suffix
2065 return self._cache_key_partition()[2]
2065 return self._cache_key_partition()[2]
2066
2066
2067 @classmethod
2067 @classmethod
2068 def clear_cache(cls):
2068 def clear_cache(cls):
2069 """
2069 """
2070 Delete all cache keys from database.
2070 Delete all cache keys from database.
2071 Should only be run when all instances are down and all entries thus stale.
2071 Should only be run when all instances are down and all entries thus stale.
2072 """
2072 """
2073 cls.query().delete()
2073 cls.query().delete()
2074 Session().commit()
2074 Session().commit()
2075
2075
2076 @classmethod
2076 @classmethod
2077 def _get_cache_key(cls, key):
2077 def _get_cache_key(cls, key):
2078 """
2078 """
2079 Wrapper for generating a unique cache key for this instance and "key".
2079 Wrapper for generating a unique cache key for this instance and "key".
2080 key must / will start with a repo_name which will be stored in .cache_args .
2080 key must / will start with a repo_name which will be stored in .cache_args .
2081 """
2081 """
2082 import kallithea
2082 import kallithea
2083 prefix = kallithea.CONFIG.get('instance_id', '')
2083 prefix = kallithea.CONFIG.get('instance_id', '')
2084 return "%s%s" % (prefix, key)
2084 return "%s%s" % (prefix, key)
2085
2085
2086 @classmethod
2086 @classmethod
2087 def set_invalidate(cls, repo_name, delete=False):
2087 def set_invalidate(cls, repo_name, delete=False):
2088 """
2088 """
2089 Mark all caches of a repo as invalid in the database.
2089 Mark all caches of a repo as invalid in the database.
2090 """
2090 """
2091 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
2091 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
2092 log.debug('for repo %s got %s invalidation objects'
2092 log.debug('for repo %s got %s invalidation objects'
2093 % (safe_str(repo_name), inv_objs))
2093 % (safe_str(repo_name), inv_objs))
2094
2094
2095 for inv_obj in inv_objs:
2095 for inv_obj in inv_objs:
2096 log.debug('marking %s key for invalidation based on repo_name=%s'
2096 log.debug('marking %s key for invalidation based on repo_name=%s'
2097 % (inv_obj, safe_str(repo_name)))
2097 % (inv_obj, safe_str(repo_name)))
2098 if delete:
2098 if delete:
2099 Session().delete(inv_obj)
2099 Session().delete(inv_obj)
2100 else:
2100 else:
2101 inv_obj.cache_active = False
2101 inv_obj.cache_active = False
2102 Session().add(inv_obj)
2102 Session().add(inv_obj)
2103 Session().commit()
2103 Session().commit()
2104
2104
2105 @classmethod
2105 @classmethod
2106 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
2106 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
2107 """
2107 """
2108 Mark this cache key as active and currently cached.
2108 Mark this cache key as active and currently cached.
2109 Return True if the existing cache registration still was valid.
2109 Return True if the existing cache registration still was valid.
2110 Return False to indicate that it had been invalidated and caches should be refreshed.
2110 Return False to indicate that it had been invalidated and caches should be refreshed.
2111 """
2111 """
2112
2112
2113 key = (repo_name + '_' + kind) if kind else repo_name
2113 key = (repo_name + '_' + kind) if kind else repo_name
2114 cache_key = cls._get_cache_key(key)
2114 cache_key = cls._get_cache_key(key)
2115
2115
2116 if valid_cache_keys and cache_key in valid_cache_keys:
2116 if valid_cache_keys and cache_key in valid_cache_keys:
2117 return True
2117 return True
2118
2118
2119 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2119 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2120 if not inv_obj:
2120 if not inv_obj:
2121 inv_obj = CacheInvalidation(cache_key, repo_name)
2121 inv_obj = CacheInvalidation(cache_key, repo_name)
2122 if inv_obj.cache_active:
2122 if inv_obj.cache_active:
2123 return True
2123 return True
2124 inv_obj.cache_active = True
2124 inv_obj.cache_active = True
2125 Session().add(inv_obj)
2125 Session().add(inv_obj)
2126 Session().commit()
2126 Session().commit()
2127 return False
2127 return False
2128
2128
2129 @classmethod
2129 @classmethod
2130 def get_valid_cache_keys(cls):
2130 def get_valid_cache_keys(cls):
2131 """
2131 """
2132 Return opaque object with information of which caches still are valid
2132 Return opaque object with information of which caches still are valid
2133 and can be used without checking for invalidation.
2133 and can be used without checking for invalidation.
2134 """
2134 """
2135 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
2135 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
2136
2136
2137
2137
2138 class ChangesetComment(Base, BaseModel):
2138 class ChangesetComment(Base, BaseModel):
2139 __tablename__ = 'changeset_comments'
2139 __tablename__ = 'changeset_comments'
2140 __table_args__ = (
2140 __table_args__ = (
2141 Index('cc_revision_idx', 'revision'),
2141 Index('cc_revision_idx', 'revision'),
2142 Index('cc_pull_request_id_idx', 'pull_request_id'),
2142 Index('cc_pull_request_id_idx', 'pull_request_id'),
2143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2145 )
2145 )
2146 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2146 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2147 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2147 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2148 revision = Column('revision', String(40), nullable=True)
2148 revision = Column('revision', String(40), nullable=True)
2149 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2149 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2150 line_no = Column('line_no', Unicode(10), nullable=True)
2150 line_no = Column('line_no', Unicode(10), nullable=True)
2151 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2151 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2152 f_path = Column('f_path', Unicode(1000), nullable=True)
2152 f_path = Column('f_path', Unicode(1000), nullable=True)
2153 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2153 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2154 text = Column('text', UnicodeText(25000), nullable=False)
2154 text = Column('text', UnicodeText(25000), nullable=False)
2155 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2155 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2156 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2156 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2157
2157
2158 author = relationship('User')
2158 author = relationship('User')
2159 repo = relationship('Repository')
2159 repo = relationship('Repository')
2160 # status_change is frequently used directly in templates - make it a lazy
2160 # status_change is frequently used directly in templates - make it a lazy
2161 # join to avoid fetching each related ChangesetStatus on demand.
2161 # join to avoid fetching each related ChangesetStatus on demand.
2162 # There will only be one ChangesetStatus referencing each comment so the join will not explode.
2162 # There will only be one ChangesetStatus referencing each comment so the join will not explode.
2163 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
2163 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
2164 pull_request = relationship('PullRequest')
2164 pull_request = relationship('PullRequest')
2165
2165
2166 @classmethod
2166 @classmethod
2167 def get_users(cls, revision=None, pull_request_id=None):
2167 def get_users(cls, revision=None, pull_request_id=None):
2168 """
2168 """
2169 Returns user associated with this ChangesetComment. ie those
2169 Returns user associated with this ChangesetComment. ie those
2170 who actually commented
2170 who actually commented
2171
2171
2172 :param cls:
2172 :param cls:
2173 :param revision:
2173 :param revision:
2174 """
2174 """
2175 q = Session().query(User)\
2175 q = Session().query(User)\
2176 .join(ChangesetComment.author)
2176 .join(ChangesetComment.author)
2177 if revision is not None:
2177 if revision is not None:
2178 q = q.filter(cls.revision == revision)
2178 q = q.filter(cls.revision == revision)
2179 elif pull_request_id is not None:
2179 elif pull_request_id is not None:
2180 q = q.filter(cls.pull_request_id == pull_request_id)
2180 q = q.filter(cls.pull_request_id == pull_request_id)
2181 return q.all()
2181 return q.all()
2182
2182
2183
2183
2184 class ChangesetStatus(Base, BaseModel):
2184 class ChangesetStatus(Base, BaseModel):
2185 __tablename__ = 'changeset_statuses'
2185 __tablename__ = 'changeset_statuses'
2186 __table_args__ = (
2186 __table_args__ = (
2187 Index('cs_revision_idx', 'revision'),
2187 Index('cs_revision_idx', 'revision'),
2188 Index('cs_version_idx', 'version'),
2188 Index('cs_version_idx', 'version'),
2189 Index('cs_pull_request_id_idx', 'pull_request_id'),
2189 Index('cs_pull_request_id_idx', 'pull_request_id'),
2190 Index('cs_changeset_comment_id_idx', 'changeset_comment_id'),
2190 Index('cs_changeset_comment_id_idx', 'changeset_comment_id'),
2191 Index('cs_pull_request_id_user_id_version_idx', 'pull_request_id', 'user_id', 'version'),
2191 Index('cs_pull_request_id_user_id_version_idx', 'pull_request_id', 'user_id', 'version'),
2192 UniqueConstraint('repo_id', 'revision', 'version'),
2192 UniqueConstraint('repo_id', 'revision', 'version'),
2193 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2193 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2194 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2194 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2195 )
2195 )
2196 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2196 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2197 STATUS_APPROVED = 'approved'
2197 STATUS_APPROVED = 'approved'
2198 STATUS_REJECTED = 'rejected'
2198 STATUS_REJECTED = 'rejected'
2199 STATUS_UNDER_REVIEW = 'under_review'
2199 STATUS_UNDER_REVIEW = 'under_review'
2200
2200
2201 STATUSES = [
2201 STATUSES = [
2202 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2202 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2203 (STATUS_APPROVED, _("Approved")),
2203 (STATUS_APPROVED, _("Approved")),
2204 (STATUS_REJECTED, _("Rejected")),
2204 (STATUS_REJECTED, _("Rejected")),
2205 (STATUS_UNDER_REVIEW, _("Under Review")),
2205 (STATUS_UNDER_REVIEW, _("Under Review")),
2206 ]
2206 ]
2207
2207
2208 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2208 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2209 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2209 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2210 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2210 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2211 revision = Column('revision', String(40), nullable=False)
2211 revision = Column('revision', String(40), nullable=False)
2212 status = Column('status', String(128), nullable=False, default=DEFAULT)
2212 status = Column('status', String(128), nullable=False, default=DEFAULT)
2213 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2213 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2214 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2214 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2215 version = Column('version', Integer(), nullable=False, default=0)
2215 version = Column('version', Integer(), nullable=False, default=0)
2216 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2216 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2217
2217
2218 author = relationship('User')
2218 author = relationship('User')
2219 repo = relationship('Repository')
2219 repo = relationship('Repository')
2220 comment = relationship('ChangesetComment')
2220 comment = relationship('ChangesetComment')
2221 pull_request = relationship('PullRequest')
2221 pull_request = relationship('PullRequest')
2222
2222
2223 def __unicode__(self):
2223 def __unicode__(self):
2224 return u"<%s('%s:%s')>" % (
2224 return u"<%s('%s:%s')>" % (
2225 self.__class__.__name__,
2225 self.__class__.__name__,
2226 self.status, self.author
2226 self.status, self.author
2227 )
2227 )
2228
2228
2229 @classmethod
2229 @classmethod
2230 def get_status_lbl(cls, value):
2230 def get_status_lbl(cls, value):
2231 return dict(cls.STATUSES).get(value)
2231 return dict(cls.STATUSES).get(value)
2232
2232
2233 @property
2233 @property
2234 def status_lbl(self):
2234 def status_lbl(self):
2235 return ChangesetStatus.get_status_lbl(self.status)
2235 return ChangesetStatus.get_status_lbl(self.status)
2236
2236
2237
2237
2238 class PullRequest(Base, BaseModel):
2238 class PullRequest(Base, BaseModel):
2239 __tablename__ = 'pull_requests'
2239 __tablename__ = 'pull_requests'
2240 __table_args__ = (
2240 __table_args__ = (
2241 Index('pr_org_repo_id_idx', 'org_repo_id'),
2241 Index('pr_org_repo_id_idx', 'org_repo_id'),
2242 Index('pr_other_repo_id_idx', 'other_repo_id'),
2242 Index('pr_other_repo_id_idx', 'other_repo_id'),
2243 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2243 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2244 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2244 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2245 )
2245 )
2246
2246
2247 # values for .status
2247 # values for .status
2248 STATUS_NEW = u'new'
2248 STATUS_NEW = u'new'
2249 STATUS_CLOSED = u'closed'
2249 STATUS_CLOSED = u'closed'
2250
2250
2251 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
2251 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
2252 title = Column('title', Unicode(255), nullable=True)
2252 title = Column('title', Unicode(255), nullable=True)
2253 description = Column('description', UnicodeText(10240), nullable=True)
2253 description = Column('description', UnicodeText(10240), nullable=True)
2254 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
2254 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
2255 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2255 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2256 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2256 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2257 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2257 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2258 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
2258 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
2259 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2259 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2260 org_ref = Column('org_ref', Unicode(255), nullable=False)
2260 org_ref = Column('org_ref', Unicode(255), nullable=False)
2261 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2261 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2262 other_ref = Column('other_ref', Unicode(255), nullable=False)
2262 other_ref = Column('other_ref', Unicode(255), nullable=False)
2263
2263
2264 @hybrid_property
2264 @hybrid_property
2265 def revisions(self):
2265 def revisions(self):
2266 return self._revisions.split(':')
2266 return self._revisions.split(':')
2267
2267
2268 @revisions.setter
2268 @revisions.setter
2269 def revisions(self, val):
2269 def revisions(self, val):
2270 self._revisions = safe_unicode(':'.join(val))
2270 self._revisions = safe_unicode(':'.join(val))
2271
2271
2272 @property
2272 @property
2273 def org_ref_parts(self):
2273 def org_ref_parts(self):
2274 return self.org_ref.split(':')
2274 return self.org_ref.split(':')
2275
2275
2276 @property
2276 @property
2277 def other_ref_parts(self):
2277 def other_ref_parts(self):
2278 return self.other_ref.split(':')
2278 return self.other_ref.split(':')
2279
2279
2280 author = relationship('User')
2280 author = relationship('User')
2281 reviewers = relationship('PullRequestReviewers',
2281 reviewers = relationship('PullRequestReviewers',
2282 cascade="all, delete-orphan")
2282 cascade="all, delete-orphan")
2283 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2283 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2284 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2284 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2285 statuses = relationship('ChangesetStatus')
2285 statuses = relationship('ChangesetStatus')
2286 comments = relationship('ChangesetComment',
2286 comments = relationship('ChangesetComment',
2287 cascade="all, delete-orphan")
2287 cascade="all, delete-orphan")
2288
2288
2289 def is_closed(self):
2289 def is_closed(self):
2290 return self.status == self.STATUS_CLOSED
2290 return self.status == self.STATUS_CLOSED
2291
2291
2292 @property
2292 @property
2293 def last_review_status(self):
2293 def last_review_status(self):
2294 return str(self.statuses[-1].status) if self.statuses else ''
2294 return str(self.statuses[-1].status) if self.statuses else ''
2295
2295
2296 def user_review_status(self, user_id):
2296 def user_review_status(self, user_id):
2297 """Return the user's latest status votes on PR"""
2297 """Return the user's latest status votes on PR"""
2298 # note: no filtering on repo - that would be redundant
2298 # note: no filtering on repo - that would be redundant
2299 status = ChangesetStatus.query()\
2299 status = ChangesetStatus.query()\
2300 .filter(ChangesetStatus.pull_request == self)\
2300 .filter(ChangesetStatus.pull_request == self)\
2301 .filter(ChangesetStatus.user_id == user_id)\
2301 .filter(ChangesetStatus.user_id == user_id)\
2302 .order_by(ChangesetStatus.version)\
2302 .order_by(ChangesetStatus.version)\
2303 .first()
2303 .first()
2304 return str(status.status) if status else ''
2304 return str(status.status) if status else ''
2305
2305
2306 def __json__(self):
2306 def __json__(self):
2307 return dict(
2307 return dict(
2308 revisions=self.revisions
2308 revisions=self.revisions
2309 )
2309 )
2310
2310
2311 def url(self, **kwargs):
2311 def url(self, **kwargs):
2312 canonical = kwargs.pop('canonical', None)
2312 canonical = kwargs.pop('canonical', None)
2313 import kallithea.lib.helpers as h
2313 import kallithea.lib.helpers as h
2314 b = self.org_ref_parts[1]
2314 b = self.org_ref_parts[1]
2315 if b != self.other_ref_parts[1]:
2315 if b != self.other_ref_parts[1]:
2316 s = '/_/' + b
2316 s = '/_/' + b
2317 else:
2317 else:
2318 s = '/_/' + self.title
2318 s = '/_/' + self.title
2319 kwargs['extra'] = urlreadable(s)
2319 kwargs['extra'] = urlreadable(s)
2320 if canonical:
2320 if canonical:
2321 return h.canonical_url('pullrequest_show', repo_name=self.other_repo.repo_name,
2321 return h.canonical_url('pullrequest_show', repo_name=self.other_repo.repo_name,
2322 pull_request_id=self.pull_request_id, **kwargs)
2322 pull_request_id=self.pull_request_id, **kwargs)
2323 return h.url('pullrequest_show', repo_name=self.other_repo.repo_name,
2323 return h.url('pullrequest_show', repo_name=self.other_repo.repo_name,
2324 pull_request_id=self.pull_request_id, **kwargs)
2324 pull_request_id=self.pull_request_id, **kwargs)
2325
2325
2326 class PullRequestReviewers(Base, BaseModel):
2326 class PullRequestReviewers(Base, BaseModel):
2327 __tablename__ = 'pull_request_reviewers'
2327 __tablename__ = 'pull_request_reviewers'
2328 __table_args__ = (
2328 __table_args__ = (
2329 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2329 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2330 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2330 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2331 )
2331 )
2332
2332
2333 def __init__(self, user=None, pull_request=None):
2333 def __init__(self, user=None, pull_request=None):
2334 self.user = user
2334 self.user = user
2335 self.pull_request = pull_request
2335 self.pull_request = pull_request
2336
2336
2337 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2337 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2338 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2338 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2339 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2339 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2340
2340
2341 user = relationship('User')
2341 user = relationship('User')
2342 pull_request = relationship('PullRequest')
2342 pull_request = relationship('PullRequest')
2343
2343
2344
2344
2345 class Notification(Base, BaseModel):
2345 class Notification(Base, BaseModel):
2346 __tablename__ = 'notifications'
2346 __tablename__ = 'notifications'
2347 __table_args__ = (
2347 __table_args__ = (
2348 Index('notification_type_idx', 'type'),
2348 Index('notification_type_idx', 'type'),
2349 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2349 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2350 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2350 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2351 )
2351 )
2352
2352
2353 TYPE_CHANGESET_COMMENT = u'cs_comment'
2353 TYPE_CHANGESET_COMMENT = u'cs_comment'
2354 TYPE_MESSAGE = u'message'
2354 TYPE_MESSAGE = u'message'
2355 TYPE_MENTION = u'mention'
2355 TYPE_MENTION = u'mention'
2356 TYPE_REGISTRATION = u'registration'
2356 TYPE_REGISTRATION = u'registration'
2357 TYPE_PULL_REQUEST = u'pull_request'
2357 TYPE_PULL_REQUEST = u'pull_request'
2358 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2358 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2359
2359
2360 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2360 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2361 subject = Column('subject', Unicode(512), nullable=True)
2361 subject = Column('subject', Unicode(512), nullable=True)
2362 body = Column('body', UnicodeText(50000), nullable=True)
2362 body = Column('body', UnicodeText(50000), nullable=True)
2363 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2363 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2364 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2364 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2365 type_ = Column('type', Unicode(255))
2365 type_ = Column('type', Unicode(255))
2366
2366
2367 created_by_user = relationship('User')
2367 created_by_user = relationship('User')
2368 notifications_to_users = relationship('UserNotification', cascade="all, delete-orphan")
2368 notifications_to_users = relationship('UserNotification', cascade="all, delete-orphan")
2369
2369
2370 @property
2370 @property
2371 def recipients(self):
2371 def recipients(self):
2372 return [x.user for x in UserNotification.query()\
2372 return [x.user for x in UserNotification.query()\
2373 .filter(UserNotification.notification == self)\
2373 .filter(UserNotification.notification == self)\
2374 .order_by(UserNotification.user_id.asc()).all()]
2374 .order_by(UserNotification.user_id.asc()).all()]
2375
2375
2376 @classmethod
2376 @classmethod
2377 def create(cls, created_by, subject, body, recipients, type_=None):
2377 def create(cls, created_by, subject, body, recipients, type_=None):
2378 if type_ is None:
2378 if type_ is None:
2379 type_ = Notification.TYPE_MESSAGE
2379 type_ = Notification.TYPE_MESSAGE
2380
2380
2381 notification = cls()
2381 notification = cls()
2382 notification.created_by_user = created_by
2382 notification.created_by_user = created_by
2383 notification.subject = subject
2383 notification.subject = subject
2384 notification.body = body
2384 notification.body = body
2385 notification.type_ = type_
2385 notification.type_ = type_
2386 notification.created_on = datetime.datetime.now()
2386 notification.created_on = datetime.datetime.now()
2387
2387
2388 for u in recipients:
2388 for u in recipients:
2389 assoc = UserNotification()
2389 assoc = UserNotification()
2390 assoc.notification = notification
2390 assoc.notification = notification
2391 assoc.user_id = u.user_id
2391 assoc.user_id = u.user_id
2392 Session().add(assoc)
2392 Session().add(assoc)
2393 Session().add(notification)
2393 Session().add(notification)
2394 Session().flush() # assign notificaiton.notification_id
2394 Session().flush() # assign notificaiton.notification_id
2395 return notification
2395 return notification
2396
2396
2397 @property
2397 @property
2398 def description(self):
2398 def description(self):
2399 from kallithea.model.notification import NotificationModel
2399 from kallithea.model.notification import NotificationModel
2400 return NotificationModel().make_description(self)
2400 return NotificationModel().make_description(self)
2401
2401
2402
2402
2403 class UserNotification(Base, BaseModel):
2403 class UserNotification(Base, BaseModel):
2404 __tablename__ = 'user_to_notification'
2404 __tablename__ = 'user_to_notification'
2405 __table_args__ = (
2405 __table_args__ = (
2406 UniqueConstraint('user_id', 'notification_id'),
2406 UniqueConstraint('user_id', 'notification_id'),
2407 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2407 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2408 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2408 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2409 )
2409 )
2410 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2410 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2411 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2411 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2412 read = Column('read', Boolean, default=False)
2412 read = Column('read', Boolean, default=False)
2413 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2413 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2414
2414
2415 user = relationship('User')
2415 user = relationship('User')
2416 notification = relationship('Notification')
2416 notification = relationship('Notification')
2417
2417
2418 def mark_as_read(self):
2418 def mark_as_read(self):
2419 self.read = True
2419 self.read = True
2420 Session().add(self)
2420 Session().add(self)
2421
2421
2422
2422
2423 class Gist(Base, BaseModel):
2423 class Gist(Base, BaseModel):
2424 __tablename__ = 'gists'
2424 __tablename__ = 'gists'
2425 __table_args__ = (
2425 __table_args__ = (
2426 Index('g_gist_access_id_idx', 'gist_access_id'),
2426 Index('g_gist_access_id_idx', 'gist_access_id'),
2427 Index('g_created_on_idx', 'created_on'),
2427 Index('g_created_on_idx', 'created_on'),
2428 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2428 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2429 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2429 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2430 )
2430 )
2431 GIST_PUBLIC = u'public'
2431 GIST_PUBLIC = u'public'
2432 GIST_PRIVATE = u'private'
2432 GIST_PRIVATE = u'private'
2433 DEFAULT_FILENAME = u'gistfile1.txt'
2433 DEFAULT_FILENAME = u'gistfile1.txt'
2434
2434
2435 gist_id = Column('gist_id', Integer(), primary_key=True)
2435 gist_id = Column('gist_id', Integer(), primary_key=True)
2436 gist_access_id = Column('gist_access_id', Unicode(250))
2436 gist_access_id = Column('gist_access_id', Unicode(250))
2437 gist_description = Column('gist_description', UnicodeText(1024))
2437 gist_description = Column('gist_description', UnicodeText(1024))
2438 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
2438 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
2439 gist_expires = Column('gist_expires', Float(53), nullable=False)
2439 gist_expires = Column('gist_expires', Float(53), nullable=False)
2440 gist_type = Column('gist_type', Unicode(128), nullable=False)
2440 gist_type = Column('gist_type', Unicode(128), nullable=False)
2441 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2441 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2442 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2442 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2443
2443
2444 owner = relationship('User')
2444 owner = relationship('User')
2445
2445
2446 def __repr__(self):
2446 def __repr__(self):
2447 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
2447 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
2448
2448
2449 @classmethod
2449 @classmethod
2450 def get_or_404(cls, id_):
2450 def get_or_404(cls, id_):
2451 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2451 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2452 if not res:
2452 if not res:
2453 raise HTTPNotFound
2453 raise HTTPNotFound
2454 return res
2454 return res
2455
2455
2456 @classmethod
2456 @classmethod
2457 def get_by_access_id(cls, gist_access_id):
2457 def get_by_access_id(cls, gist_access_id):
2458 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2458 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2459
2459
2460 def gist_url(self):
2460 def gist_url(self):
2461 import kallithea
2461 import kallithea
2462 alias_url = kallithea.CONFIG.get('gist_alias_url')
2462 alias_url = kallithea.CONFIG.get('gist_alias_url')
2463 if alias_url:
2463 if alias_url:
2464 return alias_url.replace('{gistid}', self.gist_access_id)
2464 return alias_url.replace('{gistid}', self.gist_access_id)
2465
2465
2466 import kallithea.lib.helpers as h
2466 import kallithea.lib.helpers as h
2467 return h.canonical_url('gist', gist_id=self.gist_access_id)
2467 return h.canonical_url('gist', gist_id=self.gist_access_id)
2468
2468
2469 @classmethod
2469 @classmethod
2470 def base_path(cls):
2470 def base_path(cls):
2471 """
2471 """
2472 Returns base path where all gists are stored
2472 Returns base path where all gists are stored
2473
2473
2474 :param cls:
2474 :param cls:
2475 """
2475 """
2476 from kallithea.model.gist import GIST_STORE_LOC
2476 from kallithea.model.gist import GIST_STORE_LOC
2477 q = Session().query(Ui)\
2477 q = Session().query(Ui)\
2478 .filter(Ui.ui_key == URL_SEP)
2478 .filter(Ui.ui_key == URL_SEP)
2479 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2479 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2480 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2480 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2481
2481
2482 def get_api_data(self):
2482 def get_api_data(self):
2483 """
2483 """
2484 Common function for generating gist related data for API
2484 Common function for generating gist related data for API
2485 """
2485 """
2486 gist = self
2486 gist = self
2487 data = dict(
2487 data = dict(
2488 gist_id=gist.gist_id,
2488 gist_id=gist.gist_id,
2489 type=gist.gist_type,
2489 type=gist.gist_type,
2490 access_id=gist.gist_access_id,
2490 access_id=gist.gist_access_id,
2491 description=gist.gist_description,
2491 description=gist.gist_description,
2492 url=gist.gist_url(),
2492 url=gist.gist_url(),
2493 expires=gist.gist_expires,
2493 expires=gist.gist_expires,
2494 created_on=gist.created_on,
2494 created_on=gist.created_on,
2495 )
2495 )
2496 return data
2496 return data
2497
2497
2498 def __json__(self):
2498 def __json__(self):
2499 data = dict(
2499 data = dict(
2500 )
2500 )
2501 data.update(self.get_api_data())
2501 data.update(self.get_api_data())
2502 return data
2502 return data
2503 ## SCM functions
2503 ## SCM functions
2504
2504
2505 @property
2505 @property
2506 def scm_instance(self):
2506 def scm_instance(self):
2507 from kallithea.lib.vcs import get_repo
2507 from kallithea.lib.vcs import get_repo
2508 base_path = self.base_path()
2508 base_path = self.base_path()
2509 return get_repo(os.path.join(*map(safe_str,
2509 return get_repo(os.path.join(*map(safe_str,
2510 [base_path, self.gist_access_id])))
2510 [base_path, self.gist_access_id])))
2511
2511
2512
2512
2513 class DbMigrateVersion(Base, BaseModel):
2513 class DbMigrateVersion(Base, BaseModel):
2514 __tablename__ = 'db_migrate_version'
2514 __tablename__ = 'db_migrate_version'
2515 __table_args__ = (
2515 __table_args__ = (
2516 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2516 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2517 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2517 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2518 )
2518 )
2519 repository_id = Column('repository_id', String(250), primary_key=True)
2519 repository_id = Column('repository_id', String(250), primary_key=True)
2520 repository_path = Column('repository_path', Text)
2520 repository_path = Column('repository_path', Text)
2521 version = Column('version', Integer)
2521 version = Column('version', Integer)
@@ -1,525 +1,525 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 this is forms validation classes
15 these are form validation classes
16 http://formencode.org/module-formencode.validators.html
16 http://formencode.org/module-formencode.validators.html
17 for list off all availible validators
17 for list of all available validators
18
18
19 we can create our own validators
19 we can create our own validators
20
20
21 The table below outlines the options which can be used in a schema in addition to the validators themselves
21 The table below outlines the options which can be used in a schema in addition to the validators themselves
22 pre_validators [] These validators will be applied before the schema
22 pre_validators [] These validators will be applied before the schema
23 chained_validators [] These validators will be applied after the schema
23 chained_validators [] These validators will be applied after the schema
24 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
24 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
25 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
25 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
26 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
26 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
27 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
27 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
28
28
29
29
30 <name> = formencode.validators.<name of validator>
30 <name> = formencode.validators.<name of validator>
31 <name> must equal form name
31 <name> must equal form name
32 list=[1,2,3,4,5]
32 list=[1,2,3,4,5]
33 for SELECT use formencode.All(OneOf(list), Int())
33 for SELECT use formencode.All(OneOf(list), Int())
34
34
35 """
35 """
36 import logging
36 import logging
37
37
38 import formencode
38 import formencode
39 from formencode import All
39 from formencode import All
40
40
41 from pylons.i18n.translation import _
41 from pylons.i18n.translation import _
42
42
43 from kallithea import BACKENDS
43 from kallithea import BACKENDS
44 from kallithea.model import validators as v
44 from kallithea.model import validators as v
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class LoginForm(formencode.Schema):
49 class LoginForm(formencode.Schema):
50 allow_extra_fields = True
50 allow_extra_fields = True
51 filter_extra_fields = True
51 filter_extra_fields = True
52 username = v.UnicodeString(
52 username = v.UnicodeString(
53 strip=True,
53 strip=True,
54 min=1,
54 min=1,
55 not_empty=True,
55 not_empty=True,
56 messages={
56 messages={
57 'empty': _(u'Please enter a login'),
57 'empty': _(u'Please enter a login'),
58 'tooShort': _(u'Enter a value %(min)i characters long or more')}
58 'tooShort': _(u'Enter a value %(min)i characters long or more')}
59 )
59 )
60
60
61 password = v.UnicodeString(
61 password = v.UnicodeString(
62 strip=False,
62 strip=False,
63 min=3,
63 min=3,
64 not_empty=True,
64 not_empty=True,
65 messages={
65 messages={
66 'empty': _(u'Please enter a password'),
66 'empty': _(u'Please enter a password'),
67 'tooShort': _(u'Enter %(min)i characters or more')}
67 'tooShort': _(u'Enter %(min)i characters or more')}
68 )
68 )
69
69
70 remember = v.StringBoolean(if_missing=False)
70 remember = v.StringBoolean(if_missing=False)
71
71
72 chained_validators = [v.ValidAuth()]
72 chained_validators = [v.ValidAuth()]
73
73
74
74
75 def PasswordChangeForm(username):
75 def PasswordChangeForm(username):
76 class _PasswordChangeForm(formencode.Schema):
76 class _PasswordChangeForm(formencode.Schema):
77 allow_extra_fields = True
77 allow_extra_fields = True
78 filter_extra_fields = True
78 filter_extra_fields = True
79
79
80 current_password = v.ValidOldPassword(username)(not_empty=True)
80 current_password = v.ValidOldPassword(username)(not_empty=True)
81 new_password = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
81 new_password = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
82 new_password_confirmation = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
82 new_password_confirmation = All(v.ValidPassword(), v.UnicodeString(strip=False, min=6))
83
83
84 chained_validators = [v.ValidPasswordsMatch('new_password',
84 chained_validators = [v.ValidPasswordsMatch('new_password',
85 'new_password_confirmation')]
85 'new_password_confirmation')]
86 return _PasswordChangeForm
86 return _PasswordChangeForm
87
87
88
88
89 def UserForm(edit=False, old_data={}):
89 def UserForm(edit=False, old_data={}):
90 class _UserForm(formencode.Schema):
90 class _UserForm(formencode.Schema):
91 allow_extra_fields = True
91 allow_extra_fields = True
92 filter_extra_fields = True
92 filter_extra_fields = True
93 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
93 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
94 v.ValidUsername(edit, old_data))
94 v.ValidUsername(edit, old_data))
95 if edit:
95 if edit:
96 new_password = All(
96 new_password = All(
97 v.ValidPassword(),
97 v.ValidPassword(),
98 v.UnicodeString(strip=False, min=6, not_empty=False)
98 v.UnicodeString(strip=False, min=6, not_empty=False)
99 )
99 )
100 password_confirmation = All(
100 password_confirmation = All(
101 v.ValidPassword(),
101 v.ValidPassword(),
102 v.UnicodeString(strip=False, min=6, not_empty=False),
102 v.UnicodeString(strip=False, min=6, not_empty=False),
103 )
103 )
104 admin = v.StringBoolean(if_missing=False)
104 admin = v.StringBoolean(if_missing=False)
105 else:
105 else:
106 password = All(
106 password = All(
107 v.ValidPassword(),
107 v.ValidPassword(),
108 v.UnicodeString(strip=False, min=6, not_empty=True)
108 v.UnicodeString(strip=False, min=6, not_empty=True)
109 )
109 )
110 password_confirmation = All(
110 password_confirmation = All(
111 v.ValidPassword(),
111 v.ValidPassword(),
112 v.UnicodeString(strip=False, min=6, not_empty=False)
112 v.UnicodeString(strip=False, min=6, not_empty=False)
113 )
113 )
114
114
115 active = v.StringBoolean(if_missing=False)
115 active = v.StringBoolean(if_missing=False)
116 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
116 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
117 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
117 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
118 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
118 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
119 extern_name = v.UnicodeString(strip=True)
119 extern_name = v.UnicodeString(strip=True)
120 extern_type = v.UnicodeString(strip=True)
120 extern_type = v.UnicodeString(strip=True)
121 chained_validators = [v.ValidPasswordsMatch()]
121 chained_validators = [v.ValidPasswordsMatch()]
122 return _UserForm
122 return _UserForm
123
123
124
124
125 def UserGroupForm(edit=False, old_data={}, available_members=[]):
125 def UserGroupForm(edit=False, old_data={}, available_members=[]):
126 class _UserGroupForm(formencode.Schema):
126 class _UserGroupForm(formencode.Schema):
127 allow_extra_fields = True
127 allow_extra_fields = True
128 filter_extra_fields = True
128 filter_extra_fields = True
129
129
130 users_group_name = All(
130 users_group_name = All(
131 v.UnicodeString(strip=True, min=1, not_empty=True),
131 v.UnicodeString(strip=True, min=1, not_empty=True),
132 v.ValidUserGroup(edit, old_data)
132 v.ValidUserGroup(edit, old_data)
133 )
133 )
134 user_group_description = v.UnicodeString(strip=True, min=1,
134 user_group_description = v.UnicodeString(strip=True, min=1,
135 not_empty=False)
135 not_empty=False)
136
136
137 users_group_active = v.StringBoolean(if_missing=False)
137 users_group_active = v.StringBoolean(if_missing=False)
138
138
139 if edit:
139 if edit:
140 users_group_members = v.OneOf(
140 users_group_members = v.OneOf(
141 available_members, hideList=False, testValueList=True,
141 available_members, hideList=False, testValueList=True,
142 if_missing=None, not_empty=False
142 if_missing=None, not_empty=False
143 )
143 )
144
144
145 return _UserGroupForm
145 return _UserGroupForm
146
146
147
147
148 def RepoGroupForm(edit=False, old_data={}, available_groups=[],
148 def RepoGroupForm(edit=False, old_data={}, available_groups=[],
149 can_create_in_root=False):
149 can_create_in_root=False):
150 class _RepoGroupForm(formencode.Schema):
150 class _RepoGroupForm(formencode.Schema):
151 allow_extra_fields = True
151 allow_extra_fields = True
152 filter_extra_fields = False
152 filter_extra_fields = False
153
153
154 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
154 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
155 v.SlugifyName(),
155 v.SlugifyName(),
156 v.ValidRegex(msg=_('Name must not contain only digits'))(r'(?!^\d+$)^.+$'))
156 v.ValidRegex(msg=_('Name must not contain only digits'))(r'(?!^\d+$)^.+$'))
157 group_description = v.UnicodeString(strip=True, min=1,
157 group_description = v.UnicodeString(strip=True, min=1,
158 not_empty=False)
158 not_empty=False)
159 group_copy_permissions = v.StringBoolean(if_missing=False)
159 group_copy_permissions = v.StringBoolean(if_missing=False)
160
160
161 if edit:
161 if edit:
162 #FIXME: do a special check that we cannot move a group to one of
162 #FIXME: do a special check that we cannot move a group to one of
163 #its children
163 #its children
164 pass
164 pass
165 group_parent_id = All(v.CanCreateGroup(can_create_in_root),
165 group_parent_id = All(v.CanCreateGroup(can_create_in_root),
166 v.OneOf(available_groups, hideList=False,
166 v.OneOf(available_groups, hideList=False,
167 testValueList=True,
167 testValueList=True,
168 if_missing=None, not_empty=True))
168 if_missing=None, not_empty=True))
169 enable_locking = v.StringBoolean(if_missing=False)
169 enable_locking = v.StringBoolean(if_missing=False)
170 chained_validators = [v.ValidRepoGroup(edit, old_data)]
170 chained_validators = [v.ValidRepoGroup(edit, old_data)]
171
171
172 return _RepoGroupForm
172 return _RepoGroupForm
173
173
174
174
175 def RegisterForm(edit=False, old_data={}):
175 def RegisterForm(edit=False, old_data={}):
176 class _RegisterForm(formencode.Schema):
176 class _RegisterForm(formencode.Schema):
177 allow_extra_fields = True
177 allow_extra_fields = True
178 filter_extra_fields = True
178 filter_extra_fields = True
179 username = All(
179 username = All(
180 v.ValidUsername(edit, old_data),
180 v.ValidUsername(edit, old_data),
181 v.UnicodeString(strip=True, min=1, not_empty=True)
181 v.UnicodeString(strip=True, min=1, not_empty=True)
182 )
182 )
183 password = All(
183 password = All(
184 v.ValidPassword(),
184 v.ValidPassword(),
185 v.UnicodeString(strip=False, min=6, not_empty=True)
185 v.UnicodeString(strip=False, min=6, not_empty=True)
186 )
186 )
187 password_confirmation = All(
187 password_confirmation = All(
188 v.ValidPassword(),
188 v.ValidPassword(),
189 v.UnicodeString(strip=False, min=6, not_empty=True)
189 v.UnicodeString(strip=False, min=6, not_empty=True)
190 )
190 )
191 active = v.StringBoolean(if_missing=False)
191 active = v.StringBoolean(if_missing=False)
192 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
192 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
193 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
193 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
194 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
194 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
195
195
196 chained_validators = [v.ValidPasswordsMatch()]
196 chained_validators = [v.ValidPasswordsMatch()]
197
197
198 return _RegisterForm
198 return _RegisterForm
199
199
200
200
201 def PasswordResetForm():
201 def PasswordResetForm():
202 class _PasswordResetForm(formencode.Schema):
202 class _PasswordResetForm(formencode.Schema):
203 allow_extra_fields = True
203 allow_extra_fields = True
204 filter_extra_fields = True
204 filter_extra_fields = True
205 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
205 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
206 return _PasswordResetForm
206 return _PasswordResetForm
207
207
208
208
209 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
209 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
210 repo_groups=[], landing_revs=[]):
210 repo_groups=[], landing_revs=[]):
211 class _RepoForm(formencode.Schema):
211 class _RepoForm(formencode.Schema):
212 allow_extra_fields = True
212 allow_extra_fields = True
213 filter_extra_fields = False
213 filter_extra_fields = False
214 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
214 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
215 v.SlugifyName())
215 v.SlugifyName())
216 repo_group = All(v.CanWriteGroup(old_data),
216 repo_group = All(v.CanWriteGroup(old_data),
217 v.OneOf(repo_groups, hideList=True))
217 v.OneOf(repo_groups, hideList=True))
218 repo_type = v.OneOf(supported_backends, required=False,
218 repo_type = v.OneOf(supported_backends, required=False,
219 if_missing=old_data.get('repo_type'))
219 if_missing=old_data.get('repo_type'))
220 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
220 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
221 repo_private = v.StringBoolean(if_missing=False)
221 repo_private = v.StringBoolean(if_missing=False)
222 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
222 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
223 repo_copy_permissions = v.StringBoolean(if_missing=False)
223 repo_copy_permissions = v.StringBoolean(if_missing=False)
224 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
224 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
225
225
226 repo_enable_statistics = v.StringBoolean(if_missing=False)
226 repo_enable_statistics = v.StringBoolean(if_missing=False)
227 repo_enable_downloads = v.StringBoolean(if_missing=False)
227 repo_enable_downloads = v.StringBoolean(if_missing=False)
228 repo_enable_locking = v.StringBoolean(if_missing=False)
228 repo_enable_locking = v.StringBoolean(if_missing=False)
229
229
230 if edit:
230 if edit:
231 #this is repo owner
231 #this is repo owner
232 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
232 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
233 clone_uri_change = v.UnicodeString(not_empty=False, if_missing=v.Missing)
233 clone_uri_change = v.UnicodeString(not_empty=False, if_missing=v.Missing)
234
234
235 chained_validators = [v.ValidCloneUri(),
235 chained_validators = [v.ValidCloneUri(),
236 v.ValidRepoName(edit, old_data)]
236 v.ValidRepoName(edit, old_data)]
237 return _RepoForm
237 return _RepoForm
238
238
239
239
240 def RepoPermsForm():
240 def RepoPermsForm():
241 class _RepoPermsForm(formencode.Schema):
241 class _RepoPermsForm(formencode.Schema):
242 allow_extra_fields = True
242 allow_extra_fields = True
243 filter_extra_fields = False
243 filter_extra_fields = False
244 chained_validators = [v.ValidPerms(type_='repo')]
244 chained_validators = [v.ValidPerms(type_='repo')]
245 return _RepoPermsForm
245 return _RepoPermsForm
246
246
247
247
248 def RepoGroupPermsForm(valid_recursive_choices):
248 def RepoGroupPermsForm(valid_recursive_choices):
249 class _RepoGroupPermsForm(formencode.Schema):
249 class _RepoGroupPermsForm(formencode.Schema):
250 allow_extra_fields = True
250 allow_extra_fields = True
251 filter_extra_fields = False
251 filter_extra_fields = False
252 recursive = v.OneOf(valid_recursive_choices)
252 recursive = v.OneOf(valid_recursive_choices)
253 chained_validators = [v.ValidPerms(type_='repo_group')]
253 chained_validators = [v.ValidPerms(type_='repo_group')]
254 return _RepoGroupPermsForm
254 return _RepoGroupPermsForm
255
255
256
256
257 def UserGroupPermsForm():
257 def UserGroupPermsForm():
258 class _UserPermsForm(formencode.Schema):
258 class _UserPermsForm(formencode.Schema):
259 allow_extra_fields = True
259 allow_extra_fields = True
260 filter_extra_fields = False
260 filter_extra_fields = False
261 chained_validators = [v.ValidPerms(type_='user_group')]
261 chained_validators = [v.ValidPerms(type_='user_group')]
262 return _UserPermsForm
262 return _UserPermsForm
263
263
264
264
265 def RepoFieldForm():
265 def RepoFieldForm():
266 class _RepoFieldForm(formencode.Schema):
266 class _RepoFieldForm(formencode.Schema):
267 filter_extra_fields = True
267 filter_extra_fields = True
268 allow_extra_fields = True
268 allow_extra_fields = True
269
269
270 new_field_key = All(v.FieldKey(),
270 new_field_key = All(v.FieldKey(),
271 v.UnicodeString(strip=True, min=3, not_empty=True))
271 v.UnicodeString(strip=True, min=3, not_empty=True))
272 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
272 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
273 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
273 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
274 if_missing='str')
274 if_missing='str')
275 new_field_label = v.UnicodeString(not_empty=False)
275 new_field_label = v.UnicodeString(not_empty=False)
276 new_field_desc = v.UnicodeString(not_empty=False)
276 new_field_desc = v.UnicodeString(not_empty=False)
277
277
278 return _RepoFieldForm
278 return _RepoFieldForm
279
279
280
280
281 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
281 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
282 repo_groups=[], landing_revs=[]):
282 repo_groups=[], landing_revs=[]):
283 class _RepoForkForm(formencode.Schema):
283 class _RepoForkForm(formencode.Schema):
284 allow_extra_fields = True
284 allow_extra_fields = True
285 filter_extra_fields = False
285 filter_extra_fields = False
286 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
286 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
287 v.SlugifyName())
287 v.SlugifyName())
288 repo_group = All(v.CanWriteGroup(),
288 repo_group = All(v.CanWriteGroup(),
289 v.OneOf(repo_groups, hideList=True))
289 v.OneOf(repo_groups, hideList=True))
290 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
290 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
291 description = v.UnicodeString(strip=True, min=1, not_empty=True)
291 description = v.UnicodeString(strip=True, min=1, not_empty=True)
292 private = v.StringBoolean(if_missing=False)
292 private = v.StringBoolean(if_missing=False)
293 copy_permissions = v.StringBoolean(if_missing=False)
293 copy_permissions = v.StringBoolean(if_missing=False)
294 update_after_clone = v.StringBoolean(if_missing=False)
294 update_after_clone = v.StringBoolean(if_missing=False)
295 fork_parent_id = v.UnicodeString()
295 fork_parent_id = v.UnicodeString()
296 chained_validators = [v.ValidForkName(edit, old_data)]
296 chained_validators = [v.ValidForkName(edit, old_data)]
297 landing_rev = v.OneOf(landing_revs, hideList=True)
297 landing_rev = v.OneOf(landing_revs, hideList=True)
298
298
299 return _RepoForkForm
299 return _RepoForkForm
300
300
301
301
302 def ApplicationSettingsForm():
302 def ApplicationSettingsForm():
303 class _ApplicationSettingsForm(formencode.Schema):
303 class _ApplicationSettingsForm(formencode.Schema):
304 allow_extra_fields = True
304 allow_extra_fields = True
305 filter_extra_fields = False
305 filter_extra_fields = False
306 title = v.UnicodeString(strip=True, not_empty=False)
306 title = v.UnicodeString(strip=True, not_empty=False)
307 realm = v.UnicodeString(strip=True, min=1, not_empty=True)
307 realm = v.UnicodeString(strip=True, min=1, not_empty=True)
308 ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
308 ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
309 captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
309 captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
310 captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
310 captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
311
311
312 return _ApplicationSettingsForm
312 return _ApplicationSettingsForm
313
313
314
314
315 def ApplicationVisualisationForm():
315 def ApplicationVisualisationForm():
316 class _ApplicationVisualisationForm(formencode.Schema):
316 class _ApplicationVisualisationForm(formencode.Schema):
317 allow_extra_fields = True
317 allow_extra_fields = True
318 filter_extra_fields = False
318 filter_extra_fields = False
319 show_public_icon = v.StringBoolean(if_missing=False)
319 show_public_icon = v.StringBoolean(if_missing=False)
320 show_private_icon = v.StringBoolean(if_missing=False)
320 show_private_icon = v.StringBoolean(if_missing=False)
321 stylify_metatags = v.StringBoolean(if_missing=False)
321 stylify_metatags = v.StringBoolean(if_missing=False)
322
322
323 repository_fields = v.StringBoolean(if_missing=False)
323 repository_fields = v.StringBoolean(if_missing=False)
324 lightweight_journal = v.StringBoolean(if_missing=False)
324 lightweight_journal = v.StringBoolean(if_missing=False)
325 dashboard_items = v.Int(min=5, not_empty=True)
325 dashboard_items = v.Int(min=5, not_empty=True)
326 admin_grid_items = v.Int(min=5, not_empty=True)
326 admin_grid_items = v.Int(min=5, not_empty=True)
327 show_version = v.StringBoolean(if_missing=False)
327 show_version = v.StringBoolean(if_missing=False)
328 use_gravatar = v.StringBoolean(if_missing=False)
328 use_gravatar = v.StringBoolean(if_missing=False)
329 gravatar_url = v.UnicodeString(min=3)
329 gravatar_url = v.UnicodeString(min=3)
330 clone_uri_tmpl = v.UnicodeString(min=3)
330 clone_uri_tmpl = v.UnicodeString(min=3)
331
331
332 return _ApplicationVisualisationForm
332 return _ApplicationVisualisationForm
333
333
334
334
335 def ApplicationUiSettingsForm():
335 def ApplicationUiSettingsForm():
336 class _ApplicationUiSettingsForm(formencode.Schema):
336 class _ApplicationUiSettingsForm(formencode.Schema):
337 allow_extra_fields = True
337 allow_extra_fields = True
338 filter_extra_fields = False
338 filter_extra_fields = False
339 web_push_ssl = v.StringBoolean(if_missing=False)
339 web_push_ssl = v.StringBoolean(if_missing=False)
340 paths_root_path = All(
340 paths_root_path = All(
341 v.ValidPath(),
341 v.ValidPath(),
342 v.UnicodeString(strip=True, min=1, not_empty=True)
342 v.UnicodeString(strip=True, min=1, not_empty=True)
343 )
343 )
344 hooks_changegroup_update = v.StringBoolean(if_missing=False)
344 hooks_changegroup_update = v.StringBoolean(if_missing=False)
345 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
345 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
346 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
346 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
347 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
347 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
348
348
349 extensions_largefiles = v.StringBoolean(if_missing=False)
349 extensions_largefiles = v.StringBoolean(if_missing=False)
350 extensions_hgsubversion = v.StringBoolean(if_missing=False)
350 extensions_hgsubversion = v.StringBoolean(if_missing=False)
351 extensions_hggit = v.StringBoolean(if_missing=False)
351 extensions_hggit = v.StringBoolean(if_missing=False)
352
352
353 return _ApplicationUiSettingsForm
353 return _ApplicationUiSettingsForm
354
354
355
355
356 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
356 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
357 user_group_perms_choices, create_choices,
357 user_group_perms_choices, create_choices,
358 create_on_write_choices, repo_group_create_choices,
358 create_on_write_choices, repo_group_create_choices,
359 user_group_create_choices, fork_choices,
359 user_group_create_choices, fork_choices,
360 register_choices, extern_activate_choices):
360 register_choices, extern_activate_choices):
361 class _DefaultPermissionsForm(formencode.Schema):
361 class _DefaultPermissionsForm(formencode.Schema):
362 allow_extra_fields = True
362 allow_extra_fields = True
363 filter_extra_fields = True
363 filter_extra_fields = True
364 overwrite_default_repo = v.StringBoolean(if_missing=False)
364 overwrite_default_repo = v.StringBoolean(if_missing=False)
365 overwrite_default_group = v.StringBoolean(if_missing=False)
365 overwrite_default_group = v.StringBoolean(if_missing=False)
366 overwrite_default_user_group = v.StringBoolean(if_missing=False)
366 overwrite_default_user_group = v.StringBoolean(if_missing=False)
367 anonymous = v.StringBoolean(if_missing=False)
367 anonymous = v.StringBoolean(if_missing=False)
368 default_repo_perm = v.OneOf(repo_perms_choices)
368 default_repo_perm = v.OneOf(repo_perms_choices)
369 default_group_perm = v.OneOf(group_perms_choices)
369 default_group_perm = v.OneOf(group_perms_choices)
370 default_user_group_perm = v.OneOf(user_group_perms_choices)
370 default_user_group_perm = v.OneOf(user_group_perms_choices)
371
371
372 default_repo_create = v.OneOf(create_choices)
372 default_repo_create = v.OneOf(create_choices)
373 create_on_write = v.OneOf(create_on_write_choices)
373 create_on_write = v.OneOf(create_on_write_choices)
374 default_user_group_create = v.OneOf(user_group_create_choices)
374 default_user_group_create = v.OneOf(user_group_create_choices)
375 #default_repo_group_create = v.OneOf(repo_group_create_choices) #not impl. yet
375 #default_repo_group_create = v.OneOf(repo_group_create_choices) #not impl. yet
376 default_fork = v.OneOf(fork_choices)
376 default_fork = v.OneOf(fork_choices)
377
377
378 default_register = v.OneOf(register_choices)
378 default_register = v.OneOf(register_choices)
379 default_extern_activate = v.OneOf(extern_activate_choices)
379 default_extern_activate = v.OneOf(extern_activate_choices)
380 return _DefaultPermissionsForm
380 return _DefaultPermissionsForm
381
381
382
382
383 def CustomDefaultPermissionsForm():
383 def CustomDefaultPermissionsForm():
384 class _CustomDefaultPermissionsForm(formencode.Schema):
384 class _CustomDefaultPermissionsForm(formencode.Schema):
385 filter_extra_fields = True
385 filter_extra_fields = True
386 allow_extra_fields = True
386 allow_extra_fields = True
387 inherit_default_permissions = v.StringBoolean(if_missing=False)
387 inherit_default_permissions = v.StringBoolean(if_missing=False)
388
388
389 create_repo_perm = v.StringBoolean(if_missing=False)
389 create_repo_perm = v.StringBoolean(if_missing=False)
390 create_user_group_perm = v.StringBoolean(if_missing=False)
390 create_user_group_perm = v.StringBoolean(if_missing=False)
391 #create_repo_group_perm Impl. later
391 #create_repo_group_perm Impl. later
392
392
393 fork_repo_perm = v.StringBoolean(if_missing=False)
393 fork_repo_perm = v.StringBoolean(if_missing=False)
394
394
395 return _CustomDefaultPermissionsForm
395 return _CustomDefaultPermissionsForm
396
396
397
397
398 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
398 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
399 class _DefaultsForm(formencode.Schema):
399 class _DefaultsForm(formencode.Schema):
400 allow_extra_fields = True
400 allow_extra_fields = True
401 filter_extra_fields = True
401 filter_extra_fields = True
402 default_repo_type = v.OneOf(supported_backends)
402 default_repo_type = v.OneOf(supported_backends)
403 default_repo_private = v.StringBoolean(if_missing=False)
403 default_repo_private = v.StringBoolean(if_missing=False)
404 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
404 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
405 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
405 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
406 default_repo_enable_locking = v.StringBoolean(if_missing=False)
406 default_repo_enable_locking = v.StringBoolean(if_missing=False)
407
407
408 return _DefaultsForm
408 return _DefaultsForm
409
409
410
410
411 def AuthSettingsForm(current_active_modules):
411 def AuthSettingsForm(current_active_modules):
412 class _AuthSettingsForm(formencode.Schema):
412 class _AuthSettingsForm(formencode.Schema):
413 allow_extra_fields = True
413 allow_extra_fields = True
414 filter_extra_fields = True
414 filter_extra_fields = True
415 auth_plugins = All(v.ValidAuthPlugins(),
415 auth_plugins = All(v.ValidAuthPlugins(),
416 v.UniqueListFromString()(not_empty=True))
416 v.UniqueListFromString()(not_empty=True))
417
417
418 def __init__(self, *args, **kwargs):
418 def __init__(self, *args, **kwargs):
419 # The auth plugins tell us what form validators they use
419 # The auth plugins tell us what form validators they use
420 if current_active_modules:
420 if current_active_modules:
421 import kallithea.lib.auth_modules
421 import kallithea.lib.auth_modules
422 from kallithea.lib.auth_modules import LazyFormencode
422 from kallithea.lib.auth_modules import LazyFormencode
423 for module in current_active_modules:
423 for module in current_active_modules:
424 plugin = kallithea.lib.auth_modules.loadplugin(module)
424 plugin = kallithea.lib.auth_modules.loadplugin(module)
425 plugin_name = plugin.name
425 plugin_name = plugin.name
426 for sv in plugin.plugin_settings():
426 for sv in plugin.plugin_settings():
427 newk = "auth_%s_%s" % (plugin_name, sv["name"])
427 newk = "auth_%s_%s" % (plugin_name, sv["name"])
428 # can be a LazyFormencode object from plugin settings
428 # can be a LazyFormencode object from plugin settings
429 validator = sv["validator"]
429 validator = sv["validator"]
430 if isinstance(validator, LazyFormencode):
430 if isinstance(validator, LazyFormencode):
431 validator = validator()
431 validator = validator()
432 #init all lazy validators from formencode.All
432 #init all lazy validators from formencode.All
433 if isinstance(validator, All):
433 if isinstance(validator, All):
434 init_validators = []
434 init_validators = []
435 for validator in validator.validators:
435 for validator in validator.validators:
436 if isinstance(validator, LazyFormencode):
436 if isinstance(validator, LazyFormencode):
437 validator = validator()
437 validator = validator()
438 init_validators.append(validator)
438 init_validators.append(validator)
439 validator.validators = init_validators
439 validator.validators = init_validators
440
440
441 self.add_field(newk, validator)
441 self.add_field(newk, validator)
442 formencode.Schema.__init__(self, *args, **kwargs)
442 formencode.Schema.__init__(self, *args, **kwargs)
443
443
444 return _AuthSettingsForm
444 return _AuthSettingsForm
445
445
446
446
447 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
447 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
448 tls_kind_choices):
448 tls_kind_choices):
449 class _LdapSettingsForm(formencode.Schema):
449 class _LdapSettingsForm(formencode.Schema):
450 allow_extra_fields = True
450 allow_extra_fields = True
451 filter_extra_fields = True
451 filter_extra_fields = True
452 #pre_validators = [LdapLibValidator]
452 #pre_validators = [LdapLibValidator]
453 ldap_active = v.StringBoolean(if_missing=False)
453 ldap_active = v.StringBoolean(if_missing=False)
454 ldap_host = v.UnicodeString(strip=True,)
454 ldap_host = v.UnicodeString(strip=True,)
455 ldap_port = v.Number(strip=True,)
455 ldap_port = v.Number(strip=True,)
456 ldap_tls_kind = v.OneOf(tls_kind_choices)
456 ldap_tls_kind = v.OneOf(tls_kind_choices)
457 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
457 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
458 ldap_dn_user = v.UnicodeString(strip=True,)
458 ldap_dn_user = v.UnicodeString(strip=True,)
459 ldap_dn_pass = v.UnicodeString(strip=True,)
459 ldap_dn_pass = v.UnicodeString(strip=True,)
460 ldap_base_dn = v.UnicodeString(strip=True,)
460 ldap_base_dn = v.UnicodeString(strip=True,)
461 ldap_filter = v.UnicodeString(strip=True,)
461 ldap_filter = v.UnicodeString(strip=True,)
462 ldap_search_scope = v.OneOf(search_scope_choices)
462 ldap_search_scope = v.OneOf(search_scope_choices)
463 ldap_attr_login = v.AttrLoginValidator()(not_empty=True)
463 ldap_attr_login = v.AttrLoginValidator()(not_empty=True)
464 ldap_attr_firstname = v.UnicodeString(strip=True,)
464 ldap_attr_firstname = v.UnicodeString(strip=True,)
465 ldap_attr_lastname = v.UnicodeString(strip=True,)
465 ldap_attr_lastname = v.UnicodeString(strip=True,)
466 ldap_attr_email = v.UnicodeString(strip=True,)
466 ldap_attr_email = v.UnicodeString(strip=True,)
467
467
468 return _LdapSettingsForm
468 return _LdapSettingsForm
469
469
470
470
471 def UserExtraEmailForm():
471 def UserExtraEmailForm():
472 class _UserExtraEmailForm(formencode.Schema):
472 class _UserExtraEmailForm(formencode.Schema):
473 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
473 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
474 return _UserExtraEmailForm
474 return _UserExtraEmailForm
475
475
476
476
477 def UserExtraIpForm():
477 def UserExtraIpForm():
478 class _UserExtraIpForm(formencode.Schema):
478 class _UserExtraIpForm(formencode.Schema):
479 ip = v.ValidIp()(not_empty=True)
479 ip = v.ValidIp()(not_empty=True)
480 return _UserExtraIpForm
480 return _UserExtraIpForm
481
481
482
482
483 def PullRequestForm(repo_id):
483 def PullRequestForm(repo_id):
484 class _PullRequestForm(formencode.Schema):
484 class _PullRequestForm(formencode.Schema):
485 allow_extra_fields = True
485 allow_extra_fields = True
486 filter_extra_fields = True
486 filter_extra_fields = True
487
487
488 org_repo = v.UnicodeString(strip=True, required=True)
488 org_repo = v.UnicodeString(strip=True, required=True)
489 org_ref = v.UnicodeString(strip=True, required=True)
489 org_ref = v.UnicodeString(strip=True, required=True)
490 other_repo = v.UnicodeString(strip=True, required=True)
490 other_repo = v.UnicodeString(strip=True, required=True)
491 other_ref = v.UnicodeString(strip=True, required=True)
491 other_ref = v.UnicodeString(strip=True, required=True)
492 review_members = v.Set()
492 review_members = v.Set()
493
493
494 pullrequest_title = v.UnicodeString(strip=True, required=True)
494 pullrequest_title = v.UnicodeString(strip=True, required=True)
495 pullrequest_desc = v.UnicodeString(strip=True, required=False)
495 pullrequest_desc = v.UnicodeString(strip=True, required=False)
496
496
497 return _PullRequestForm
497 return _PullRequestForm
498
498
499
499
500 def PullRequestPostForm():
500 def PullRequestPostForm():
501 class _PullRequestPostForm(formencode.Schema):
501 class _PullRequestPostForm(formencode.Schema):
502 allow_extra_fields = True
502 allow_extra_fields = True
503 filter_extra_fields = True
503 filter_extra_fields = True
504
504
505 pullrequest_title = v.UnicodeString(strip=True, required=True)
505 pullrequest_title = v.UnicodeString(strip=True, required=True)
506 pullrequest_desc = v.UnicodeString(strip=True, required=False)
506 pullrequest_desc = v.UnicodeString(strip=True, required=False)
507 review_members = v.Set()
507 review_members = v.Set()
508 updaterev = v.UnicodeString(strip=True, required=False, if_missing=None)
508 updaterev = v.UnicodeString(strip=True, required=False, if_missing=None)
509
509
510 return _PullRequestPostForm
510 return _PullRequestPostForm
511
511
512
512
513 def GistForm(lifetime_options):
513 def GistForm(lifetime_options):
514 class _GistForm(formencode.Schema):
514 class _GistForm(formencode.Schema):
515
515
516 filename = All(v.BasePath()(),
516 filename = All(v.BasePath()(),
517 v.UnicodeString(strip=True, required=False))
517 v.UnicodeString(strip=True, required=False))
518 description = v.UnicodeString(required=False, if_missing=u'')
518 description = v.UnicodeString(required=False, if_missing=u'')
519 lifetime = v.OneOf(lifetime_options)
519 lifetime = v.OneOf(lifetime_options)
520 mimetype = v.UnicodeString(required=False, if_missing=None)
520 mimetype = v.UnicodeString(required=False, if_missing=None)
521 content = v.UnicodeString(required=True, not_empty=True)
521 content = v.UnicodeString(required=True, not_empty=True)
522 public = v.UnicodeString(required=False, if_missing=u'')
522 public = v.UnicodeString(required=False, if_missing=u'')
523 private = v.UnicodeString(required=False, if_missing=u'')
523 private = v.UnicodeString(required=False, if_missing=u'')
524
524
525 return _GistForm
525 return _GistForm
@@ -1,886 +1,886 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.scm
15 kallithea.model.scm
16 ~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~
17
17
18 Scm model for Kallithea
18 Scm model for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 9, 2010
22 :created_on: Apr 9, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 from __future__ import with_statement
28 from __future__ import with_statement
29 import os
29 import os
30 import re
30 import re
31 import time
31 import time
32 import traceback
32 import traceback
33 import logging
33 import logging
34 import cStringIO
34 import cStringIO
35 import pkg_resources
35 import pkg_resources
36 from os.path import join as jn
36 from os.path import join as jn
37
37
38 from sqlalchemy import func
38 from sqlalchemy import func
39 from pylons.i18n.translation import _
39 from pylons.i18n.translation import _
40
40
41 import kallithea
41 import kallithea
42 from kallithea.lib.vcs import get_backend
42 from kallithea.lib.vcs import get_backend
43 from kallithea.lib.vcs.exceptions import RepositoryError
43 from kallithea.lib.vcs.exceptions import RepositoryError
44 from kallithea.lib.vcs.utils.lazy import LazyProperty
44 from kallithea.lib.vcs.utils.lazy import LazyProperty
45 from kallithea.lib.vcs.nodes import FileNode
45 from kallithea.lib.vcs.nodes import FileNode
46 from kallithea.lib.vcs.backends.base import EmptyChangeset
46 from kallithea.lib.vcs.backends.base import EmptyChangeset
47
47
48 from kallithea import BACKENDS
48 from kallithea import BACKENDS
49 from kallithea.lib import helpers as h
49 from kallithea.lib import helpers as h
50 from kallithea.lib.utils2 import safe_str, safe_unicode, get_server_url,\
50 from kallithea.lib.utils2 import safe_str, safe_unicode, get_server_url,\
51 _set_extras
51 _set_extras
52 from kallithea.lib.auth import HasRepoPermissionAny, HasRepoGroupPermissionAny,\
52 from kallithea.lib.auth import HasRepoPermissionAny, HasRepoGroupPermissionAny,\
53 HasUserGroupPermissionAny
53 HasUserGroupPermissionAny
54 from kallithea.lib.utils import get_filesystem_repos, make_ui, \
54 from kallithea.lib.utils import get_filesystem_repos, make_ui, \
55 action_logger
55 action_logger
56 from kallithea.model import BaseModel
56 from kallithea.model import BaseModel
57 from kallithea.model.db import Repository, Ui, CacheInvalidation, \
57 from kallithea.model.db import Repository, Ui, CacheInvalidation, \
58 UserFollowing, UserLog, User, RepoGroup, PullRequest
58 UserFollowing, UserLog, User, RepoGroup, PullRequest
59 from kallithea.lib.hooks import log_push_action
59 from kallithea.lib.hooks import log_push_action
60 from kallithea.lib.exceptions import NonRelativePathError, IMCCommitError
60 from kallithea.lib.exceptions import NonRelativePathError, IMCCommitError
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 class UserTemp(object):
65 class UserTemp(object):
66 def __init__(self, user_id):
66 def __init__(self, user_id):
67 self.user_id = user_id
67 self.user_id = user_id
68
68
69 def __repr__(self):
69 def __repr__(self):
70 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
70 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
71
71
72
72
73 class RepoTemp(object):
73 class RepoTemp(object):
74 def __init__(self, repo_id):
74 def __init__(self, repo_id):
75 self.repo_id = repo_id
75 self.repo_id = repo_id
76
76
77 def __repr__(self):
77 def __repr__(self):
78 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
78 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
79
79
80
80
81 class CachedRepoList(object):
81 class CachedRepoList(object):
82 """
82 """
83 Cached repo list. Uses super-fast in-memory cache after initialization.
83 Cached repo list. Uses super-fast in-memory cache after initialization.
84 """
84 """
85
85
86 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
86 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
87 self.db_repo_list = db_repo_list
87 self.db_repo_list = db_repo_list
88 self.repos_path = repos_path
88 self.repos_path = repos_path
89 self.order_by = order_by
89 self.order_by = order_by
90 self.reversed = (order_by or '').startswith('-')
90 self.reversed = (order_by or '').startswith('-')
91 if not perm_set:
91 if not perm_set:
92 perm_set = ['repository.read', 'repository.write',
92 perm_set = ['repository.read', 'repository.write',
93 'repository.admin']
93 'repository.admin']
94 self.perm_set = perm_set
94 self.perm_set = perm_set
95
95
96 def __len__(self):
96 def __len__(self):
97 return len(self.db_repo_list)
97 return len(self.db_repo_list)
98
98
99 def __repr__(self):
99 def __repr__(self):
100 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
100 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
101
101
102 def __iter__(self):
102 def __iter__(self):
103 # pre-propagated valid_cache_keys to save executing select statements
103 # pre-propagated valid_cache_keys to save executing select statements
104 # for each repo
104 # for each repo
105 valid_cache_keys = CacheInvalidation.get_valid_cache_keys()
105 valid_cache_keys = CacheInvalidation.get_valid_cache_keys()
106
106
107 for dbr in self.db_repo_list:
107 for dbr in self.db_repo_list:
108 scmr = dbr.scm_instance_cached(valid_cache_keys)
108 scmr = dbr.scm_instance_cached(valid_cache_keys)
109 # check permission at this level
109 # check permission at this level
110 if not HasRepoPermissionAny(
110 if not HasRepoPermissionAny(
111 *self.perm_set)(dbr.repo_name, 'get repo check'):
111 *self.perm_set)(dbr.repo_name, 'get repo check'):
112 continue
112 continue
113
113
114 try:
114 try:
115 last_change = scmr.last_change
115 last_change = scmr.last_change
116 tip = h.get_changeset_safe(scmr, 'tip')
116 tip = h.get_changeset_safe(scmr, 'tip')
117 except Exception:
117 except Exception:
118 log.error(
118 log.error(
119 '%s this repository is present in database but it '
119 '%s this repository is present in database but it '
120 'cannot be created as an scm instance, org_exc:%s'
120 'cannot be created as an scm instance, org_exc:%s'
121 % (dbr.repo_name, traceback.format_exc())
121 % (dbr.repo_name, traceback.format_exc())
122 )
122 )
123 continue
123 continue
124
124
125 tmp_d = {}
125 tmp_d = {}
126 tmp_d['name'] = dbr.repo_name
126 tmp_d['name'] = dbr.repo_name
127 tmp_d['name_sort'] = tmp_d['name'].lower()
127 tmp_d['name_sort'] = tmp_d['name'].lower()
128 tmp_d['raw_name'] = tmp_d['name'].lower()
128 tmp_d['raw_name'] = tmp_d['name'].lower()
129 tmp_d['description'] = dbr.description
129 tmp_d['description'] = dbr.description
130 tmp_d['description_sort'] = tmp_d['description'].lower()
130 tmp_d['description_sort'] = tmp_d['description'].lower()
131 tmp_d['last_change'] = last_change
131 tmp_d['last_change'] = last_change
132 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
132 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
133 tmp_d['tip'] = tip.raw_id
133 tmp_d['tip'] = tip.raw_id
134 tmp_d['tip_sort'] = tip.revision
134 tmp_d['tip_sort'] = tip.revision
135 tmp_d['rev'] = tip.revision
135 tmp_d['rev'] = tip.revision
136 tmp_d['contact'] = dbr.user.full_contact
136 tmp_d['contact'] = dbr.user.full_contact
137 tmp_d['contact_sort'] = tmp_d['contact']
137 tmp_d['contact_sort'] = tmp_d['contact']
138 tmp_d['owner_sort'] = tmp_d['contact']
138 tmp_d['owner_sort'] = tmp_d['contact']
139 tmp_d['repo_archives'] = list(scmr._get_archives())
139 tmp_d['repo_archives'] = list(scmr._get_archives())
140 tmp_d['last_msg'] = tip.message
140 tmp_d['last_msg'] = tip.message
141 tmp_d['author'] = tip.author
141 tmp_d['author'] = tip.author
142 tmp_d['dbrepo'] = dbr.get_dict()
142 tmp_d['dbrepo'] = dbr.get_dict()
143 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
143 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
144 yield tmp_d
144 yield tmp_d
145
145
146
146
147 class SimpleCachedRepoList(CachedRepoList):
147 class SimpleCachedRepoList(CachedRepoList):
148 """
148 """
149 Lighter version of CachedRepoList without the scm initialisation
149 Lighter version of CachedRepoList without the scm initialisation
150 """
150 """
151
151
152 def __iter__(self):
152 def __iter__(self):
153 for dbr in self.db_repo_list:
153 for dbr in self.db_repo_list:
154 # check permission at this level
154 # check permission at this level
155 if not HasRepoPermissionAny(
155 if not HasRepoPermissionAny(
156 *self.perm_set)(dbr.repo_name, 'get repo check'):
156 *self.perm_set)(dbr.repo_name, 'get repo check'):
157 continue
157 continue
158
158
159 tmp_d = {
159 tmp_d = {
160 'name': dbr.repo_name,
160 'name': dbr.repo_name,
161 'dbrepo': dbr.get_dict(),
161 'dbrepo': dbr.get_dict(),
162 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
162 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
163 }
163 }
164 yield tmp_d
164 yield tmp_d
165
165
166
166
167 class _PermCheckIterator(object):
167 class _PermCheckIterator(object):
168 def __init__(self, obj_list, obj_attr, perm_set, perm_checker, extra_kwargs=None):
168 def __init__(self, obj_list, obj_attr, perm_set, perm_checker, extra_kwargs=None):
169 """
169 """
170 Creates iterator from given list of objects, additionally
170 Creates iterator from given list of objects, additionally
171 checking permission for them from perm_set var
171 checking permission for them from perm_set var
172
172
173 :param obj_list: list of db objects
173 :param obj_list: list of db objects
174 :param obj_attr: attribute of object to pass into perm_checker
174 :param obj_attr: attribute of object to pass into perm_checker
175 :param perm_set: list of permissions to check
175 :param perm_set: list of permissions to check
176 :param perm_checker: callable to check permissions against
176 :param perm_checker: callable to check permissions against
177 """
177 """
178 self.obj_list = obj_list
178 self.obj_list = obj_list
179 self.obj_attr = obj_attr
179 self.obj_attr = obj_attr
180 self.perm_set = perm_set
180 self.perm_set = perm_set
181 self.perm_checker = perm_checker
181 self.perm_checker = perm_checker
182 self.extra_kwargs = extra_kwargs or {}
182 self.extra_kwargs = extra_kwargs or {}
183
183
184 def __len__(self):
184 def __len__(self):
185 return len(self.obj_list)
185 return len(self.obj_list)
186
186
187 def __repr__(self):
187 def __repr__(self):
188 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
188 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
189
189
190 def __iter__(self):
190 def __iter__(self):
191 for db_obj in self.obj_list:
191 for db_obj in self.obj_list:
192 # check permission at this level
192 # check permission at this level
193 name = getattr(db_obj, self.obj_attr, None)
193 name = getattr(db_obj, self.obj_attr, None)
194 if not self.perm_checker(*self.perm_set)(
194 if not self.perm_checker(*self.perm_set)(
195 name, self.__class__.__name__, **self.extra_kwargs):
195 name, self.__class__.__name__, **self.extra_kwargs):
196 continue
196 continue
197
197
198 yield db_obj
198 yield db_obj
199
199
200
200
201 class RepoList(_PermCheckIterator):
201 class RepoList(_PermCheckIterator):
202
202
203 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
203 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
204 if not perm_set:
204 if not perm_set:
205 perm_set = ['repository.read', 'repository.write', 'repository.admin']
205 perm_set = ['repository.read', 'repository.write', 'repository.admin']
206
206
207 super(RepoList, self).__init__(obj_list=db_repo_list,
207 super(RepoList, self).__init__(obj_list=db_repo_list,
208 obj_attr='repo_name', perm_set=perm_set,
208 obj_attr='repo_name', perm_set=perm_set,
209 perm_checker=HasRepoPermissionAny,
209 perm_checker=HasRepoPermissionAny,
210 extra_kwargs=extra_kwargs)
210 extra_kwargs=extra_kwargs)
211
211
212
212
213 class RepoGroupList(_PermCheckIterator):
213 class RepoGroupList(_PermCheckIterator):
214
214
215 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
215 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
216 if not perm_set:
216 if not perm_set:
217 perm_set = ['group.read', 'group.write', 'group.admin']
217 perm_set = ['group.read', 'group.write', 'group.admin']
218
218
219 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
219 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
220 obj_attr='group_name', perm_set=perm_set,
220 obj_attr='group_name', perm_set=perm_set,
221 perm_checker=HasRepoGroupPermissionAny,
221 perm_checker=HasRepoGroupPermissionAny,
222 extra_kwargs=extra_kwargs)
222 extra_kwargs=extra_kwargs)
223
223
224
224
225 class UserGroupList(_PermCheckIterator):
225 class UserGroupList(_PermCheckIterator):
226
226
227 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
227 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
228 if not perm_set:
228 if not perm_set:
229 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
229 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
230
230
231 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
231 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
232 obj_attr='users_group_name', perm_set=perm_set,
232 obj_attr='users_group_name', perm_set=perm_set,
233 perm_checker=HasUserGroupPermissionAny,
233 perm_checker=HasUserGroupPermissionAny,
234 extra_kwargs=extra_kwargs)
234 extra_kwargs=extra_kwargs)
235
235
236
236
237 class ScmModel(BaseModel):
237 class ScmModel(BaseModel):
238 """
238 """
239 Generic Scm Model
239 Generic Scm Model
240 """
240 """
241
241
242 def __get_repo(self, instance):
242 def __get_repo(self, instance):
243 cls = Repository
243 cls = Repository
244 if isinstance(instance, cls):
244 if isinstance(instance, cls):
245 return instance
245 return instance
246 elif isinstance(instance, int) or safe_str(instance).isdigit():
246 elif isinstance(instance, int) or safe_str(instance).isdigit():
247 return cls.get(instance)
247 return cls.get(instance)
248 elif isinstance(instance, basestring):
248 elif isinstance(instance, basestring):
249 return cls.get_by_repo_name(instance)
249 return cls.get_by_repo_name(instance)
250 elif instance:
250 elif instance:
251 raise Exception('given object must be int, basestr or Instance'
251 raise Exception('given object must be int, basestr or Instance'
252 ' of %s got %s' % (type(cls), type(instance)))
252 ' of %s got %s' % (type(cls), type(instance)))
253
253
254 @LazyProperty
254 @LazyProperty
255 def repos_path(self):
255 def repos_path(self):
256 """
256 """
257 Gets the repositories root path from database
257 Gets the repositories root path from database
258 """
258 """
259
259
260 q = self.sa.query(Ui).filter(Ui.ui_key == '/').one()
260 q = self.sa.query(Ui).filter(Ui.ui_key == '/').one()
261
261
262 return q.ui_value
262 return q.ui_value
263
263
264 def repo_scan(self, repos_path=None):
264 def repo_scan(self, repos_path=None):
265 """
265 """
266 Listing of repositories in given path. This path should not be a
266 Listing of repositories in given path. This path should not be a
267 repository itself. Return a dictionary of repository objects
267 repository itself. Return a dictionary of repository objects
268
268
269 :param repos_path: path to directory containing repositories
269 :param repos_path: path to directory containing repositories
270 """
270 """
271
271
272 if repos_path is None:
272 if repos_path is None:
273 repos_path = self.repos_path
273 repos_path = self.repos_path
274
274
275 log.info('scanning for repositories in %s' % repos_path)
275 log.info('scanning for repositories in %s' % repos_path)
276
276
277 baseui = make_ui('db')
277 baseui = make_ui('db')
278 repos = {}
278 repos = {}
279
279
280 for name, path in get_filesystem_repos(repos_path, recursive=True):
280 for name, path in get_filesystem_repos(repos_path, recursive=True):
281 # name need to be decomposed and put back together using the /
281 # name need to be decomposed and put back together using the /
282 # since this is internal storage separator for kallithea
282 # since this is internal storage separator for kallithea
283 name = Repository.normalize_repo_name(name)
283 name = Repository.normalize_repo_name(name)
284
284
285 try:
285 try:
286 if name in repos:
286 if name in repos:
287 raise RepositoryError('Duplicate repository name %s '
287 raise RepositoryError('Duplicate repository name %s '
288 'found in %s' % (name, path))
288 'found in %s' % (name, path))
289 else:
289 else:
290
290
291 klass = get_backend(path[0])
291 klass = get_backend(path[0])
292
292
293 if path[0] == 'hg' and path[0] in BACKENDS.keys():
293 if path[0] == 'hg' and path[0] in BACKENDS.keys():
294 repos[name] = klass(safe_str(path[1]), baseui=baseui)
294 repos[name] = klass(safe_str(path[1]), baseui=baseui)
295
295
296 if path[0] == 'git' and path[0] in BACKENDS.keys():
296 if path[0] == 'git' and path[0] in BACKENDS.keys():
297 repos[name] = klass(path[1])
297 repos[name] = klass(path[1])
298 except OSError:
298 except OSError:
299 continue
299 continue
300 log.debug('found %s paths with repositories' % (len(repos)))
300 log.debug('found %s paths with repositories' % (len(repos)))
301 return repos
301 return repos
302
302
303 def get_repos(self, all_repos=None, sort_key=None, simple=False):
303 def get_repos(self, all_repos=None, sort_key=None, simple=False):
304 """
304 """
305 Get all repos from db and for each repo create its
305 Get all repos from db and for each repo create its
306 backend instance and fill that backed with information from database
306 backend instance and fill that backed with information from database
307
307
308 :param all_repos: list of repository names as strings
308 :param all_repos: list of repository names as strings
309 give specific repositories list, good for filtering
309 give specific repositories list, good for filtering
310
310
311 :param sort_key: initial sorting of repos
311 :param sort_key: initial sorting of repos
312 :param simple: use SimpleCachedList - one without the SCM info
312 :param simple: use SimpleCachedList - one without the SCM info
313 """
313 """
314 if all_repos is None:
314 if all_repos is None:
315 all_repos = self.sa.query(Repository)\
315 all_repos = self.sa.query(Repository)\
316 .filter(Repository.group_id == None)\
316 .filter(Repository.group_id == None)\
317 .order_by(func.lower(Repository.repo_name)).all()
317 .order_by(func.lower(Repository.repo_name)).all()
318 if simple:
318 if simple:
319 repo_iter = SimpleCachedRepoList(all_repos,
319 repo_iter = SimpleCachedRepoList(all_repos,
320 repos_path=self.repos_path,
320 repos_path=self.repos_path,
321 order_by=sort_key)
321 order_by=sort_key)
322 else:
322 else:
323 repo_iter = CachedRepoList(all_repos,
323 repo_iter = CachedRepoList(all_repos,
324 repos_path=self.repos_path,
324 repos_path=self.repos_path,
325 order_by=sort_key)
325 order_by=sort_key)
326
326
327 return repo_iter
327 return repo_iter
328
328
329 def get_repo_groups(self, all_groups=None):
329 def get_repo_groups(self, all_groups=None):
330 if all_groups is None:
330 if all_groups is None:
331 all_groups = RepoGroup.query()\
331 all_groups = RepoGroup.query()\
332 .filter(RepoGroup.group_parent_id == None).all()
332 .filter(RepoGroup.group_parent_id == None).all()
333 return [x for x in RepoGroupList(all_groups)]
333 return [x for x in RepoGroupList(all_groups)]
334
334
335 def mark_for_invalidation(self, repo_name, delete=False):
335 def mark_for_invalidation(self, repo_name, delete=False):
336 """
336 """
337 Mark caches of this repo invalid in the database.
337 Mark caches of this repo invalid in the database.
338
338
339 :param repo_name: the repo for which caches should be marked invalid
339 :param repo_name: the repo for which caches should be marked invalid
340 """
340 """
341 CacheInvalidation.set_invalidate(repo_name, delete=delete)
341 CacheInvalidation.set_invalidate(repo_name, delete=delete)
342 repo = Repository.get_by_repo_name(repo_name)
342 repo = Repository.get_by_repo_name(repo_name)
343 if repo:
343 if repo:
344 repo.update_changeset_cache()
344 repo.update_changeset_cache()
345
345
346 def toggle_following_repo(self, follow_repo_id, user_id):
346 def toggle_following_repo(self, follow_repo_id, user_id):
347
347
348 f = self.sa.query(UserFollowing)\
348 f = self.sa.query(UserFollowing)\
349 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
349 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
350 .filter(UserFollowing.user_id == user_id).scalar()
350 .filter(UserFollowing.user_id == user_id).scalar()
351
351
352 if f is not None:
352 if f is not None:
353 try:
353 try:
354 self.sa.delete(f)
354 self.sa.delete(f)
355 action_logger(UserTemp(user_id),
355 action_logger(UserTemp(user_id),
356 'stopped_following_repo',
356 'stopped_following_repo',
357 RepoTemp(follow_repo_id))
357 RepoTemp(follow_repo_id))
358 return
358 return
359 except Exception:
359 except Exception:
360 log.error(traceback.format_exc())
360 log.error(traceback.format_exc())
361 raise
361 raise
362
362
363 try:
363 try:
364 f = UserFollowing()
364 f = UserFollowing()
365 f.user_id = user_id
365 f.user_id = user_id
366 f.follows_repo_id = follow_repo_id
366 f.follows_repo_id = follow_repo_id
367 self.sa.add(f)
367 self.sa.add(f)
368
368
369 action_logger(UserTemp(user_id),
369 action_logger(UserTemp(user_id),
370 'started_following_repo',
370 'started_following_repo',
371 RepoTemp(follow_repo_id))
371 RepoTemp(follow_repo_id))
372 except Exception:
372 except Exception:
373 log.error(traceback.format_exc())
373 log.error(traceback.format_exc())
374 raise
374 raise
375
375
376 def toggle_following_user(self, follow_user_id, user_id):
376 def toggle_following_user(self, follow_user_id, user_id):
377 f = self.sa.query(UserFollowing)\
377 f = self.sa.query(UserFollowing)\
378 .filter(UserFollowing.follows_user_id == follow_user_id)\
378 .filter(UserFollowing.follows_user_id == follow_user_id)\
379 .filter(UserFollowing.user_id == user_id).scalar()
379 .filter(UserFollowing.user_id == user_id).scalar()
380
380
381 if f is not None:
381 if f is not None:
382 try:
382 try:
383 self.sa.delete(f)
383 self.sa.delete(f)
384 return
384 return
385 except Exception:
385 except Exception:
386 log.error(traceback.format_exc())
386 log.error(traceback.format_exc())
387 raise
387 raise
388
388
389 try:
389 try:
390 f = UserFollowing()
390 f = UserFollowing()
391 f.user_id = user_id
391 f.user_id = user_id
392 f.follows_user_id = follow_user_id
392 f.follows_user_id = follow_user_id
393 self.sa.add(f)
393 self.sa.add(f)
394 except Exception:
394 except Exception:
395 log.error(traceback.format_exc())
395 log.error(traceback.format_exc())
396 raise
396 raise
397
397
398 def is_following_repo(self, repo_name, user_id, cache=False):
398 def is_following_repo(self, repo_name, user_id, cache=False):
399 r = self.sa.query(Repository)\
399 r = self.sa.query(Repository)\
400 .filter(Repository.repo_name == repo_name).scalar()
400 .filter(Repository.repo_name == repo_name).scalar()
401
401
402 f = self.sa.query(UserFollowing)\
402 f = self.sa.query(UserFollowing)\
403 .filter(UserFollowing.follows_repository == r)\
403 .filter(UserFollowing.follows_repository == r)\
404 .filter(UserFollowing.user_id == user_id).scalar()
404 .filter(UserFollowing.user_id == user_id).scalar()
405
405
406 return f is not None
406 return f is not None
407
407
408 def is_following_user(self, username, user_id, cache=False):
408 def is_following_user(self, username, user_id, cache=False):
409 u = User.get_by_username(username)
409 u = User.get_by_username(username)
410
410
411 f = self.sa.query(UserFollowing)\
411 f = self.sa.query(UserFollowing)\
412 .filter(UserFollowing.follows_user == u)\
412 .filter(UserFollowing.follows_user == u)\
413 .filter(UserFollowing.user_id == user_id).scalar()
413 .filter(UserFollowing.user_id == user_id).scalar()
414
414
415 return f is not None
415 return f is not None
416
416
417 def get_followers(self, repo):
417 def get_followers(self, repo):
418 repo = self._get_repo(repo)
418 repo = self._get_repo(repo)
419
419
420 return self.sa.query(UserFollowing)\
420 return self.sa.query(UserFollowing)\
421 .filter(UserFollowing.follows_repository == repo).count()
421 .filter(UserFollowing.follows_repository == repo).count()
422
422
423 def get_forks(self, repo):
423 def get_forks(self, repo):
424 repo = self._get_repo(repo)
424 repo = self._get_repo(repo)
425 return self.sa.query(Repository)\
425 return self.sa.query(Repository)\
426 .filter(Repository.fork == repo).count()
426 .filter(Repository.fork == repo).count()
427
427
428 def get_pull_requests(self, repo):
428 def get_pull_requests(self, repo):
429 repo = self._get_repo(repo)
429 repo = self._get_repo(repo)
430 return self.sa.query(PullRequest)\
430 return self.sa.query(PullRequest)\
431 .filter(PullRequest.other_repo == repo)\
431 .filter(PullRequest.other_repo == repo)\
432 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
432 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
433
433
434 def mark_as_fork(self, repo, fork, user):
434 def mark_as_fork(self, repo, fork, user):
435 repo = self.__get_repo(repo)
435 repo = self.__get_repo(repo)
436 fork = self.__get_repo(fork)
436 fork = self.__get_repo(fork)
437 if fork and repo.repo_id == fork.repo_id:
437 if fork and repo.repo_id == fork.repo_id:
438 raise Exception("Cannot set repository as fork of itself")
438 raise Exception("Cannot set repository as fork of itself")
439
439
440 if fork and repo.repo_type != fork.repo_type:
440 if fork and repo.repo_type != fork.repo_type:
441 raise RepositoryError("Cannot set repository as fork of repository with other type")
441 raise RepositoryError("Cannot set repository as fork of repository with other type")
442
442
443 repo.fork = fork
443 repo.fork = fork
444 self.sa.add(repo)
444 self.sa.add(repo)
445 return repo
445 return repo
446
446
447 def _handle_rc_scm_extras(self, username, repo_name, repo_alias,
447 def _handle_rc_scm_extras(self, username, repo_name, repo_alias,
448 action=None):
448 action=None):
449 from kallithea import CONFIG
449 from kallithea import CONFIG
450 from kallithea.lib.base import _get_ip_addr
450 from kallithea.lib.base import _get_ip_addr
451 try:
451 try:
452 from pylons import request
452 from pylons import request
453 environ = request.environ
453 environ = request.environ
454 except TypeError:
454 except TypeError:
455 # we might use this outside of request context, let's fake the
455 # we might use this outside of request context, let's fake the
456 # environ data
456 # environ data
457 from webob import Request
457 from webob import Request
458 environ = Request.blank('').environ
458 environ = Request.blank('').environ
459 extras = {
459 extras = {
460 'ip': _get_ip_addr(environ),
460 'ip': _get_ip_addr(environ),
461 'username': username,
461 'username': username,
462 'action': action or 'push_local',
462 'action': action or 'push_local',
463 'repository': repo_name,
463 'repository': repo_name,
464 'scm': repo_alias,
464 'scm': repo_alias,
465 'config': CONFIG['__file__'],
465 'config': CONFIG['__file__'],
466 'server_url': get_server_url(environ),
466 'server_url': get_server_url(environ),
467 'make_lock': None,
467 'make_lock': None,
468 'locked_by': [None, None]
468 'locked_by': [None, None]
469 }
469 }
470 _set_extras(extras)
470 _set_extras(extras)
471
471
472 def _handle_push(self, repo, username, action, repo_name, revisions):
472 def _handle_push(self, repo, username, action, repo_name, revisions):
473 """
473 """
474 Triggers push action hooks
474 Triggers push action hooks
475
475
476 :param repo: SCM repo
476 :param repo: SCM repo
477 :param username: username who pushes
477 :param username: username who pushes
478 :param action: push/push_loca/push_remote
478 :param action: push/push_local/push_remote
479 :param repo_name: name of repo
479 :param repo_name: name of repo
480 :param revisions: list of revisions that we pushed
480 :param revisions: list of revisions that we pushed
481 """
481 """
482 self._handle_rc_scm_extras(username, repo_name, repo_alias=repo.alias)
482 self._handle_rc_scm_extras(username, repo_name, repo_alias=repo.alias)
483 _scm_repo = repo._repo
483 _scm_repo = repo._repo
484 # trigger push hook
484 # trigger push hook
485 if repo.alias == 'hg':
485 if repo.alias == 'hg':
486 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
486 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
487 elif repo.alias == 'git':
487 elif repo.alias == 'git':
488 log_push_action(None, _scm_repo, _git_revs=revisions)
488 log_push_action(None, _scm_repo, _git_revs=revisions)
489
489
490 def _get_IMC_module(self, scm_type):
490 def _get_IMC_module(self, scm_type):
491 """
491 """
492 Returns InMemoryCommit class based on scm_type
492 Returns InMemoryCommit class based on scm_type
493
493
494 :param scm_type:
494 :param scm_type:
495 """
495 """
496 if scm_type == 'hg':
496 if scm_type == 'hg':
497 from kallithea.lib.vcs.backends.hg import MercurialInMemoryChangeset
497 from kallithea.lib.vcs.backends.hg import MercurialInMemoryChangeset
498 return MercurialInMemoryChangeset
498 return MercurialInMemoryChangeset
499
499
500 if scm_type == 'git':
500 if scm_type == 'git':
501 from kallithea.lib.vcs.backends.git import GitInMemoryChangeset
501 from kallithea.lib.vcs.backends.git import GitInMemoryChangeset
502 return GitInMemoryChangeset
502 return GitInMemoryChangeset
503
503
504 raise Exception('Invalid scm_type, must be one of hg,git got %s'
504 raise Exception('Invalid scm_type, must be one of hg,git got %s'
505 % (scm_type,))
505 % (scm_type,))
506
506
507 def pull_changes(self, repo, username):
507 def pull_changes(self, repo, username):
508 dbrepo = self.__get_repo(repo)
508 dbrepo = self.__get_repo(repo)
509 clone_uri = dbrepo.clone_uri
509 clone_uri = dbrepo.clone_uri
510 if not clone_uri:
510 if not clone_uri:
511 raise Exception("This repository doesn't have a clone uri")
511 raise Exception("This repository doesn't have a clone uri")
512
512
513 repo = dbrepo.scm_instance
513 repo = dbrepo.scm_instance
514 repo_name = dbrepo.repo_name
514 repo_name = dbrepo.repo_name
515 try:
515 try:
516 if repo.alias == 'git':
516 if repo.alias == 'git':
517 repo.fetch(clone_uri)
517 repo.fetch(clone_uri)
518 # git doesn't really have something like post-fetch action
518 # git doesn't really have something like post-fetch action
519 # we fake that now. #TODO: extract fetched revisions somehow
519 # we fake that now. #TODO: extract fetched revisions somehow
520 # here
520 # here
521 self._handle_push(repo,
521 self._handle_push(repo,
522 username=username,
522 username=username,
523 action='push_remote',
523 action='push_remote',
524 repo_name=repo_name,
524 repo_name=repo_name,
525 revisions=[])
525 revisions=[])
526 else:
526 else:
527 self._handle_rc_scm_extras(username, dbrepo.repo_name,
527 self._handle_rc_scm_extras(username, dbrepo.repo_name,
528 repo.alias, action='push_remote')
528 repo.alias, action='push_remote')
529 repo.pull(clone_uri)
529 repo.pull(clone_uri)
530
530
531 self.mark_for_invalidation(repo_name)
531 self.mark_for_invalidation(repo_name)
532 except Exception:
532 except Exception:
533 log.error(traceback.format_exc())
533 log.error(traceback.format_exc())
534 raise
534 raise
535
535
536 def commit_change(self, repo, repo_name, cs, user, author, message,
536 def commit_change(self, repo, repo_name, cs, user, author, message,
537 content, f_path):
537 content, f_path):
538 """
538 """
539 Commits changes
539 Commits changes
540
540
541 :param repo: SCM instance
541 :param repo: SCM instance
542
542
543 """
543 """
544 user = self._get_user(user)
544 user = self._get_user(user)
545 IMC = self._get_IMC_module(repo.alias)
545 IMC = self._get_IMC_module(repo.alias)
546
546
547 # decoding here will force that we have proper encoded values
547 # decoding here will force that we have proper encoded values
548 # in any other case this will throw exceptions and deny commit
548 # in any other case this will throw exceptions and deny commit
549 content = safe_str(content)
549 content = safe_str(content)
550 path = safe_str(f_path)
550 path = safe_str(f_path)
551 # message and author needs to be unicode
551 # message and author needs to be unicode
552 # proper backend should then translate that into required type
552 # proper backend should then translate that into required type
553 message = safe_unicode(message)
553 message = safe_unicode(message)
554 author = safe_unicode(author)
554 author = safe_unicode(author)
555 imc = IMC(repo)
555 imc = IMC(repo)
556 imc.change(FileNode(path, content, mode=cs.get_file_mode(f_path)))
556 imc.change(FileNode(path, content, mode=cs.get_file_mode(f_path)))
557 try:
557 try:
558 tip = imc.commit(message=message, author=author,
558 tip = imc.commit(message=message, author=author,
559 parents=[cs], branch=cs.branch)
559 parents=[cs], branch=cs.branch)
560 except Exception, e:
560 except Exception, e:
561 log.error(traceback.format_exc())
561 log.error(traceback.format_exc())
562 raise IMCCommitError(str(e))
562 raise IMCCommitError(str(e))
563 finally:
563 finally:
564 # always clear caches, if commit fails we want fresh object also
564 # always clear caches, if commit fails we want fresh object also
565 self.mark_for_invalidation(repo_name)
565 self.mark_for_invalidation(repo_name)
566 self._handle_push(repo,
566 self._handle_push(repo,
567 username=user.username,
567 username=user.username,
568 action='push_local',
568 action='push_local',
569 repo_name=repo_name,
569 repo_name=repo_name,
570 revisions=[tip.raw_id])
570 revisions=[tip.raw_id])
571 return tip
571 return tip
572
572
573 def _sanitize_path(self, f_path):
573 def _sanitize_path(self, f_path):
574 if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
574 if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
575 raise NonRelativePathError('%s is not an relative path' % f_path)
575 raise NonRelativePathError('%s is not an relative path' % f_path)
576 if f_path:
576 if f_path:
577 f_path = os.path.normpath(f_path)
577 f_path = os.path.normpath(f_path)
578 return f_path
578 return f_path
579
579
580 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
580 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
581 """
581 """
582 Recursively walk root dir and return a set of all paths found.
582 Recursively walk root dir and return a set of all paths found.
583
583
584 :param repo_name: name of repository
584 :param repo_name: name of repository
585 :param revision: revision for which to list nodes
585 :param revision: revision for which to list nodes
586 :param root_path: root path to list
586 :param root_path: root path to list
587 :param flat: return as a list, if False returns a dict with description
587 :param flat: return as a list, if False returns a dict with description
588
588
589 """
589 """
590 _files = list()
590 _files = list()
591 _dirs = list()
591 _dirs = list()
592 try:
592 try:
593 _repo = self.__get_repo(repo_name)
593 _repo = self.__get_repo(repo_name)
594 changeset = _repo.scm_instance.get_changeset(revision)
594 changeset = _repo.scm_instance.get_changeset(revision)
595 root_path = root_path.lstrip('/')
595 root_path = root_path.lstrip('/')
596 for topnode, dirs, files in changeset.walk(root_path):
596 for topnode, dirs, files in changeset.walk(root_path):
597 for f in files:
597 for f in files:
598 _files.append(f.path if flat else {"name": f.path,
598 _files.append(f.path if flat else {"name": f.path,
599 "type": "file"})
599 "type": "file"})
600 for d in dirs:
600 for d in dirs:
601 _dirs.append(d.path if flat else {"name": d.path,
601 _dirs.append(d.path if flat else {"name": d.path,
602 "type": "dir"})
602 "type": "dir"})
603 except RepositoryError:
603 except RepositoryError:
604 log.debug(traceback.format_exc())
604 log.debug(traceback.format_exc())
605 raise
605 raise
606
606
607 return _dirs, _files
607 return _dirs, _files
608
608
609 def create_nodes(self, user, repo, message, nodes, parent_cs=None,
609 def create_nodes(self, user, repo, message, nodes, parent_cs=None,
610 author=None, trigger_push_hook=True):
610 author=None, trigger_push_hook=True):
611 """
611 """
612 Commits specified nodes to repo.
612 Commits specified nodes to repo.
613
613
614 :param user: Kallithea User object or user_id, the commiter
614 :param user: Kallithea User object or user_id, the committer
615 :param repo: Kallithea Repository object
615 :param repo: Kallithea Repository object
616 :param message: commit message
616 :param message: commit message
617 :param nodes: mapping {filename:{'content':content},...}
617 :param nodes: mapping {filename:{'content':content},...}
618 :param parent_cs: parent changeset, can be empty than it's initial commit
618 :param parent_cs: parent changeset, can be empty than it's initial commit
619 :param author: author of commit, cna be different that commiter only for git
619 :param author: author of commit, cna be different that committer only for git
620 :param trigger_push_hook: trigger push hooks
620 :param trigger_push_hook: trigger push hooks
621
621
622 :returns: new commited changeset
622 :returns: new committed changeset
623 """
623 """
624
624
625 user = self._get_user(user)
625 user = self._get_user(user)
626 scm_instance = repo.scm_instance_no_cache()
626 scm_instance = repo.scm_instance_no_cache()
627
627
628 processed_nodes = []
628 processed_nodes = []
629 for f_path in nodes:
629 for f_path in nodes:
630 f_path = self._sanitize_path(f_path)
630 f_path = self._sanitize_path(f_path)
631 content = nodes[f_path]['content']
631 content = nodes[f_path]['content']
632 f_path = safe_str(f_path)
632 f_path = safe_str(f_path)
633 # decoding here will force that we have proper encoded values
633 # decoding here will force that we have proper encoded values
634 # in any other case this will throw exceptions and deny commit
634 # in any other case this will throw exceptions and deny commit
635 if isinstance(content, (basestring,)):
635 if isinstance(content, (basestring,)):
636 content = safe_str(content)
636 content = safe_str(content)
637 elif isinstance(content, (file, cStringIO.OutputType,)):
637 elif isinstance(content, (file, cStringIO.OutputType,)):
638 content = content.read()
638 content = content.read()
639 else:
639 else:
640 raise Exception('Content is of unrecognized type %s' % (
640 raise Exception('Content is of unrecognized type %s' % (
641 type(content)
641 type(content)
642 ))
642 ))
643 processed_nodes.append((f_path, content))
643 processed_nodes.append((f_path, content))
644
644
645 message = safe_unicode(message)
645 message = safe_unicode(message)
646 commiter = user.full_contact
646 committer = user.full_contact
647 author = safe_unicode(author) if author else commiter
647 author = safe_unicode(author) if author else committer
648
648
649 IMC = self._get_IMC_module(scm_instance.alias)
649 IMC = self._get_IMC_module(scm_instance.alias)
650 imc = IMC(scm_instance)
650 imc = IMC(scm_instance)
651
651
652 if not parent_cs:
652 if not parent_cs:
653 parent_cs = EmptyChangeset(alias=scm_instance.alias)
653 parent_cs = EmptyChangeset(alias=scm_instance.alias)
654
654
655 if isinstance(parent_cs, EmptyChangeset):
655 if isinstance(parent_cs, EmptyChangeset):
656 # EmptyChangeset means we we're editing empty repository
656 # EmptyChangeset means we we're editing empty repository
657 parents = None
657 parents = None
658 else:
658 else:
659 parents = [parent_cs]
659 parents = [parent_cs]
660 # add multiple nodes
660 # add multiple nodes
661 for path, content in processed_nodes:
661 for path, content in processed_nodes:
662 imc.add(FileNode(path, content=content))
662 imc.add(FileNode(path, content=content))
663
663
664 tip = imc.commit(message=message,
664 tip = imc.commit(message=message,
665 author=author,
665 author=author,
666 parents=parents,
666 parents=parents,
667 branch=parent_cs.branch)
667 branch=parent_cs.branch)
668
668
669 self.mark_for_invalidation(repo.repo_name)
669 self.mark_for_invalidation(repo.repo_name)
670 if trigger_push_hook:
670 if trigger_push_hook:
671 self._handle_push(scm_instance,
671 self._handle_push(scm_instance,
672 username=user.username,
672 username=user.username,
673 action='push_local',
673 action='push_local',
674 repo_name=repo.repo_name,
674 repo_name=repo.repo_name,
675 revisions=[tip.raw_id])
675 revisions=[tip.raw_id])
676 return tip
676 return tip
677
677
678 def update_nodes(self, user, repo, message, nodes, parent_cs=None,
678 def update_nodes(self, user, repo, message, nodes, parent_cs=None,
679 author=None, trigger_push_hook=True):
679 author=None, trigger_push_hook=True):
680 user = self._get_user(user)
680 user = self._get_user(user)
681 scm_instance = repo.scm_instance_no_cache()
681 scm_instance = repo.scm_instance_no_cache()
682
682
683 message = safe_unicode(message)
683 message = safe_unicode(message)
684 commiter = user.full_contact
684 committer = user.full_contact
685 author = safe_unicode(author) if author else commiter
685 author = safe_unicode(author) if author else committer
686
686
687 imc_class = self._get_IMC_module(scm_instance.alias)
687 imc_class = self._get_IMC_module(scm_instance.alias)
688 imc = imc_class(scm_instance)
688 imc = imc_class(scm_instance)
689
689
690 if not parent_cs:
690 if not parent_cs:
691 parent_cs = EmptyChangeset(alias=scm_instance.alias)
691 parent_cs = EmptyChangeset(alias=scm_instance.alias)
692
692
693 if isinstance(parent_cs, EmptyChangeset):
693 if isinstance(parent_cs, EmptyChangeset):
694 # EmptyChangeset means we we're editing empty repository
694 # EmptyChangeset means we we're editing empty repository
695 parents = None
695 parents = None
696 else:
696 else:
697 parents = [parent_cs]
697 parents = [parent_cs]
698
698
699 # add multiple nodes
699 # add multiple nodes
700 for _filename, data in nodes.items():
700 for _filename, data in nodes.items():
701 # new filename, can be renamed from the old one
701 # new filename, can be renamed from the old one
702 filename = self._sanitize_path(data['filename'])
702 filename = self._sanitize_path(data['filename'])
703 old_filename = self._sanitize_path(_filename)
703 old_filename = self._sanitize_path(_filename)
704 content = data['content']
704 content = data['content']
705
705
706 filenode = FileNode(old_filename, content=content)
706 filenode = FileNode(old_filename, content=content)
707 op = data['op']
707 op = data['op']
708 if op == 'add':
708 if op == 'add':
709 imc.add(filenode)
709 imc.add(filenode)
710 elif op == 'del':
710 elif op == 'del':
711 imc.remove(filenode)
711 imc.remove(filenode)
712 elif op == 'mod':
712 elif op == 'mod':
713 if filename != old_filename:
713 if filename != old_filename:
714 #TODO: handle renames, needs vcs lib changes
714 #TODO: handle renames, needs vcs lib changes
715 imc.remove(filenode)
715 imc.remove(filenode)
716 imc.add(FileNode(filename, content=content))
716 imc.add(FileNode(filename, content=content))
717 else:
717 else:
718 imc.change(filenode)
718 imc.change(filenode)
719
719
720 # commit changes
720 # commit changes
721 tip = imc.commit(message=message,
721 tip = imc.commit(message=message,
722 author=author,
722 author=author,
723 parents=parents,
723 parents=parents,
724 branch=parent_cs.branch)
724 branch=parent_cs.branch)
725
725
726 self.mark_for_invalidation(repo.repo_name)
726 self.mark_for_invalidation(repo.repo_name)
727 if trigger_push_hook:
727 if trigger_push_hook:
728 self._handle_push(scm_instance,
728 self._handle_push(scm_instance,
729 username=user.username,
729 username=user.username,
730 action='push_local',
730 action='push_local',
731 repo_name=repo.repo_name,
731 repo_name=repo.repo_name,
732 revisions=[tip.raw_id])
732 revisions=[tip.raw_id])
733
733
734 def delete_nodes(self, user, repo, message, nodes, parent_cs=None,
734 def delete_nodes(self, user, repo, message, nodes, parent_cs=None,
735 author=None, trigger_push_hook=True):
735 author=None, trigger_push_hook=True):
736 """
736 """
737 Deletes specified nodes from repo.
737 Deletes specified nodes from repo.
738
738
739 :param user: Kallithea User object or user_id, the commiter
739 :param user: Kallithea User object or user_id, the committer
740 :param repo: Kallithea Repository object
740 :param repo: Kallithea Repository object
741 :param message: commit message
741 :param message: commit message
742 :param nodes: mapping {filename:{'content':content},...}
742 :param nodes: mapping {filename:{'content':content},...}
743 :param parent_cs: parent changeset, can be empty than it's initial commit
743 :param parent_cs: parent changeset, can be empty than it's initial commit
744 :param author: author of commit, cna be different that commiter only for git
744 :param author: author of commit, cna be different that committer only for git
745 :param trigger_push_hook: trigger push hooks
745 :param trigger_push_hook: trigger push hooks
746
746
747 :returns: new commited changeset after deletion
747 :returns: new committed changeset after deletion
748 """
748 """
749
749
750 user = self._get_user(user)
750 user = self._get_user(user)
751 scm_instance = repo.scm_instance_no_cache()
751 scm_instance = repo.scm_instance_no_cache()
752
752
753 processed_nodes = []
753 processed_nodes = []
754 for f_path in nodes:
754 for f_path in nodes:
755 f_path = self._sanitize_path(f_path)
755 f_path = self._sanitize_path(f_path)
756 # content can be empty but for compatibility it allows same dicts
756 # content can be empty but for compatibility it allows same dicts
757 # structure as add_nodes
757 # structure as add_nodes
758 content = nodes[f_path].get('content')
758 content = nodes[f_path].get('content')
759 processed_nodes.append((f_path, content))
759 processed_nodes.append((f_path, content))
760
760
761 message = safe_unicode(message)
761 message = safe_unicode(message)
762 commiter = user.full_contact
762 committer = user.full_contact
763 author = safe_unicode(author) if author else commiter
763 author = safe_unicode(author) if author else committer
764
764
765 IMC = self._get_IMC_module(scm_instance.alias)
765 IMC = self._get_IMC_module(scm_instance.alias)
766 imc = IMC(scm_instance)
766 imc = IMC(scm_instance)
767
767
768 if not parent_cs:
768 if not parent_cs:
769 parent_cs = EmptyChangeset(alias=scm_instance.alias)
769 parent_cs = EmptyChangeset(alias=scm_instance.alias)
770
770
771 if isinstance(parent_cs, EmptyChangeset):
771 if isinstance(parent_cs, EmptyChangeset):
772 # EmptyChangeset means we we're editing empty repository
772 # EmptyChangeset means we we're editing empty repository
773 parents = None
773 parents = None
774 else:
774 else:
775 parents = [parent_cs]
775 parents = [parent_cs]
776 # add multiple nodes
776 # add multiple nodes
777 for path, content in processed_nodes:
777 for path, content in processed_nodes:
778 imc.remove(FileNode(path, content=content))
778 imc.remove(FileNode(path, content=content))
779
779
780 tip = imc.commit(message=message,
780 tip = imc.commit(message=message,
781 author=author,
781 author=author,
782 parents=parents,
782 parents=parents,
783 branch=parent_cs.branch)
783 branch=parent_cs.branch)
784
784
785 self.mark_for_invalidation(repo.repo_name)
785 self.mark_for_invalidation(repo.repo_name)
786 if trigger_push_hook:
786 if trigger_push_hook:
787 self._handle_push(scm_instance,
787 self._handle_push(scm_instance,
788 username=user.username,
788 username=user.username,
789 action='push_local',
789 action='push_local',
790 repo_name=repo.repo_name,
790 repo_name=repo.repo_name,
791 revisions=[tip.raw_id])
791 revisions=[tip.raw_id])
792 return tip
792 return tip
793
793
794 def get_unread_journal(self):
794 def get_unread_journal(self):
795 return self.sa.query(UserLog).count()
795 return self.sa.query(UserLog).count()
796
796
797 def get_repo_landing_revs(self, repo=None):
797 def get_repo_landing_revs(self, repo=None):
798 """
798 """
799 Generates select option with tags branches and bookmarks (for hg only)
799 Generates select option with tags branches and bookmarks (for hg only)
800 grouped by type
800 grouped by type
801
801
802 :param repo:
802 :param repo:
803 """
803 """
804
804
805 hist_l = []
805 hist_l = []
806 choices = []
806 choices = []
807 repo = self.__get_repo(repo)
807 repo = self.__get_repo(repo)
808 hist_l.append(['rev:tip', _('latest tip')])
808 hist_l.append(['rev:tip', _('latest tip')])
809 choices.append('rev:tip')
809 choices.append('rev:tip')
810 if not repo:
810 if not repo:
811 return choices, hist_l
811 return choices, hist_l
812
812
813 repo = repo.scm_instance
813 repo = repo.scm_instance
814
814
815 branches_group = ([(u'branch:%s' % k, k) for k, v in
815 branches_group = ([(u'branch:%s' % k, k) for k, v in
816 repo.branches.iteritems()], _("Branches"))
816 repo.branches.iteritems()], _("Branches"))
817 hist_l.append(branches_group)
817 hist_l.append(branches_group)
818 choices.extend([x[0] for x in branches_group[0]])
818 choices.extend([x[0] for x in branches_group[0]])
819
819
820 if repo.alias == 'hg':
820 if repo.alias == 'hg':
821 bookmarks_group = ([(u'book:%s' % k, k) for k, v in
821 bookmarks_group = ([(u'book:%s' % k, k) for k, v in
822 repo.bookmarks.iteritems()], _("Bookmarks"))
822 repo.bookmarks.iteritems()], _("Bookmarks"))
823 hist_l.append(bookmarks_group)
823 hist_l.append(bookmarks_group)
824 choices.extend([x[0] for x in bookmarks_group[0]])
824 choices.extend([x[0] for x in bookmarks_group[0]])
825
825
826 tags_group = ([(u'tag:%s' % k, k) for k, v in
826 tags_group = ([(u'tag:%s' % k, k) for k, v in
827 repo.tags.iteritems()], _("Tags"))
827 repo.tags.iteritems()], _("Tags"))
828 hist_l.append(tags_group)
828 hist_l.append(tags_group)
829 choices.extend([x[0] for x in tags_group[0]])
829 choices.extend([x[0] for x in tags_group[0]])
830
830
831 return choices, hist_l
831 return choices, hist_l
832
832
833 def install_git_hook(self, repo, force_create=False):
833 def install_git_hook(self, repo, force_create=False):
834 """
834 """
835 Creates a kallithea hook inside a git repository
835 Creates a kallithea hook inside a git repository
836
836
837 :param repo: Instance of VCS repo
837 :param repo: Instance of VCS repo
838 :param force_create: Create even if same name hook exists
838 :param force_create: Create even if same name hook exists
839 """
839 """
840
840
841 loc = jn(repo.path, 'hooks')
841 loc = jn(repo.path, 'hooks')
842 if not repo.bare:
842 if not repo.bare:
843 loc = jn(repo.path, '.git', 'hooks')
843 loc = jn(repo.path, '.git', 'hooks')
844 if not os.path.isdir(loc):
844 if not os.path.isdir(loc):
845 os.makedirs(loc)
845 os.makedirs(loc)
846
846
847 tmpl_post = pkg_resources.resource_string(
847 tmpl_post = pkg_resources.resource_string(
848 'kallithea', jn('config', 'post_receive_tmpl.py')
848 'kallithea', jn('config', 'post_receive_tmpl.py')
849 )
849 )
850 tmpl_pre = pkg_resources.resource_string(
850 tmpl_pre = pkg_resources.resource_string(
851 'kallithea', jn('config', 'pre_receive_tmpl.py')
851 'kallithea', jn('config', 'pre_receive_tmpl.py')
852 )
852 )
853
853
854 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
854 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
855 _hook_file = jn(loc, '%s-receive' % h_type)
855 _hook_file = jn(loc, '%s-receive' % h_type)
856 has_hook = False
856 has_hook = False
857 log.debug('Installing git hook in repo %s' % repo)
857 log.debug('Installing git hook in repo %s' % repo)
858 if os.path.exists(_hook_file):
858 if os.path.exists(_hook_file):
859 # let's take a look at this hook, maybe it's kallithea ?
859 # let's take a look at this hook, maybe it's kallithea ?
860 log.debug('hook exists, checking if it is from kallithea')
860 log.debug('hook exists, checking if it is from kallithea')
861 with open(_hook_file, 'rb') as f:
861 with open(_hook_file, 'rb') as f:
862 data = f.read()
862 data = f.read()
863 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
863 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
864 % 'KALLITHEA_HOOK_VER').search(data)
864 % 'KALLITHEA_HOOK_VER').search(data)
865 if matches:
865 if matches:
866 try:
866 try:
867 ver = matches.groups()[0]
867 ver = matches.groups()[0]
868 log.debug('got %s it is kallithea' % (ver))
868 log.debug('got %s it is kallithea' % (ver))
869 has_hook = True
869 has_hook = True
870 except Exception:
870 except Exception:
871 log.error(traceback.format_exc())
871 log.error(traceback.format_exc())
872 else:
872 else:
873 # there is no hook in this dir, so we want to create one
873 # there is no hook in this dir, so we want to create one
874 has_hook = True
874 has_hook = True
875
875
876 if has_hook or force_create:
876 if has_hook or force_create:
877 log.debug('writing %s hook file !' % (h_type,))
877 log.debug('writing %s hook file !' % (h_type,))
878 try:
878 try:
879 with open(_hook_file, 'wb') as f:
879 with open(_hook_file, 'wb') as f:
880 tmpl = tmpl.replace('_TMPL_', kallithea.__version__)
880 tmpl = tmpl.replace('_TMPL_', kallithea.__version__)
881 f.write(tmpl)
881 f.write(tmpl)
882 os.chmod(_hook_file, 0755)
882 os.chmod(_hook_file, 0755)
883 except IOError, e:
883 except IOError, e:
884 log.error('error writing %s: %s' % (_hook_file, e))
884 log.error('error writing %s: %s' % (_hook_file, e))
885 else:
885 else:
886 log.debug('skipping writing hook file')
886 log.debug('skipping writing hook file')
@@ -1,469 +1,469 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.user
15 kallithea.model.user
16 ~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~
17
17
18 users model for Kallithea
18 users model for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 9, 2010
22 :created_on: Apr 9, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28
28
29 import logging
29 import logging
30 import traceback
30 import traceback
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.exc import DatabaseError
34
34
35 from kallithea import EXTERN_TYPE_INTERNAL
35 from kallithea import EXTERN_TYPE_INTERNAL
36 from kallithea.lib.utils2 import safe_unicode, generate_api_key, get_current_authuser
36 from kallithea.lib.utils2 import safe_unicode, generate_api_key, get_current_authuser
37 from kallithea.lib.caching_query import FromCache
37 from kallithea.lib.caching_query import FromCache
38 from kallithea.model import BaseModel
38 from kallithea.model import BaseModel
39 from kallithea.model.db import User, UserToPerm, Notification, \
39 from kallithea.model.db import User, UserToPerm, Notification, \
40 UserEmailMap, UserIpMap
40 UserEmailMap, UserIpMap
41 from kallithea.lib.exceptions import DefaultUserException, \
41 from kallithea.lib.exceptions import DefaultUserException, \
42 UserOwnsReposException
42 UserOwnsReposException
43 from kallithea.model.meta import Session
43 from kallithea.model.meta import Session
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class UserModel(BaseModel):
49 class UserModel(BaseModel):
50 cls = User
50 cls = User
51
51
52 def get(self, user_id, cache=False):
52 def get(self, user_id, cache=False):
53 user = self.sa.query(User)
53 user = self.sa.query(User)
54 if cache:
54 if cache:
55 user = user.options(FromCache("sql_cache_short",
55 user = user.options(FromCache("sql_cache_short",
56 "get_user_%s" % user_id))
56 "get_user_%s" % user_id))
57 return user.get(user_id)
57 return user.get(user_id)
58
58
59 def get_user(self, user):
59 def get_user(self, user):
60 return self._get_user(user)
60 return self._get_user(user)
61
61
62 def get_by_username(self, username, cache=False, case_insensitive=False):
62 def get_by_username(self, username, cache=False, case_insensitive=False):
63
63
64 if case_insensitive:
64 if case_insensitive:
65 user = self.sa.query(User).filter(User.username.ilike(username))
65 user = self.sa.query(User).filter(User.username.ilike(username))
66 else:
66 else:
67 user = self.sa.query(User)\
67 user = self.sa.query(User)\
68 .filter(User.username == username)
68 .filter(User.username == username)
69 if cache:
69 if cache:
70 user = user.options(FromCache("sql_cache_short",
70 user = user.options(FromCache("sql_cache_short",
71 "get_user_%s" % username))
71 "get_user_%s" % username))
72 return user.scalar()
72 return user.scalar()
73
73
74 def get_by_email(self, email, cache=False, case_insensitive=False):
74 def get_by_email(self, email, cache=False, case_insensitive=False):
75 return User.get_by_email(email, case_insensitive, cache)
75 return User.get_by_email(email, case_insensitive, cache)
76
76
77 def get_by_api_key(self, api_key, cache=False):
77 def get_by_api_key(self, api_key, cache=False):
78 return User.get_by_api_key(api_key, cache)
78 return User.get_by_api_key(api_key, cache)
79
79
80 def create(self, form_data, cur_user=None):
80 def create(self, form_data, cur_user=None):
81 if not cur_user:
81 if not cur_user:
82 cur_user = getattr(get_current_authuser(), 'username', None)
82 cur_user = getattr(get_current_authuser(), 'username', None)
83
83
84 from kallithea.lib.hooks import log_create_user, check_allowed_create_user
84 from kallithea.lib.hooks import log_create_user, check_allowed_create_user
85 _fd = form_data
85 _fd = form_data
86 user_data = {
86 user_data = {
87 'username': _fd['username'], 'password': _fd['password'],
87 'username': _fd['username'], 'password': _fd['password'],
88 'email': _fd['email'], 'firstname': _fd['firstname'], 'lastname': _fd['lastname'],
88 'email': _fd['email'], 'firstname': _fd['firstname'], 'lastname': _fd['lastname'],
89 'active': _fd['active'], 'admin': False
89 'active': _fd['active'], 'admin': False
90 }
90 }
91 # raises UserCreationError if it's not allowed
91 # raises UserCreationError if it's not allowed
92 check_allowed_create_user(user_data, cur_user)
92 check_allowed_create_user(user_data, cur_user)
93 from kallithea.lib.auth import get_crypt_password
93 from kallithea.lib.auth import get_crypt_password
94
94
95 new_user = User()
95 new_user = User()
96 for k, v in form_data.items():
96 for k, v in form_data.items():
97 if k == 'password':
97 if k == 'password':
98 v = get_crypt_password(v)
98 v = get_crypt_password(v)
99 if k == 'firstname':
99 if k == 'firstname':
100 k = 'name'
100 k = 'name'
101 setattr(new_user, k, v)
101 setattr(new_user, k, v)
102
102
103 new_user.api_key = generate_api_key(form_data['username'])
103 new_user.api_key = generate_api_key(form_data['username'])
104 self.sa.add(new_user)
104 self.sa.add(new_user)
105
105
106 log_create_user(new_user.get_dict(), cur_user)
106 log_create_user(new_user.get_dict(), cur_user)
107 return new_user
107 return new_user
108
108
109 def create_or_update(self, username, password, email, firstname='',
109 def create_or_update(self, username, password, email, firstname='',
110 lastname='', active=True, admin=False,
110 lastname='', active=True, admin=False,
111 extern_type=None, extern_name=None, cur_user=None):
111 extern_type=None, extern_name=None, cur_user=None):
112 """
112 """
113 Creates a new instance if not found, or updates current one
113 Creates a new instance if not found, or updates current one
114
114
115 :param username:
115 :param username:
116 :param password:
116 :param password:
117 :param email:
117 :param email:
118 :param active:
118 :param active:
119 :param firstname:
119 :param firstname:
120 :param lastname:
120 :param lastname:
121 :param active:
121 :param active:
122 :param admin:
122 :param admin:
123 :param extern_name:
123 :param extern_name:
124 :param extern_type:
124 :param extern_type:
125 :param cur_user:
125 :param cur_user:
126 """
126 """
127 if not cur_user:
127 if not cur_user:
128 cur_user = getattr(get_current_authuser(), 'username', None)
128 cur_user = getattr(get_current_authuser(), 'username', None)
129
129
130 from kallithea.lib.auth import get_crypt_password, check_password
130 from kallithea.lib.auth import get_crypt_password, check_password
131 from kallithea.lib.hooks import log_create_user, check_allowed_create_user
131 from kallithea.lib.hooks import log_create_user, check_allowed_create_user
132 user_data = {
132 user_data = {
133 'username': username, 'password': password,
133 'username': username, 'password': password,
134 'email': email, 'firstname': firstname, 'lastname': lastname,
134 'email': email, 'firstname': firstname, 'lastname': lastname,
135 'active': active, 'admin': admin
135 'active': active, 'admin': admin
136 }
136 }
137 # raises UserCreationError if it's not allowed
137 # raises UserCreationError if it's not allowed
138 check_allowed_create_user(user_data, cur_user)
138 check_allowed_create_user(user_data, cur_user)
139
139
140 log.debug('Checking for %s account in Kallithea database' % username)
140 log.debug('Checking for %s account in Kallithea database' % username)
141 user = User.get_by_username(username, case_insensitive=True)
141 user = User.get_by_username(username, case_insensitive=True)
142 if user is None:
142 if user is None:
143 log.debug('creating new user %s' % username)
143 log.debug('creating new user %s' % username)
144 new_user = User()
144 new_user = User()
145 edit = False
145 edit = False
146 else:
146 else:
147 log.debug('updating user %s' % username)
147 log.debug('updating user %s' % username)
148 new_user = user
148 new_user = user
149 edit = True
149 edit = True
150
150
151 try:
151 try:
152 new_user.username = username
152 new_user.username = username
153 new_user.admin = admin
153 new_user.admin = admin
154 new_user.email = email
154 new_user.email = email
155 new_user.active = active
155 new_user.active = active
156 new_user.extern_name = safe_unicode(extern_name) if extern_name else None
156 new_user.extern_name = safe_unicode(extern_name) if extern_name else None
157 new_user.extern_type = safe_unicode(extern_type) if extern_type else None
157 new_user.extern_type = safe_unicode(extern_type) if extern_type else None
158 new_user.name = firstname
158 new_user.name = firstname
159 new_user.lastname = lastname
159 new_user.lastname = lastname
160
160
161 if not edit:
161 if not edit:
162 new_user.api_key = generate_api_key(username)
162 new_user.api_key = generate_api_key(username)
163
163
164 # set password only if creating an user or password is changed
164 # set password only if creating an user or password is changed
165 password_change = new_user.password and not check_password(password,
165 password_change = new_user.password and not check_password(password,
166 new_user.password)
166 new_user.password)
167 if not edit or password_change:
167 if not edit or password_change:
168 reason = 'new password' if edit else 'new user'
168 reason = 'new password' if edit else 'new user'
169 log.debug('Updating password reason=>%s' % (reason,))
169 log.debug('Updating password reason=>%s' % (reason,))
170 new_user.password = get_crypt_password(password) if password else None
170 new_user.password = get_crypt_password(password) if password else None
171
171
172 self.sa.add(new_user)
172 self.sa.add(new_user)
173
173
174 if not edit:
174 if not edit:
175 log_create_user(new_user.get_dict(), cur_user)
175 log_create_user(new_user.get_dict(), cur_user)
176 return new_user
176 return new_user
177 except (DatabaseError,):
177 except (DatabaseError,):
178 log.error(traceback.format_exc())
178 log.error(traceback.format_exc())
179 raise
179 raise
180
180
181 def create_registration(self, form_data):
181 def create_registration(self, form_data):
182 from kallithea.model.notification import NotificationModel
182 from kallithea.model.notification import NotificationModel
183 import kallithea.lib.helpers as h
183 import kallithea.lib.helpers as h
184
184
185 form_data['admin'] = False
185 form_data['admin'] = False
186 form_data['extern_name'] = EXTERN_TYPE_INTERNAL
186 form_data['extern_name'] = EXTERN_TYPE_INTERNAL
187 form_data['extern_type'] = EXTERN_TYPE_INTERNAL
187 form_data['extern_type'] = EXTERN_TYPE_INTERNAL
188 new_user = self.create(form_data)
188 new_user = self.create(form_data)
189
189
190 self.sa.add(new_user)
190 self.sa.add(new_user)
191 self.sa.flush()
191 self.sa.flush()
192
192
193 # notification to admins
193 # notification to admins
194 subject = _('New user registration')
194 subject = _('New user registration')
195 body = ('New user registration\n'
195 body = ('New user registration\n'
196 '---------------------\n'
196 '---------------------\n'
197 '- Username: %s\n'
197 '- Username: %s\n'
198 '- Full Name: %s\n'
198 '- Full Name: %s\n'
199 '- Email: %s\n')
199 '- Email: %s\n')
200 body = body % (new_user.username, new_user.full_name, new_user.email)
200 body = body % (new_user.username, new_user.full_name, new_user.email)
201 edit_url = h.canonical_url('edit_user', id=new_user.user_id)
201 edit_url = h.canonical_url('edit_user', id=new_user.user_id)
202 email_kwargs = {'registered_user_url': edit_url, 'new_username': new_user.username}
202 email_kwargs = {'registered_user_url': edit_url, 'new_username': new_user.username}
203 NotificationModel().create(created_by=new_user, subject=subject,
203 NotificationModel().create(created_by=new_user, subject=subject,
204 body=body, recipients=None,
204 body=body, recipients=None,
205 type_=Notification.TYPE_REGISTRATION,
205 type_=Notification.TYPE_REGISTRATION,
206 email_kwargs=email_kwargs)
206 email_kwargs=email_kwargs)
207
207
208 def update(self, user_id, form_data, skip_attrs=[]):
208 def update(self, user_id, form_data, skip_attrs=[]):
209 from kallithea.lib.auth import get_crypt_password
209 from kallithea.lib.auth import get_crypt_password
210
210
211 user = self.get(user_id, cache=False)
211 user = self.get(user_id, cache=False)
212 if user.username == User.DEFAULT_USER:
212 if user.username == User.DEFAULT_USER:
213 raise DefaultUserException(
213 raise DefaultUserException(
214 _("You can't Edit this user since it's "
214 _("You can't Edit this user since it's "
215 "crucial for entire application"))
215 "crucial for entire application"))
216
216
217 for k, v in form_data.items():
217 for k, v in form_data.items():
218 if k in skip_attrs:
218 if k in skip_attrs:
219 continue
219 continue
220 if k == 'new_password' and v:
220 if k == 'new_password' and v:
221 user.password = get_crypt_password(v)
221 user.password = get_crypt_password(v)
222 else:
222 else:
223 # old legacy thing orm models store firstname as name,
223 # old legacy thing orm models store firstname as name,
224 # need proper refactor to username
224 # need proper refactor to username
225 if k == 'firstname':
225 if k == 'firstname':
226 k = 'name'
226 k = 'name'
227 setattr(user, k, v)
227 setattr(user, k, v)
228 self.sa.add(user)
228 self.sa.add(user)
229
229
230 def update_user(self, user, **kwargs):
230 def update_user(self, user, **kwargs):
231 from kallithea.lib.auth import get_crypt_password
231 from kallithea.lib.auth import get_crypt_password
232
232
233 user = self._get_user(user)
233 user = self._get_user(user)
234 if user.username == User.DEFAULT_USER:
234 if user.username == User.DEFAULT_USER:
235 raise DefaultUserException(
235 raise DefaultUserException(
236 _("You can't Edit this user since it's"
236 _("You can't Edit this user since it's"
237 " crucial for entire application")
237 " crucial for entire application")
238 )
238 )
239
239
240 for k, v in kwargs.items():
240 for k, v in kwargs.items():
241 if k == 'password' and v:
241 if k == 'password' and v:
242 v = get_crypt_password(v)
242 v = get_crypt_password(v)
243
243
244 setattr(user, k, v)
244 setattr(user, k, v)
245 self.sa.add(user)
245 self.sa.add(user)
246 return user
246 return user
247
247
248 def delete(self, user, cur_user=None):
248 def delete(self, user, cur_user=None):
249 if not cur_user:
249 if not cur_user:
250 cur_user = getattr(get_current_authuser(), 'username', None)
250 cur_user = getattr(get_current_authuser(), 'username', None)
251 user = self._get_user(user)
251 user = self._get_user(user)
252
252
253 if user.username == User.DEFAULT_USER:
253 if user.username == User.DEFAULT_USER:
254 raise DefaultUserException(
254 raise DefaultUserException(
255 _(u"You can't remove this user since it's"
255 _(u"You can't remove this user since it's"
256 " crucial for entire application")
256 " crucial for entire application")
257 )
257 )
258 if user.repositories:
258 if user.repositories:
259 repos = [x.repo_name for x in user.repositories]
259 repos = [x.repo_name for x in user.repositories]
260 raise UserOwnsReposException(
260 raise UserOwnsReposException(
261 _(u'User "%s" still owns %s repositories and cannot be '
261 _(u'User "%s" still owns %s repositories and cannot be '
262 'removed. Switch owners or remove those repositories: %s')
262 'removed. Switch owners or remove those repositories: %s')
263 % (user.username, len(repos), ', '.join(repos))
263 % (user.username, len(repos), ', '.join(repos))
264 )
264 )
265 if user.repo_groups:
265 if user.repo_groups:
266 repogroups = [x.group_name for x in user.repo_groups]
266 repogroups = [x.group_name for x in user.repo_groups]
267 raise UserOwnsReposException(
267 raise UserOwnsReposException(
268 _(u'User "%s" still owns %s repository groups and cannot be '
268 _(u'User "%s" still owns %s repository groups and cannot be '
269 'removed. Switch owners or remove those repository groups: %s')
269 'removed. Switch owners or remove those repository groups: %s')
270 % (user.username, len(repogroups), ', '.join(repogroups))
270 % (user.username, len(repogroups), ', '.join(repogroups))
271 )
271 )
272 if user.user_groups:
272 if user.user_groups:
273 usergroups = [x.users_group_name for x in user.user_groups]
273 usergroups = [x.users_group_name for x in user.user_groups]
274 raise UserOwnsReposException(
274 raise UserOwnsReposException(
275 _(u'User "%s" still owns %s user groups and cannot be '
275 _(u'User "%s" still owns %s user groups and cannot be '
276 'removed. Switch owners or remove those user groups: %s')
276 'removed. Switch owners or remove those user groups: %s')
277 % (user.username, len(usergroups), ', '.join(usergroups))
277 % (user.username, len(usergroups), ', '.join(usergroups))
278 )
278 )
279 self.sa.delete(user)
279 self.sa.delete(user)
280
280
281 from kallithea.lib.hooks import log_delete_user
281 from kallithea.lib.hooks import log_delete_user
282 log_delete_user(user.get_dict(), cur_user)
282 log_delete_user(user.get_dict(), cur_user)
283
283
284 def reset_password_link(self, data):
284 def reset_password_link(self, data):
285 from kallithea.lib.celerylib import tasks, run_task
285 from kallithea.lib.celerylib import tasks, run_task
286 from kallithea.model.notification import EmailNotificationModel
286 from kallithea.model.notification import EmailNotificationModel
287 import kallithea.lib.helpers as h
287 import kallithea.lib.helpers as h
288
288
289 user_email = data['email']
289 user_email = data['email']
290 user = User.get_by_email(user_email)
290 user = User.get_by_email(user_email)
291 if user:
291 if user:
292 log.debug('password reset user found %s' % user)
292 log.debug('password reset user found %s' % user)
293 link = h.canonical_url('reset_password_confirmation', key=user.api_key)
293 link = h.canonical_url('reset_password_confirmation', key=user.api_key)
294 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
294 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
295 body = EmailNotificationModel().get_email_tmpl(reg_type,
295 body = EmailNotificationModel().get_email_tmpl(reg_type,
296 'txt',
296 'txt',
297 user=user.short_contact,
297 user=user.short_contact,
298 reset_url=link)
298 reset_url=link)
299 html_body = EmailNotificationModel().get_email_tmpl(reg_type,
299 html_body = EmailNotificationModel().get_email_tmpl(reg_type,
300 'html',
300 'html',
301 user=user.short_contact,
301 user=user.short_contact,
302 reset_url=link)
302 reset_url=link)
303 log.debug('sending email')
303 log.debug('sending email')
304 run_task(tasks.send_email, [user_email],
304 run_task(tasks.send_email, [user_email],
305 _("Password reset link"), body, html_body)
305 _("Password reset link"), body, html_body)
306 log.info('send new password mail to %s' % user_email)
306 log.info('send new password mail to %s' % user_email)
307 else:
307 else:
308 log.debug("password reset email %s not found" % user_email)
308 log.debug("password reset email %s not found" % user_email)
309
309
310 return True
310 return True
311
311
312 def reset_password(self, data):
312 def reset_password(self, data):
313 from kallithea.lib.celerylib import tasks, run_task
313 from kallithea.lib.celerylib import tasks, run_task
314 from kallithea.lib import auth
314 from kallithea.lib import auth
315 user_email = data['email']
315 user_email = data['email']
316 user = User.get_by_email(user_email)
316 user = User.get_by_email(user_email)
317 new_passwd = auth.PasswordGenerator().gen_password(8,
317 new_passwd = auth.PasswordGenerator().gen_password(8,
318 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
318 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
319 if user:
319 if user:
320 user.password = auth.get_crypt_password(new_passwd)
320 user.password = auth.get_crypt_password(new_passwd)
321 Session().add(user)
321 Session().add(user)
322 Session().commit()
322 Session().commit()
323 log.info('change password for %s' % user_email)
323 log.info('change password for %s' % user_email)
324 if new_passwd is None:
324 if new_passwd is None:
325 raise Exception('unable to generate new password')
325 raise Exception('unable to generate new password')
326
326
327 run_task(tasks.send_email, [user_email],
327 run_task(tasks.send_email, [user_email],
328 _('Your new password'),
328 _('Your new password'),
329 _('Your new Kallithea password:%s') % (new_passwd,))
329 _('Your new Kallithea password:%s') % (new_passwd,))
330 log.info('send new password mail to %s' % user_email)
330 log.info('send new password mail to %s' % user_email)
331
331
332 return True
332 return True
333
333
334 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
334 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
335 """
335 """
336 Fetches auth_user by user_id,or api_key if present.
336 Fetches auth_user by user_id,or api_key if present.
337 Fills auth_user attributes with those taken from database.
337 Fills auth_user attributes with those taken from database.
338 Additionally sets is_authenitated if lookup fails
338 Additionally sets is_authenticated if lookup fails
339 present in database
339 present in database
340
340
341 :param auth_user: instance of user to set attributes
341 :param auth_user: instance of user to set attributes
342 :param user_id: user id to fetch by
342 :param user_id: user id to fetch by
343 :param api_key: api key to fetch by
343 :param api_key: api key to fetch by
344 :param username: username to fetch by
344 :param username: username to fetch by
345 """
345 """
346 if user_id is None and api_key is None and username is None:
346 if user_id is None and api_key is None and username is None:
347 raise Exception('You need to pass user_id, api_key or username')
347 raise Exception('You need to pass user_id, api_key or username')
348
348
349 dbuser = None
349 dbuser = None
350 if user_id is not None:
350 if user_id is not None:
351 dbuser = self.get(user_id)
351 dbuser = self.get(user_id)
352 elif api_key is not None:
352 elif api_key is not None:
353 dbuser = self.get_by_api_key(api_key)
353 dbuser = self.get_by_api_key(api_key)
354 elif username is not None:
354 elif username is not None:
355 dbuser = self.get_by_username(username)
355 dbuser = self.get_by_username(username)
356
356
357 if dbuser is not None and dbuser.active:
357 if dbuser is not None and dbuser.active:
358 log.debug('filling %s data' % dbuser)
358 log.debug('filling %s data' % dbuser)
359 for k, v in dbuser.get_dict().iteritems():
359 for k, v in dbuser.get_dict().iteritems():
360 if k not in ['api_keys', 'permissions']:
360 if k not in ['api_keys', 'permissions']:
361 setattr(auth_user, k, v)
361 setattr(auth_user, k, v)
362 return True
362 return True
363 return False
363 return False
364
364
365 def has_perm(self, user, perm):
365 def has_perm(self, user, perm):
366 perm = self._get_perm(perm)
366 perm = self._get_perm(perm)
367 user = self._get_user(user)
367 user = self._get_user(user)
368
368
369 return UserToPerm.query().filter(UserToPerm.user == user)\
369 return UserToPerm.query().filter(UserToPerm.user == user)\
370 .filter(UserToPerm.permission == perm).scalar() is not None
370 .filter(UserToPerm.permission == perm).scalar() is not None
371
371
372 def grant_perm(self, user, perm):
372 def grant_perm(self, user, perm):
373 """
373 """
374 Grant user global permissions
374 Grant user global permissions
375
375
376 :param user:
376 :param user:
377 :param perm:
377 :param perm:
378 """
378 """
379 user = self._get_user(user)
379 user = self._get_user(user)
380 perm = self._get_perm(perm)
380 perm = self._get_perm(perm)
381 # if this permission is already granted skip it
381 # if this permission is already granted skip it
382 _perm = UserToPerm.query()\
382 _perm = UserToPerm.query()\
383 .filter(UserToPerm.user == user)\
383 .filter(UserToPerm.user == user)\
384 .filter(UserToPerm.permission == perm)\
384 .filter(UserToPerm.permission == perm)\
385 .scalar()
385 .scalar()
386 if _perm:
386 if _perm:
387 return
387 return
388 new = UserToPerm()
388 new = UserToPerm()
389 new.user = user
389 new.user = user
390 new.permission = perm
390 new.permission = perm
391 self.sa.add(new)
391 self.sa.add(new)
392 return new
392 return new
393
393
394 def revoke_perm(self, user, perm):
394 def revoke_perm(self, user, perm):
395 """
395 """
396 Revoke users global permissions
396 Revoke users global permissions
397
397
398 :param user:
398 :param user:
399 :param perm:
399 :param perm:
400 """
400 """
401 user = self._get_user(user)
401 user = self._get_user(user)
402 perm = self._get_perm(perm)
402 perm = self._get_perm(perm)
403
403
404 obj = UserToPerm.query()\
404 obj = UserToPerm.query()\
405 .filter(UserToPerm.user == user)\
405 .filter(UserToPerm.user == user)\
406 .filter(UserToPerm.permission == perm)\
406 .filter(UserToPerm.permission == perm)\
407 .scalar()
407 .scalar()
408 if obj:
408 if obj:
409 self.sa.delete(obj)
409 self.sa.delete(obj)
410
410
411 def add_extra_email(self, user, email):
411 def add_extra_email(self, user, email):
412 """
412 """
413 Adds email address to UserEmailMap
413 Adds email address to UserEmailMap
414
414
415 :param user:
415 :param user:
416 :param email:
416 :param email:
417 """
417 """
418 from kallithea.model import forms
418 from kallithea.model import forms
419 form = forms.UserExtraEmailForm()()
419 form = forms.UserExtraEmailForm()()
420 data = form.to_python(dict(email=email))
420 data = form.to_python(dict(email=email))
421 user = self._get_user(user)
421 user = self._get_user(user)
422
422
423 obj = UserEmailMap()
423 obj = UserEmailMap()
424 obj.user = user
424 obj.user = user
425 obj.email = data['email']
425 obj.email = data['email']
426 self.sa.add(obj)
426 self.sa.add(obj)
427 return obj
427 return obj
428
428
429 def delete_extra_email(self, user, email_id):
429 def delete_extra_email(self, user, email_id):
430 """
430 """
431 Removes email address from UserEmailMap
431 Removes email address from UserEmailMap
432
432
433 :param user:
433 :param user:
434 :param email_id:
434 :param email_id:
435 """
435 """
436 user = self._get_user(user)
436 user = self._get_user(user)
437 obj = UserEmailMap.query().get(email_id)
437 obj = UserEmailMap.query().get(email_id)
438 if obj:
438 if obj:
439 self.sa.delete(obj)
439 self.sa.delete(obj)
440
440
441 def add_extra_ip(self, user, ip):
441 def add_extra_ip(self, user, ip):
442 """
442 """
443 Adds ip address to UserIpMap
443 Adds ip address to UserIpMap
444
444
445 :param user:
445 :param user:
446 :param ip:
446 :param ip:
447 """
447 """
448 from kallithea.model import forms
448 from kallithea.model import forms
449 form = forms.UserExtraIpForm()()
449 form = forms.UserExtraIpForm()()
450 data = form.to_python(dict(ip=ip))
450 data = form.to_python(dict(ip=ip))
451 user = self._get_user(user)
451 user = self._get_user(user)
452
452
453 obj = UserIpMap()
453 obj = UserIpMap()
454 obj.user = user
454 obj.user = user
455 obj.ip_addr = data['ip']
455 obj.ip_addr = data['ip']
456 self.sa.add(obj)
456 self.sa.add(obj)
457 return obj
457 return obj
458
458
459 def delete_extra_ip(self, user, ip_id):
459 def delete_extra_ip(self, user, ip_id):
460 """
460 """
461 Removes ip address from UserIpMap
461 Removes ip address from UserIpMap
462
462
463 :param user:
463 :param user:
464 :param ip_id:
464 :param ip_id:
465 """
465 """
466 user = self._get_user(user)
466 user = self._get_user(user)
467 obj = UserIpMap.query().get(ip_id)
467 obj = UserIpMap.query().get(ip_id)
468 if obj:
468 if obj:
469 self.sa.delete(obj)
469 self.sa.delete(obj)
@@ -1,905 +1,905 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 Set of generic validators
15 Set of generic validators
16 """
16 """
17
17
18 import os
18 import os
19 import re
19 import re
20 import formencode
20 import formencode
21 import logging
21 import logging
22 from collections import defaultdict
22 from collections import defaultdict
23 from pylons.i18n.translation import _
23 from pylons.i18n.translation import _
24 from webhelpers.pylonslib.secure_form import authentication_token
24 from webhelpers.pylonslib.secure_form import authentication_token
25 import sqlalchemy
25 import sqlalchemy
26
26
27 from formencode.validators import (
27 from formencode.validators import (
28 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
28 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
29 NotEmpty, IPAddress, CIDR, String, FancyValidator
29 NotEmpty, IPAddress, CIDR, String, FancyValidator
30 )
30 )
31 from kallithea.lib.compat import OrderedSet
31 from kallithea.lib.compat import OrderedSet
32 from kallithea.lib import ipaddr
32 from kallithea.lib import ipaddr
33 from kallithea.lib.utils import repo_name_slug
33 from kallithea.lib.utils import repo_name_slug
34 from kallithea.lib.utils2 import safe_int, str2bool, aslist
34 from kallithea.lib.utils2 import safe_int, str2bool, aslist
35 from kallithea.model.db import RepoGroup, Repository, UserGroup, User,\
35 from kallithea.model.db import RepoGroup, Repository, UserGroup, User,\
36 ChangesetStatus
36 ChangesetStatus
37 from kallithea.lib.exceptions import LdapImportError
37 from kallithea.lib.exceptions import LdapImportError
38 from kallithea.config.routing import ADMIN_PREFIX
38 from kallithea.config.routing import ADMIN_PREFIX
39 from kallithea.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
39 from kallithea.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
40
40
41 # silence warnings and pylint
41 # silence warnings and pylint
42 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
42 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
43 NotEmpty, IPAddress, CIDR, String, FancyValidator
43 NotEmpty, IPAddress, CIDR, String, FancyValidator
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47 class _Missing(object):
47 class _Missing(object):
48 pass
48 pass
49
49
50 Missing = _Missing()
50 Missing = _Missing()
51
51
52
52
53 class StateObj(object):
53 class StateObj(object):
54 """
54 """
55 this is needed to translate the messages using _() in validators
55 this is needed to translate the messages using _() in validators
56 """
56 """
57 _ = staticmethod(_)
57 _ = staticmethod(_)
58
58
59
59
60 def M(self, key, state=None, **kwargs):
60 def M(self, key, state=None, **kwargs):
61 """
61 """
62 returns string from self.message based on given key,
62 returns string from self.message based on given key,
63 passed kw params are used to substitute %(named)s params inside
63 passed kw params are used to substitute %(named)s params inside
64 translated strings
64 translated strings
65
65
66 :param msg:
66 :param msg:
67 :param state:
67 :param state:
68 """
68 """
69 if state is None:
69 if state is None:
70 state = StateObj()
70 state = StateObj()
71 else:
71 else:
72 state._ = staticmethod(_)
72 state._ = staticmethod(_)
73 #inject validator into state object
73 #inject validator into state object
74 return self.message(key, state, **kwargs)
74 return self.message(key, state, **kwargs)
75
75
76
76
77 def UniqueListFromString():
77 def UniqueListFromString():
78 class _UniqueListFromString(formencode.FancyValidator):
78 class _UniqueListFromString(formencode.FancyValidator):
79 """
79 """
80 Split value on ',' and make unique while preserving order
80 Split value on ',' and make unique while preserving order
81 """
81 """
82 messages = dict(
82 messages = dict(
83 empty=_('Value cannot be an empty list'),
83 empty=_('Value cannot be an empty list'),
84 missing_value=_('Value cannot be an empty list'),
84 missing_value=_('Value cannot be an empty list'),
85 )
85 )
86
86
87 def _to_python(self, value, state):
87 def _to_python(self, value, state):
88 value = aslist(value, ',')
88 value = aslist(value, ',')
89 seen = set()
89 seen = set()
90 return [c for c in value if not (c in seen or seen.add(c))]
90 return [c for c in value if not (c in seen or seen.add(c))]
91
91
92 def empty_value(self, value):
92 def empty_value(self, value):
93 return []
93 return []
94
94
95 return _UniqueListFromString
95 return _UniqueListFromString
96
96
97
97
98 def ValidUsername(edit=False, old_data={}):
98 def ValidUsername(edit=False, old_data={}):
99 class _validator(formencode.validators.FancyValidator):
99 class _validator(formencode.validators.FancyValidator):
100 messages = {
100 messages = {
101 'username_exists': _(u'Username "%(username)s" already exists'),
101 'username_exists': _(u'Username "%(username)s" already exists'),
102 'system_invalid_username':
102 'system_invalid_username':
103 _(u'Username "%(username)s" is forbidden'),
103 _(u'Username "%(username)s" is forbidden'),
104 'invalid_username':
104 'invalid_username':
105 _(u'Username may only contain alphanumeric characters '
105 _(u'Username may only contain alphanumeric characters '
106 'underscores, periods or dashes and must begin with '
106 'underscores, periods or dashes and must begin with '
107 'alphanumeric character or underscore')
107 'alphanumeric character or underscore')
108 }
108 }
109
109
110 def validate_python(self, value, state):
110 def validate_python(self, value, state):
111 if value in ['default', 'new_user']:
111 if value in ['default', 'new_user']:
112 msg = M(self, 'system_invalid_username', state, username=value)
112 msg = M(self, 'system_invalid_username', state, username=value)
113 raise formencode.Invalid(msg, value, state)
113 raise formencode.Invalid(msg, value, state)
114 #check if user is unique
114 #check if user is unique
115 old_un = None
115 old_un = None
116 if edit:
116 if edit:
117 old_un = User.get(old_data.get('user_id')).username
117 old_un = User.get(old_data.get('user_id')).username
118
118
119 if old_un != value or not edit:
119 if old_un != value or not edit:
120 if User.get_by_username(value, case_insensitive=True):
120 if User.get_by_username(value, case_insensitive=True):
121 msg = M(self, 'username_exists', state, username=value)
121 msg = M(self, 'username_exists', state, username=value)
122 raise formencode.Invalid(msg, value, state)
122 raise formencode.Invalid(msg, value, state)
123
123
124 if re.match(r'^[a-zA-Z0-9\_]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
124 if re.match(r'^[a-zA-Z0-9\_]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
125 msg = M(self, 'invalid_username', state)
125 msg = M(self, 'invalid_username', state)
126 raise formencode.Invalid(msg, value, state)
126 raise formencode.Invalid(msg, value, state)
127 return _validator
127 return _validator
128
128
129
129
130 def ValidRegex(msg=None):
130 def ValidRegex(msg=None):
131 class _validator(formencode.validators.Regex):
131 class _validator(formencode.validators.Regex):
132 messages = dict(invalid=msg or _('The input is not valid'))
132 messages = dict(invalid=msg or _('The input is not valid'))
133 return _validator
133 return _validator
134
134
135
135
136 def ValidRepoUser():
136 def ValidRepoUser():
137 class _validator(formencode.validators.FancyValidator):
137 class _validator(formencode.validators.FancyValidator):
138 messages = {
138 messages = {
139 'invalid_username': _(u'Username %(username)s is not valid')
139 'invalid_username': _(u'Username %(username)s is not valid')
140 }
140 }
141
141
142 def validate_python(self, value, state):
142 def validate_python(self, value, state):
143 try:
143 try:
144 User.query().filter(User.active == True)\
144 User.query().filter(User.active == True)\
145 .filter(User.username == value).one()
145 .filter(User.username == value).one()
146 except sqlalchemy.exc.InvalidRequestError: # NoResultFound/MultipleResultsFound
146 except sqlalchemy.exc.InvalidRequestError: # NoResultFound/MultipleResultsFound
147 msg = M(self, 'invalid_username', state, username=value)
147 msg = M(self, 'invalid_username', state, username=value)
148 raise formencode.Invalid(msg, value, state,
148 raise formencode.Invalid(msg, value, state,
149 error_dict=dict(username=msg)
149 error_dict=dict(username=msg)
150 )
150 )
151
151
152 return _validator
152 return _validator
153
153
154
154
155 def ValidUserGroup(edit=False, old_data={}):
155 def ValidUserGroup(edit=False, old_data={}):
156 class _validator(formencode.validators.FancyValidator):
156 class _validator(formencode.validators.FancyValidator):
157 messages = {
157 messages = {
158 'invalid_group': _(u'Invalid user group name'),
158 'invalid_group': _(u'Invalid user group name'),
159 'group_exist': _(u'User group "%(usergroup)s" already exists'),
159 'group_exist': _(u'User group "%(usergroup)s" already exists'),
160 'invalid_usergroup_name':
160 'invalid_usergroup_name':
161 _(u'user group name may only contain alphanumeric '
161 _(u'user group name may only contain alphanumeric '
162 'characters underscores, periods or dashes and must begin '
162 'characters underscores, periods or dashes and must begin '
163 'with alphanumeric character')
163 'with alphanumeric character')
164 }
164 }
165
165
166 def validate_python(self, value, state):
166 def validate_python(self, value, state):
167 if value in ['default']:
167 if value in ['default']:
168 msg = M(self, 'invalid_group', state)
168 msg = M(self, 'invalid_group', state)
169 raise formencode.Invalid(msg, value, state,
169 raise formencode.Invalid(msg, value, state,
170 error_dict=dict(users_group_name=msg)
170 error_dict=dict(users_group_name=msg)
171 )
171 )
172 #check if group is unique
172 #check if group is unique
173 old_ugname = None
173 old_ugname = None
174 if edit:
174 if edit:
175 old_id = old_data.get('users_group_id')
175 old_id = old_data.get('users_group_id')
176 old_ugname = UserGroup.get(old_id).users_group_name
176 old_ugname = UserGroup.get(old_id).users_group_name
177
177
178 if old_ugname != value or not edit:
178 if old_ugname != value or not edit:
179 is_existing_group = UserGroup.get_by_group_name(value,
179 is_existing_group = UserGroup.get_by_group_name(value,
180 case_insensitive=True)
180 case_insensitive=True)
181 if is_existing_group:
181 if is_existing_group:
182 msg = M(self, 'group_exist', state, usergroup=value)
182 msg = M(self, 'group_exist', state, usergroup=value)
183 raise formencode.Invalid(msg, value, state,
183 raise formencode.Invalid(msg, value, state,
184 error_dict=dict(users_group_name=msg)
184 error_dict=dict(users_group_name=msg)
185 )
185 )
186
186
187 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
187 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
188 msg = M(self, 'invalid_usergroup_name', state)
188 msg = M(self, 'invalid_usergroup_name', state)
189 raise formencode.Invalid(msg, value, state,
189 raise formencode.Invalid(msg, value, state,
190 error_dict=dict(users_group_name=msg)
190 error_dict=dict(users_group_name=msg)
191 )
191 )
192
192
193 return _validator
193 return _validator
194
194
195
195
196 def ValidRepoGroup(edit=False, old_data={}):
196 def ValidRepoGroup(edit=False, old_data={}):
197 class _validator(formencode.validators.FancyValidator):
197 class _validator(formencode.validators.FancyValidator):
198 messages = {
198 messages = {
199 'group_parent_id': _(u'Cannot assign this group as parent'),
199 'group_parent_id': _(u'Cannot assign this group as parent'),
200 'group_exists': _(u'Group "%(group_name)s" already exists'),
200 'group_exists': _(u'Group "%(group_name)s" already exists'),
201 'repo_exists':
201 'repo_exists':
202 _(u'Repository with name "%(group_name)s" already exists')
202 _(u'Repository with name "%(group_name)s" already exists')
203 }
203 }
204
204
205 def validate_python(self, value, state):
205 def validate_python(self, value, state):
206 # TODO WRITE VALIDATIONS
206 # TODO WRITE VALIDATIONS
207 group_name = value.get('group_name')
207 group_name = value.get('group_name')
208 group_parent_id = value.get('group_parent_id')
208 group_parent_id = value.get('group_parent_id')
209
209
210 # slugify repo group just in case :)
210 # slugify repo group just in case :)
211 slug = repo_name_slug(group_name)
211 slug = repo_name_slug(group_name)
212
212
213 # check for parent of self
213 # check for parent of self
214 parent_of_self = lambda: (
214 parent_of_self = lambda: (
215 old_data['group_id'] == int(group_parent_id)
215 old_data['group_id'] == int(group_parent_id)
216 if group_parent_id else False
216 if group_parent_id else False
217 )
217 )
218 if edit and parent_of_self():
218 if edit and parent_of_self():
219 msg = M(self, 'group_parent_id', state)
219 msg = M(self, 'group_parent_id', state)
220 raise formencode.Invalid(msg, value, state,
220 raise formencode.Invalid(msg, value, state,
221 error_dict=dict(group_parent_id=msg)
221 error_dict=dict(group_parent_id=msg)
222 )
222 )
223
223
224 old_gname = None
224 old_gname = None
225 if edit:
225 if edit:
226 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
226 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
227
227
228 if old_gname != group_name or not edit:
228 if old_gname != group_name or not edit:
229
229
230 # check group
230 # check group
231 gr = RepoGroup.query()\
231 gr = RepoGroup.query()\
232 .filter(RepoGroup.group_name == slug)\
232 .filter(RepoGroup.group_name == slug)\
233 .filter(RepoGroup.group_parent_id == group_parent_id)\
233 .filter(RepoGroup.group_parent_id == group_parent_id)\
234 .scalar()
234 .scalar()
235
235
236 if gr:
236 if gr:
237 msg = M(self, 'group_exists', state, group_name=slug)
237 msg = M(self, 'group_exists', state, group_name=slug)
238 raise formencode.Invalid(msg, value, state,
238 raise formencode.Invalid(msg, value, state,
239 error_dict=dict(group_name=msg)
239 error_dict=dict(group_name=msg)
240 )
240 )
241
241
242 # check for same repo
242 # check for same repo
243 repo = Repository.query()\
243 repo = Repository.query()\
244 .filter(Repository.repo_name == slug)\
244 .filter(Repository.repo_name == slug)\
245 .scalar()
245 .scalar()
246
246
247 if repo:
247 if repo:
248 msg = M(self, 'repo_exists', state, group_name=slug)
248 msg = M(self, 'repo_exists', state, group_name=slug)
249 raise formencode.Invalid(msg, value, state,
249 raise formencode.Invalid(msg, value, state,
250 error_dict=dict(group_name=msg)
250 error_dict=dict(group_name=msg)
251 )
251 )
252
252
253 return _validator
253 return _validator
254
254
255
255
256 def ValidPassword():
256 def ValidPassword():
257 class _validator(formencode.validators.FancyValidator):
257 class _validator(formencode.validators.FancyValidator):
258 messages = {
258 messages = {
259 'invalid_password':
259 'invalid_password':
260 _(u'Invalid characters (non-ascii) in password')
260 _(u'Invalid characters (non-ascii) in password')
261 }
261 }
262
262
263 def validate_python(self, value, state):
263 def validate_python(self, value, state):
264 try:
264 try:
265 (value or '').decode('ascii')
265 (value or '').decode('ascii')
266 except UnicodeError:
266 except UnicodeError:
267 msg = M(self, 'invalid_password', state)
267 msg = M(self, 'invalid_password', state)
268 raise formencode.Invalid(msg, value, state,)
268 raise formencode.Invalid(msg, value, state,)
269 return _validator
269 return _validator
270
270
271
271
272 def ValidOldPassword(username):
272 def ValidOldPassword(username):
273 class _validator(formencode.validators.FancyValidator):
273 class _validator(formencode.validators.FancyValidator):
274 messages = {
274 messages = {
275 'invalid_password': _(u'Invalid old password')
275 'invalid_password': _(u'Invalid old password')
276 }
276 }
277
277
278 def validate_python(self, value, state):
278 def validate_python(self, value, state):
279 from kallithea.lib import auth_modules
279 from kallithea.lib import auth_modules
280 if not auth_modules.authenticate(username, value, ''):
280 if not auth_modules.authenticate(username, value, ''):
281 msg = M(self, 'invalid_password', state)
281 msg = M(self, 'invalid_password', state)
282 raise formencode.Invalid(msg, value, state,
282 raise formencode.Invalid(msg, value, state,
283 error_dict=dict(current_password=msg)
283 error_dict=dict(current_password=msg)
284 )
284 )
285 return _validator
285 return _validator
286
286
287
287
288 def ValidPasswordsMatch(passwd='new_password', passwd_confirmation='password_confirmation'):
288 def ValidPasswordsMatch(passwd='new_password', passwd_confirmation='password_confirmation'):
289 class _validator(formencode.validators.FancyValidator):
289 class _validator(formencode.validators.FancyValidator):
290 messages = {
290 messages = {
291 'password_mismatch': _(u'Passwords do not match'),
291 'password_mismatch': _(u'Passwords do not match'),
292 }
292 }
293
293
294 def validate_python(self, value, state):
294 def validate_python(self, value, state):
295
295
296 pass_val = value.get('password') or value.get(passwd)
296 pass_val = value.get('password') or value.get(passwd)
297 if pass_val != value[passwd_confirmation]:
297 if pass_val != value[passwd_confirmation]:
298 msg = M(self, 'password_mismatch', state)
298 msg = M(self, 'password_mismatch', state)
299 raise formencode.Invalid(msg, value, state,
299 raise formencode.Invalid(msg, value, state,
300 error_dict={passwd:msg, passwd_confirmation: msg}
300 error_dict={passwd:msg, passwd_confirmation: msg}
301 )
301 )
302 return _validator
302 return _validator
303
303
304
304
305 def ValidAuth():
305 def ValidAuth():
306 class _validator(formencode.validators.FancyValidator):
306 class _validator(formencode.validators.FancyValidator):
307 messages = {
307 messages = {
308 'invalid_password': _(u'invalid password'),
308 'invalid_password': _(u'invalid password'),
309 'invalid_username': _(u'invalid user name'),
309 'invalid_username': _(u'invalid user name'),
310 'disabled_account': _(u'Your account is disabled')
310 'disabled_account': _(u'Your account is disabled')
311 }
311 }
312
312
313 def validate_python(self, value, state):
313 def validate_python(self, value, state):
314 from kallithea.lib import auth_modules
314 from kallithea.lib import auth_modules
315
315
316 password = value['password']
316 password = value['password']
317 username = value['username']
317 username = value['username']
318
318
319 if not auth_modules.authenticate(username, password):
319 if not auth_modules.authenticate(username, password):
320 user = User.get_by_username(username)
320 user = User.get_by_username(username)
321 if user and not user.active:
321 if user and not user.active:
322 log.warning('user %s is disabled' % username)
322 log.warning('user %s is disabled' % username)
323 msg = M(self, 'disabled_account', state)
323 msg = M(self, 'disabled_account', state)
324 raise formencode.Invalid(msg, value, state,
324 raise formencode.Invalid(msg, value, state,
325 error_dict=dict(username=msg)
325 error_dict=dict(username=msg)
326 )
326 )
327 else:
327 else:
328 log.warning('user %s failed to authenticate' % username)
328 log.warning('user %s failed to authenticate' % username)
329 msg = M(self, 'invalid_username', state)
329 msg = M(self, 'invalid_username', state)
330 msg2 = M(self, 'invalid_password', state)
330 msg2 = M(self, 'invalid_password', state)
331 raise formencode.Invalid(msg, value, state,
331 raise formencode.Invalid(msg, value, state,
332 error_dict=dict(username=msg, password=msg2)
332 error_dict=dict(username=msg, password=msg2)
333 )
333 )
334 return _validator
334 return _validator
335
335
336
336
337 def ValidAuthToken():
337 def ValidAuthToken():
338 class _validator(formencode.validators.FancyValidator):
338 class _validator(formencode.validators.FancyValidator):
339 messages = {
339 messages = {
340 'invalid_token': _(u'Token mismatch')
340 'invalid_token': _(u'Token mismatch')
341 }
341 }
342
342
343 def validate_python(self, value, state):
343 def validate_python(self, value, state):
344 if value != authentication_token():
344 if value != authentication_token():
345 msg = M(self, 'invalid_token', state)
345 msg = M(self, 'invalid_token', state)
346 raise formencode.Invalid(msg, value, state)
346 raise formencode.Invalid(msg, value, state)
347 return _validator
347 return _validator
348
348
349
349
350 def ValidRepoName(edit=False, old_data={}):
350 def ValidRepoName(edit=False, old_data={}):
351 class _validator(formencode.validators.FancyValidator):
351 class _validator(formencode.validators.FancyValidator):
352 messages = {
352 messages = {
353 'invalid_repo_name':
353 'invalid_repo_name':
354 _(u'Repository name %(repo)s is disallowed'),
354 _(u'Repository name %(repo)s is disallowed'),
355 'repository_exists':
355 'repository_exists':
356 _(u'Repository named %(repo)s already exists'),
356 _(u'Repository named %(repo)s already exists'),
357 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
357 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
358 'exists in group "%(group)s"'),
358 'exists in group "%(group)s"'),
359 'same_group_exists': _(u'Repository group with name "%(repo)s" '
359 'same_group_exists': _(u'Repository group with name "%(repo)s" '
360 'already exists')
360 'already exists')
361 }
361 }
362
362
363 def _to_python(self, value, state):
363 def _to_python(self, value, state):
364 repo_name = repo_name_slug(value.get('repo_name', ''))
364 repo_name = repo_name_slug(value.get('repo_name', ''))
365 repo_group = value.get('repo_group')
365 repo_group = value.get('repo_group')
366 if repo_group:
366 if repo_group:
367 gr = RepoGroup.get(repo_group)
367 gr = RepoGroup.get(repo_group)
368 group_path = gr.full_path
368 group_path = gr.full_path
369 group_name = gr.group_name
369 group_name = gr.group_name
370 # value needs to be aware of group name in order to check
370 # value needs to be aware of group name in order to check
371 # db key This is an actual just the name to store in the
371 # db key This is an actual just the name to store in the
372 # database
372 # database
373 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
373 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
374 else:
374 else:
375 group_name = group_path = ''
375 group_name = group_path = ''
376 repo_name_full = repo_name
376 repo_name_full = repo_name
377
377
378 value['repo_name'] = repo_name
378 value['repo_name'] = repo_name
379 value['repo_name_full'] = repo_name_full
379 value['repo_name_full'] = repo_name_full
380 value['group_path'] = group_path
380 value['group_path'] = group_path
381 value['group_name'] = group_name
381 value['group_name'] = group_name
382 return value
382 return value
383
383
384 def validate_python(self, value, state):
384 def validate_python(self, value, state):
385
385
386 repo_name = value.get('repo_name')
386 repo_name = value.get('repo_name')
387 repo_name_full = value.get('repo_name_full')
387 repo_name_full = value.get('repo_name_full')
388 group_path = value.get('group_path')
388 group_path = value.get('group_path')
389 group_name = value.get('group_name')
389 group_name = value.get('group_name')
390
390
391 if repo_name in [ADMIN_PREFIX, '']:
391 if repo_name in [ADMIN_PREFIX, '']:
392 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
392 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
393 raise formencode.Invalid(msg, value, state,
393 raise formencode.Invalid(msg, value, state,
394 error_dict=dict(repo_name=msg)
394 error_dict=dict(repo_name=msg)
395 )
395 )
396
396
397 rename = old_data.get('repo_name') != repo_name_full
397 rename = old_data.get('repo_name') != repo_name_full
398 create = not edit
398 create = not edit
399 if rename or create:
399 if rename or create:
400
400
401 if group_path != '':
401 if group_path != '':
402 if Repository.get_by_repo_name(repo_name_full):
402 if Repository.get_by_repo_name(repo_name_full):
403 msg = M(self, 'repository_in_group_exists', state,
403 msg = M(self, 'repository_in_group_exists', state,
404 repo=repo_name, group=group_name)
404 repo=repo_name, group=group_name)
405 raise formencode.Invalid(msg, value, state,
405 raise formencode.Invalid(msg, value, state,
406 error_dict=dict(repo_name=msg)
406 error_dict=dict(repo_name=msg)
407 )
407 )
408 elif RepoGroup.get_by_group_name(repo_name_full):
408 elif RepoGroup.get_by_group_name(repo_name_full):
409 msg = M(self, 'same_group_exists', state,
409 msg = M(self, 'same_group_exists', state,
410 repo=repo_name)
410 repo=repo_name)
411 raise formencode.Invalid(msg, value, state,
411 raise formencode.Invalid(msg, value, state,
412 error_dict=dict(repo_name=msg)
412 error_dict=dict(repo_name=msg)
413 )
413 )
414
414
415 elif Repository.get_by_repo_name(repo_name_full):
415 elif Repository.get_by_repo_name(repo_name_full):
416 msg = M(self, 'repository_exists', state,
416 msg = M(self, 'repository_exists', state,
417 repo=repo_name)
417 repo=repo_name)
418 raise formencode.Invalid(msg, value, state,
418 raise formencode.Invalid(msg, value, state,
419 error_dict=dict(repo_name=msg)
419 error_dict=dict(repo_name=msg)
420 )
420 )
421 return value
421 return value
422 return _validator
422 return _validator
423
423
424
424
425 def ValidForkName(*args, **kwargs):
425 def ValidForkName(*args, **kwargs):
426 return ValidRepoName(*args, **kwargs)
426 return ValidRepoName(*args, **kwargs)
427
427
428
428
429 def SlugifyName():
429 def SlugifyName():
430 class _validator(formencode.validators.FancyValidator):
430 class _validator(formencode.validators.FancyValidator):
431
431
432 def _to_python(self, value, state):
432 def _to_python(self, value, state):
433 return repo_name_slug(value)
433 return repo_name_slug(value)
434
434
435 def validate_python(self, value, state):
435 def validate_python(self, value, state):
436 pass
436 pass
437
437
438 return _validator
438 return _validator
439
439
440
440
441 def ValidCloneUri():
441 def ValidCloneUri():
442 from kallithea.lib.utils import make_ui
442 from kallithea.lib.utils import make_ui
443
443
444 def url_handler(repo_type, url, ui):
444 def url_handler(repo_type, url, ui):
445 if repo_type == 'hg':
445 if repo_type == 'hg':
446 from kallithea.lib.vcs.backends.hg.repository import MercurialRepository
446 from kallithea.lib.vcs.backends.hg.repository import MercurialRepository
447 if url.startswith('http') or url.startswith('ssh'):
447 if url.startswith('http') or url.startswith('ssh'):
448 # initially check if it's at least the proper URL
448 # initially check if it's at least the proper URL
449 # or does it pass basic auth
449 # or does it pass basic auth
450 MercurialRepository._check_url(url, ui)
450 MercurialRepository._check_url(url, ui)
451 elif url.startswith('svn+http'):
451 elif url.startswith('svn+http'):
452 from hgsubversion.svnrepo import svnremoterepo
452 from hgsubversion.svnrepo import svnremoterepo
453 svnremoterepo(ui, url).svn.uuid
453 svnremoterepo(ui, url).svn.uuid
454 elif url.startswith('git+http'):
454 elif url.startswith('git+http'):
455 raise NotImplementedError()
455 raise NotImplementedError()
456 else:
456 else:
457 raise Exception('clone from URI %s not allowed' % (url,))
457 raise Exception('clone from URI %s not allowed' % (url,))
458
458
459 elif repo_type == 'git':
459 elif repo_type == 'git':
460 from kallithea.lib.vcs.backends.git.repository import GitRepository
460 from kallithea.lib.vcs.backends.git.repository import GitRepository
461 if url.startswith('http'):
461 if url.startswith('http'):
462 # initially check if it's at least the proper URL
462 # initially check if it's at least the proper URL
463 # or does it pass basic auth
463 # or does it pass basic auth
464 GitRepository._check_url(url)
464 GitRepository._check_url(url)
465 elif url.startswith('svn+http'):
465 elif url.startswith('svn+http'):
466 raise NotImplementedError()
466 raise NotImplementedError()
467 elif url.startswith('hg+http'):
467 elif url.startswith('hg+http'):
468 raise NotImplementedError()
468 raise NotImplementedError()
469 else:
469 else:
470 raise Exception('clone from URI %s not allowed' % (url))
470 raise Exception('clone from URI %s not allowed' % (url))
471
471
472 class _validator(formencode.validators.FancyValidator):
472 class _validator(formencode.validators.FancyValidator):
473 messages = {
473 messages = {
474 'clone_uri': _(u'invalid clone URL'),
474 'clone_uri': _(u'invalid clone URL'),
475 'invalid_clone_uri': _(u'Invalid clone URL, provide a '
475 'invalid_clone_uri': _(u'Invalid clone URL, provide a '
476 'valid clone http(s)/svn+http(s)/ssh URL')
476 'valid clone http(s)/svn+http(s)/ssh URL')
477 }
477 }
478
478
479 def validate_python(self, value, state):
479 def validate_python(self, value, state):
480 repo_type = value.get('repo_type')
480 repo_type = value.get('repo_type')
481 url = value.get('clone_uri')
481 url = value.get('clone_uri')
482
482
483 if not url:
483 if not url:
484 pass
484 pass
485 else:
485 else:
486 try:
486 try:
487 url_handler(repo_type, url, make_ui('db', clear_session=False))
487 url_handler(repo_type, url, make_ui('db', clear_session=False))
488 except Exception:
488 except Exception:
489 log.exception('URL validation failed')
489 log.exception('URL validation failed')
490 msg = M(self, 'clone_uri')
490 msg = M(self, 'clone_uri')
491 raise formencode.Invalid(msg, value, state,
491 raise formencode.Invalid(msg, value, state,
492 error_dict=dict(clone_uri=msg)
492 error_dict=dict(clone_uri=msg)
493 )
493 )
494 return _validator
494 return _validator
495
495
496
496
497 def ValidForkType(old_data={}):
497 def ValidForkType(old_data={}):
498 class _validator(formencode.validators.FancyValidator):
498 class _validator(formencode.validators.FancyValidator):
499 messages = {
499 messages = {
500 'invalid_fork_type': _(u'Fork has to be the same type as parent')
500 'invalid_fork_type': _(u'Fork has to be the same type as parent')
501 }
501 }
502
502
503 def validate_python(self, value, state):
503 def validate_python(self, value, state):
504 if old_data['repo_type'] != value:
504 if old_data['repo_type'] != value:
505 msg = M(self, 'invalid_fork_type', state)
505 msg = M(self, 'invalid_fork_type', state)
506 raise formencode.Invalid(msg, value, state,
506 raise formencode.Invalid(msg, value, state,
507 error_dict=dict(repo_type=msg)
507 error_dict=dict(repo_type=msg)
508 )
508 )
509 return _validator
509 return _validator
510
510
511
511
512 def CanWriteGroup(old_data=None):
512 def CanWriteGroup(old_data=None):
513 class _validator(formencode.validators.FancyValidator):
513 class _validator(formencode.validators.FancyValidator):
514 messages = {
514 messages = {
515 'permission_denied': _(u"You don't have permissions "
515 'permission_denied': _(u"You don't have permissions "
516 "to create repository in this group"),
516 "to create repository in this group"),
517 'permission_denied_root': _(u"no permission to create repository "
517 'permission_denied_root': _(u"no permission to create repository "
518 "in root location")
518 "in root location")
519 }
519 }
520
520
521 def _to_python(self, value, state):
521 def _to_python(self, value, state):
522 #root location
522 #root location
523 if value in [-1, "-1"]:
523 if value in [-1, "-1"]:
524 return None
524 return None
525 return value
525 return value
526
526
527 def validate_python(self, value, state):
527 def validate_python(self, value, state):
528 gr = RepoGroup.get(value)
528 gr = RepoGroup.get(value)
529 gr_name = gr.group_name if gr else None # None means ROOT location
529 gr_name = gr.group_name if gr else None # None means ROOT location
530 # create repositories with write permission on group is set to true
530 # create repositories with write permission on group is set to true
531 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
531 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
532 group_admin = HasRepoGroupPermissionAny('group.admin')(gr_name,
532 group_admin = HasRepoGroupPermissionAny('group.admin')(gr_name,
533 'can write into group validator')
533 'can write into group validator')
534 group_write = HasRepoGroupPermissionAny('group.write')(gr_name,
534 group_write = HasRepoGroupPermissionAny('group.write')(gr_name,
535 'can write into group validator')
535 'can write into group validator')
536 forbidden = not (group_admin or (group_write and create_on_write))
536 forbidden = not (group_admin or (group_write and create_on_write))
537 can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository')
537 can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository')
538 gid = (old_data['repo_group'].get('group_id')
538 gid = (old_data['repo_group'].get('group_id')
539 if (old_data and 'repo_group' in old_data) else None)
539 if (old_data and 'repo_group' in old_data) else None)
540 value_changed = gid != safe_int(value)
540 value_changed = gid != safe_int(value)
541 new = not old_data
541 new = not old_data
542 # do check if we changed the value, there's a case that someone got
542 # do check if we changed the value, there's a case that someone got
543 # revoked write permissions to a repository, he still created, we
543 # revoked write permissions to a repository, he still created, we
544 # don't need to check permission if he didn't change the value of
544 # don't need to check permission if he didn't change the value of
545 # groups in form box
545 # groups in form box
546 if value_changed or new:
546 if value_changed or new:
547 #parent group need to be existing
547 #parent group need to be existing
548 if gr and forbidden:
548 if gr and forbidden:
549 msg = M(self, 'permission_denied', state)
549 msg = M(self, 'permission_denied', state)
550 raise formencode.Invalid(msg, value, state,
550 raise formencode.Invalid(msg, value, state,
551 error_dict=dict(repo_type=msg)
551 error_dict=dict(repo_type=msg)
552 )
552 )
553 ## check if we can write to root location !
553 ## check if we can write to root location !
554 elif gr is None and not can_create_repos():
554 elif gr is None and not can_create_repos():
555 msg = M(self, 'permission_denied_root', state)
555 msg = M(self, 'permission_denied_root', state)
556 raise formencode.Invalid(msg, value, state,
556 raise formencode.Invalid(msg, value, state,
557 error_dict=dict(repo_type=msg)
557 error_dict=dict(repo_type=msg)
558 )
558 )
559
559
560 return _validator
560 return _validator
561
561
562
562
563 def CanCreateGroup(can_create_in_root=False):
563 def CanCreateGroup(can_create_in_root=False):
564 class _validator(formencode.validators.FancyValidator):
564 class _validator(formencode.validators.FancyValidator):
565 messages = {
565 messages = {
566 'permission_denied': _(u"You don't have permissions "
566 'permission_denied': _(u"You don't have permissions "
567 "to create a group in this location")
567 "to create a group in this location")
568 }
568 }
569
569
570 def to_python(self, value, state):
570 def to_python(self, value, state):
571 #root location
571 #root location
572 if value in [-1, "-1"]:
572 if value in [-1, "-1"]:
573 return None
573 return None
574 return value
574 return value
575
575
576 def validate_python(self, value, state):
576 def validate_python(self, value, state):
577 gr = RepoGroup.get(value)
577 gr = RepoGroup.get(value)
578 gr_name = gr.group_name if gr else None # None means ROOT location
578 gr_name = gr.group_name if gr else None # None means ROOT location
579
579
580 if can_create_in_root and gr is None:
580 if can_create_in_root and gr is None:
581 #we can create in root, we're fine no validations required
581 #we can create in root, we're fine no validations required
582 return
582 return
583
583
584 forbidden_in_root = gr is None and not can_create_in_root
584 forbidden_in_root = gr is None and not can_create_in_root
585 val = HasRepoGroupPermissionAny('group.admin')
585 val = HasRepoGroupPermissionAny('group.admin')
586 forbidden = not val(gr_name, 'can create group validator')
586 forbidden = not val(gr_name, 'can create group validator')
587 if forbidden_in_root or forbidden:
587 if forbidden_in_root or forbidden:
588 msg = M(self, 'permission_denied', state)
588 msg = M(self, 'permission_denied', state)
589 raise formencode.Invalid(msg, value, state,
589 raise formencode.Invalid(msg, value, state,
590 error_dict=dict(group_parent_id=msg)
590 error_dict=dict(group_parent_id=msg)
591 )
591 )
592
592
593 return _validator
593 return _validator
594
594
595
595
596 def ValidPerms(type_='repo'):
596 def ValidPerms(type_='repo'):
597 if type_ == 'repo_group':
597 if type_ == 'repo_group':
598 EMPTY_PERM = 'group.none'
598 EMPTY_PERM = 'group.none'
599 elif type_ == 'repo':
599 elif type_ == 'repo':
600 EMPTY_PERM = 'repository.none'
600 EMPTY_PERM = 'repository.none'
601 elif type_ == 'user_group':
601 elif type_ == 'user_group':
602 EMPTY_PERM = 'usergroup.none'
602 EMPTY_PERM = 'usergroup.none'
603
603
604 class _validator(formencode.validators.FancyValidator):
604 class _validator(formencode.validators.FancyValidator):
605 messages = {
605 messages = {
606 'perm_new_member_name':
606 'perm_new_member_name':
607 _(u'This username or user group name is not valid')
607 _(u'This username or user group name is not valid')
608 }
608 }
609
609
610 def to_python(self, value, state):
610 def to_python(self, value, state):
611 perms_update = OrderedSet()
611 perms_update = OrderedSet()
612 perms_new = OrderedSet()
612 perms_new = OrderedSet()
613 # build a list of permission to update and new permission to create
613 # build a list of permission to update and new permission to create
614
614
615 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
615 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
616 new_perms_group = defaultdict(dict)
616 new_perms_group = defaultdict(dict)
617 for k, v in value.copy().iteritems():
617 for k, v in value.copy().iteritems():
618 if k.startswith('perm_new_member'):
618 if k.startswith('perm_new_member'):
619 del value[k]
619 del value[k]
620 _type, part = k.split('perm_new_member_')
620 _type, part = k.split('perm_new_member_')
621 args = part.split('_')
621 args = part.split('_')
622 if len(args) == 1:
622 if len(args) == 1:
623 new_perms_group[args[0]]['perm'] = v
623 new_perms_group[args[0]]['perm'] = v
624 elif len(args) == 2:
624 elif len(args) == 2:
625 _key, pos = args
625 _key, pos = args
626 new_perms_group[pos][_key] = v
626 new_perms_group[pos][_key] = v
627
627
628 # fill new permissions in order of how they were added
628 # fill new permissions in order of how they were added
629 for k in sorted(map(int, new_perms_group.keys())):
629 for k in sorted(map(int, new_perms_group.keys())):
630 perm_dict = new_perms_group[str(k)]
630 perm_dict = new_perms_group[str(k)]
631 new_member = perm_dict.get('name')
631 new_member = perm_dict.get('name')
632 new_perm = perm_dict.get('perm')
632 new_perm = perm_dict.get('perm')
633 new_type = perm_dict.get('type')
633 new_type = perm_dict.get('type')
634 if new_member and new_perm and new_type:
634 if new_member and new_perm and new_type:
635 perms_new.add((new_member, new_perm, new_type))
635 perms_new.add((new_member, new_perm, new_type))
636
636
637 for k, v in value.iteritems():
637 for k, v in value.iteritems():
638 if k.startswith('u_perm_') or k.startswith('g_perm_'):
638 if k.startswith('u_perm_') or k.startswith('g_perm_'):
639 member = k[7:]
639 member = k[7:]
640 t = {'u': 'user',
640 t = {'u': 'user',
641 'g': 'users_group'
641 'g': 'users_group'
642 }[k[0]]
642 }[k[0]]
643 if member == User.DEFAULT_USER:
643 if member == User.DEFAULT_USER:
644 if str2bool(value.get('repo_private')):
644 if str2bool(value.get('repo_private')):
645 # set none for default when updating to
645 # set none for default when updating to
646 # private repo protects agains form manipulation
646 # private repo protects against form manipulation
647 v = EMPTY_PERM
647 v = EMPTY_PERM
648 perms_update.add((member, v, t))
648 perms_update.add((member, v, t))
649
649
650 value['perms_updates'] = list(perms_update)
650 value['perms_updates'] = list(perms_update)
651 value['perms_new'] = list(perms_new)
651 value['perms_new'] = list(perms_new)
652
652
653 # update permissions
653 # update permissions
654 for k, v, t in perms_new:
654 for k, v, t in perms_new:
655 try:
655 try:
656 if t is 'user':
656 if t is 'user':
657 self.user_db = User.query()\
657 self.user_db = User.query()\
658 .filter(User.active == True)\
658 .filter(User.active == True)\
659 .filter(User.username == k).one()
659 .filter(User.username == k).one()
660 if t is 'users_group':
660 if t is 'users_group':
661 self.user_db = UserGroup.query()\
661 self.user_db = UserGroup.query()\
662 .filter(UserGroup.users_group_active == True)\
662 .filter(UserGroup.users_group_active == True)\
663 .filter(UserGroup.users_group_name == k).one()
663 .filter(UserGroup.users_group_name == k).one()
664
664
665 except Exception:
665 except Exception:
666 log.exception('Updated permission failed')
666 log.exception('Updated permission failed')
667 msg = M(self, 'perm_new_member_type', state)
667 msg = M(self, 'perm_new_member_type', state)
668 raise formencode.Invalid(msg, value, state,
668 raise formencode.Invalid(msg, value, state,
669 error_dict=dict(perm_new_member_name=msg)
669 error_dict=dict(perm_new_member_name=msg)
670 )
670 )
671 return value
671 return value
672 return _validator
672 return _validator
673
673
674
674
675 def ValidSettings():
675 def ValidSettings():
676 class _validator(formencode.validators.FancyValidator):
676 class _validator(formencode.validators.FancyValidator):
677 def _to_python(self, value, state):
677 def _to_python(self, value, state):
678 # settings form for users that are not admin
678 # settings form for users that are not admin
679 # can't edit certain parameters, it's extra backup if they mangle
679 # can't edit certain parameters, it's extra backup if they mangle
680 # with forms
680 # with forms
681
681
682 forbidden_params = [
682 forbidden_params = [
683 'user', 'repo_type', 'repo_enable_locking',
683 'user', 'repo_type', 'repo_enable_locking',
684 'repo_enable_downloads', 'repo_enable_statistics'
684 'repo_enable_downloads', 'repo_enable_statistics'
685 ]
685 ]
686
686
687 for param in forbidden_params:
687 for param in forbidden_params:
688 if param in value:
688 if param in value:
689 del value[param]
689 del value[param]
690 return value
690 return value
691
691
692 def validate_python(self, value, state):
692 def validate_python(self, value, state):
693 pass
693 pass
694 return _validator
694 return _validator
695
695
696
696
697 def ValidPath():
697 def ValidPath():
698 class _validator(formencode.validators.FancyValidator):
698 class _validator(formencode.validators.FancyValidator):
699 messages = {
699 messages = {
700 'invalid_path': _(u'This is not a valid path')
700 'invalid_path': _(u'This is not a valid path')
701 }
701 }
702
702
703 def validate_python(self, value, state):
703 def validate_python(self, value, state):
704 if not os.path.isdir(value):
704 if not os.path.isdir(value):
705 msg = M(self, 'invalid_path', state)
705 msg = M(self, 'invalid_path', state)
706 raise formencode.Invalid(msg, value, state,
706 raise formencode.Invalid(msg, value, state,
707 error_dict=dict(paths_root_path=msg)
707 error_dict=dict(paths_root_path=msg)
708 )
708 )
709 return _validator
709 return _validator
710
710
711
711
712 def UniqSystemEmail(old_data={}):
712 def UniqSystemEmail(old_data={}):
713 class _validator(formencode.validators.FancyValidator):
713 class _validator(formencode.validators.FancyValidator):
714 messages = {
714 messages = {
715 'email_taken': _(u'This e-mail address is already taken')
715 'email_taken': _(u'This e-mail address is already taken')
716 }
716 }
717
717
718 def _to_python(self, value, state):
718 def _to_python(self, value, state):
719 return value.lower()
719 return value.lower()
720
720
721 def validate_python(self, value, state):
721 def validate_python(self, value, state):
722 if (old_data.get('email') or '').lower() != value:
722 if (old_data.get('email') or '').lower() != value:
723 user = User.get_by_email(value, case_insensitive=True)
723 user = User.get_by_email(value, case_insensitive=True)
724 if user:
724 if user:
725 msg = M(self, 'email_taken', state)
725 msg = M(self, 'email_taken', state)
726 raise formencode.Invalid(msg, value, state,
726 raise formencode.Invalid(msg, value, state,
727 error_dict=dict(email=msg)
727 error_dict=dict(email=msg)
728 )
728 )
729 return _validator
729 return _validator
730
730
731
731
732 def ValidSystemEmail():
732 def ValidSystemEmail():
733 class _validator(formencode.validators.FancyValidator):
733 class _validator(formencode.validators.FancyValidator):
734 messages = {
734 messages = {
735 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
735 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
736 }
736 }
737
737
738 def _to_python(self, value, state):
738 def _to_python(self, value, state):
739 return value.lower()
739 return value.lower()
740
740
741 def validate_python(self, value, state):
741 def validate_python(self, value, state):
742 user = User.get_by_email(value, case_insensitive=True)
742 user = User.get_by_email(value, case_insensitive=True)
743 if user is None:
743 if user is None:
744 msg = M(self, 'non_existing_email', state, email=value)
744 msg = M(self, 'non_existing_email', state, email=value)
745 raise formencode.Invalid(msg, value, state,
745 raise formencode.Invalid(msg, value, state,
746 error_dict=dict(email=msg)
746 error_dict=dict(email=msg)
747 )
747 )
748
748
749 return _validator
749 return _validator
750
750
751
751
752 def LdapLibValidator():
752 def LdapLibValidator():
753 class _validator(formencode.validators.FancyValidator):
753 class _validator(formencode.validators.FancyValidator):
754 messages = {
754 messages = {
755
755
756 }
756 }
757
757
758 def validate_python(self, value, state):
758 def validate_python(self, value, state):
759 try:
759 try:
760 import ldap
760 import ldap
761 ldap # pyflakes silence !
761 ldap # pyflakes silence !
762 except ImportError:
762 except ImportError:
763 raise LdapImportError()
763 raise LdapImportError()
764
764
765 return _validator
765 return _validator
766
766
767
767
768 def AttrLoginValidator():
768 def AttrLoginValidator():
769 class _validator(formencode.validators.UnicodeString):
769 class _validator(formencode.validators.UnicodeString):
770 messages = {
770 messages = {
771 'invalid_cn':
771 'invalid_cn':
772 _(u'The LDAP Login attribute of the CN must be specified - '
772 _(u'The LDAP Login attribute of the CN must be specified - '
773 'this is the name of the attribute that is equivalent '
773 'this is the name of the attribute that is equivalent '
774 'to "username"')
774 'to "username"')
775 }
775 }
776 messages['empty'] = messages['invalid_cn']
776 messages['empty'] = messages['invalid_cn']
777
777
778 return _validator
778 return _validator
779
779
780
780
781 def NotReviewedRevisions(repo_id):
781 def NotReviewedRevisions(repo_id):
782 class _validator(formencode.validators.FancyValidator):
782 class _validator(formencode.validators.FancyValidator):
783 messages = {
783 messages = {
784 'rev_already_reviewed':
784 'rev_already_reviewed':
785 _(u'Revisions %(revs)s are already part of pull request '
785 _(u'Revisions %(revs)s are already part of pull request '
786 'or have set status')
786 'or have set status')
787 }
787 }
788
788
789 def validate_python(self, value, state):
789 def validate_python(self, value, state):
790 # check revisions if they are not reviewed, or a part of another
790 # check revisions if they are not reviewed, or a part of another
791 # pull request
791 # pull request
792 statuses = ChangesetStatus.query()\
792 statuses = ChangesetStatus.query()\
793 .filter(ChangesetStatus.revision.in_(value))\
793 .filter(ChangesetStatus.revision.in_(value))\
794 .filter(ChangesetStatus.repo_id == repo_id)\
794 .filter(ChangesetStatus.repo_id == repo_id)\
795 .all()
795 .all()
796
796
797 errors = []
797 errors = []
798 for cs in statuses:
798 for cs in statuses:
799 if cs.pull_request_id:
799 if cs.pull_request_id:
800 errors.append(['pull_req', cs.revision[:12]])
800 errors.append(['pull_req', cs.revision[:12]])
801 elif cs.status:
801 elif cs.status:
802 errors.append(['status', cs.revision[:12]])
802 errors.append(['status', cs.revision[:12]])
803
803
804 if errors:
804 if errors:
805 revs = ','.join([x[1] for x in errors])
805 revs = ','.join([x[1] for x in errors])
806 msg = M(self, 'rev_already_reviewed', state, revs=revs)
806 msg = M(self, 'rev_already_reviewed', state, revs=revs)
807 raise formencode.Invalid(msg, value, state,
807 raise formencode.Invalid(msg, value, state,
808 error_dict=dict(revisions=revs)
808 error_dict=dict(revisions=revs)
809 )
809 )
810
810
811 return _validator
811 return _validator
812
812
813
813
814 def ValidIp():
814 def ValidIp():
815 class _validator(CIDR):
815 class _validator(CIDR):
816 messages = dict(
816 messages = dict(
817 badFormat=_('Please enter a valid IPv4 or IpV6 address'),
817 badFormat=_('Please enter a valid IPv4 or IpV6 address'),
818 illegalBits=_('The network size (bits) must be within the range'
818 illegalBits=_('The network size (bits) must be within the range'
819 ' of 0-32 (not %(bits)r)')
819 ' of 0-32 (not %(bits)r)')
820 )
820 )
821
821
822 def to_python(self, value, state):
822 def to_python(self, value, state):
823 v = super(_validator, self).to_python(value, state)
823 v = super(_validator, self).to_python(value, state)
824 v = v.strip()
824 v = v.strip()
825 net = ipaddr.IPNetwork(address=v)
825 net = ipaddr.IPNetwork(address=v)
826 if isinstance(net, ipaddr.IPv4Network):
826 if isinstance(net, ipaddr.IPv4Network):
827 #if IPv4 doesn't end with a mask, add /32
827 #if IPv4 doesn't end with a mask, add /32
828 if '/' not in value:
828 if '/' not in value:
829 v += '/32'
829 v += '/32'
830 if isinstance(net, ipaddr.IPv6Network):
830 if isinstance(net, ipaddr.IPv6Network):
831 #if IPv6 doesn't end with a mask, add /128
831 #if IPv6 doesn't end with a mask, add /128
832 if '/' not in value:
832 if '/' not in value:
833 v += '/128'
833 v += '/128'
834 return v
834 return v
835
835
836 def validate_python(self, value, state):
836 def validate_python(self, value, state):
837 try:
837 try:
838 addr = value.strip()
838 addr = value.strip()
839 #this raises an ValueError if address is not IpV4 or IpV6
839 #this raises an ValueError if address is not IpV4 or IpV6
840 ipaddr.IPNetwork(address=addr)
840 ipaddr.IPNetwork(address=addr)
841 except ValueError:
841 except ValueError:
842 raise formencode.Invalid(self.message('badFormat', state),
842 raise formencode.Invalid(self.message('badFormat', state),
843 value, state)
843 value, state)
844
844
845 return _validator
845 return _validator
846
846
847
847
848 def FieldKey():
848 def FieldKey():
849 class _validator(formencode.validators.FancyValidator):
849 class _validator(formencode.validators.FancyValidator):
850 messages = dict(
850 messages = dict(
851 badFormat=_('Key name can only consist of letters, '
851 badFormat=_('Key name can only consist of letters, '
852 'underscore, dash or numbers')
852 'underscore, dash or numbers')
853 )
853 )
854
854
855 def validate_python(self, value, state):
855 def validate_python(self, value, state):
856 if not re.match('[a-zA-Z0-9_-]+$', value):
856 if not re.match('[a-zA-Z0-9_-]+$', value):
857 raise formencode.Invalid(self.message('badFormat', state),
857 raise formencode.Invalid(self.message('badFormat', state),
858 value, state)
858 value, state)
859 return _validator
859 return _validator
860
860
861
861
862 def BasePath():
862 def BasePath():
863 class _validator(formencode.validators.FancyValidator):
863 class _validator(formencode.validators.FancyValidator):
864 messages = dict(
864 messages = dict(
865 badPath=_('Filename cannot be inside a directory')
865 badPath=_('Filename cannot be inside a directory')
866 )
866 )
867
867
868 def _to_python(self, value, state):
868 def _to_python(self, value, state):
869 return value
869 return value
870
870
871 def validate_python(self, value, state):
871 def validate_python(self, value, state):
872 if value != os.path.basename(value):
872 if value != os.path.basename(value):
873 raise formencode.Invalid(self.message('badPath', state),
873 raise formencode.Invalid(self.message('badPath', state),
874 value, state)
874 value, state)
875 return _validator
875 return _validator
876
876
877
877
878 def ValidAuthPlugins():
878 def ValidAuthPlugins():
879 class _validator(formencode.validators.FancyValidator):
879 class _validator(formencode.validators.FancyValidator):
880 messages = dict(
880 messages = dict(
881 import_duplicate=_('Plugins %(loaded)s and %(next_to_load)s both export the same name')
881 import_duplicate=_('Plugins %(loaded)s and %(next_to_load)s both export the same name')
882 )
882 )
883
883
884 def _to_python(self, value, state):
884 def _to_python(self, value, state):
885 # filter empty values
885 # filter empty values
886 return filter(lambda s: s not in [None, ''], value)
886 return filter(lambda s: s not in [None, ''], value)
887
887
888 def validate_python(self, value, state):
888 def validate_python(self, value, state):
889 from kallithea.lib import auth_modules
889 from kallithea.lib import auth_modules
890 module_list = value
890 module_list = value
891 unique_names = {}
891 unique_names = {}
892 try:
892 try:
893 for module in module_list:
893 for module in module_list:
894 plugin = auth_modules.loadplugin(module)
894 plugin = auth_modules.loadplugin(module)
895 plugin_name = plugin.name
895 plugin_name = plugin.name
896 if plugin_name in unique_names:
896 if plugin_name in unique_names:
897 msg = M(self, 'import_duplicate', state,
897 msg = M(self, 'import_duplicate', state,
898 loaded=unique_names[plugin_name],
898 loaded=unique_names[plugin_name],
899 next_to_load=plugin_name)
899 next_to_load=plugin_name)
900 raise formencode.Invalid(msg, value, state)
900 raise formencode.Invalid(msg, value, state)
901 unique_names[plugin_name] = plugin
901 unique_names[plugin_name] = plugin
902 except (ImportError, AttributeError, TypeError), e:
902 except (ImportError, AttributeError, TypeError), e:
903 raise formencode.Invalid(str(e), value, state)
903 raise formencode.Invalid(str(e), value, state)
904
904
905 return _validator
905 return _validator
General Comments 0
You need to be logged in to leave comments. Login now