##// END OF EJS Templates
dbmigrate: python3 changes...
super-admin -
r5042:4c5af799 default
parent child Browse files
Show More

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

@@ -1,20 +1,20 b''
1 # -*- coding: utf-8 -*-
1
2 2
3 3 # Copyright (C) 2012-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
@@ -1,14 +1,14 b''
1 1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
2
3 3
4 4 from sqlalchemy.util import OrderedDict
5 5
6 6
7 7 __all__ = ['databases', 'operations']
8 8
9 9 databases = ('sqlite', 'postgres', 'mysql', 'oracle', 'mssql', 'firebird')
10 10
11 11 # Map operation names to function names
12 12 operations = OrderedDict()
13 13 operations['upgrade'] = 'upgrade'
14 14 operations['downgrade'] = 'downgrade'
@@ -1,302 +1,302 b''
1 1 """
2 2 Code to generate a Python model from a database or differences
3 3 between a model and database.
4 4
5 5 Some of this is borrowed heavily from the AutoCode project at:
6 6 http://code.google.com/p/sqlautocode/
7 7 """
8 8
9 9 import sys
10 10 import logging
11 11
12 12 import sqlalchemy
13 13
14 14 import rhodecode.lib.dbmigrate.migrate
15 15 import rhodecode.lib.dbmigrate.migrate.changeset
16 16
17 17
18 18 log = logging.getLogger(__name__)
19 19 HEADER = """
20 20 ## File autogenerated by genmodel.py
21 21
22 22 from sqlalchemy import *
23 23 """
24 24
25 25 META_DEFINITION = "meta = MetaData()"
26 26
27 27 DECLARATIVE_DEFINITION = """
28 28 from sqlalchemy.ext import declarative
29 29
30 30 Base = declarative.declarative_base()
31 31 """
32 32
33 33
34 34 class ModelGenerator(object):
35 35 """Various transformations from an A, B diff.
36 36
37 37 In the implementation, A tends to be called the model and B
38 38 the database (although this is not true of all diffs).
39 39 The diff is directionless, but transformations apply the diff
40 40 in a particular direction, described in the method name.
41 41 """
42 42
43 43 def __init__(self, diff, engine, declarative=False):
44 44 self.diff = diff
45 45 self.engine = engine
46 46 self.declarative = declarative
47 47
48 48 def column_repr(self, col):
49 49 kwarg = []
50 50 if col.key != col.name:
51 51 kwarg.append('key')
52 52 if col.primary_key:
53 53 col.primary_key = True # otherwise it dumps it as 1
54 54 kwarg.append('primary_key')
55 55 if not col.nullable:
56 56 kwarg.append('nullable')
57 57 if col.onupdate:
58 58 kwarg.append('onupdate')
59 59 if col.default:
60 60 if col.primary_key:
61 61 # I found that PostgreSQL automatically creates a
62 62 # default value for the sequence, but let's not show
63 63 # that.
64 64 pass
65 65 else:
66 66 kwarg.append('default')
67 67 args = ['%s=%r' % (k, getattr(col, k)) for k in kwarg]
68 68
69 69 # crs: not sure if this is good idea, but it gets rid of extra
70 70 # u''
71 name = col.name.encode('utf8')
71 name = col.name#.encode('utf8')
72 72
73 73 type_ = col.type
74 74 for cls in col.type.__class__.__mro__:
75 75 if cls.__module__ == 'sqlalchemy.types' and \
76 76 not cls.__name__.isupper():
77 77 if cls is not type_.__class__:
78 78 type_ = cls()
79 79 break
80 80
81 81 type_repr = repr(type_)
82 82 if type_repr.endswith('()'):
83 83 type_repr = type_repr[:-2]
84 84
85 85 constraints = [repr(cn) for cn in col.constraints]
86 86
87 87 data = {
88 88 'name': name,
89 89 'commonStuff': ', '.join([type_repr] + constraints + args),
90 90 }
91 91
92 92 if self.declarative:
93 93 return """%(name)s = Column(%(commonStuff)s)""" % data
94 94 else:
95 95 return """Column(%(name)r, %(commonStuff)s)""" % data
96 96
97 97 def _getTableDefn(self, table, metaName='meta'):
98 98 out = []
99 99 tableName = table.name
100 100 if self.declarative:
101 101 out.append("class %(table)s(Base):" % {'table': tableName})
102 102 out.append(" __tablename__ = '%(table)s'\n" %
103 103 {'table': tableName})
104 104 for col in table.columns:
105 105 out.append(" %s" % self.column_repr(col))
106 106 out.append('\n')
107 107 else:
108 108 out.append("%(table)s = Table('%(table)s', %(meta)s," %
109 109 {'table': tableName, 'meta': metaName})
110 110 for col in table.columns:
111 111 out.append(" %s," % self.column_repr(col))
112 112 out.append(")\n")
113 113 return out
114 114
115 115 def _get_tables(self,missingA=False,missingB=False,modified=False):
116 116 to_process = []
117 117 for bool_,names,metadata in (
118 118 (missingA,self.diff.tables_missing_from_A,self.diff.metadataB),
119 119 (missingB,self.diff.tables_missing_from_B,self.diff.metadataA),
120 120 (modified,self.diff.tables_different,self.diff.metadataA),
121 121 ):
122 122 if bool_:
123 123 for name in names:
124 124 yield metadata.tables.get(name)
125 125
126 126 def _genModelHeader(self, tables):
127 127 out = []
128 128 import_index = []
129 129
130 130 out.append(HEADER)
131 131
132 132 for table in tables:
133 133 for col in table.columns:
134 134 if "dialects" in col.type.__module__ and \
135 135 col.type.__class__ not in import_index:
136 136 out.append("from " + col.type.__module__ +
137 137 " import " + col.type.__class__.__name__)
138 138 import_index.append(col.type.__class__)
139 139
140 140 out.append("")
141 141
142 142 if self.declarative:
143 143 out.append(DECLARATIVE_DEFINITION)
144 144 else:
145 145 out.append(META_DEFINITION)
146 146 out.append("")
147 147
148 148 return out
149 149
150 150 def genBDefinition(self):
151 151 """Generates the source code for a definition of B.
152 152
153 153 Assumes a diff where A is empty.
154 154
155 155 Was: toPython. Assume database (B) is current and model (A) is empty.
156 156 """
157 157
158 158 out = []
159 159 out.extend(self._genModelHeader(self._get_tables(missingA=True)))
160 160 for table in self._get_tables(missingA=True):
161 161 out.extend(self._getTableDefn(table))
162 162 return '\n'.join(out)
163 163
164 164 def genB2AMigration(self, indent=' '):
165 165 """Generate a migration from B to A.
166 166
167 167 Was: toUpgradeDowngradePython
168 168 Assume model (A) is most current and database (B) is out-of-date.
169 169 """
170 170
171 171 decls = ['from rhodecode.lib.dbmigrate.migrate.changeset import schema',
172 172 'pre_meta = MetaData()',
173 173 'post_meta = MetaData()',
174 174 ]
175 175 upgradeCommands = ['pre_meta.bind = migrate_engine',
176 176 'post_meta.bind = migrate_engine']
177 177 downgradeCommands = list(upgradeCommands)
178 178
179 179 for tn in self.diff.tables_missing_from_A:
180 180 pre_table = self.diff.metadataB.tables[tn]
181 181 decls.extend(self._getTableDefn(pre_table, metaName='pre_meta'))
182 182 upgradeCommands.append(
183 183 "pre_meta.tables[%(table)r].drop()" % {'table': tn})
184 184 downgradeCommands.append(
185 185 "pre_meta.tables[%(table)r].create()" % {'table': tn})
186 186
187 187 for tn in self.diff.tables_missing_from_B:
188 188 post_table = self.diff.metadataA.tables[tn]
189 189 decls.extend(self._getTableDefn(post_table, metaName='post_meta'))
190 190 upgradeCommands.append(
191 191 "post_meta.tables[%(table)r].create()" % {'table': tn})
192 192 downgradeCommands.append(
193 193 "post_meta.tables[%(table)r].drop()" % {'table': tn})
194 194
195 195 for (tn, td) in list(self.diff.tables_different.items()):
196 196 if td.columns_missing_from_A or td.columns_different:
197 197 pre_table = self.diff.metadataB.tables[tn]
198 198 decls.extend(self._getTableDefn(
199 199 pre_table, metaName='pre_meta'))
200 200 if td.columns_missing_from_B or td.columns_different:
201 201 post_table = self.diff.metadataA.tables[tn]
202 202 decls.extend(self._getTableDefn(
203 203 post_table, metaName='post_meta'))
204 204
205 205 for col in td.columns_missing_from_A:
206 206 upgradeCommands.append(
207 207 'pre_meta.tables[%r].columns[%r].drop()' % (tn, col))
208 208 downgradeCommands.append(
209 209 'pre_meta.tables[%r].columns[%r].create()' % (tn, col))
210 210 for col in td.columns_missing_from_B:
211 211 upgradeCommands.append(
212 212 'post_meta.tables[%r].columns[%r].create()' % (tn, col))
213 213 downgradeCommands.append(
214 214 'post_meta.tables[%r].columns[%r].drop()' % (tn, col))
215 215 for modelCol, databaseCol, modelDecl, databaseDecl in td.columns_different:
216 216 upgradeCommands.append(
217 217 'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
218 218 tn, modelCol.name, databaseCol.name))
219 219 downgradeCommands.append(
220 220 'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
221 221 tn, modelCol.name, databaseCol.name))
222 222
223 223 return (
224 224 '\n'.join(decls),
225 225 '\n'.join('%s%s' % (indent, line) for line in upgradeCommands),
226 226 '\n'.join('%s%s' % (indent, line) for line in downgradeCommands))
227 227
228 228 def _db_can_handle_this_change(self,td):
229 229 """Check if the database can handle going from B to A."""
230 230
231 231 if (td.columns_missing_from_B
232 232 and not td.columns_missing_from_A
233 233 and not td.columns_different):
234 234 # Even sqlite can handle column additions.
235 235 return True
236 236 else:
237 237 return not self.engine.url.drivername.startswith('sqlite')
238 238
239 239 def runB2A(self):
240 240 """Goes from B to A.
241 241
242 242 Was: applyModel. Apply model (A) to current database (B).
243 243 """
244 244
245 245 meta = sqlalchemy.MetaData(self.engine)
246 246
247 247 for table in self._get_tables(missingA=True):
248 248 table = table.tometadata(meta)
249 249 table.drop()
250 250 for table in self._get_tables(missingB=True):
251 251 table = table.tometadata(meta)
252 252 table.create()
253 253 for modelTable in self._get_tables(modified=True):
254 254 tableName = modelTable.name
255 255 modelTable = modelTable.tometadata(meta)
256 256 dbTable = self.diff.metadataB.tables[tableName]
257 257
258 258 td = self.diff.tables_different[tableName]
259 259
260 260 if self._db_can_handle_this_change(td):
261 261
262 262 for col in td.columns_missing_from_B:
263 263 modelTable.columns[col].create()
264 264 for col in td.columns_missing_from_A:
265 265 dbTable.columns[col].drop()
266 266 # XXX handle column changes here.
267 267 else:
268 268 # Sqlite doesn't support drop column, so you have to
269 269 # do more: create temp table, copy data to it, drop
270 270 # old table, create new table, copy data back.
271 271 #
272 272 # I wonder if this is guaranteed to be unique?
273 273 tempName = '_temp_%s' % modelTable.name
274 274
275 275 def getCopyStatement():
276 276 preparer = self.engine.dialect.preparer
277 277 commonCols = []
278 278 for modelCol in modelTable.columns:
279 279 if modelCol.name in dbTable.columns:
280 280 commonCols.append(modelCol.name)
281 281 commonColsStr = ', '.join(commonCols)
282 282 return 'INSERT INTO %s (%s) SELECT %s FROM %s' % \
283 283 (tableName, commonColsStr, commonColsStr, tempName)
284 284
285 285 # Move the data in one transaction, so that we don't
286 286 # leave the database in a nasty state.
287 287 connection = self.engine.connect()
288 288 trans = connection.begin()
289 289 try:
290 290 connection.execute(
291 291 'CREATE TEMPORARY TABLE %s as SELECT * from %s' % \
292 292 (tempName, modelTable.name))
293 293 # make sure the drop takes place inside our
294 294 # transaction with the bind parameter
295 295 modelTable.drop(bind=connection)
296 296 modelTable.create(bind=connection)
297 297 connection.execute(getCopyStatement())
298 298 connection.execute('DROP TABLE %s' % tempName)
299 299 trans.commit()
300 300 except:
301 301 trans.rollback()
302 302 raise
@@ -1,221 +1,220 b''
1 1 """
2 2 Database schema version management.
3 3 """
4 4 import sys
5 5 import logging
6 6
7 from sqlalchemy import (Table, Column, MetaData, String, Text, Integer,
8 create_engine)
7 from sqlalchemy import (Table, Column, MetaData, String, Text, Integer, create_engine)
9 8 from sqlalchemy.sql import and_
10 9 from sqlalchemy import exc as sa_exceptions
11 10 from sqlalchemy.sql import bindparam
12 11
13 12 from rhodecode.lib.dbmigrate.migrate import exceptions
14 13 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_07
15 14 from rhodecode.lib.dbmigrate.migrate.versioning import genmodel, schemadiff
16 15 from rhodecode.lib.dbmigrate.migrate.versioning.repository import Repository
17 16 from rhodecode.lib.dbmigrate.migrate.versioning.util import load_model
18 17 from rhodecode.lib.dbmigrate.migrate.versioning.version import VerNum
19 18
20 19
21 20 log = logging.getLogger(__name__)
22 21
23 22
24 23 class ControlledSchema(object):
25 24 """A database under version control"""
26 25
27 26 def __init__(self, engine, repository):
28 27 if isinstance(repository, str):
29 28 repository = Repository(repository)
30 29 self.engine = engine
31 30 self.repository = repository
32 31 self.meta = MetaData(engine)
33 32 self.load()
34 33
35 34 def __eq__(self, other):
36 35 """Compare two schemas by repositories and versions"""
37 36 return (self.repository is other.repository \
38 37 and self.version == other.version)
39 38
40 39 def load(self):
41 40 """Load controlled schema version info from DB"""
42 41 tname = self.repository.version_table
43 42 try:
44 43 if not hasattr(self, 'table') or self.table is None:
45 44 self.table = Table(tname, self.meta, autoload=True)
46 45
47 46 result = self.engine.execute(self.table.select(
48 47 self.table.c.repository_id == str(self.repository.id)))
49 48
50 49 data = list(result)[0]
51 50 except:
52 51 cls, exc, tb = sys.exc_info()
53 52 raise exceptions.DatabaseNotControlledError(exc.__str__()).with_traceback(tb)
54 53
55 54 self.version = data['version']
56 55 return data
57 56
58 57 def drop(self):
59 58 """
60 59 Remove version control from a database.
61 60 """
62 61 if SQLA_07:
63 62 try:
64 63 self.table.drop()
65 64 except sa_exceptions.DatabaseError:
66 65 raise exceptions.DatabaseNotControlledError(str(self.table))
67 66 else:
68 67 try:
69 68 self.table.drop()
70 69 except (sa_exceptions.SQLError):
71 70 raise exceptions.DatabaseNotControlledError(str(self.table))
72 71
73 72 def changeset(self, version=None):
74 73 """API to Changeset creation.
75 74
76 75 Uses self.version for start version and engine.name
77 76 to get database name.
78 77 """
79 78 database = self.engine.name
80 79 start_ver = self.version
81 80 changeset = self.repository.changeset(database, start_ver, version)
82 81 return changeset
83 82
84 83 def runchange(self, ver, change, step):
85 84 startver = ver
86 85 endver = ver + step
87 86 # Current database version must be correct! Don't run if corrupt!
88 87 if self.version != startver:
89 88 raise exceptions.InvalidVersionError("%s is not %s" % \
90 89 (self.version, startver))
91 90 # Run the change
92 91 change.run(self.engine, step)
93 92
94 93 # Update/refresh database version
95 94 self.update_repository_table(startver, endver)
96 95 self.load()
97 96
98 97 def update_repository_table(self, startver, endver):
99 98 """Update version_table with new information"""
100 99 update = self.table.update(and_(self.table.c.version == int(startver),
101 100 self.table.c.repository_id == str(self.repository.id)))
102 101 self.engine.execute(update, version=int(endver))
103 102
104 103 def upgrade(self, version=None):
105 104 """
106 105 Upgrade (or downgrade) to a specified version, or latest version.
107 106 """
108 107 changeset = self.changeset(version)
109 108 for ver, change in changeset:
110 109 self.runchange(ver, change, changeset.step)
111 110
112 111 def update_db_from_model(self, model):
113 112 """
114 113 Modify the database to match the structure of the current Python model.
115 114 """
116 115 model = load_model(model)
117 116
118 117 diff = schemadiff.getDiffOfModelAgainstDatabase(
119 118 model, self.engine, excludeTables=[self.repository.version_table]
120 119 )
121 120 genmodel.ModelGenerator(diff,self.engine).runB2A()
122 121
123 122 self.update_repository_table(self.version, int(self.repository.latest))
124 123
125 124 self.load()
126 125
127 126 @classmethod
128 127 def create(cls, engine, repository, version=None):
129 128 """
130 129 Declare a database to be under a repository's version control.
131 130
132 131 :raises: :exc:`DatabaseAlreadyControlledError`
133 132 :returns: :class:`ControlledSchema`
134 133 """
135 134 # Confirm that the version # is valid: positive, integer,
136 135 # exists in repos
137 136 if isinstance(repository, str):
138 137 repository = Repository(repository)
139 138 version = cls._validate_version(repository, version)
140 139 table = cls._create_table_version(engine, repository, version)
141 140 # TODO: history table
142 141 # Load repository information and return
143 142 return cls(engine, repository)
144 143
145 144 @classmethod
146 145 def _validate_version(cls, repository, version):
147 146 """
148 147 Ensures this is a valid version number for this repository.
149 148
150 149 :raises: :exc:`InvalidVersionError` if invalid
151 150 :return: valid version number
152 151 """
153 152 if version is None:
154 153 version = 0
155 154 try:
156 version = VerNum(version) # raises valueerror
155 version = VerNum(version) # raises valueerror
157 156 if version < 0 or version > repository.latest:
158 157 raise ValueError()
159 158 except ValueError:
160 159 raise exceptions.InvalidVersionError(version)
161 160 return version
162 161
163 162 @classmethod
164 163 def _create_table_version(cls, engine, repository, version):
165 164 """
166 165 Creates the versioning table in a database.
167 166
168 167 :raises: :exc:`DatabaseAlreadyControlledError`
169 168 """
170 169 # Create tables
171 170 tname = repository.version_table
172 171 meta = MetaData(engine)
173 172
174 173 table = Table(
175 174 tname, meta,
176 175 Column('repository_id', String(250), primary_key=True),
177 176 Column('repository_path', Text),
178 177 Column('version', Integer), )
179 178
180 179 # there can be multiple repositories/schemas in the same db
181 180 if not table.exists():
182 181 table.create()
183 182
184 183 # test for existing repository_id
185 184 s = table.select(table.c.repository_id == bindparam("repository_id"))
186 185 result = engine.execute(s, repository_id=repository.id)
187 186 if result.fetchone():
188 187 raise exceptions.DatabaseAlreadyControlledError
189 188
190 189 # Insert data
191 190 engine.execute(table.insert().values(
192 191 repository_id=repository.id,
193 192 repository_path=repository.path,
194 193 version=int(version)))
195 194 return table
196 195
197 196 @classmethod
198 197 def compare_model_to_db(cls, engine, model, repository):
199 198 """
200 199 Compare the current model against the current database.
201 200 """
202 201 if isinstance(repository, str):
203 202 repository = Repository(repository)
204 203 model = load_model(model)
205 204
206 205 diff = schemadiff.getDiffOfModelAgainstDatabase(
207 206 model, engine, excludeTables=[repository.version_table])
208 207 return diff
209 208
210 209 @classmethod
211 210 def create_model(cls, engine, repository, declarative=False):
212 211 """
213 212 Dump the current database as a Python model.
214 213 """
215 214 if isinstance(repository, str):
216 215 repository = Repository(repository)
217 216
218 217 diff = schemadiff.getDiffOfModelAgainstDatabase(
219 218 MetaData(), engine, excludeTables=[repository.version_table]
220 219 )
221 220 return genmodel.ModelGenerator(diff, engine, declarative).genBDefinition()
@@ -1,6 +1,6 b''
1 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2
3 3
4 4 from rhodecode.lib.dbmigrate.migrate.versioning.script.base import BaseScript
5 5 from rhodecode.lib.dbmigrate.migrate.versioning.script.py import PythonScript
6 6 from rhodecode.lib.dbmigrate.migrate.versioning.script.sql import SqlScript
@@ -1,56 +1,56 b''
1 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2
3 3 import logging
4 4
5 5 from rhodecode.lib.dbmigrate.migrate import exceptions
6 6 from rhodecode.lib.dbmigrate.migrate.versioning.config import operations
7 7 from rhodecode.lib.dbmigrate.migrate.versioning import pathed
8 8
9 9
10 10 log = logging.getLogger(__name__)
11 11
12 12 class BaseScript(pathed.Pathed):
13 13 """Base class for other types of scripts.
14 14 All scripts have the following properties:
15 15
16 16 source (script.source())
17 17 The source code of the script
18 18 version (script.version())
19 19 The version number of the script
20 20 operations (script.operations())
21 21 The operations defined by the script: upgrade(), downgrade() or both.
22 22 Returns a tuple of operations.
23 23 Can also check for an operation with ex. script.operation(Script.ops.up)
24 24 """ # TODO: sphinxfy this and implement it correctly
25 25
26 26 def __init__(self, path):
27 27 log.debug('Loading script %s...', path)
28 28 self.verify(path)
29 29 super(BaseScript, self).__init__(path)
30 30 log.debug('Script %s loaded successfully', path)
31 31
32 32 @classmethod
33 33 def verify(cls, path):
34 34 """Ensure this is a valid script
35 35 This version simply ensures the script file's existence
36 36
37 37 :raises: :exc:`InvalidScriptError <migrate.exceptions.InvalidScriptError>`
38 38 """
39 39 try:
40 40 cls.require_found(path)
41 41 except:
42 42 raise exceptions.InvalidScriptError(path)
43 43
44 44 def source(self):
45 45 """:returns: source code of the script.
46 46 :rtype: string
47 47 """
48 48 with open(self.path) as fd:
49 49 ret = fd.read()
50 50 return ret
51 51
52 52 def run(self, engine):
53 53 """Core of each BaseScript subclass.
54 54 This method executes the script.
55 55 """
56 56 raise NotImplementedError()
@@ -1,159 +1,159 b''
1 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2
3 3
4 4 import shutil
5 5 import warnings
6 6 import logging
7 7 import inspect
8 8 from io import StringIO
9 9
10 10 from rhodecode.lib.dbmigrate import migrate
11 11 from rhodecode.lib.dbmigrate.migrate.versioning import genmodel, schemadiff
12 12 from rhodecode.lib.dbmigrate.migrate.versioning.config import operations
13 13 from rhodecode.lib.dbmigrate.migrate.versioning.template import Template
14 14 from rhodecode.lib.dbmigrate.migrate.versioning.script import base
15 15 from rhodecode.lib.dbmigrate.migrate.versioning.util import import_path, load_model, with_engine
16 16 from rhodecode.lib.dbmigrate.migrate.exceptions import MigrateDeprecationWarning, InvalidScriptError, ScriptError
17 17
18 18 log = logging.getLogger(__name__)
19 19 __all__ = ['PythonScript']
20 20
21 21
22 22 class PythonScript(base.BaseScript):
23 23 """Base for Python scripts"""
24 24
25 25 @classmethod
26 26 def create(cls, path, **opts):
27 27 """Create an empty migration script at specified path
28 28
29 29 :returns: :class:`PythonScript instance <migrate.versioning.script.py.PythonScript>`"""
30 30 cls.require_notfound(path)
31 31
32 32 src = Template(opts.pop('templates_path', None)).get_script(theme=opts.pop('templates_theme', None))
33 33 shutil.copy(src, path)
34 34
35 35 return cls(path)
36 36
37 37 @classmethod
38 38 def make_update_script_for_model(cls, engine, oldmodel,
39 39 model, repository, **opts):
40 40 """Create a migration script based on difference between two SA models.
41 41
42 42 :param repository: path to migrate repository
43 43 :param oldmodel: dotted.module.name:SAClass or SAClass object
44 44 :param model: dotted.module.name:SAClass or SAClass object
45 45 :param engine: SQLAlchemy engine
46 46 :type repository: string or :class:`Repository instance <migrate.versioning.repository.Repository>`
47 47 :type oldmodel: string or Class
48 48 :type model: string or Class
49 49 :type engine: Engine instance
50 50 :returns: Upgrade / Downgrade script
51 51 :rtype: string
52 52 """
53 53
54 54 if isinstance(repository, str):
55 55 # oh dear, an import cycle!
56 56 from rhodecode.lib.dbmigrate.migrate.versioning.repository import Repository
57 57 repository = Repository(repository)
58 58
59 59 oldmodel = load_model(oldmodel)
60 60 model = load_model(model)
61 61
62 62 # Compute differences.
63 63 diff = schemadiff.getDiffOfModelAgainstModel(
64 64 model,
65 65 oldmodel,
66 66 excludeTables=[repository.version_table])
67 67 # TODO: diff can be False (there is no difference?)
68 68 decls, upgradeCommands, downgradeCommands = \
69 69 genmodel.ModelGenerator(diff,engine).genB2AMigration()
70 70
71 71 # Store differences into file.
72 72 src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None))
73 73 with open(src) as f:
74 74 contents = f.read()
75 75
76 76 # generate source
77 77 search = 'def upgrade(migrate_engine):'
78 78 contents = contents.replace(search, '\n\n'.join((decls, search)), 1)
79 79 if upgradeCommands:
80 80 contents = contents.replace(' pass', upgradeCommands, 1)
81 81 if downgradeCommands:
82 82 contents = contents.replace(' pass', downgradeCommands, 1)
83 83 return contents
84 84
85 85 @classmethod
86 86 def verify_module(cls, path):
87 87 """Ensure path is a valid script
88 88
89 89 :param path: Script location
90 90 :type path: string
91 91 :raises: :exc:`InvalidScriptError <migrate.exceptions.InvalidScriptError>`
92 92 :returns: Python module
93 93 """
94 94 # Try to import and get the upgrade() func
95 95 module = import_path(path)
96 96 try:
97 97 assert callable(module.upgrade)
98 98 except Exception as e:
99 99 raise InvalidScriptError(path + ': %s' % str(e))
100 100 return module
101 101
102 102 def preview_sql(self, url, step, **args):
103 103 """Mocks SQLAlchemy Engine to store all executed calls in a string
104 104 and runs :meth:`PythonScript.run <migrate.versioning.script.py.PythonScript.run>`
105 105
106 106 :returns: SQL file
107 107 """
108 108 buf = StringIO()
109 109 args['engine_arg_strategy'] = 'mock'
110 110 args['engine_arg_executor'] = lambda s, p = '': buf.write(str(s) + p)
111 111
112 112 @with_engine
113 113 def go(url, step, **kw):
114 114 engine = kw.pop('engine')
115 115 self.run(engine, step)
116 116 return buf.getvalue()
117 117
118 118 return go(url, step, **args)
119 119
120 120 def run(self, engine, step):
121 121 """Core method of Script file.
122 122 Exectues :func:`update` or :func:`downgrade` functions
123 123
124 124 :param engine: SQLAlchemy Engine
125 125 :param step: Operation to run
126 126 :type engine: string
127 127 :type step: int
128 128 """
129 129 if step > 0:
130 130 op = 'upgrade'
131 131 elif step < 0:
132 132 op = 'downgrade'
133 133 else:
134 134 raise ScriptError("%d is not a valid step" % step)
135 135
136 136 funcname = base.operations[op]
137 137 script_func = self._func(funcname)
138 138
139 139 # check for old way of using engine
140 140 if not inspect.getargspec(script_func)[0]:
141 141 raise TypeError("upgrade/downgrade functions must accept engine"
142 142 " parameter (since version 0.5.4)")
143 143
144 144 script_func(engine)
145 145
146 146 @property
147 147 def module(self):
148 148 """Calls :meth:`migrate.versioning.script.py.verify_module`
149 149 and returns it.
150 150 """
151 151 if not hasattr(self, '_module'):
152 152 self._module = self.verify_module(self.path)
153 153 return self._module
154 154
155 155 def _func(self, funcname):
156 156 if not hasattr(self.module, funcname):
157 157 msg = "Function '%s' is not defined in this script"
158 158 raise ScriptError(msg % funcname)
159 159 return getattr(self.module, funcname)
@@ -1,49 +1,49 b''
1 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2
3 3 import logging
4 4 import shutil
5 5
6 6 from rhodecode.lib.dbmigrate.migrate.versioning.script import base
7 7 from rhodecode.lib.dbmigrate.migrate.versioning.template import Template
8 8
9 9
10 10 log = logging.getLogger(__name__)
11 11
12 12 class SqlScript(base.BaseScript):
13 13 """A file containing plain SQL statements."""
14 14
15 15 @classmethod
16 16 def create(cls, path, **opts):
17 17 """Create an empty migration script at specified path
18 18
19 19 :returns: :class:`SqlScript instance <migrate.versioning.script.sql.SqlScript>`"""
20 20 cls.require_notfound(path)
21 21
22 22 src = Template(opts.pop('templates_path', None)).get_sql_script(theme=opts.pop('templates_theme', None))
23 23 shutil.copy(src, path)
24 24 return cls(path)
25 25
26 26 # TODO: why is step parameter even here?
27 27 def run(self, engine, step=None, executemany=True):
28 28 """Runs SQL script through raw dbapi execute call"""
29 29 text = self.source()
30 30 # Don't rely on SA's autocommit here
31 31 # (SA uses .startswith to check if a commit is needed. What if script
32 32 # starts with a comment?)
33 33 conn = engine.connect()
34 34 try:
35 35 trans = conn.begin()
36 36 try:
37 37 # HACK: SQLite doesn't allow multiple statements through
38 38 # its execute() method, but it provides executescript() instead
39 39 dbapi = conn.engine.raw_connection()
40 40 if executemany and getattr(dbapi, 'executescript', None):
41 41 dbapi.executescript(text)
42 42 else:
43 43 conn.execute(text)
44 44 trans.commit()
45 45 except:
46 46 trans.rollback()
47 47 raise
48 48 finally:
49 49 conn.close()
@@ -1,215 +1,215 b''
1 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2
3 3
4 4 """The migrate command-line tool."""
5 5
6 6 import sys
7 7 import inspect
8 8 import logging
9 9 from optparse import OptionParser, BadOptionError
10 10
11 11 from rhodecode.lib.dbmigrate.migrate import exceptions
12 12 from rhodecode.lib.dbmigrate.migrate.versioning import api
13 13 from rhodecode.lib.dbmigrate.migrate.versioning.config import *
14 14 from rhodecode.lib.dbmigrate.migrate.versioning.util import asbool
15 15
16 16
17 17 alias = {
18 18 's': api.script,
19 19 'vc': api.version_control,
20 20 'dbv': api.db_version,
21 21 'v': api.version,
22 22 }
23 23
24 24 def alias_setup():
25 25 global alias
26 26 for key, val in list(alias.items()):
27 27 setattr(api, key, val)
28 28 alias_setup()
29 29
30 30
31 31 class PassiveOptionParser(OptionParser):
32 32
33 33 def _process_args(self, largs, rargs, values):
34 34 """little hack to support all --some_option=value parameters"""
35 35
36 36 while rargs:
37 37 arg = rargs[0]
38 38 if arg == "--":
39 39 del rargs[0]
40 40 return
41 41 elif arg[0:2] == "--":
42 42 # if parser does not know about the option
43 43 # pass it along (make it anonymous)
44 44 try:
45 45 opt = arg.split('=', 1)[0]
46 46 self._match_long_opt(opt)
47 47 except BadOptionError:
48 48 largs.append(arg)
49 49 del rargs[0]
50 50 else:
51 51 self._process_long_opt(rargs, values)
52 52 elif arg[:1] == "-" and len(arg) > 1:
53 53 self._process_short_opts(rargs, values)
54 54 elif self.allow_interspersed_args:
55 55 largs.append(arg)
56 56 del rargs[0]
57 57
58 58 def main(argv=None, **kwargs):
59 59 """Shell interface to :mod:`migrate.versioning.api`.
60 60
61 61 kwargs are default options that can be overriden with passing
62 62 --some_option as command line option
63 63
64 64 :param disable_logging: Let migrate configure logging
65 65 :type disable_logging: bool
66 66 """
67 67 if argv is not None:
68 68 argv = argv
69 69 else:
70 70 argv = list(sys.argv[1:])
71 71 commands = list(api.__all__)
72 72 commands.sort()
73 73
74 74 usage = """%%prog COMMAND ...
75 75
76 76 Available commands:
77 77 %s
78 78
79 79 Enter "%%prog help COMMAND" for information on a particular command.
80 80 """ % '\n\t'.join(["%s - %s" % (command.ljust(28), api.command_desc.get(command)) for command in commands])
81 81
82 82 parser = PassiveOptionParser(usage=usage)
83 83 parser.add_option("-d", "--debug",
84 84 action="store_true",
85 85 dest="debug",
86 86 default=False,
87 87 help="Shortcut to turn on DEBUG mode for logging")
88 88 parser.add_option("-q", "--disable_logging",
89 89 action="store_true",
90 90 dest="disable_logging",
91 91 default=False,
92 92 help="Use this option to disable logging configuration")
93 93 help_commands = ['help', '-h', '--help']
94 94 HELP = False
95 95
96 96 try:
97 97 command = argv.pop(0)
98 98 if command in help_commands:
99 99 HELP = True
100 100 command = argv.pop(0)
101 101 except IndexError:
102 102 parser.print_help()
103 103 return
104 104
105 105 command_func = getattr(api, command, None)
106 106 if command_func is None or command.startswith('_'):
107 107 parser.error("Invalid command %s" % command)
108 108
109 109 parser.set_usage(inspect.getdoc(command_func))
110 110 f_args, f_varargs, f_kwargs, f_defaults = inspect.getargspec(command_func)
111 111 for arg in f_args:
112 112 parser.add_option(
113 113 "--%s" % arg,
114 114 dest=arg,
115 115 action='store',
116 116 type="string")
117 117
118 118 # display help of the current command
119 119 if HELP:
120 120 parser.print_help()
121 121 return
122 122
123 123 options, args = parser.parse_args(argv)
124 124
125 125 # override kwargs with anonymous parameters
126 126 override_kwargs = {}
127 127 for arg in list(args):
128 128 if arg.startswith('--'):
129 129 args.remove(arg)
130 130 if '=' in arg:
131 131 opt, value = arg[2:].split('=', 1)
132 132 else:
133 133 opt = arg[2:]
134 134 value = True
135 135 override_kwargs[opt] = value
136 136
137 137 # override kwargs with options if user is overwriting
138 138 for key, value in list(options.__dict__.items()):
139 139 if value is not None:
140 140 override_kwargs[key] = value
141 141
142 142 # arguments that function accepts without passed kwargs
143 143 f_required = list(f_args)
144 144 candidates = dict(kwargs)
145 145 candidates.update(override_kwargs)
146 146 for key, value in list(candidates.items()):
147 147 if key in f_args:
148 148 f_required.remove(key)
149 149
150 150 # map function arguments to parsed arguments
151 151 for arg in args:
152 152 try:
153 153 kw = f_required.pop(0)
154 154 except IndexError:
155 155 parser.error("Too many arguments for command %s: %s" % (command,
156 156 arg))
157 157 kwargs[kw] = arg
158 158
159 159 # apply overrides
160 160 kwargs.update(override_kwargs)
161 161
162 162 # configure options
163 163 for key, value in list(options.__dict__.items()):
164 164 kwargs.setdefault(key, value)
165 165
166 166 # configure logging
167 167 if not asbool(kwargs.pop('disable_logging', False)):
168 168 # filter to log =< INFO into stdout and rest to stderr
169 169 class SingleLevelFilter(logging.Filter):
170 170 def __init__(self, min=None, max=None):
171 171 self.min = min or 0
172 172 self.max = max or 100
173 173
174 174 def filter(self, record):
175 175 return self.min <= record.levelno <= self.max
176 176
177 177 logger = logging.getLogger()
178 178 h1 = logging.StreamHandler(sys.stdout)
179 179 f1 = SingleLevelFilter(max=logging.INFO)
180 180 h1.addFilter(f1)
181 181 h2 = logging.StreamHandler(sys.stderr)
182 182 f2 = SingleLevelFilter(min=logging.WARN)
183 183 h2.addFilter(f2)
184 184 logger.addHandler(h1)
185 185 logger.addHandler(h2)
186 186
187 187 if options.debug:
188 188 logger.setLevel(logging.DEBUG)
189 189 else:
190 190 logger.setLevel(logging.INFO)
191 191
192 192 log = logging.getLogger(__name__)
193 193
194 194 # check if all args are given
195 195 try:
196 196 num_defaults = len(f_defaults)
197 197 except TypeError:
198 198 num_defaults = 0
199 199 f_args_default = f_args[len(f_args) - num_defaults:]
200 200 required = list(set(f_required) - set(f_args_default))
201 201 required.sort()
202 202 if required:
203 203 parser.error("Not enough arguments for command %s: %s not specified" \
204 204 % (command, ', '.join(required)))
205 205
206 206 # handle command
207 207 try:
208 208 ret = command_func(**kwargs)
209 209 if ret is not None:
210 210 log.info(ret)
211 211 except (exceptions.UsageError, exceptions.KnownError) as e:
212 212 parser.error(e.args[0])
213 213
214 214 if __name__ == "__main__":
215 215 main()
@@ -1,93 +1,93 b''
1 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2
3 3
4 4 import os
5 5 import shutil
6 6 import sys
7 7
8 8 from pkg_resources import resource_filename
9 9
10 10 from rhodecode.lib.dbmigrate.migrate.versioning.config import *
11 11 from rhodecode.lib.dbmigrate.migrate.versioning import pathed
12 12
13 13
14 14 class Collection(pathed.Pathed):
15 15 """A collection of templates of a specific type"""
16 16 _mask = None
17 17
18 18 def get_path(self, file):
19 19 return os.path.join(self.path, str(file))
20 20
21 21
22 22 class RepositoryCollection(Collection):
23 23 _mask = '%s'
24 24
25 25 class ScriptCollection(Collection):
26 26 _mask = '%s.py_tmpl'
27 27
28 28 class ManageCollection(Collection):
29 29 _mask = '%s.py_tmpl'
30 30
31 31 class SQLScriptCollection(Collection):
32 32 _mask = '%s.py_tmpl'
33 33
34 34 class Template(pathed.Pathed):
35 35 """Finds the paths/packages of various Migrate templates.
36 36
37 37 :param path: Templates are loaded from rhodecode.lib.dbmigrate.migrate package
38 38 if `path` is not provided.
39 39 """
40 40 pkg = 'rhodecode.lib.dbmigrate.migrate.versioning.templates'
41 41
42 42 def __new__(cls, path=None):
43 43 if path is None:
44 44 path = cls._find_path(cls.pkg)
45 45 return super(Template, cls).__new__(cls, path)
46 46
47 47 def __init__(self, path=None):
48 48 if path is None:
49 49 path = Template._find_path(self.pkg)
50 50 super(Template, self).__init__(path)
51 51 self.repository = RepositoryCollection(os.path.join(path, 'repository'))
52 52 self.script = ScriptCollection(os.path.join(path, 'script'))
53 53 self.manage = ManageCollection(os.path.join(path, 'manage'))
54 54 self.sql_script = SQLScriptCollection(os.path.join(path, 'sql_script'))
55 55
56 56 @classmethod
57 57 def _find_path(cls, pkg):
58 58 """Returns absolute path to dotted python package."""
59 59 tmp_pkg = pkg.rsplit('.', 1)
60 60
61 61 if len(tmp_pkg) != 1:
62 62 return resource_filename(tmp_pkg[0], tmp_pkg[1])
63 63 else:
64 64 return resource_filename(tmp_pkg[0], '')
65 65
66 66 def _get_item(self, collection, theme=None):
67 67 """Locates and returns collection.
68 68
69 69 :param collection: name of collection to locate
70 70 :param type_: type of subfolder in collection (defaults to "_default")
71 71 :returns: (package, source)
72 72 :rtype: str, str
73 73 """
74 74 item = getattr(self, collection)
75 75 theme_mask = getattr(item, '_mask')
76 76 theme = theme_mask % (theme or 'default')
77 77 return item.get_path(theme)
78 78
79 79 def get_repository(self, *a, **kw):
80 80 """Calls self._get_item('repository', *a, **kw)"""
81 81 return self._get_item('repository', *a, **kw)
82 82
83 83 def get_script(self, *a, **kw):
84 84 """Calls self._get_item('script', *a, **kw)"""
85 85 return self._get_item('script', *a, **kw)
86 86
87 87 def get_sql_script(self, *a, **kw):
88 88 """Calls self._get_item('sql_script', *a, **kw)"""
89 89 return self._get_item('sql_script', *a, **kw)
90 90
91 91 def get_manage(self, *a, **kw):
92 92 """Calls self._get_item('manage', *a, **kw)"""
93 93 return self._get_item('manage', *a, **kw)
@@ -1,180 +1,180 b''
1 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2
3 3 """.. currentmodule:: migrate.versioning.util"""
4 4
5 5 import warnings
6 6 import logging
7 7 from decorator import decorator
8 8 from pkg_resources import EntryPoint
9 9
10 10 from sqlalchemy import create_engine
11 11 from sqlalchemy.engine import Engine
12 12 from sqlalchemy.pool import StaticPool
13 13
14 14 from rhodecode.lib.dbmigrate.migrate import exceptions
15 15 from rhodecode.lib.dbmigrate.migrate.versioning.util.keyedinstance import KeyedInstance
16 16 from rhodecode.lib.dbmigrate.migrate.versioning.util.importpath import import_path
17 17
18 18
19 19 log = logging.getLogger(__name__)
20 20
21 21
22 22 def load_model(dotted_name):
23 23 """Import module and use module-level variable".
24 24
25 25 :param dotted_name: path to model in form of string: ``some.python.module:Class``
26 26
27 27 .. versionchanged:: 0.5.4
28 28
29 29 """
30 30 if isinstance(dotted_name, str):
31 31 if ':' not in dotted_name:
32 32 # backwards compatibility
33 33 warnings.warn('model should be in form of module.model:User '
34 34 'and not module.model.User', exceptions.MigrateDeprecationWarning)
35 35 dotted_name = ':'.join(dotted_name.rsplit('.', 1))
36 36 return EntryPoint.parse('x=%s' % dotted_name).load(False)
37 37 else:
38 38 # Assume it's already loaded.
39 39 return dotted_name
40 40
41 41 def asbool(obj):
42 42 """Do everything to use object as bool"""
43 43 if isinstance(obj, str):
44 44 obj = obj.strip().lower()
45 45 if obj in ['true', 'yes', 'on', 'y', 't', '1']:
46 46 return True
47 47 elif obj in ['false', 'no', 'off', 'n', 'f', '0']:
48 48 return False
49 49 else:
50 50 raise ValueError("String is not true/false: %r" % obj)
51 51 if obj in (True, False):
52 52 return bool(obj)
53 53 else:
54 54 raise ValueError("String is not true/false: %r" % obj)
55 55
56 56 def guess_obj_type(obj):
57 57 """Do everything to guess object type from string
58 58
59 59 Tries to convert to `int`, `bool` and finally returns if not succeded.
60 60
61 61 .. versionadded: 0.5.4
62 62 """
63 63
64 64 result = None
65 65
66 66 try:
67 67 result = int(obj)
68 68 except:
69 69 pass
70 70
71 71 if result is None:
72 72 try:
73 73 result = asbool(obj)
74 74 except:
75 75 pass
76 76
77 77 if result is not None:
78 78 return result
79 79 else:
80 80 return obj
81 81
82 82 @decorator
83 83 def catch_known_errors(f, *a, **kw):
84 84 """Decorator that catches known api errors
85 85
86 86 .. versionadded: 0.5.4
87 87 """
88 88
89 89 try:
90 90 return f(*a, **kw)
91 91 except exceptions.PathFoundError as e:
92 92 raise exceptions.KnownError("The path %s already exists" % e.args[0])
93 93
94 94 def construct_engine(engine, **opts):
95 95 """.. versionadded:: 0.5.4
96 96
97 97 Constructs and returns SQLAlchemy engine.
98 98
99 99 Currently, there are 2 ways to pass create_engine options to :mod:`migrate.versioning.api` functions:
100 100
101 101 :param engine: connection string or a existing engine
102 102 :param engine_dict: python dictionary of options to pass to `create_engine`
103 103 :param engine_arg_*: keyword parameters to pass to `create_engine` (evaluated with :func:`migrate.versioning.util.guess_obj_type`)
104 104 :type engine_dict: dict
105 105 :type engine: string or Engine instance
106 106 :type engine_arg_*: string
107 107 :returns: SQLAlchemy Engine
108 108
109 109 .. note::
110 110
111 111 keyword parameters override ``engine_dict`` values.
112 112
113 113 """
114 114 if isinstance(engine, Engine):
115 115 return engine
116 116 elif not isinstance(engine, str):
117 117 raise ValueError("you need to pass either an existing engine or a database uri")
118 118
119 119 # get options for create_engine
120 120 if opts.get('engine_dict') and isinstance(opts['engine_dict'], dict):
121 121 kwargs = opts['engine_dict']
122 122 else:
123 123 kwargs = {}
124 124
125 125 # DEPRECATED: handle echo the old way
126 126 echo = asbool(opts.get('echo', False))
127 127 if echo:
128 warnings.warn('echo=True parameter is deprecated, pass '
129 'engine_arg_echo=True or engine_dict={"echo": True}',
128 warnings.warn(
129 'echo=True parameter is deprecated, pass engine_arg_echo=True or engine_dict={"echo": True}',
130 130 exceptions.MigrateDeprecationWarning)
131 131 kwargs['echo'] = echo
132 132
133 133 # parse keyword arguments
134 134 for key, value in list(opts.items()):
135 135 if key.startswith('engine_arg_'):
136 136 kwargs[key[11:]] = guess_obj_type(value)
137 137
138 138 log.debug('Constructing engine')
139 139 # TODO: return create_engine(engine, poolclass=StaticPool, **kwargs)
140 140 # seems like 0.5.x branch does not work with engine.dispose and staticpool
141 141 return create_engine(engine, **kwargs)
142 142
143 143 @decorator
144 144 def with_engine(f, *a, **kw):
145 145 """Decorator for :mod:`migrate.versioning.api` functions
146 146 to safely close resources after function usage.
147 147
148 148 Passes engine parameters to :func:`construct_engine` and
149 149 resulting parameter is available as kw['engine'].
150 150
151 151 Engine is disposed after wrapped function is executed.
152 152
153 153 .. versionadded: 0.6.0
154 154 """
155 155 url = a[0]
156 156 engine = construct_engine(url, **kw)
157 157
158 158 try:
159 159 kw['engine'] = engine
160 160 return f(*a, **kw)
161 161 finally:
162 162 if isinstance(engine, Engine) and engine is not url:
163 163 log.debug('Disposing SQLAlchemy engine %s', engine)
164 164 engine.dispose()
165 165
166 166
167 167 class Memoize:
168 168 """Memoize(fn) - an instance which acts like fn but memoizes its arguments
169 169 Will only work on functions with non-mutable arguments
170 170
171 171 ActiveState Code 52201
172 172 """
173 173 def __init__(self, fn):
174 174 self.fn = fn
175 175 self.memo = {}
176 176
177 177 def __call__(self, *args):
178 178 if args not in self.memo:
179 179 self.memo[args] = self.fn(*args)
180 180 return self.memo[args]
@@ -1,36 +1,36 b''
1 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2
3 3
4 4 class KeyedInstance(object):
5 5 """A class whose instances have a unique identifier of some sort
6 6 No two instances with the same unique ID should exist - if we try to create
7 7 a second instance, the first should be returned.
8 8 """
9 9
10 10 _instances = {}
11 11
12 12 def __new__(cls, *p, **k):
13 13 instances = cls._instances
14 14 clskey = str(cls)
15 15 if clskey not in instances:
16 16 instances[clskey] = {}
17 17 instances = instances[clskey]
18 18
19 19 key = cls._key(*p, **k)
20 20 if key not in instances:
21 21 instances[key] = super(KeyedInstance, cls).__new__(cls)
22 22 return instances[key]
23 23
24 24 @classmethod
25 25 def _key(cls, *p, **k):
26 26 """Given a unique identifier, return a dictionary key
27 27 This should be overridden by child classes, to specify which parameters
28 28 should determine an object's uniqueness
29 29 """
30 30 raise NotImplementedError()
31 31
32 32 @classmethod
33 33 def clear(cls):
34 34 # Allow cls.clear() as well as uniqueInstance.clear(cls)
35 35 if str(cls) in cls._instances:
36 36 del cls._instances[str(cls)]
@@ -1,263 +1,263 b''
1 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2
3 3
4 4 import os
5 5 import re
6 6 import shutil
7 7 import logging
8 8
9 9 from rhodecode.lib.dbmigrate.migrate import exceptions
10 10 from rhodecode.lib.dbmigrate.migrate.versioning import pathed, script
11 11 from datetime import datetime
12 12
13 13
14 14 log = logging.getLogger(__name__)
15 15
16 16 class VerNum(object):
17 17 """A version number that behaves like a string and int at the same time"""
18 18
19 19 _instances = {}
20 20
21 21 def __new__(cls, value):
22 22 val = str(value)
23 23 if val not in cls._instances:
24 24 cls._instances[val] = super(VerNum, cls).__new__(cls)
25 25 ret = cls._instances[val]
26 26 return ret
27 27
28 28 def __init__(self,value):
29 29 self.value = str(int(value))
30 30 if self < 0:
31 31 raise ValueError("Version number cannot be negative")
32 32
33 33 def __add__(self, value):
34 34 ret = int(self) + int(value)
35 35 return VerNum(ret)
36 36
37 37 def __sub__(self, value):
38 38 return self + (int(value) * -1)
39 39
40 40 def __eq__(self, value):
41 41 return int(self) == int(value)
42 42
43 43 def __ne__(self, value):
44 44 return int(self) != int(value)
45 45
46 46 def __lt__(self, value):
47 47 return int(self) < int(value)
48 48
49 49 def __gt__(self, value):
50 50 return int(self) > int(value)
51 51
52 52 def __ge__(self, value):
53 53 return int(self) >= int(value)
54 54
55 55 def __le__(self, value):
56 56 return int(self) <= int(value)
57 57
58 58 def __repr__(self):
59 59 return "<VerNum(%s)>" % self.value
60 60
61 61 def __str__(self):
62 62 return str(self.value)
63 63
64 64 def __int__(self):
65 65 return int(self.value)
66 66
67 67
68 68 class Collection(pathed.Pathed):
69 69 """A collection of versioning scripts in a repository"""
70 70
71 71 FILENAME_WITH_VERSION = re.compile(r'^(\d{3,}).*')
72 72
73 73 def __init__(self, path):
74 74 """Collect current version scripts in repository
75 75 and store them in self.versions
76 76 """
77 77 super(Collection, self).__init__(path)
78 78
79 79 # Create temporary list of files, allowing skipped version numbers.
80 80 files = os.listdir(path)
81 81 if '1' in files:
82 82 # deprecation
83 83 raise Exception('It looks like you have a repository in the old '
84 84 'format (with directories for each version). '
85 85 'Please convert repository before proceeding.')
86 86
87 87 tempVersions = {}
88 88 for filename in files:
89 89 match = self.FILENAME_WITH_VERSION.match(filename)
90 90 if match:
91 91 num = int(match.group(1))
92 92 tempVersions.setdefault(num, []).append(filename)
93 93 else:
94 94 pass # Must be a helper file or something, let's ignore it.
95 95
96 96 # Create the versions member where the keys
97 97 # are VerNum's and the values are Version's.
98 98 self.versions = {}
99 99 for num, files in list(tempVersions.items()):
100 100 self.versions[VerNum(num)] = Version(num, path, files)
101 101
102 102 @property
103 103 def latest(self):
104 104 """:returns: Latest version in Collection"""
105 105 return max([VerNum(0)] + list(self.versions.keys()))
106 106
107 107 def _next_ver_num(self, use_timestamp_numbering):
108 108 if use_timestamp_numbering == True:
109 109 return VerNum(int(datetime.utcnow().strftime('%Y%m%d%H%M%S')))
110 110 else:
111 111 return self.latest + 1
112 112
113 113 def create_new_python_version(self, description, **k):
114 114 """Create Python files for new version"""
115 115 ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
116 116 extra = str_to_filename(description)
117 117
118 118 if extra:
119 119 if extra == '_':
120 120 extra = ''
121 121 elif not extra.startswith('_'):
122 122 extra = '_%s' % extra
123 123
124 124 filename = '%03d%s.py' % (ver, extra)
125 125 filepath = self._version_path(filename)
126 126
127 127 script.PythonScript.create(filepath, **k)
128 128 self.versions[ver] = Version(ver, self.path, [filename])
129 129
130 130 def create_new_sql_version(self, database, description, **k):
131 131 """Create SQL files for new version"""
132 132 ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
133 133 self.versions[ver] = Version(ver, self.path, [])
134 134
135 135 extra = str_to_filename(description)
136 136
137 137 if extra:
138 138 if extra == '_':
139 139 extra = ''
140 140 elif not extra.startswith('_'):
141 141 extra = '_%s' % extra
142 142
143 143 # Create new files.
144 144 for op in ('upgrade', 'downgrade'):
145 145 filename = '%03d%s_%s_%s.sql' % (ver, extra, database, op)
146 146 filepath = self._version_path(filename)
147 147 script.SqlScript.create(filepath, **k)
148 148 self.versions[ver].add_script(filepath)
149 149
150 150 def version(self, vernum=None):
151 151 """Returns latest Version if vernum is not given.
152 152 Otherwise, returns wanted version"""
153 153 if vernum is None:
154 154 vernum = self.latest
155 155 return self.versions[VerNum(vernum)]
156 156
157 157 @classmethod
158 158 def clear(cls):
159 159 super(Collection, cls).clear()
160 160
161 161 def _version_path(self, ver):
162 162 """Returns path of file in versions repository"""
163 163 return os.path.join(self.path, str(ver))
164 164
165 165
166 166 class Version(object):
167 167 """A single version in a collection
168 168 :param vernum: Version Number
169 169 :param path: Path to script files
170 170 :param filelist: List of scripts
171 171 :type vernum: int, VerNum
172 172 :type path: string
173 173 :type filelist: list
174 174 """
175 175
176 176 def __init__(self, vernum, path, filelist):
177 177 self.version = VerNum(vernum)
178 178
179 179 # Collect scripts in this folder
180 180 self.sql = {}
181 181 self.python = None
182 182
183 183 for script in filelist:
184 184 self.add_script(os.path.join(path, script))
185 185
186 186 def script(self, database=None, operation=None):
187 187 """Returns SQL or Python Script"""
188 188 for db in (database, 'default'):
189 189 # Try to return a .sql script first
190 190 try:
191 191 return self.sql[db][operation]
192 192 except KeyError:
193 193 continue # No .sql script exists
194 194
195 195 # TODO: maybe add force Python parameter?
196 196 ret = self.python
197 197
198 198 assert ret is not None, \
199 199 "There is no script for %d version" % self.version
200 200 return ret
201 201
202 202 def add_script(self, path):
203 203 """Add script to Collection/Version"""
204 204 if path.endswith(Extensions.py):
205 205 self._add_script_py(path)
206 206 elif path.endswith(Extensions.sql):
207 207 self._add_script_sql(path)
208 208
209 209 SQL_FILENAME = re.compile(r'^.*\.sql')
210 210
211 211 def _add_script_sql(self, path):
212 212 basename = os.path.basename(path)
213 213 match = self.SQL_FILENAME.match(basename)
214 214
215 215 if match:
216 216 basename = basename.replace('.sql', '')
217 217 parts = basename.split('_')
218 218 if len(parts) < 3:
219 219 raise exceptions.ScriptError(
220 220 "Invalid SQL script name %s " % basename + \
221 221 "(needs to be ###_description_database_operation.sql)")
222 222 version = parts[0]
223 223 op = parts[-1]
224 224 # NOTE(mriedem): check for ibm_db_sa as the database in the name
225 225 if 'ibm_db_sa' in basename:
226 226 if len(parts) == 6:
227 227 dbms = '_'.join(parts[-4: -1])
228 228 else:
229 229 raise exceptions.ScriptError(
230 230 "Invalid ibm_db_sa SQL script name '%s'; "
231 231 "(needs to be "
232 232 "###_description_ibm_db_sa_operation.sql)" % basename)
233 233 else:
234 234 dbms = parts[-2]
235 235 else:
236 236 raise exceptions.ScriptError(
237 237 "Invalid SQL script name %s " % basename + \
238 238 "(needs to be ###_description_database_operation.sql)")
239 239
240 240 # File the script into a dictionary
241 241 self.sql.setdefault(dbms, {})[op] = script.SqlScript(path)
242 242
243 243 def _add_script_py(self, path):
244 244 if self.python is not None:
245 245 raise exceptions.ScriptError('You can only have one Python script '
246 246 'per version, but you have: %s and %s' % (self.python, path))
247 247 self.python = script.PythonScript(path)
248 248
249 249
250 250 class Extensions:
251 251 """A namespace for file extensions"""
252 252 py = 'py'
253 253 sql = 'sql'
254 254
255 255 def str_to_filename(s):
256 256 """Replaces spaces, (double and single) quotes
257 257 and double underscores to underscores
258 258 """
259 259
260 260 s = s.replace(' ', '_').replace('"', '_').replace("'", '_').replace(".", "_")
261 261 while '__' in s:
262 262 s = s.replace('__', '_')
263 263 return s
@@ -1,27 +1,26 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20
22 21 def url(*args, **kwargs):
23 22 """
24 23 Dummy url generator to be used inside the old db migration schemas that rely on it.
25 24 It would protect from errors after removal of pylons.
26 25 """
27 26 return '/'
@@ -1,114 +1,113 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 from sqlalchemy import *
22 21 from sqlalchemy.exc import DatabaseError
23 22 from sqlalchemy.orm import relation, backref, class_mapper
24 23 from sqlalchemy.orm.session import Session
25 24 from rhodecode.model.meta import Base
26 25
27 26 class BaseModel(object):
28 27 """Base Model for all classess
29 28
30 29 """
31 30
32 31 @classmethod
33 32 def _get_keys(cls):
34 33 """return column names for this model """
35 34 return class_mapper(cls).c.keys()
36 35
37 36 def get_dict(self):
38 37 """return dict with keys and values corresponding
39 38 to this model data """
40 39
41 40 d = {}
42 41 for k in self._get_keys():
43 42 d[k] = getattr(self, k)
44 43 return d
45 44
46 45 def get_appstruct(self):
47 46 """return list with keys and values tupples corresponding
48 47 to this model data """
49 48
50 49 l = []
51 50 for k in self._get_keys():
52 51 l.append((k, getattr(self, k),))
53 52 return l
54 53
55 54 def populate_obj(self, populate_dict):
56 55 """populate model with data from given populate_dict"""
57 56
58 57 for k in self._get_keys():
59 58 if k in populate_dict:
60 59 setattr(self, k, populate_dict[k])
61 60
62 61 @classmethod
63 62 def query(cls):
64 63 return Session.query(cls)
65 64
66 65 @classmethod
67 66 def get(cls, id_):
68 67 if id_:
69 68 return cls.query().get(id_)
70 69
71 70 @classmethod
72 71 def getAll(cls):
73 72 return cls.query().all()
74 73
75 74 @classmethod
76 75 def delete(cls, id_):
77 76 obj = cls.query().get(id_)
78 77 Session.delete(obj)
79 78 Session.commit()
80 79
81 80
82 81 class UserFollowing(Base, BaseModel):
83 82 __tablename__ = 'user_followings'
84 83 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
85 84 UniqueConstraint('user_id', 'follows_user_id')
86 85 , {'useexisting':True})
87 86
88 87 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
89 88 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
90 89 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
91 90 follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
92 91
93 92 user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
94 93
95 94 follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
96 95 follows_repository = relation('Repository')
97 96
98 97
99 98 class CacheInvalidation(Base, BaseModel):
100 99 __tablename__ = 'cache_invalidation'
101 100 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
102 101 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
103 102 cache_key = Column("cache_key", String(None), nullable=True, unique=None, default=None)
104 103 cache_args = Column("cache_args", String(None), nullable=True, unique=None, default=None)
105 104 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
106 105
107 106
108 107 def __init__(self, cache_key, cache_args=''):
109 108 self.cache_key = cache_key
110 109 self.cache_args = cache_args
111 110 self.cache_active = False
112 111
113 112 def __repr__(self):
114 113 return "<CacheKey('%s:%s')>" % (self.cache_id, self.cache_key)
@@ -1,1043 +1,1042 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 import os
22 21 import logging
23 22 import datetime
24 23 import traceback
25 24 from datetime import date
26 25
27 26 from sqlalchemy import *
28 27 from sqlalchemy.ext.hybrid import hybrid_property
29 28 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
30 29 from beaker.cache import cache_region, region_invalidate
31 30
32 31 from rhodecode.lib.vcs import get_backend
33 32 from rhodecode.lib.vcs.utils.helpers import get_scm
34 33 from rhodecode.lib.vcs.exceptions import VCSError
35 34 from zope.cachedescriptors.property import Lazy as LazyProperty
36 35 from rhodecode.lib.auth import generate_auth_token
37 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, safe_unicode
36 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe
38 37 from rhodecode.lib.exceptions import UserGroupAssignedException
39 38 from rhodecode.lib.ext_json import json
40 39
41 40 from rhodecode.model.meta import Base, Session
42 41 from rhodecode.lib.caching_query import FromCache
43 42
44 43
45 44 log = logging.getLogger(__name__)
46 45
47 46 #==============================================================================
48 47 # BASE CLASSES
49 48 #==============================================================================
50 49
51 50 class ModelSerializer(json.JSONEncoder):
52 51 """
53 52 Simple Serializer for JSON,
54 53
55 54 usage::
56 55
57 56 to make object customized for serialization implement a __json__
58 57 method that will return a dict for serialization into json
59 58
60 59 example::
61 60
62 61 class Task(object):
63 62
64 63 def __init__(self, name, value):
65 64 self.name = name
66 65 self.value = value
67 66
68 67 def __json__(self):
69 68 return dict(name=self.name,
70 69 value=self.value)
71 70
72 71 """
73 72
74 73 def default(self, obj):
75 74
76 75 if hasattr(obj, '__json__'):
77 76 return obj.__json__()
78 77 else:
79 78 return json.JSONEncoder.default(self, obj)
80 79
81 80 class BaseModel(object):
82 81 """Base Model for all classess
83 82
84 83 """
85 84
86 85 @classmethod
87 86 def _get_keys(cls):
88 87 """return column names for this model """
89 88 return class_mapper(cls).c.keys()
90 89
91 90 def get_dict(self):
92 91 """return dict with keys and values corresponding
93 92 to this model data """
94 93
95 94 d = {}
96 95 for k in self._get_keys():
97 96 d[k] = getattr(self, k)
98 97 return d
99 98
100 99 def get_appstruct(self):
101 100 """return list with keys and values tupples corresponding
102 101 to this model data """
103 102
104 103 l = []
105 104 for k in self._get_keys():
106 105 l.append((k, getattr(self, k),))
107 106 return l
108 107
109 108 def populate_obj(self, populate_dict):
110 109 """populate model with data from given populate_dict"""
111 110
112 111 for k in self._get_keys():
113 112 if k in populate_dict:
114 113 setattr(self, k, populate_dict[k])
115 114
116 115 @classmethod
117 116 def query(cls):
118 117 return Session.query(cls)
119 118
120 119 @classmethod
121 120 def get(cls, id_):
122 121 if id_:
123 122 return cls.query().get(id_)
124 123
125 124 @classmethod
126 125 def getAll(cls):
127 126 return cls.query().all()
128 127
129 128 @classmethod
130 129 def delete(cls, id_):
131 130 obj = cls.query().get(id_)
132 131 Session.delete(obj)
133 132 Session.commit()
134 133
135 134
136 135 class RhodeCodeSetting(Base, BaseModel):
137 136 __tablename__ = 'rhodecode_settings'
138 137 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
139 138 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
140 139 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
141 140 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
142 141
143 142 def __init__(self, k='', v=''):
144 143 self.app_settings_name = k
145 144 self.app_settings_value = v
146 145
147 146
148 147 @validates('_app_settings_value')
149 148 def validate_settings_value(self, key, val):
150 assert type(val) == unicode
149 assert type(val) == str
151 150 return val
152 151
153 152 @hybrid_property
154 153 def app_settings_value(self):
155 154 v = self._app_settings_value
156 155 if v == 'ldap_active':
157 156 v = str2bool(v)
158 157 return v
159 158
160 159 @app_settings_value.setter
161 160 def app_settings_value(self, val):
162 161 """
163 162 Setter that will always make sure we use unicode in app_settings_value
164 163
165 164 :param val:
166 165 """
167 self._app_settings_value = safe_unicode(val)
166 self._app_settings_value = safe_str(val)
168 167
169 168 def __repr__(self):
170 169 return "<%s('%s:%s')>" % (self.__class__.__name__,
171 170 self.app_settings_name, self.app_settings_value)
172 171
173 172
174 173 @classmethod
175 174 def get_by_name(cls, ldap_key):
176 175 return cls.query()\
177 176 .filter(cls.app_settings_name == ldap_key).scalar()
178 177
179 178 @classmethod
180 179 def get_app_settings(cls, cache=False):
181 180
182 181 ret = cls.query()
183 182
184 183 if cache:
185 184 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
186 185
187 186 if not ret:
188 187 raise Exception('Could not get application settings !')
189 188 settings = {}
190 189 for each in ret:
191 190 settings['rhodecode_' + each.app_settings_name] = \
192 191 each.app_settings_value
193 192
194 193 return settings
195 194
196 195 @classmethod
197 196 def get_ldap_settings(cls, cache=False):
198 197 ret = cls.query()\
199 198 .filter(cls.app_settings_name.startswith('ldap_')).all()
200 199 fd = {}
201 200 for row in ret:
202 201 fd.update({row.app_settings_name:row.app_settings_value})
203 202
204 203 return fd
205 204
206 205
207 206 class RhodeCodeUi(Base, BaseModel):
208 207 __tablename__ = 'rhodecode_ui'
209 208 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
210 209
211 210 HOOK_REPO_SIZE = 'changegroup.repo_size'
212 211 HOOK_PUSH = 'pretxnchangegroup.push_logger'
213 212 HOOK_PULL = 'preoutgoing.pull_logger'
214 213
215 214 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
216 215 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
217 216 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
218 217 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
219 218 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
220 219
221 220
222 221 @classmethod
223 222 def get_by_key(cls, key):
224 223 return cls.query().filter(cls.ui_key == key)
225 224
226 225
227 226 @classmethod
228 227 def get_builtin_hooks(cls):
229 228 q = cls.query()
230 229 q = q.filter(cls.ui_key.in_([cls.HOOK_REPO_SIZE,
231 230 cls.HOOK_PUSH, cls.HOOK_PULL]))
232 231 return q.all()
233 232
234 233 @classmethod
235 234 def get_custom_hooks(cls):
236 235 q = cls.query()
237 236 q = q.filter(~cls.ui_key.in_([cls.HOOK_REPO_SIZE,
238 237 cls.HOOK_PUSH, cls.HOOK_PULL]))
239 238 q = q.filter(cls.ui_section == 'hooks')
240 239 return q.all()
241 240
242 241 @classmethod
243 242 def create_or_update_hook(cls, key, val):
244 243 new_ui = cls.get_by_key(key).scalar() or cls()
245 244 new_ui.ui_section = 'hooks'
246 245 new_ui.ui_active = True
247 246 new_ui.ui_key = key
248 247 new_ui.ui_value = val
249 248
250 249 Session.add(new_ui)
251 250 Session.commit()
252 251
253 252
254 253 class User(Base, BaseModel):
255 254 __tablename__ = 'users'
256 255 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
257 256 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
258 257 username = Column("username", String(255), nullable=True, unique=None, default=None)
259 258 password = Column("password", String(255), nullable=True, unique=None, default=None)
260 259 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
261 260 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
262 261 name = Column("name", String(255), nullable=True, unique=None, default=None)
263 262 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
264 263 email = Column("email", String(255), nullable=True, unique=None, default=None)
265 264 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
266 265 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
267 266 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
268 267
269 268 user_log = relationship('UserLog', cascade='all')
270 269 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
271 270
272 271 repositories = relationship('Repository')
273 272 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
274 273 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
275 274
276 275 group_member = relationship('UserGroupMember', cascade='all')
277 276
278 277 @property
279 278 def full_contact(self):
280 279 return '%s %s <%s>' % (self.name, self.lastname, self.email)
281 280
282 281 @property
283 282 def short_contact(self):
284 283 return '%s %s' % (self.name, self.lastname)
285 284
286 285 @property
287 286 def is_admin(self):
288 287 return self.admin
289 288
290 289 def __repr__(self):
291 290 try:
292 291 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
293 292 self.user_id, self.username)
294 293 except:
295 294 return self.__class__.__name__
296 295
297 296 @classmethod
298 297 def get_by_username(cls, username, case_insensitive=False):
299 298 if case_insensitive:
300 299 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
301 300 else:
302 301 return Session.query(cls).filter(cls.username == username).scalar()
303 302
304 303 @classmethod
305 304 def get_by_auth_token(cls, auth_token):
306 305 return cls.query().filter(cls.api_key == auth_token).one()
307 306
308 307 def update_lastlogin(self):
309 308 """Update user lastlogin"""
310 309
311 310 self.last_login = datetime.datetime.now()
312 311 Session.add(self)
313 312 Session.commit()
314 313 log.debug('updated user %s lastlogin', self.username)
315 314
316 315 @classmethod
317 316 def create(cls, form_data):
318 317 from rhodecode.lib.auth import get_crypt_password
319 318
320 319 try:
321 320 new_user = cls()
322 321 for k, v in form_data.items():
323 322 if k == 'password':
324 323 v = get_crypt_password(v)
325 324 setattr(new_user, k, v)
326 325
327 326 new_user.api_key = generate_auth_token(form_data['username'])
328 327 Session.add(new_user)
329 328 Session.commit()
330 329 return new_user
331 330 except:
332 331 log.error(traceback.format_exc())
333 332 Session.rollback()
334 333 raise
335 334
336 335 class UserLog(Base, BaseModel):
337 336 __tablename__ = 'user_logs'
338 337 __table_args__ = {'extend_existing':True}
339 338 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
340 339 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
341 340 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
342 341 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
343 342 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
344 343 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
345 344 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
346 345
347 346 @property
348 347 def action_as_day(self):
349 348 return date(*self.action_date.timetuple()[:3])
350 349
351 350 user = relationship('User')
352 351 repository = relationship('Repository')
353 352
354 353
355 354 class UserGroup(Base, BaseModel):
356 355 __tablename__ = 'users_groups'
357 356 __table_args__ = {'extend_existing':True}
358 357
359 358 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
360 359 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
361 360 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
362 361
363 362 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
364 363
365 364 def __repr__(self):
366 365 return '<userGroup(%s)>' % (self.users_group_name)
367 366
368 367 @classmethod
369 368 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
370 369 if case_insensitive:
371 370 gr = cls.query()\
372 371 .filter(cls.users_group_name.ilike(group_name))
373 372 else:
374 373 gr = cls.query()\
375 374 .filter(cls.users_group_name == group_name)
376 375 if cache:
377 376 gr = gr.options(FromCache("sql_cache_short",
378 377 "get_user_%s" % group_name))
379 378 return gr.scalar()
380 379
381 380 @classmethod
382 381 def get(cls, users_group_id, cache=False):
383 382 users_group = cls.query()
384 383 if cache:
385 384 users_group = users_group.options(FromCache("sql_cache_short",
386 385 "get_users_group_%s" % users_group_id))
387 386 return users_group.get(users_group_id)
388 387
389 388 @classmethod
390 389 def create(cls, form_data):
391 390 try:
392 391 new_user_group = cls()
393 392 for k, v in form_data.items():
394 393 setattr(new_user_group, k, v)
395 394
396 395 Session.add(new_user_group)
397 396 Session.commit()
398 397 return new_user_group
399 398 except:
400 399 log.error(traceback.format_exc())
401 400 Session.rollback()
402 401 raise
403 402
404 403 @classmethod
405 404 def update(cls, users_group_id, form_data):
406 405
407 406 try:
408 407 users_group = cls.get(users_group_id, cache=False)
409 408
410 409 for k, v in form_data.items():
411 410 if k == 'users_group_members':
412 411 users_group.members = []
413 412 Session.flush()
414 413 members_list = []
415 414 if v:
416 415 v = [v] if isinstance(v, str) else v
417 416 for u_id in set(v):
418 417 member = UserGroupMember(users_group_id, u_id)
419 418 members_list.append(member)
420 419 setattr(users_group, 'members', members_list)
421 420 setattr(users_group, k, v)
422 421
423 422 Session.add(users_group)
424 423 Session.commit()
425 424 except:
426 425 log.error(traceback.format_exc())
427 426 Session.rollback()
428 427 raise
429 428
430 429 @classmethod
431 430 def delete(cls, user_group_id):
432 431 try:
433 432
434 433 # check if this group is not assigned to repo
435 434 assigned_groups = UserGroupRepoToPerm.query()\
436 435 .filter(UserGroupRepoToPerm.users_group_id ==
437 436 user_group_id).all()
438 437
439 438 if assigned_groups:
440 439 raise UserGroupAssignedException(
441 440 'UserGroup assigned to %s' % assigned_groups)
442 441
443 442 users_group = cls.get(user_group_id, cache=False)
444 443 Session.delete(users_group)
445 444 Session.commit()
446 445 except:
447 446 log.error(traceback.format_exc())
448 447 Session.rollback()
449 448 raise
450 449
451 450 class UserGroupMember(Base, BaseModel):
452 451 __tablename__ = 'users_groups_members'
453 452 __table_args__ = {'extend_existing':True}
454 453
455 454 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
456 455 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
457 456 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
458 457
459 458 user = relationship('User', lazy='joined')
460 459 users_group = relationship('UserGroup')
461 460
462 461 def __init__(self, gr_id='', u_id=''):
463 462 self.users_group_id = gr_id
464 463 self.user_id = u_id
465 464
466 465 @staticmethod
467 466 def add_user_to_group(group, user):
468 467 ugm = UserGroupMember()
469 468 ugm.users_group = group
470 469 ugm.user = user
471 470 Session.add(ugm)
472 471 Session.commit()
473 472 return ugm
474 473
475 474 class Repository(Base, BaseModel):
476 475 __tablename__ = 'repositories'
477 476 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
478 477
479 478 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
480 479 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
481 480 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
482 481 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default='hg')
483 482 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
484 483 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
485 484 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
486 485 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
487 486 description = Column("description", String(10000), nullable=True, unique=None, default=None)
488 487 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
489 488
490 489 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
491 490 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
492 491
493 492
494 493 user = relationship('User')
495 494 fork = relationship('Repository', remote_side=repo_id)
496 495 group = relationship('RepoGroup')
497 496 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
498 497 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
499 498 stats = relationship('Statistics', cascade='all', uselist=False)
500 499
501 500 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
502 501
503 502 logs = relationship('UserLog', cascade='all')
504 503
505 504 def __repr__(self):
506 505 return "<%s('%s:%s')>" % (self.__class__.__name__,
507 506 self.repo_id, self.repo_name)
508 507
509 508 @classmethod
510 509 def url_sep(cls):
511 510 return '/'
512 511
513 512 @classmethod
514 513 def get_by_repo_name(cls, repo_name):
515 514 q = Session.query(cls).filter(cls.repo_name == repo_name)
516 515 q = q.options(joinedload(Repository.fork))\
517 516 .options(joinedload(Repository.user))\
518 517 .options(joinedload(Repository.group))
519 518 return q.one()
520 519
521 520 @classmethod
522 521 def get_repo_forks(cls, repo_id):
523 522 return cls.query().filter(Repository.fork_id == repo_id)
524 523
525 524 @classmethod
526 525 def base_path(cls):
527 526 """
528 527 Returns base path when all repos are stored
529 528
530 529 :param cls:
531 530 """
532 531 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
533 532 cls.url_sep())
534 533 q.options(FromCache("sql_cache_short", "repository_repo_path"))
535 534 return q.one().ui_value
536 535
537 536 @property
538 537 def just_name(self):
539 538 return self.repo_name.split(Repository.url_sep())[-1]
540 539
541 540 @property
542 541 def groups_with_parents(self):
543 542 groups = []
544 543 if self.group is None:
545 544 return groups
546 545
547 546 cur_gr = self.group
548 547 groups.insert(0, cur_gr)
549 548 while 1:
550 549 gr = getattr(cur_gr, 'parent_group', None)
551 550 cur_gr = cur_gr.parent_group
552 551 if gr is None:
553 552 break
554 553 groups.insert(0, gr)
555 554
556 555 return groups
557 556
558 557 @property
559 558 def groups_and_repo(self):
560 559 return self.groups_with_parents, self.just_name
561 560
562 561 @LazyProperty
563 562 def repo_path(self):
564 563 """
565 564 Returns base full path for that repository means where it actually
566 565 exists on a filesystem
567 566 """
568 567 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
569 568 Repository.url_sep())
570 569 q.options(FromCache("sql_cache_short", "repository_repo_path"))
571 570 return q.one().ui_value
572 571
573 572 @property
574 573 def repo_full_path(self):
575 574 p = [self.repo_path]
576 575 # we need to split the name by / since this is how we store the
577 576 # names in the database, but that eventually needs to be converted
578 577 # into a valid system path
579 578 p += self.repo_name.split(Repository.url_sep())
580 579 return os.path.join(*p)
581 580
582 581 def get_new_name(self, repo_name):
583 582 """
584 583 returns new full repository name based on assigned group and new new
585 584
586 585 :param group_name:
587 586 """
588 587 path_prefix = self.group.full_path_splitted if self.group else []
589 588 return Repository.url_sep().join(path_prefix + [repo_name])
590 589
591 590 @property
592 591 def _config(self):
593 592 """
594 593 Returns db based config object.
595 594 """
596 595 from rhodecode.lib.utils import make_db_config
597 596 return make_db_config(clear_session=False)
598 597
599 598 @classmethod
600 599 def is_valid(cls, repo_name):
601 600 """
602 601 returns True if given repo name is a valid filesystem repository
603 602
604 603 :param cls:
605 604 :param repo_name:
606 605 """
607 606 from rhodecode.lib.utils import is_valid_repo
608 607
609 608 return is_valid_repo(repo_name, cls.base_path())
610 609
611 610
612 611 #==========================================================================
613 612 # SCM PROPERTIES
614 613 #==========================================================================
615 614
616 615 def get_commit(self, rev):
617 616 return get_commit_safe(self.scm_instance, rev)
618 617
619 618 @property
620 619 def tip(self):
621 620 return self.get_commit('tip')
622 621
623 622 @property
624 623 def author(self):
625 624 return self.tip.author
626 625
627 626 @property
628 627 def last_change(self):
629 628 return self.scm_instance.last_change
630 629
631 630 #==========================================================================
632 631 # SCM CACHE INSTANCE
633 632 #==========================================================================
634 633
635 634 @property
636 635 def invalidate(self):
637 636 return CacheInvalidation.invalidate(self.repo_name)
638 637
639 638 def set_invalidate(self):
640 639 """
641 640 set a cache for invalidation for this instance
642 641 """
643 642 CacheInvalidation.set_invalidate(self.repo_name)
644 643
645 644 @LazyProperty
646 645 def scm_instance(self):
647 646 return self.__get_instance()
648 647
649 648 @property
650 649 def scm_instance_cached(self):
651 650 return self.__get_instance()
652 651
653 652 def __get_instance(self):
654 653
655 654 repo_full_path = self.repo_full_path
656 655
657 656 try:
658 657 alias = get_scm(repo_full_path)[0]
659 658 log.debug('Creating instance of %s repository', alias)
660 659 backend = get_backend(alias)
661 660 except VCSError:
662 661 log.error(traceback.format_exc())
663 662 log.error('Perhaps this repository is in db and not in '
664 663 'filesystem run rescan repositories with '
665 664 '"destroy old data " option from admin panel')
666 665 return
667 666
668 667 if alias == 'hg':
669 668
670 669 repo = backend(safe_str(repo_full_path), create=False,
671 670 config=self._config)
672 671
673 672 else:
674 673 repo = backend(repo_full_path, create=False)
675 674
676 675 return repo
677 676
678 677
679 678 class Group(Base, BaseModel):
680 679 __tablename__ = 'groups'
681 680 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
682 681 {'extend_existing':True},)
683 682 __mapper_args__ = {'order_by':'group_name'}
684 683
685 684 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
686 685 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
687 686 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
688 687 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
689 688
690 689 parent_group = relationship('Group', remote_side=group_id)
691 690
692 691 def __init__(self, group_name='', parent_group=None):
693 692 self.group_name = group_name
694 693 self.parent_group = parent_group
695 694
696 695 def __repr__(self):
697 696 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
698 697 self.group_name)
699 698
700 699 @classmethod
701 700 def url_sep(cls):
702 701 return '/'
703 702
704 703 @classmethod
705 704 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
706 705 if case_insensitive:
707 706 gr = cls.query()\
708 707 .filter(cls.group_name.ilike(group_name))
709 708 else:
710 709 gr = cls.query()\
711 710 .filter(cls.group_name == group_name)
712 711 if cache:
713 712 gr = gr.options(FromCache("sql_cache_short",
714 713 "get_group_%s" % group_name))
715 714 return gr.scalar()
716 715
717 716 @property
718 717 def parents(self):
719 718 parents_recursion_limit = 5
720 719 groups = []
721 720 if self.parent_group is None:
722 721 return groups
723 722 cur_gr = self.parent_group
724 723 groups.insert(0, cur_gr)
725 724 cnt = 0
726 725 while 1:
727 726 cnt += 1
728 727 gr = getattr(cur_gr, 'parent_group', None)
729 728 cur_gr = cur_gr.parent_group
730 729 if gr is None:
731 730 break
732 731 if cnt == parents_recursion_limit:
733 732 # this will prevent accidental infinit loops
734 733 log.error('group nested more than %s',
735 734 parents_recursion_limit)
736 735 break
737 736
738 737 groups.insert(0, gr)
739 738 return groups
740 739
741 740 @property
742 741 def children(self):
743 742 return Group.query().filter(Group.parent_group == self)
744 743
745 744 @property
746 745 def name(self):
747 746 return self.group_name.split(Group.url_sep())[-1]
748 747
749 748 @property
750 749 def full_path(self):
751 750 return self.group_name
752 751
753 752 @property
754 753 def full_path_splitted(self):
755 754 return self.group_name.split(Group.url_sep())
756 755
757 756 @property
758 757 def repositories(self):
759 758 return Repository.query().filter(Repository.group == self)
760 759
761 760 @property
762 761 def repositories_recursive_count(self):
763 762 cnt = self.repositories.count()
764 763
765 764 def children_count(group):
766 765 cnt = 0
767 766 for child in group.children:
768 767 cnt += child.repositories.count()
769 768 cnt += children_count(child)
770 769 return cnt
771 770
772 771 return cnt + children_count(self)
773 772
774 773
775 774 def get_new_name(self, group_name):
776 775 """
777 776 returns new full group name based on parent and new name
778 777
779 778 :param group_name:
780 779 """
781 780 path_prefix = (self.parent_group.full_path_splitted if
782 781 self.parent_group else [])
783 782 return Group.url_sep().join(path_prefix + [group_name])
784 783
785 784
786 785 class Permission(Base, BaseModel):
787 786 __tablename__ = 'permissions'
788 787 __table_args__ = {'extend_existing':True}
789 788 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
790 789 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
791 790 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
792 791
793 792 def __repr__(self):
794 793 return "<%s('%s:%s')>" % (self.__class__.__name__,
795 794 self.permission_id, self.permission_name)
796 795
797 796 @classmethod
798 797 def get_by_key(cls, key):
799 798 return cls.query().filter(cls.permission_name == key).scalar()
800 799
801 800 class UserRepoToPerm(Base, BaseModel):
802 801 __tablename__ = 'repo_to_perm'
803 802 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
804 803 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
805 804 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
806 805 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
807 806 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
808 807
809 808 user = relationship('User')
810 809 permission = relationship('Permission')
811 810 repository = relationship('Repository')
812 811
813 812 class UserToPerm(Base, BaseModel):
814 813 __tablename__ = 'user_to_perm'
815 814 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
816 815 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
817 816 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
818 817 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
819 818
820 819 user = relationship('User')
821 820 permission = relationship('Permission')
822 821
823 822 @classmethod
824 823 def has_perm(cls, user_id, perm):
825 824 if not isinstance(perm, Permission):
826 825 raise Exception('perm needs to be an instance of Permission class')
827 826
828 827 return cls.query().filter(cls.user_id == user_id)\
829 828 .filter(cls.permission == perm).scalar() is not None
830 829
831 830 @classmethod
832 831 def grant_perm(cls, user_id, perm):
833 832 if not isinstance(perm, Permission):
834 833 raise Exception('perm needs to be an instance of Permission class')
835 834
836 835 new = cls()
837 836 new.user_id = user_id
838 837 new.permission = perm
839 838 try:
840 839 Session.add(new)
841 840 Session.commit()
842 841 except:
843 842 Session.rollback()
844 843
845 844
846 845 @classmethod
847 846 def revoke_perm(cls, user_id, perm):
848 847 if not isinstance(perm, Permission):
849 848 raise Exception('perm needs to be an instance of Permission class')
850 849
851 850 try:
852 851 cls.query().filter(cls.user_id == user_id) \
853 852 .filter(cls.permission == perm).delete()
854 853 Session.commit()
855 854 except:
856 855 Session.rollback()
857 856
858 857 class UserGroupRepoToPerm(Base, BaseModel):
859 858 __tablename__ = 'users_group_repo_to_perm'
860 859 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
861 860 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
862 861 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
863 862 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
864 863 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
865 864
866 865 users_group = relationship('UserGroup')
867 866 permission = relationship('Permission')
868 867 repository = relationship('Repository')
869 868
870 869 def __repr__(self):
871 870 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
872 871
873 872 class UserGroupToPerm(Base, BaseModel):
874 873 __tablename__ = 'users_group_to_perm'
875 874 __table_args__ = {'extend_existing':True}
876 875 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
877 876 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
878 877 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
879 878
880 879 users_group = relationship('UserGroup')
881 880 permission = relationship('Permission')
882 881
883 882
884 883 @classmethod
885 884 def has_perm(cls, users_group_id, perm):
886 885 if not isinstance(perm, Permission):
887 886 raise Exception('perm needs to be an instance of Permission class')
888 887
889 888 return cls.query().filter(cls.users_group_id ==
890 889 users_group_id)\
891 890 .filter(cls.permission == perm)\
892 891 .scalar() is not None
893 892
894 893 @classmethod
895 894 def grant_perm(cls, users_group_id, perm):
896 895 if not isinstance(perm, Permission):
897 896 raise Exception('perm needs to be an instance of Permission class')
898 897
899 898 new = cls()
900 899 new.users_group_id = users_group_id
901 900 new.permission = perm
902 901 try:
903 902 Session.add(new)
904 903 Session.commit()
905 904 except:
906 905 Session.rollback()
907 906
908 907
909 908 @classmethod
910 909 def revoke_perm(cls, users_group_id, perm):
911 910 if not isinstance(perm, Permission):
912 911 raise Exception('perm needs to be an instance of Permission class')
913 912
914 913 try:
915 914 cls.query().filter(cls.users_group_id == users_group_id) \
916 915 .filter(cls.permission == perm).delete()
917 916 Session.commit()
918 917 except:
919 918 Session.rollback()
920 919
921 920
922 921 class UserRepoGroupToPerm(Base, BaseModel):
923 922 __tablename__ = 'group_to_perm'
924 923 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
925 924
926 925 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
927 926 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
928 927 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
929 928 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
930 929
931 930 user = relationship('User')
932 931 permission = relationship('Permission')
933 932 group = relationship('RepoGroup')
934 933
935 934 class Statistics(Base, BaseModel):
936 935 __tablename__ = 'statistics'
937 936 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
938 937 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
939 938 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
940 939 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
941 940 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
942 941 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
943 942 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
944 943
945 944 repository = relationship('Repository', single_parent=True)
946 945
947 946 class UserFollowing(Base, BaseModel):
948 947 __tablename__ = 'user_followings'
949 948 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
950 949 UniqueConstraint('user_id', 'follows_user_id')
951 950 , {'extend_existing':True})
952 951
953 952 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
954 953 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
955 954 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
956 955 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
957 956 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
958 957
959 958 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
960 959
961 960 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
962 961 follows_repository = relationship('Repository', order_by='Repository.repo_name')
963 962
964 963
965 964 @classmethod
966 965 def get_repo_followers(cls, repo_id):
967 966 return cls.query().filter(cls.follows_repo_id == repo_id)
968 967
969 968 class CacheInvalidation(Base, BaseModel):
970 969 __tablename__ = 'cache_invalidation'
971 970 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
972 971 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
973 972 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
974 973 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
975 974 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
976 975
977 976
978 977 def __init__(self, cache_key, cache_args=''):
979 978 self.cache_key = cache_key
980 979 self.cache_args = cache_args
981 980 self.cache_active = False
982 981
983 982 def __repr__(self):
984 983 return "<%s('%s:%s')>" % (self.__class__.__name__,
985 984 self.cache_id, self.cache_key)
986 985
987 986 @classmethod
988 987 def invalidate(cls, key):
989 988 """
990 989 Returns Invalidation object if this given key should be invalidated
991 990 None otherwise. `cache_active = False` means that this cache
992 991 state is not valid and needs to be invalidated
993 992
994 993 :param key:
995 994 """
996 995 return cls.query()\
997 996 .filter(CacheInvalidation.cache_key == key)\
998 997 .filter(CacheInvalidation.cache_active == False)\
999 998 .scalar()
1000 999
1001 1000 @classmethod
1002 1001 def set_invalidate(cls, key):
1003 1002 """
1004 1003 Mark this Cache key for invalidation
1005 1004
1006 1005 :param key:
1007 1006 """
1008 1007
1009 1008 log.debug('marking %s for invalidation', key)
1010 1009 inv_obj = Session.query(cls)\
1011 1010 .filter(cls.cache_key == key).scalar()
1012 1011 if inv_obj:
1013 1012 inv_obj.cache_active = False
1014 1013 else:
1015 1014 log.debug('cache key not found in invalidation db -> creating one')
1016 1015 inv_obj = CacheInvalidation(key)
1017 1016
1018 1017 try:
1019 1018 Session.add(inv_obj)
1020 1019 Session.commit()
1021 1020 except Exception:
1022 1021 log.error(traceback.format_exc())
1023 1022 Session.rollback()
1024 1023
1025 1024 @classmethod
1026 1025 def set_valid(cls, key):
1027 1026 """
1028 1027 Mark this cache key as active and currently cached
1029 1028
1030 1029 :param key:
1031 1030 """
1032 1031 inv_obj = Session.query(CacheInvalidation)\
1033 1032 .filter(CacheInvalidation.cache_key == key).scalar()
1034 1033 inv_obj.cache_active = True
1035 1034 Session.add(inv_obj)
1036 1035 Session.commit()
1037 1036
1038 1037 class DbMigrateVersion(Base, BaseModel):
1039 1038 __tablename__ = 'db_migrate_version'
1040 1039 __table_args__ = {'extend_existing':True}
1041 1040 repository_id = Column('repository_id', String(250), primary_key=True)
1042 1041 repository_path = Column('repository_path', Text)
1043 1042 version = Column('version', Integer)
@@ -1,1263 +1,1261 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 import os
22 21 import logging
23 22 import datetime
24 23 import traceback
25 24 from collections import defaultdict
26 25
27 26 from sqlalchemy import *
28 27 from sqlalchemy.ext.hybrid import hybrid_property
29 28 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
30 29 from beaker.cache import cache_region, region_invalidate
31 30
32 31 from rhodecode.lib.vcs import get_backend
33 32 from rhodecode.lib.vcs.utils.helpers import get_scm
34 33 from rhodecode.lib.vcs.exceptions import VCSError
35 34 from zope.cachedescriptors.property import Lazy as LazyProperty
36 35
37 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
38 safe_unicode
36 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe
39 37 from rhodecode.lib.ext_json import json
40 38 from rhodecode.lib.caching_query import FromCache
41 39
42 40 from rhodecode.model.meta import Base, Session
43 41 import hashlib
44 42
45 43
46 44 log = logging.getLogger(__name__)
47 45
48 46 #==============================================================================
49 47 # BASE CLASSES
50 48 #==============================================================================
51 49
52 50 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
53 51
54 52
55 53 class ModelSerializer(json.JSONEncoder):
56 54 """
57 55 Simple Serializer for JSON,
58 56
59 57 usage::
60 58
61 59 to make object customized for serialization implement a __json__
62 60 method that will return a dict for serialization into json
63 61
64 62 example::
65 63
66 64 class Task(object):
67 65
68 66 def __init__(self, name, value):
69 67 self.name = name
70 68 self.value = value
71 69
72 70 def __json__(self):
73 71 return dict(name=self.name,
74 72 value=self.value)
75 73
76 74 """
77 75
78 76 def default(self, obj):
79 77
80 78 if hasattr(obj, '__json__'):
81 79 return obj.__json__()
82 80 else:
83 81 return json.JSONEncoder.default(self, obj)
84 82
85 83
86 84 class BaseModel(object):
87 85 """
88 86 Base Model for all classess
89 87 """
90 88
91 89 @classmethod
92 90 def _get_keys(cls):
93 91 """return column names for this model """
94 92 return class_mapper(cls).c.keys()
95 93
96 94 def get_dict(self):
97 95 """
98 96 return dict with keys and values corresponding
99 97 to this model data """
100 98
101 99 d = {}
102 100 for k in self._get_keys():
103 101 d[k] = getattr(self, k)
104 102
105 103 # also use __json__() if present to get additional fields
106 104 for k, val in getattr(self, '__json__', lambda: {})().items():
107 105 d[k] = val
108 106 return d
109 107
110 108 def get_appstruct(self):
111 109 """return list with keys and values tupples corresponding
112 110 to this model data """
113 111
114 112 l = []
115 113 for k in self._get_keys():
116 114 l.append((k, getattr(self, k),))
117 115 return l
118 116
119 117 def populate_obj(self, populate_dict):
120 118 """populate model with data from given populate_dict"""
121 119
122 120 for k in self._get_keys():
123 121 if k in populate_dict:
124 122 setattr(self, k, populate_dict[k])
125 123
126 124 @classmethod
127 125 def query(cls):
128 126 return Session.query(cls)
129 127
130 128 @classmethod
131 129 def get(cls, id_):
132 130 if id_:
133 131 return cls.query().get(id_)
134 132
135 133 @classmethod
136 134 def getAll(cls):
137 135 return cls.query().all()
138 136
139 137 @classmethod
140 138 def delete(cls, id_):
141 139 obj = cls.query().get(id_)
142 140 Session.delete(obj)
143 141
144 142 def __repr__(self):
145 143 if hasattr(self, '__unicode__'):
146 144 # python repr needs to return str
147 145 return safe_str(self.__unicode__())
148 146 return '<DB:%s>' % (self.__class__.__name__)
149 147
150 148
151 149 class RhodeCodeSetting(Base, BaseModel):
152 150 __tablename__ = 'rhodecode_settings'
153 151 __table_args__ = (
154 152 UniqueConstraint('app_settings_name'),
155 153 {'extend_existing': True, 'mysql_engine':'InnoDB',
156 154 'mysql_charset': 'utf8'}
157 155 )
158 156 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
159 157 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
160 158 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
161 159
162 160 def __init__(self, k='', v=''):
163 161 self.app_settings_name = k
164 162 self.app_settings_value = v
165 163
166 164 @validates('_app_settings_value')
167 165 def validate_settings_value(self, key, val):
168 assert type(val) == unicode
166 assert type(val) == str
169 167 return val
170 168
171 169 @hybrid_property
172 170 def app_settings_value(self):
173 171 v = self._app_settings_value
174 172 if self.app_settings_name == 'ldap_active':
175 173 v = str2bool(v)
176 174 return v
177 175
178 176 @app_settings_value.setter
179 177 def app_settings_value(self, val):
180 178 """
181 179 Setter that will always make sure we use unicode in app_settings_value
182 180
183 181 :param val:
184 182 """
185 self._app_settings_value = safe_unicode(val)
183 self._app_settings_value = safe_str(val)
186 184
187 185 def __unicode__(self):
188 186 return u"<%s('%s:%s')>" % (
189 187 self.__class__.__name__,
190 188 self.app_settings_name, self.app_settings_value
191 189 )
192 190
193 191 @classmethod
194 192 def get_by_name(cls, ldap_key):
195 193 return cls.query()\
196 194 .filter(cls.app_settings_name == ldap_key).scalar()
197 195
198 196 @classmethod
199 197 def get_app_settings(cls, cache=False):
200 198
201 199 ret = cls.query()
202 200
203 201 if cache:
204 202 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
205 203
206 204 if not ret:
207 205 raise Exception('Could not get application settings !')
208 206 settings = {}
209 207 for each in ret:
210 208 settings['rhodecode_' + each.app_settings_name] = \
211 209 each.app_settings_value
212 210
213 211 return settings
214 212
215 213 @classmethod
216 214 def get_ldap_settings(cls, cache=False):
217 215 ret = cls.query()\
218 216 .filter(cls.app_settings_name.startswith('ldap_')).all()
219 217 fd = {}
220 218 for row in ret:
221 219 fd.update({row.app_settings_name:row.app_settings_value})
222 220
223 221 return fd
224 222
225 223
226 224 class RhodeCodeUi(Base, BaseModel):
227 225 __tablename__ = 'rhodecode_ui'
228 226 __table_args__ = (
229 227 UniqueConstraint('ui_key'),
230 228 {'extend_existing': True, 'mysql_engine':'InnoDB',
231 229 'mysql_charset': 'utf8'}
232 230 )
233 231
234 232 HOOK_REPO_SIZE = 'changegroup.repo_size'
235 233 HOOK_PUSH = 'pretxnchangegroup.push_logger'
236 234 HOOK_PULL = 'preoutgoing.pull_logger'
237 235
238 236 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
239 237 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
240 238 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
241 239 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
242 240 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
243 241
244 242 @classmethod
245 243 def get_by_key(cls, key):
246 244 return cls.query().filter(cls.ui_key == key)
247 245
248 246 @classmethod
249 247 def get_builtin_hooks(cls):
250 248 q = cls.query()
251 249 q = q.filter(cls.ui_key.in_([cls.HOOK_REPO_SIZE,
252 250 cls.HOOK_PUSH, cls.HOOK_PULL]))
253 251 return q.all()
254 252
255 253 @classmethod
256 254 def get_custom_hooks(cls):
257 255 q = cls.query()
258 256 q = q.filter(~cls.ui_key.in_([cls.HOOK_REPO_SIZE,
259 257 cls.HOOK_PUSH, cls.HOOK_PULL]))
260 258 q = q.filter(cls.ui_section == 'hooks')
261 259 return q.all()
262 260
263 261 @classmethod
264 262 def create_or_update_hook(cls, key, val):
265 263 new_ui = cls.get_by_key(key).scalar() or cls()
266 264 new_ui.ui_section = 'hooks'
267 265 new_ui.ui_active = True
268 266 new_ui.ui_key = key
269 267 new_ui.ui_value = val
270 268
271 269 Session.add(new_ui)
272 270
273 271
274 272 class User(Base, BaseModel):
275 273 __tablename__ = 'users'
276 274 __table_args__ = (
277 275 UniqueConstraint('username'), UniqueConstraint('email'),
278 276 {'extend_existing': True, 'mysql_engine':'InnoDB',
279 277 'mysql_charset': 'utf8'}
280 278 )
281 279 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
282 280 username = Column("username", String(255), nullable=True, unique=None, default=None)
283 281 password = Column("password", String(255), nullable=True, unique=None, default=None)
284 282 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
285 283 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
286 284 name = Column("name", String(255), nullable=True, unique=None, default=None)
287 285 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
288 286 _email = Column("email", String(255), nullable=True, unique=None, default=None)
289 287 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
290 288 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
291 289 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
292 290
293 291 user_log = relationship('UserLog', cascade='all')
294 292 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
295 293
296 294 repositories = relationship('Repository')
297 295 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
298 296 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
299 297 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
300 298
301 299 group_member = relationship('UserGroupMember', cascade='all')
302 300
303 301 notifications = relationship('UserNotification', cascade='all')
304 302 # notifications assigned to this user
305 303 user_created_notifications = relationship('Notification', cascade='all')
306 304 # comments created by this user
307 305 user_comments = relationship('ChangesetComment', cascade='all')
308 306
309 307 @hybrid_property
310 308 def email(self):
311 309 return self._email
312 310
313 311 @email.setter
314 312 def email(self, val):
315 313 self._email = val.lower() if val else None
316 314
317 315 @property
318 316 def full_name(self):
319 317 return '%s %s' % (self.name, self.lastname)
320 318
321 319 @property
322 320 def full_name_or_username(self):
323 321 return ('%s %s' % (self.name, self.lastname)
324 322 if (self.name and self.lastname) else self.username)
325 323
326 324 @property
327 325 def full_contact(self):
328 326 return '%s %s <%s>' % (self.name, self.lastname, self.email)
329 327
330 328 @property
331 329 def short_contact(self):
332 330 return '%s %s' % (self.name, self.lastname)
333 331
334 332 @property
335 333 def is_admin(self):
336 334 return self.admin
337 335
338 336 def __unicode__(self):
339 337 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
340 338 self.user_id, self.username)
341 339
342 340 @classmethod
343 341 def get_by_username(cls, username, case_insensitive=False, cache=False):
344 342 if case_insensitive:
345 343 q = cls.query().filter(cls.username.ilike(username))
346 344 else:
347 345 q = cls.query().filter(cls.username == username)
348 346
349 347 if cache:
350 348 q = q.options(FromCache(
351 349 "sql_cache_short",
352 350 "get_user_%s" % _hash_key(username)
353 351 )
354 352 )
355 353 return q.scalar()
356 354
357 355 @classmethod
358 356 def get_by_auth_token(cls, auth_token, cache=False):
359 357 q = cls.query().filter(cls.api_key == auth_token)
360 358
361 359 if cache:
362 360 q = q.options(FromCache("sql_cache_short",
363 361 "get_auth_token_%s" % auth_token))
364 362 return q.scalar()
365 363
366 364 @classmethod
367 365 def get_by_email(cls, email, case_insensitive=False, cache=False):
368 366 if case_insensitive:
369 367 q = cls.query().filter(cls.email.ilike(email))
370 368 else:
371 369 q = cls.query().filter(cls.email == email)
372 370
373 371 if cache:
374 372 q = q.options(FromCache("sql_cache_short",
375 373 "get_auth_token_%s" % email))
376 374 return q.scalar()
377 375
378 376 def update_lastlogin(self):
379 377 """Update user lastlogin"""
380 378 self.last_login = datetime.datetime.now()
381 379 Session.add(self)
382 380 log.debug('updated user %s lastlogin', self.username)
383 381
384 382 def __json__(self):
385 383 return dict(
386 384 user_id=self.user_id,
387 385 first_name=self.name,
388 386 last_name=self.lastname,
389 387 email=self.email,
390 388 full_name=self.full_name,
391 389 full_name_or_username=self.full_name_or_username,
392 390 short_contact=self.short_contact,
393 391 full_contact=self.full_contact
394 392 )
395 393
396 394
397 395 class UserLog(Base, BaseModel):
398 396 __tablename__ = 'user_logs'
399 397 __table_args__ = (
400 398 {'extend_existing': True, 'mysql_engine':'InnoDB',
401 399 'mysql_charset': 'utf8'},
402 400 )
403 401 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
404 402 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
405 403 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
406 404 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
407 405 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
408 406 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
409 407 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
410 408
411 409 @property
412 410 def action_as_day(self):
413 411 return datetime.date(*self.action_date.timetuple()[:3])
414 412
415 413 user = relationship('User')
416 414 repository = relationship('Repository', cascade='')
417 415
418 416
419 417 class UserGroup(Base, BaseModel):
420 418 __tablename__ = 'users_groups'
421 419 __table_args__ = (
422 420 {'extend_existing': True, 'mysql_engine':'InnoDB',
423 421 'mysql_charset': 'utf8'},
424 422 )
425 423
426 424 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
427 425 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
428 426 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
429 427
430 428 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
431 429 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
432 430 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
433 431
434 432 def __unicode__(self):
435 433 return u'<userGroup(%s)>' % (self.users_group_name)
436 434
437 435 @classmethod
438 436 def get_by_group_name(cls, group_name, cache=False,
439 437 case_insensitive=False):
440 438 if case_insensitive:
441 439 q = cls.query().filter(cls.users_group_name.ilike(group_name))
442 440 else:
443 441 q = cls.query().filter(cls.users_group_name == group_name)
444 442 if cache:
445 443 q = q.options(FromCache(
446 444 "sql_cache_short",
447 445 "get_user_%s" % _hash_key(group_name)
448 446 )
449 447 )
450 448 return q.scalar()
451 449
452 450 @classmethod
453 451 def get(cls, users_group_id, cache=False):
454 452 users_group = cls.query()
455 453 if cache:
456 454 users_group = users_group.options(FromCache("sql_cache_short",
457 455 "get_users_group_%s" % users_group_id))
458 456 return users_group.get(users_group_id)
459 457
460 458
461 459 class UserGroupMember(Base, BaseModel):
462 460 __tablename__ = 'users_groups_members'
463 461 __table_args__ = (
464 462 {'extend_existing': True, 'mysql_engine':'InnoDB',
465 463 'mysql_charset': 'utf8'},
466 464 )
467 465
468 466 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
469 467 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
470 468 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
471 469
472 470 user = relationship('User', lazy='joined')
473 471 users_group = relationship('UserGroup')
474 472
475 473 def __init__(self, gr_id='', u_id=''):
476 474 self.users_group_id = gr_id
477 475 self.user_id = u_id
478 476
479 477
480 478 class Repository(Base, BaseModel):
481 479 __tablename__ = 'repositories'
482 480 __table_args__ = (
483 481 UniqueConstraint('repo_name'),
484 482 {'extend_existing': True, 'mysql_engine':'InnoDB',
485 483 'mysql_charset': 'utf8'},
486 484 )
487 485
488 486 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
489 487 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
490 488 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
491 489 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default='hg')
492 490 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
493 491 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
494 492 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
495 493 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
496 494 description = Column("description", String(10000), nullable=True, unique=None, default=None)
497 495 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
498 496
499 497 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
500 498 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
501 499
502 500 user = relationship('User')
503 501 fork = relationship('Repository', remote_side=repo_id)
504 502 group = relationship('RepoGroup')
505 503 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
506 504 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
507 505 stats = relationship('Statistics', cascade='all', uselist=False)
508 506
509 507 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
510 508
511 509 logs = relationship('UserLog')
512 510
513 511 def __unicode__(self):
514 512 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
515 513 self.repo_name)
516 514
517 515 @classmethod
518 516 def url_sep(cls):
519 517 return '/'
520 518
521 519 @classmethod
522 520 def get_by_repo_name(cls, repo_name):
523 521 q = Session.query(cls).filter(cls.repo_name == repo_name)
524 522 q = q.options(joinedload(Repository.fork))\
525 523 .options(joinedload(Repository.user))\
526 524 .options(joinedload(Repository.group))
527 525 return q.scalar()
528 526
529 527 @classmethod
530 528 def get_repo_forks(cls, repo_id):
531 529 return cls.query().filter(Repository.fork_id == repo_id)
532 530
533 531 @classmethod
534 532 def base_path(cls):
535 533 """
536 534 Returns base path when all repos are stored
537 535
538 536 :param cls:
539 537 """
540 538 q = Session.query(RhodeCodeUi)\
541 539 .filter(RhodeCodeUi.ui_key == cls.url_sep())
542 540 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
543 541 return q.one().ui_value
544 542
545 543 @property
546 544 def just_name(self):
547 545 return self.repo_name.split(Repository.url_sep())[-1]
548 546
549 547 @property
550 548 def groups_with_parents(self):
551 549 groups = []
552 550 if self.group is None:
553 551 return groups
554 552
555 553 cur_gr = self.group
556 554 groups.insert(0, cur_gr)
557 555 while 1:
558 556 gr = getattr(cur_gr, 'parent_group', None)
559 557 cur_gr = cur_gr.parent_group
560 558 if gr is None:
561 559 break
562 560 groups.insert(0, gr)
563 561
564 562 return groups
565 563
566 564 @property
567 565 def groups_and_repo(self):
568 566 return self.groups_with_parents, self.just_name
569 567
570 568 @LazyProperty
571 569 def repo_path(self):
572 570 """
573 571 Returns base full path for that repository means where it actually
574 572 exists on a filesystem
575 573 """
576 574 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
577 575 Repository.url_sep())
578 576 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
579 577 return q.one().ui_value
580 578
581 579 @property
582 580 def repo_full_path(self):
583 581 p = [self.repo_path]
584 582 # we need to split the name by / since this is how we store the
585 583 # names in the database, but that eventually needs to be converted
586 584 # into a valid system path
587 585 p += self.repo_name.split(Repository.url_sep())
588 586 return os.path.join(*p)
589 587
590 588 def get_new_name(self, repo_name):
591 589 """
592 590 returns new full repository name based on assigned group and new new
593 591
594 592 :param group_name:
595 593 """
596 594 path_prefix = self.group.full_path_splitted if self.group else []
597 595 return Repository.url_sep().join(path_prefix + [repo_name])
598 596
599 597 @property
600 598 def _config(self):
601 599 """
602 600 Returns db based config object.
603 601 """
604 602 from rhodecode.lib.utils import make_db_config
605 603 return make_db_config(clear_session=False)
606 604
607 605 @classmethod
608 606 def is_valid(cls, repo_name):
609 607 """
610 608 returns True if given repo name is a valid filesystem repository
611 609
612 610 :param cls:
613 611 :param repo_name:
614 612 """
615 613 from rhodecode.lib.utils import is_valid_repo
616 614
617 615 return is_valid_repo(repo_name, cls.base_path())
618 616
619 617 #==========================================================================
620 618 # SCM PROPERTIES
621 619 #==========================================================================
622 620
623 621 def get_commit(self, rev):
624 622 return get_commit_safe(self.scm_instance, rev)
625 623
626 624 @property
627 625 def tip(self):
628 626 return self.get_commit('tip')
629 627
630 628 @property
631 629 def author(self):
632 630 return self.tip.author
633 631
634 632 @property
635 633 def last_change(self):
636 634 return self.scm_instance.last_change
637 635
638 636 def comments(self, revisions=None):
639 637 """
640 638 Returns comments for this repository grouped by revisions
641 639
642 640 :param revisions: filter query by revisions only
643 641 """
644 642 cmts = ChangesetComment.query()\
645 643 .filter(ChangesetComment.repo == self)
646 644 if revisions:
647 645 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
648 646 grouped = defaultdict(list)
649 647 for cmt in cmts.all():
650 648 grouped[cmt.revision].append(cmt)
651 649 return grouped
652 650
653 651 #==========================================================================
654 652 # SCM CACHE INSTANCE
655 653 #==========================================================================
656 654
657 655 @property
658 656 def invalidate(self):
659 657 return CacheInvalidation.invalidate(self.repo_name)
660 658
661 659 def set_invalidate(self):
662 660 """
663 661 set a cache for invalidation for this instance
664 662 """
665 663 CacheInvalidation.set_invalidate(self.repo_name)
666 664
667 665 @LazyProperty
668 666 def scm_instance(self):
669 667 return self.__get_instance()
670 668
671 669 @property
672 670 def scm_instance_cached(self):
673 671 return self.__get_instance()
674 672
675 673 def __get_instance(self):
676 674 repo_full_path = self.repo_full_path
677 675 try:
678 676 alias = get_scm(repo_full_path)[0]
679 677 log.debug('Creating instance of %s repository', alias)
680 678 backend = get_backend(alias)
681 679 except VCSError:
682 680 log.error(traceback.format_exc())
683 681 log.error('Perhaps this repository is in db and not in '
684 682 'filesystem run rescan repositories with '
685 683 '"destroy old data " option from admin panel')
686 684 return
687 685
688 686 if alias == 'hg':
689 687
690 688 repo = backend(safe_str(repo_full_path), create=False,
691 689 config=self._config)
692 690 else:
693 691 repo = backend(repo_full_path, create=False)
694 692
695 693 return repo
696 694
697 695
698 696 class RepoGroup(Base, BaseModel):
699 697 __tablename__ = 'groups'
700 698 __table_args__ = (
701 699 UniqueConstraint('group_name', 'group_parent_id'),
702 700 {'extend_existing': True, 'mysql_engine':'InnoDB',
703 701 'mysql_charset': 'utf8'},
704 702 )
705 703 __mapper_args__ = {'order_by': 'group_name'}
706 704
707 705 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
708 706 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
709 707 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
710 708 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
711 709
712 710 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
713 711 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
714 712
715 713 parent_group = relationship('RepoGroup', remote_side=group_id)
716 714
717 715 def __init__(self, group_name='', parent_group=None):
718 716 self.group_name = group_name
719 717 self.parent_group = parent_group
720 718
721 719 def __unicode__(self):
722 720 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
723 721 self.group_name)
724 722
725 723 @classmethod
726 724 def url_sep(cls):
727 725 return '/'
728 726
729 727 @classmethod
730 728 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
731 729 if case_insensitive:
732 730 gr = cls.query()\
733 731 .filter(cls.group_name.ilike(group_name))
734 732 else:
735 733 gr = cls.query()\
736 734 .filter(cls.group_name == group_name)
737 735 if cache:
738 736 gr = gr.options(FromCache(
739 737 "sql_cache_short",
740 738 "get_group_%s" % _hash_key(group_name)
741 739 )
742 740 )
743 741 return gr.scalar()
744 742
745 743 @property
746 744 def parents(self):
747 745 parents_recursion_limit = 5
748 746 groups = []
749 747 if self.parent_group is None:
750 748 return groups
751 749 cur_gr = self.parent_group
752 750 groups.insert(0, cur_gr)
753 751 cnt = 0
754 752 while 1:
755 753 cnt += 1
756 754 gr = getattr(cur_gr, 'parent_group', None)
757 755 cur_gr = cur_gr.parent_group
758 756 if gr is None:
759 757 break
760 758 if cnt == parents_recursion_limit:
761 759 # this will prevent accidental infinit loops
762 760 log.error('group nested more than %s', parents_recursion_limit)
763 761 break
764 762
765 763 groups.insert(0, gr)
766 764 return groups
767 765
768 766 @property
769 767 def children(self):
770 768 return RepoGroup.query().filter(RepoGroup.parent_group == self)
771 769
772 770 @property
773 771 def name(self):
774 772 return self.group_name.split(RepoGroup.url_sep())[-1]
775 773
776 774 @property
777 775 def full_path(self):
778 776 return self.group_name
779 777
780 778 @property
781 779 def full_path_splitted(self):
782 780 return self.group_name.split(RepoGroup.url_sep())
783 781
784 782 @property
785 783 def repositories(self):
786 784 return Repository.query()\
787 785 .filter(Repository.group == self)\
788 786 .order_by(Repository.repo_name)
789 787
790 788 @property
791 789 def repositories_recursive_count(self):
792 790 cnt = self.repositories.count()
793 791
794 792 def children_count(group):
795 793 cnt = 0
796 794 for child in group.children:
797 795 cnt += child.repositories.count()
798 796 cnt += children_count(child)
799 797 return cnt
800 798
801 799 return cnt + children_count(self)
802 800
803 801 def get_new_name(self, group_name):
804 802 """
805 803 returns new full group name based on parent and new name
806 804
807 805 :param group_name:
808 806 """
809 807 path_prefix = (self.parent_group.full_path_splitted if
810 808 self.parent_group else [])
811 809 return RepoGroup.url_sep().join(path_prefix + [group_name])
812 810
813 811
814 812 class Permission(Base, BaseModel):
815 813 __tablename__ = 'permissions'
816 814 __table_args__ = (
817 815 {'extend_existing': True, 'mysql_engine':'InnoDB',
818 816 'mysql_charset': 'utf8'},
819 817 )
820 818 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
821 819 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
822 820 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
823 821
824 822 def __unicode__(self):
825 823 return u"<%s('%s:%s')>" % (
826 824 self.__class__.__name__, self.permission_id, self.permission_name
827 825 )
828 826
829 827 @classmethod
830 828 def get_by_key(cls, key):
831 829 return cls.query().filter(cls.permission_name == key).scalar()
832 830
833 831 @classmethod
834 832 def get_default_repo_perms(cls, default_user_id):
835 833 q = Session.query(UserRepoToPerm, Repository, cls)\
836 834 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
837 835 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
838 836 .filter(UserRepoToPerm.user_id == default_user_id)
839 837
840 838 return q.all()
841 839
842 840 @classmethod
843 841 def get_default_group_perms(cls, default_user_id):
844 842 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
845 843 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
846 844 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
847 845 .filter(UserRepoGroupToPerm.user_id == default_user_id)
848 846
849 847 return q.all()
850 848
851 849
852 850 class UserRepoToPerm(Base, BaseModel):
853 851 __tablename__ = 'repo_to_perm'
854 852 __table_args__ = (
855 853 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
856 854 {'extend_existing': True, 'mysql_engine':'InnoDB',
857 855 'mysql_charset': 'utf8'}
858 856 )
859 857 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
860 858 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
861 859 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
862 860 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
863 861
864 862 user = relationship('User')
865 863 repository = relationship('Repository')
866 864 permission = relationship('Permission')
867 865
868 866 @classmethod
869 867 def create(cls, user, repository, permission):
870 868 n = cls()
871 869 n.user = user
872 870 n.repository = repository
873 871 n.permission = permission
874 872 Session.add(n)
875 873 return n
876 874
877 875 def __unicode__(self):
878 876 return u'<user:%s => %s >' % (self.user, self.repository)
879 877
880 878
881 879 class UserToPerm(Base, BaseModel):
882 880 __tablename__ = 'user_to_perm'
883 881 __table_args__ = (
884 882 UniqueConstraint('user_id', 'permission_id'),
885 883 {'extend_existing': True, 'mysql_engine':'InnoDB',
886 884 'mysql_charset': 'utf8'}
887 885 )
888 886 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
889 887 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
890 888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
891 889
892 890 user = relationship('User')
893 891 permission = relationship('Permission', lazy='joined')
894 892
895 893
896 894 class UserGroupRepoToPerm(Base, BaseModel):
897 895 __tablename__ = 'users_group_repo_to_perm'
898 896 __table_args__ = (
899 897 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
900 898 {'extend_existing': True, 'mysql_engine':'InnoDB',
901 899 'mysql_charset': 'utf8'}
902 900 )
903 901 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
904 902 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
905 903 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
906 904 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
907 905
908 906 users_group = relationship('UserGroup')
909 907 permission = relationship('Permission')
910 908 repository = relationship('Repository')
911 909
912 910 @classmethod
913 911 def create(cls, users_group, repository, permission):
914 912 n = cls()
915 913 n.users_group = users_group
916 914 n.repository = repository
917 915 n.permission = permission
918 916 Session.add(n)
919 917 return n
920 918
921 919 def __unicode__(self):
922 920 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
923 921
924 922
925 923 class UserGroupToPerm(Base, BaseModel):
926 924 __tablename__ = 'users_group_to_perm'
927 925 __table_args__ = (
928 926 UniqueConstraint('users_group_id', 'permission_id',),
929 927 {'extend_existing': True, 'mysql_engine':'InnoDB',
930 928 'mysql_charset': 'utf8'}
931 929 )
932 930 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
933 931 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
934 932 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
935 933
936 934 users_group = relationship('UserGroup')
937 935 permission = relationship('Permission')
938 936
939 937
940 938 class UserRepoGroupToPerm(Base, BaseModel):
941 939 __tablename__ = 'user_repo_group_to_perm'
942 940 __table_args__ = (
943 941 UniqueConstraint('user_id', 'group_id', 'permission_id'),
944 942 {'extend_existing': True, 'mysql_engine':'InnoDB',
945 943 'mysql_charset': 'utf8'}
946 944 )
947 945
948 946 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
949 947 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
950 948 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
951 949 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
952 950
953 951 user = relationship('User')
954 952 group = relationship('RepoGroup')
955 953 permission = relationship('Permission')
956 954
957 955
958 956 class UserGroupRepoGroupToPerm(Base, BaseModel):
959 957 __tablename__ = 'users_group_repo_group_to_perm'
960 958 __table_args__ = (
961 959 UniqueConstraint('users_group_id', 'group_id'),
962 960 {'extend_existing': True, 'mysql_engine':'InnoDB',
963 961 'mysql_charset': 'utf8'}
964 962 )
965 963
966 964 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)
967 965 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
968 966 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
969 967 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
970 968
971 969 users_group = relationship('UserGroup')
972 970 permission = relationship('Permission')
973 971 group = relationship('RepoGroup')
974 972
975 973
976 974 class Statistics(Base, BaseModel):
977 975 __tablename__ = 'statistics'
978 976 __table_args__ = (
979 977 UniqueConstraint('repository_id'),
980 978 {'extend_existing': True, 'mysql_engine':'InnoDB',
981 979 'mysql_charset': 'utf8'}
982 980 )
983 981 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
984 982 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
985 983 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
986 984 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
987 985 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
988 986 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
989 987
990 988 repository = relationship('Repository', single_parent=True)
991 989
992 990
993 991 class UserFollowing(Base, BaseModel):
994 992 __tablename__ = 'user_followings'
995 993 __table_args__ = (
996 994 UniqueConstraint('user_id', 'follows_repository_id'),
997 995 UniqueConstraint('user_id', 'follows_user_id'),
998 996 {'extend_existing': True, 'mysql_engine':'InnoDB',
999 997 'mysql_charset': 'utf8'}
1000 998 )
1001 999
1002 1000 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1003 1001 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1004 1002 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1005 1003 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1006 1004 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1007 1005
1008 1006 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1009 1007
1010 1008 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1011 1009 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1012 1010
1013 1011 @classmethod
1014 1012 def get_repo_followers(cls, repo_id):
1015 1013 return cls.query().filter(cls.follows_repo_id == repo_id)
1016 1014
1017 1015
1018 1016 class CacheInvalidation(Base, BaseModel):
1019 1017 __tablename__ = 'cache_invalidation'
1020 1018 __table_args__ = (
1021 1019 UniqueConstraint('cache_key'),
1022 1020 {'extend_existing': True, 'mysql_engine':'InnoDB',
1023 1021 'mysql_charset': 'utf8'},
1024 1022 )
1025 1023 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1026 1024 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
1027 1025 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
1028 1026 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1029 1027
1030 1028 def __init__(self, cache_key, cache_args=''):
1031 1029 self.cache_key = cache_key
1032 1030 self.cache_args = cache_args
1033 1031 self.cache_active = False
1034 1032
1035 1033 def __unicode__(self):
1036 1034 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1037 1035 self.cache_id, self.cache_key)
1038 1036
1039 1037 @classmethod
1040 1038 def _get_key(cls, key):
1041 1039 """
1042 1040 Wrapper for generating a key, together with a prefix
1043 1041
1044 1042 :param key:
1045 1043 """
1046 1044 import rhodecode
1047 1045 prefix = ''
1048 1046 iid = rhodecode.CONFIG.get('instance_id')
1049 1047 if iid:
1050 1048 prefix = iid
1051 1049 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1052 1050
1053 1051 @classmethod
1054 1052 def get_by_key(cls, key):
1055 1053 return cls.query().filter(cls.cache_key == key).scalar()
1056 1054
1057 1055 @classmethod
1058 1056 def _get_or_create_key(cls, key, prefix, org_key):
1059 1057 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1060 1058 if not inv_obj:
1061 1059 try:
1062 1060 inv_obj = CacheInvalidation(key, org_key)
1063 1061 Session.add(inv_obj)
1064 1062 Session.commit()
1065 1063 except Exception:
1066 1064 log.error(traceback.format_exc())
1067 1065 Session.rollback()
1068 1066 return inv_obj
1069 1067
1070 1068 @classmethod
1071 1069 def invalidate(cls, key):
1072 1070 """
1073 1071 Returns Invalidation object if this given key should be invalidated
1074 1072 None otherwise. `cache_active = False` means that this cache
1075 1073 state is not valid and needs to be invalidated
1076 1074
1077 1075 :param key:
1078 1076 """
1079 1077
1080 1078 key, _prefix, _org_key = cls._get_key(key)
1081 1079 inv = cls._get_or_create_key(key, _prefix, _org_key)
1082 1080
1083 1081 if inv and inv.cache_active is False:
1084 1082 return inv
1085 1083
1086 1084 @classmethod
1087 1085 def set_invalidate(cls, key):
1088 1086 """
1089 1087 Mark this Cache key for invalidation
1090 1088
1091 1089 :param key:
1092 1090 """
1093 1091
1094 1092 key, _prefix, _org_key = cls._get_key(key)
1095 1093 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1096 1094 log.debug('marking %s key[s] %s for invalidation', len(inv_objs), _org_key)
1097 1095 try:
1098 1096 for inv_obj in inv_objs:
1099 1097 if inv_obj:
1100 1098 inv_obj.cache_active = False
1101 1099
1102 1100 Session.add(inv_obj)
1103 1101 Session.commit()
1104 1102 except Exception:
1105 1103 log.error(traceback.format_exc())
1106 1104 Session.rollback()
1107 1105
1108 1106 @classmethod
1109 1107 def set_valid(cls, key):
1110 1108 """
1111 1109 Mark this cache key as active and currently cached
1112 1110
1113 1111 :param key:
1114 1112 """
1115 1113 inv_obj = cls.get_by_key(key)
1116 1114 inv_obj.cache_active = True
1117 1115 Session.add(inv_obj)
1118 1116 Session.commit()
1119 1117
1120 1118
1121 1119 class ChangesetComment(Base, BaseModel):
1122 1120 __tablename__ = 'changeset_comments'
1123 1121 __table_args__ = (
1124 1122 {'extend_existing': True, 'mysql_engine':'InnoDB',
1125 1123 'mysql_charset': 'utf8'},
1126 1124 )
1127 1125 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1128 1126 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1129 1127 revision = Column('revision', String(40), nullable=False)
1130 1128 line_no = Column('line_no', Unicode(10), nullable=True)
1131 1129 f_path = Column('f_path', Unicode(1000), nullable=True)
1132 1130 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1133 1131 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
1134 1132 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1135 1133
1136 1134 author = relationship('User', lazy='joined')
1137 1135 repo = relationship('Repository')
1138 1136
1139 1137 @classmethod
1140 1138 def get_users(cls, revision):
1141 1139 """
1142 1140 Returns user associated with this changesetComment. ie those
1143 1141 who actually commented
1144 1142
1145 1143 :param cls:
1146 1144 :param revision:
1147 1145 """
1148 1146 return Session.query(User)\
1149 1147 .filter(cls.revision == revision)\
1150 1148 .join(ChangesetComment.author).all()
1151 1149
1152 1150
1153 1151 class Notification(Base, BaseModel):
1154 1152 __tablename__ = 'notifications'
1155 1153 __table_args__ = (
1156 1154 {'extend_existing': True, 'mysql_engine':'InnoDB',
1157 1155 'mysql_charset': 'utf8'},
1158 1156 )
1159 1157
1160 1158 TYPE_CHANGESET_COMMENT = u'cs_comment'
1161 1159 TYPE_MESSAGE = u'message'
1162 1160 TYPE_MENTION = u'mention'
1163 1161 TYPE_REGISTRATION = u'registration'
1164 1162
1165 1163 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1166 1164 subject = Column('subject', Unicode(512), nullable=True)
1167 1165 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1168 1166 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1169 1167 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1170 1168 type_ = Column('type', Unicode(256))
1171 1169
1172 1170 created_by_user = relationship('User')
1173 1171 notifications_to_users = relationship('UserNotification', lazy='joined',
1174 1172 cascade="all, delete, delete-orphan")
1175 1173
1176 1174 @property
1177 1175 def recipients(self):
1178 1176 return [x.user for x in UserNotification.query()\
1179 1177 .filter(UserNotification.notification == self).all()]
1180 1178
1181 1179 @classmethod
1182 1180 def create(cls, created_by, subject, body, recipients, type_=None):
1183 1181 if type_ is None:
1184 1182 type_ = Notification.TYPE_MESSAGE
1185 1183
1186 1184 notification = cls()
1187 1185 notification.created_by_user = created_by
1188 1186 notification.subject = subject
1189 1187 notification.body = body
1190 1188 notification.type_ = type_
1191 1189 notification.created_on = datetime.datetime.now()
1192 1190
1193 1191 for u in recipients:
1194 1192 assoc = UserNotification()
1195 1193 assoc.notification = notification
1196 1194 u.notifications.append(assoc)
1197 1195 Session.add(notification)
1198 1196 return notification
1199 1197
1200 1198 @property
1201 1199 def description(self):
1202 1200 from rhodecode.model.notification import NotificationModel
1203 1201 return NotificationModel().make_description(self)
1204 1202
1205 1203
1206 1204 class UserNotification(Base, BaseModel):
1207 1205 __tablename__ = 'user_to_notification'
1208 1206 __table_args__ = (
1209 1207 UniqueConstraint('user_id', 'notification_id'),
1210 1208 {'extend_existing': True, 'mysql_engine':'InnoDB',
1211 1209 'mysql_charset': 'utf8'}
1212 1210 )
1213 1211 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1214 1212 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1215 1213 read = Column('read', Boolean, default=False)
1216 1214 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1217 1215
1218 1216 user = relationship('User', lazy="joined")
1219 1217 notification = relationship('Notification', lazy="joined",
1220 1218 order_by=lambda: Notification.created_on.desc(),)
1221 1219
1222 1220 def mark_as_read(self):
1223 1221 self.read = True
1224 1222 Session.add(self)
1225 1223
1226 1224
1227 1225 class DbMigrateVersion(Base, BaseModel):
1228 1226 __tablename__ = 'db_migrate_version'
1229 1227 __table_args__ = (
1230 1228 {'extend_existing': True, 'mysql_engine':'InnoDB',
1231 1229 'mysql_charset': 'utf8'},
1232 1230 )
1233 1231 repository_id = Column('repository_id', String(250), primary_key=True)
1234 1232 repository_path = Column('repository_path', Text)
1235 1233 version = Column('version', Integer)
1236 1234
1237 1235 ## this is migration from 1_4_0, but now it's here to overcome a problem of
1238 1236 ## attaching a FK to this from 1_3_0 !
1239 1237
1240 1238
1241 1239 class PullRequest(Base, BaseModel):
1242 1240 __tablename__ = 'pull_requests'
1243 1241 __table_args__ = (
1244 1242 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1245 1243 'mysql_charset': 'utf8'},
1246 1244 )
1247 1245
1248 1246 STATUS_NEW = u'new'
1249 1247 STATUS_OPEN = u'open'
1250 1248 STATUS_CLOSED = u'closed'
1251 1249
1252 1250 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1253 1251 title = Column('title', Unicode(256), nullable=True)
1254 1252 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1255 1253 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1256 1254 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1257 1255 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1258 1256 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1259 1257 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql')) # 500 revisions max
1260 1258 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1261 1259 org_ref = Column('org_ref', Unicode(256), nullable=False)
1262 1260 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1263 1261 other_ref = Column('other_ref', Unicode(256), nullable=False)
@@ -1,971 +1,969 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20
22 21 import os
23 22 import time
24 23 import logging
25 24 import datetime
26 25 import traceback
27 26 import hashlib
28 27 import collections
29 28
30 29 from sqlalchemy import *
31 30 from sqlalchemy.ext.hybrid import hybrid_property
32 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
33 32 from sqlalchemy.exc import DatabaseError
34 33 from beaker.cache import cache_region, region_invalidate
35 34 from webob.exc import HTTPNotFound
36 35
37 36 from rhodecode.translation import _
38 37 from rhodecode.lib.vcs import get_backend
39 38 from rhodecode.lib.vcs.utils.helpers import get_scm
40 39 from rhodecode.lib.vcs.exceptions import VCSError
41 40 from zope.cachedescriptors.property import Lazy as LazyProperty
42 41
43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
44 safe_unicode, remove_suffix
42 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, remove_suffix
45 43 from rhodecode.lib.ext_json import json
46 44 from rhodecode.lib.caching_query import FromCache
47 45
48 46 from rhodecode.model.meta import Base, Session
49 47
50 48 URL_SEP = '/'
51 49 log = logging.getLogger(__name__)
52 50
53 51 #==============================================================================
54 52 # BASE CLASSES
55 53 #==============================================================================
56 54
57 55 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58 56
59 57
60 58 class BaseModel(object):
61 59 """
62 60 Base Model for all classes
63 61 """
64 62
65 63 @classmethod
66 64 def _get_keys(cls):
67 65 """return column names for this model """
68 66 return class_mapper(cls).c.keys()
69 67
70 68 def get_dict(self):
71 69 """
72 70 return dict with keys and values corresponding
73 71 to this model data """
74 72
75 73 d = {}
76 74 for k in self._get_keys():
77 75 d[k] = getattr(self, k)
78 76
79 77 # also use __json__() if present to get additional fields
80 78 _json_attr = getattr(self, '__json__', None)
81 79 if _json_attr:
82 80 # update with attributes from __json__
83 81 if callable(_json_attr):
84 82 _json_attr = _json_attr()
85 83 for k, val in _json_attr.items():
86 84 d[k] = val
87 85 return d
88 86
89 87 def get_appstruct(self):
90 88 """return list with keys and values tupples corresponding
91 89 to this model data """
92 90
93 91 l = []
94 92 for k in self._get_keys():
95 93 l.append((k, getattr(self, k),))
96 94 return l
97 95
98 96 def populate_obj(self, populate_dict):
99 97 """populate model with data from given populate_dict"""
100 98
101 99 for k in self._get_keys():
102 100 if k in populate_dict:
103 101 setattr(self, k, populate_dict[k])
104 102
105 103 @classmethod
106 104 def query(cls):
107 105 return Session().query(cls)
108 106
109 107 @classmethod
110 108 def get(cls, id_):
111 109 if id_:
112 110 return cls.query().get(id_)
113 111
114 112 @classmethod
115 113 def get_or_404(cls, id_):
116 114 try:
117 115 id_ = int(id_)
118 116 except (TypeError, ValueError):
119 117 raise HTTPNotFound
120 118
121 119 res = cls.query().get(id_)
122 120 if not res:
123 121 raise HTTPNotFound
124 122 return res
125 123
126 124 @classmethod
127 125 def getAll(cls):
128 126 return cls.query().all()
129 127
130 128 @classmethod
131 129 def delete(cls, id_):
132 130 obj = cls.query().get(id_)
133 131 Session().delete(obj)
134 132
135 133 def __repr__(self):
136 134 if hasattr(self, '__unicode__'):
137 135 # python repr needs to return str
138 136 return safe_str(self.__unicode__())
139 137 return '<DB:%s>' % (self.__class__.__name__)
140 138
141 139
142 140 class RhodeCodeSetting(Base, BaseModel):
143 141 __tablename__ = 'rhodecode_settings'
144 142 __table_args__ = (
145 143 UniqueConstraint('app_settings_name'),
146 144 {'extend_existing': True, 'mysql_engine': 'InnoDB',
147 145 'mysql_charset': 'utf8'}
148 146 )
149 147 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 148 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
151 149 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
152 150
153 151 def __init__(self, k='', v=''):
154 152 self.app_settings_name = k
155 153 self.app_settings_value = v
156 154
157 155 @validates('_app_settings_value')
158 156 def validate_settings_value(self, key, val):
159 157 assert type(val) == str
160 158 return val
161 159
162 160 @hybrid_property
163 161 def app_settings_value(self):
164 162 v = self._app_settings_value
165 163 if self.app_settings_name == 'ldap_active':
166 164 v = str2bool(v)
167 165 return v
168 166
169 167 @app_settings_value.setter
170 168 def app_settings_value(self, val):
171 169 """
172 170 Setter that will always make sure we use unicode in app_settings_value
173 171
174 172 :param val:
175 173 """
176 self._app_settings_value = safe_unicode(val)
174 self._app_settings_value = safe_str(val)
177 175
178 176 def __unicode__(self):
179 177 return u"<%s('%s:%s')>" % (
180 178 self.__class__.__name__,
181 179 self.app_settings_name, self.app_settings_value
182 180 )
183 181
184 182
185 183 class RhodeCodeUi(Base, BaseModel):
186 184 __tablename__ = 'rhodecode_ui'
187 185 __table_args__ = (
188 186 UniqueConstraint('ui_key'),
189 187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
190 188 'mysql_charset': 'utf8'}
191 189 )
192 190
193 191 HOOK_REPO_SIZE = 'changegroup.repo_size'
194 192 HOOK_PUSH = 'changegroup.push_logger'
195 193 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
196 194 HOOK_PULL = 'outgoing.pull_logger'
197 195 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
198 196
199 197 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
200 198 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
201 199 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
202 200 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
203 201 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
204 202
205 203
206 204
207 205 class User(Base, BaseModel):
208 206 __tablename__ = 'users'
209 207 __table_args__ = (
210 208 UniqueConstraint('username'), UniqueConstraint('email'),
211 209 Index('u_username_idx', 'username'),
212 210 Index('u_email_idx', 'email'),
213 211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
214 212 'mysql_charset': 'utf8'}
215 213 )
216 214 DEFAULT_USER = 'default'
217 215 DEFAULT_PERMISSIONS = [
218 216 'hg.register.manual_activate', 'hg.create.repository',
219 217 'hg.fork.repository', 'repository.read', 'group.read'
220 218 ]
221 219 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
222 220 username = Column("username", String(255), nullable=True, unique=None, default=None)
223 221 password = Column("password", String(255), nullable=True, unique=None, default=None)
224 222 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
225 223 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
226 224 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
227 225 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
228 226 _email = Column("email", String(255), nullable=True, unique=None, default=None)
229 227 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
230 228 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
231 229 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
232 230 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
233 231
234 232 user_log = relationship('UserLog', cascade='all')
235 233 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
236 234
237 235 repositories = relationship('Repository')
238 236 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
239 237 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
240 238 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
241 239
242 240 group_member = relationship('UserGroupMember', cascade='all')
243 241
244 242 notifications = relationship('UserNotification', cascade='all')
245 243 # notifications assigned to this user
246 244 user_created_notifications = relationship('Notification', cascade='all')
247 245 # comments created by this user
248 246 user_comments = relationship('ChangesetComment', cascade='all')
249 247 user_emails = relationship('UserEmailMap', cascade='all')
250 248
251 249 @hybrid_property
252 250 def email(self):
253 251 return self._email
254 252
255 253 @email.setter
256 254 def email(self, val):
257 255 self._email = val.lower() if val else None
258 256
259 257 @property
260 258 def firstname(self):
261 259 # alias for future
262 260 return self.name
263 261
264 262 @property
265 263 def username_and_name(self):
266 264 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
267 265
268 266 @property
269 267 def full_name(self):
270 268 return '%s %s' % (self.firstname, self.lastname)
271 269
272 270 @property
273 271 def full_contact(self):
274 272 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
275 273
276 274 @property
277 275 def short_contact(self):
278 276 return '%s %s' % (self.firstname, self.lastname)
279 277
280 278 @property
281 279 def is_admin(self):
282 280 return self.admin
283 281
284 282 @classmethod
285 283 def get_by_username(cls, username, case_insensitive=False, cache=False):
286 284 if case_insensitive:
287 285 q = cls.query().filter(cls.username.ilike(username))
288 286 else:
289 287 q = cls.query().filter(cls.username == username)
290 288
291 289 if cache:
292 290 q = q.options(FromCache(
293 291 "sql_cache_short",
294 292 "get_user_%s" % _hash_key(username)
295 293 )
296 294 )
297 295 return q.scalar()
298 296
299 297 @classmethod
300 298 def get_by_auth_token(cls, auth_token, cache=False):
301 299 q = cls.query().filter(cls.api_key == auth_token)
302 300
303 301 if cache:
304 302 q = q.options(FromCache("sql_cache_short",
305 303 "get_auth_token_%s" % auth_token))
306 304 return q.scalar()
307 305
308 306 @classmethod
309 307 def get_by_email(cls, email, case_insensitive=False, cache=False):
310 308 if case_insensitive:
311 309 q = cls.query().filter(cls.email.ilike(email))
312 310 else:
313 311 q = cls.query().filter(cls.email == email)
314 312
315 313 if cache:
316 314 q = q.options(FromCache("sql_cache_short",
317 315 "get_email_key_%s" % email))
318 316
319 317 ret = q.scalar()
320 318 if ret is None:
321 319 q = UserEmailMap.query()
322 320 # try fetching in alternate email map
323 321 if case_insensitive:
324 322 q = q.filter(UserEmailMap.email.ilike(email))
325 323 else:
326 324 q = q.filter(UserEmailMap.email == email)
327 325 q = q.options(joinedload(UserEmailMap.user))
328 326 if cache:
329 327 q = q.options(FromCache("sql_cache_short",
330 328 "get_email_map_key_%s" % email))
331 329 ret = getattr(q.scalar(), 'user', None)
332 330
333 331 return ret
334 332
335 333
336 334 class UserEmailMap(Base, BaseModel):
337 335 __tablename__ = 'user_email_map'
338 336 __table_args__ = (
339 337 Index('uem_email_idx', 'email'),
340 338 UniqueConstraint('email'),
341 339 {'extend_existing': True, 'mysql_engine': 'InnoDB',
342 340 'mysql_charset': 'utf8'}
343 341 )
344 342 __mapper_args__ = {}
345 343
346 344 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
347 345 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
348 346 _email = Column("email", String(255), nullable=True, unique=False, default=None)
349 347 user = relationship('User', lazy='joined')
350 348
351 349 @validates('_email')
352 350 def validate_email(self, key, email):
353 351 # check if this email is not main one
354 352 main_email = Session().query(User).filter(User.email == email).scalar()
355 353 if main_email is not None:
356 354 raise AttributeError('email %s is present is user table' % email)
357 355 return email
358 356
359 357 @hybrid_property
360 358 def email(self):
361 359 return self._email
362 360
363 361 @email.setter
364 362 def email(self, val):
365 363 self._email = val.lower() if val else None
366 364
367 365
368 366 class UserLog(Base, BaseModel):
369 367 __tablename__ = 'user_logs'
370 368 __table_args__ = (
371 369 {'extend_existing': True, 'mysql_engine': 'InnoDB',
372 370 'mysql_charset': 'utf8'},
373 371 )
374 372 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
375 373 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
376 374 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
377 375 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
378 376 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
379 377 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
380 378 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
381 379
382 380
383 381 user = relationship('User')
384 382 repository = relationship('Repository', cascade='')
385 383
386 384
387 385 class UserGroup(Base, BaseModel):
388 386 __tablename__ = 'users_groups'
389 387 __table_args__ = (
390 388 {'extend_existing': True, 'mysql_engine': 'InnoDB',
391 389 'mysql_charset': 'utf8'},
392 390 )
393 391
394 392 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
395 393 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
396 394 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
397 395 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
398 396
399 397 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
400 398 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
401 399 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
402 400
403 401 def __unicode__(self):
404 402 return u'<userGroup(%s)>' % (self.users_group_name)
405 403
406 404 @classmethod
407 405 def get_by_group_name(cls, group_name, cache=False,
408 406 case_insensitive=False):
409 407 if case_insensitive:
410 408 q = cls.query().filter(cls.users_group_name.ilike(group_name))
411 409 else:
412 410 q = cls.query().filter(cls.users_group_name == group_name)
413 411 if cache:
414 412 q = q.options(FromCache(
415 413 "sql_cache_short",
416 414 "get_user_%s" % _hash_key(group_name)
417 415 )
418 416 )
419 417 return q.scalar()
420 418
421 419 @classmethod
422 420 def get(cls, users_group_id, cache=False):
423 421 user_group = cls.query()
424 422 if cache:
425 423 user_group = user_group.options(FromCache("sql_cache_short",
426 424 "get_users_group_%s" % users_group_id))
427 425 return user_group.get(users_group_id)
428 426
429 427
430 428 class UserGroupMember(Base, BaseModel):
431 429 __tablename__ = 'users_groups_members'
432 430 __table_args__ = (
433 431 {'extend_existing': True, 'mysql_engine': 'InnoDB',
434 432 'mysql_charset': 'utf8'},
435 433 )
436 434
437 435 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
438 436 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
439 437 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
440 438
441 439 user = relationship('User', lazy='joined')
442 440 users_group = relationship('UserGroup')
443 441
444 442 def __init__(self, gr_id='', u_id=''):
445 443 self.users_group_id = gr_id
446 444 self.user_id = u_id
447 445
448 446
449 447 class Repository(Base, BaseModel):
450 448 __tablename__ = 'repositories'
451 449 __table_args__ = (
452 450 UniqueConstraint('repo_name'),
453 451 Index('r_repo_name_idx', 'repo_name'),
454 452 {'extend_existing': True, 'mysql_engine': 'InnoDB',
455 453 'mysql_charset': 'utf8'},
456 454 )
457 455
458 456 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
459 457 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
460 458 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
461 459 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
462 460 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
463 461 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
464 462 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
465 463 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
466 464 description = Column("description", String(10000), nullable=True, unique=None, default=None)
467 465 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
468 466 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
469 467 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
470 468 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
471 469 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
472 470
473 471 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
474 472 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
475 473
476 474 user = relationship('User')
477 475 fork = relationship('Repository', remote_side=repo_id)
478 476 group = relationship('RepoGroup')
479 477 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
480 478 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
481 479 stats = relationship('Statistics', cascade='all', uselist=False)
482 480
483 481 followers = relationship('UserFollowing',
484 482 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
485 483 cascade='all')
486 484
487 485 logs = relationship('UserLog')
488 486 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
489 487
490 488 pull_requests_org = relationship('PullRequest',
491 489 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
492 490 cascade="all, delete, delete-orphan")
493 491
494 492 pull_requests_other = relationship('PullRequest',
495 493 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
496 494 cascade="all, delete, delete-orphan")
497 495
498 496 def __unicode__(self):
499 497 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
500 498 self.repo_name)
501 499
502 500
503 501 @classmethod
504 502 def get_by_repo_name(cls, repo_name):
505 503 q = Session().query(cls).filter(cls.repo_name == repo_name)
506 504 q = q.options(joinedload(Repository.fork))\
507 505 .options(joinedload(Repository.user))\
508 506 .options(joinedload(Repository.group))
509 507 return q.scalar()
510 508
511 509
512 510 class RepoGroup(Base, BaseModel):
513 511 __tablename__ = 'groups'
514 512 __table_args__ = (
515 513 UniqueConstraint('group_name', 'group_parent_id'),
516 514 {'extend_existing': True, 'mysql_engine': 'InnoDB',
517 515 'mysql_charset': 'utf8'},
518 516 )
519 517 __mapper_args__ = {'order_by': 'group_name'}
520 518
521 519 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
522 520 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
523 521 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
524 522 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
525 523 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
526 524
527 525 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
528 526 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
529 527 parent_group = relationship('RepoGroup', remote_side=group_id)
530 528
531 529 def __init__(self, group_name='', parent_group=None):
532 530 self.group_name = group_name
533 531 self.parent_group = parent_group
534 532
535 533 def __unicode__(self):
536 534 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
537 535 self.group_name)
538 536
539 537 @classmethod
540 538 def url_sep(cls):
541 539 return URL_SEP
542 540
543 541 @classmethod
544 542 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
545 543 if case_insensitive:
546 544 gr = cls.query()\
547 545 .filter(cls.group_name.ilike(group_name))
548 546 else:
549 547 gr = cls.query()\
550 548 .filter(cls.group_name == group_name)
551 549 if cache:
552 550 gr = gr.options(FromCache(
553 551 "sql_cache_short",
554 552 "get_group_%s" % _hash_key(group_name)
555 553 )
556 554 )
557 555 return gr.scalar()
558 556
559 557
560 558 class Permission(Base, BaseModel):
561 559 __tablename__ = 'permissions'
562 560 __table_args__ = (
563 561 Index('p_perm_name_idx', 'permission_name'),
564 562 {'extend_existing': True, 'mysql_engine': 'InnoDB',
565 563 'mysql_charset': 'utf8'},
566 564 )
567 565 PERMS = [
568 566 ('repository.none', _('Repository no access')),
569 567 ('repository.read', _('Repository read access')),
570 568 ('repository.write', _('Repository write access')),
571 569 ('repository.admin', _('Repository admin access')),
572 570
573 571 ('group.none', _('Repositories Group no access')),
574 572 ('group.read', _('Repositories Group read access')),
575 573 ('group.write', _('Repositories Group write access')),
576 574 ('group.admin', _('Repositories Group admin access')),
577 575
578 576 ('hg.admin', _('RhodeCode Administrator')),
579 577 ('hg.create.none', _('Repository creation disabled')),
580 578 ('hg.create.repository', _('Repository creation enabled')),
581 579 ('hg.fork.none', _('Repository forking disabled')),
582 580 ('hg.fork.repository', _('Repository forking enabled')),
583 581 ('hg.register.none', _('Register disabled')),
584 582 ('hg.register.manual_activate', _('Register new user with RhodeCode '
585 583 'with manual activation')),
586 584
587 585 ('hg.register.auto_activate', _('Register new user with RhodeCode '
588 586 'with auto activation')),
589 587 ]
590 588
591 589 # defines which permissions are more important higher the more important
592 590 PERM_WEIGHTS = {
593 591 'repository.none': 0,
594 592 'repository.read': 1,
595 593 'repository.write': 3,
596 594 'repository.admin': 4,
597 595
598 596 'group.none': 0,
599 597 'group.read': 1,
600 598 'group.write': 3,
601 599 'group.admin': 4,
602 600
603 601 'hg.fork.none': 0,
604 602 'hg.fork.repository': 1,
605 603 'hg.create.none': 0,
606 604 'hg.create.repository':1
607 605 }
608 606
609 607 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
610 608 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
611 609 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
612 610
613 611 def __unicode__(self):
614 612 return u"<%s('%s:%s')>" % (
615 613 self.__class__.__name__, self.permission_id, self.permission_name
616 614 )
617 615
618 616 @classmethod
619 617 def get_by_key(cls, key):
620 618 return cls.query().filter(cls.permission_name == key).scalar()
621 619
622 620
623 621 class UserRepoToPerm(Base, BaseModel):
624 622 __tablename__ = 'repo_to_perm'
625 623 __table_args__ = (
626 624 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
627 625 {'extend_existing': True, 'mysql_engine': 'InnoDB',
628 626 'mysql_charset': 'utf8'}
629 627 )
630 628 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
631 629 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
632 630 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
633 631 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
634 632
635 633 user = relationship('User')
636 634 repository = relationship('Repository')
637 635 permission = relationship('Permission')
638 636
639 637 def __unicode__(self):
640 638 return u'<user:%s => %s >' % (self.user, self.repository)
641 639
642 640
643 641 class UserToPerm(Base, BaseModel):
644 642 __tablename__ = 'user_to_perm'
645 643 __table_args__ = (
646 644 UniqueConstraint('user_id', 'permission_id'),
647 645 {'extend_existing': True, 'mysql_engine': 'InnoDB',
648 646 'mysql_charset': 'utf8'}
649 647 )
650 648 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
651 649 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
652 650 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
653 651
654 652 user = relationship('User')
655 653 permission = relationship('Permission', lazy='joined')
656 654
657 655
658 656 class UserGroupRepoToPerm(Base, BaseModel):
659 657 __tablename__ = 'users_group_repo_to_perm'
660 658 __table_args__ = (
661 659 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
662 660 {'extend_existing': True, 'mysql_engine': 'InnoDB',
663 661 'mysql_charset': 'utf8'}
664 662 )
665 663 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
666 664 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
667 665 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
668 666 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
669 667
670 668 users_group = relationship('UserGroup')
671 669 permission = relationship('Permission')
672 670 repository = relationship('Repository')
673 671
674 672 def __unicode__(self):
675 673 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
676 674
677 675
678 676 class UserGroupToPerm(Base, BaseModel):
679 677 __tablename__ = 'users_group_to_perm'
680 678 __table_args__ = (
681 679 UniqueConstraint('users_group_id', 'permission_id',),
682 680 {'extend_existing': True, 'mysql_engine': 'InnoDB',
683 681 'mysql_charset': 'utf8'}
684 682 )
685 683 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
686 684 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
687 685 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
688 686
689 687 users_group = relationship('UserGroup')
690 688 permission = relationship('Permission')
691 689
692 690
693 691 class UserRepoGroupToPerm(Base, BaseModel):
694 692 __tablename__ = 'user_repo_group_to_perm'
695 693 __table_args__ = (
696 694 UniqueConstraint('user_id', 'group_id', 'permission_id'),
697 695 {'extend_existing': True, 'mysql_engine': 'InnoDB',
698 696 'mysql_charset': 'utf8'}
699 697 )
700 698
701 699 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
702 700 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
703 701 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
704 702 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
705 703
706 704 user = relationship('User')
707 705 group = relationship('RepoGroup')
708 706 permission = relationship('Permission')
709 707
710 708
711 709 class UserGroupRepoGroupToPerm(Base, BaseModel):
712 710 __tablename__ = 'users_group_repo_group_to_perm'
713 711 __table_args__ = (
714 712 UniqueConstraint('users_group_id', 'group_id'),
715 713 {'extend_existing': True, 'mysql_engine': 'InnoDB',
716 714 'mysql_charset': 'utf8'}
717 715 )
718 716
719 717 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)
720 718 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
721 719 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
722 720 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
723 721
724 722 users_group = relationship('UserGroup')
725 723 permission = relationship('Permission')
726 724 group = relationship('RepoGroup')
727 725
728 726
729 727 class Statistics(Base, BaseModel):
730 728 __tablename__ = 'statistics'
731 729 __table_args__ = (
732 730 UniqueConstraint('repository_id'),
733 731 {'extend_existing': True, 'mysql_engine': 'InnoDB',
734 732 'mysql_charset': 'utf8'}
735 733 )
736 734 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
737 735 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
738 736 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
739 737 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
740 738 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
741 739 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
742 740
743 741 repository = relationship('Repository', single_parent=True)
744 742
745 743
746 744 class UserFollowing(Base, BaseModel):
747 745 __tablename__ = 'user_followings'
748 746 __table_args__ = (
749 747 UniqueConstraint('user_id', 'follows_repository_id'),
750 748 UniqueConstraint('user_id', 'follows_user_id'),
751 749 {'extend_existing': True, 'mysql_engine': 'InnoDB',
752 750 'mysql_charset': 'utf8'}
753 751 )
754 752
755 753 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
756 754 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
757 755 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
758 756 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
759 757 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
760 758
761 759 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
762 760
763 761 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
764 762 follows_repository = relationship('Repository', order_by='Repository.repo_name')
765 763
766 764
767 765 class CacheInvalidation(Base, BaseModel):
768 766 __tablename__ = 'cache_invalidation'
769 767 __table_args__ = (
770 768 UniqueConstraint('cache_key'),
771 769 Index('key_idx', 'cache_key'),
772 770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
773 771 'mysql_charset': 'utf8'},
774 772 )
775 773 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
776 774 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
777 775 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
778 776 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
779 777
780 778 def __init__(self, cache_key, cache_args=''):
781 779 self.cache_key = cache_key
782 780 self.cache_args = cache_args
783 781 self.cache_active = False
784 782
785 783
786 784 class ChangesetComment(Base, BaseModel):
787 785 __tablename__ = 'changeset_comments'
788 786 __table_args__ = (
789 787 Index('cc_revision_idx', 'revision'),
790 788 {'extend_existing': True, 'mysql_engine': 'InnoDB',
791 789 'mysql_charset': 'utf8'},
792 790 )
793 791 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
794 792 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
795 793 revision = Column('revision', String(40), nullable=True)
796 794 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
797 795 line_no = Column('line_no', Unicode(10), nullable=True)
798 796 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
799 797 f_path = Column('f_path', Unicode(1000), nullable=True)
800 798 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
801 799 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
802 800 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
803 801 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
804 802
805 803 author = relationship('User', lazy='joined')
806 804 repo = relationship('Repository')
807 805 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
808 806 pull_request = relationship('PullRequest', lazy='joined')
809 807
810 808 @classmethod
811 809 def get_users(cls, revision=None, pull_request_id=None):
812 810 """
813 811 Returns user associated with this ChangesetComment. ie those
814 812 who actually commented
815 813
816 814 :param cls:
817 815 :param revision:
818 816 """
819 817 q = Session().query(User)\
820 818 .join(ChangesetComment.author)
821 819 if revision:
822 820 q = q.filter(cls.revision == revision)
823 821 elif pull_request_id:
824 822 q = q.filter(cls.pull_request_id == pull_request_id)
825 823 return q.all()
826 824
827 825
828 826 class ChangesetStatus(Base, BaseModel):
829 827 __tablename__ = 'changeset_statuses'
830 828 __table_args__ = (
831 829 Index('cs_revision_idx', 'revision'),
832 830 Index('cs_version_idx', 'version'),
833 831 UniqueConstraint('repo_id', 'revision', 'version'),
834 832 {'extend_existing': True, 'mysql_engine': 'InnoDB',
835 833 'mysql_charset': 'utf8'}
836 834 )
837 835 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
838 836 STATUS_APPROVED = 'approved'
839 837 STATUS_REJECTED = 'rejected'
840 838 STATUS_UNDER_REVIEW = 'under_review'
841 839
842 840 STATUSES = [
843 841 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
844 842 (STATUS_APPROVED, _("Approved")),
845 843 (STATUS_REJECTED, _("Rejected")),
846 844 (STATUS_UNDER_REVIEW, _("Under Review")),
847 845 ]
848 846
849 847 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
850 848 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
851 849 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
852 850 revision = Column('revision', String(40), nullable=False)
853 851 status = Column('status', String(128), nullable=False, default=DEFAULT)
854 852 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
855 853 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
856 854 version = Column('version', Integer(), nullable=False, default=0)
857 855 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
858 856
859 857 author = relationship('User', lazy='joined')
860 858 repo = relationship('Repository')
861 859 comment = relationship('ChangesetComment', lazy='joined')
862 860 pull_request = relationship('PullRequest', lazy='joined')
863 861
864 862
865 863
866 864 class PullRequest(Base, BaseModel):
867 865 __tablename__ = 'pull_requests'
868 866 __table_args__ = (
869 867 {'extend_existing': True, 'mysql_engine': 'InnoDB',
870 868 'mysql_charset': 'utf8'},
871 869 )
872 870
873 871 STATUS_NEW = u'new'
874 872 STATUS_OPEN = u'open'
875 873 STATUS_CLOSED = u'closed'
876 874
877 875 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
878 876 title = Column('title', Unicode(256), nullable=True)
879 877 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
880 878 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
881 879 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
882 880 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
883 881 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
884 882 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
885 883 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
886 884 org_ref = Column('org_ref', Unicode(256), nullable=False)
887 885 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
888 886 other_ref = Column('other_ref', Unicode(256), nullable=False)
889 887
890 888 author = relationship('User', lazy='joined')
891 889 reviewers = relationship('PullRequestReviewers',
892 890 cascade="all, delete, delete-orphan")
893 891 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
894 892 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
895 893 statuses = relationship('ChangesetStatus')
896 894 comments = relationship('ChangesetComment',
897 895 cascade="all, delete, delete-orphan")
898 896
899 897
900 898 class PullRequestReviewers(Base, BaseModel):
901 899 __tablename__ = 'pull_request_reviewers'
902 900 __table_args__ = (
903 901 {'extend_existing': True, 'mysql_engine': 'InnoDB',
904 902 'mysql_charset': 'utf8'},
905 903 )
906 904
907 905 def __init__(self, user=None, pull_request=None):
908 906 self.user = user
909 907 self.pull_request = pull_request
910 908
911 909 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
912 910 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
913 911 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
914 912
915 913 user = relationship('User')
916 914 pull_request = relationship('PullRequest')
917 915
918 916
919 917 class Notification(Base, BaseModel):
920 918 __tablename__ = 'notifications'
921 919 __table_args__ = (
922 920 Index('notification_type_idx', 'type'),
923 921 {'extend_existing': True, 'mysql_engine': 'InnoDB',
924 922 'mysql_charset': 'utf8'},
925 923 )
926 924
927 925 TYPE_CHANGESET_COMMENT = u'cs_comment'
928 926 TYPE_MESSAGE = u'message'
929 927 TYPE_MENTION = u'mention'
930 928 TYPE_REGISTRATION = u'registration'
931 929 TYPE_PULL_REQUEST = u'pull_request'
932 930 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
933 931
934 932 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
935 933 subject = Column('subject', Unicode(512), nullable=True)
936 934 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
937 935 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
938 936 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
939 937 type_ = Column('type', Unicode(256))
940 938
941 939 created_by_user = relationship('User')
942 940 notifications_to_users = relationship('UserNotification', lazy='joined',
943 941 cascade="all, delete, delete-orphan")
944 942
945 943
946 944 class UserNotification(Base, BaseModel):
947 945 __tablename__ = 'user_to_notification'
948 946 __table_args__ = (
949 947 UniqueConstraint('user_id', 'notification_id'),
950 948 {'extend_existing': True, 'mysql_engine': 'InnoDB',
951 949 'mysql_charset': 'utf8'}
952 950 )
953 951 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
954 952 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
955 953 read = Column('read', Boolean, default=False)
956 954 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
957 955
958 956 user = relationship('User', lazy="joined")
959 957 notification = relationship('Notification', lazy="joined",
960 958 order_by=lambda: Notification.created_on.desc(),)
961 959
962 960
963 961 class DbMigrateVersion(Base, BaseModel):
964 962 __tablename__ = 'db_migrate_version'
965 963 __table_args__ = (
966 964 {'extend_existing': True, 'mysql_engine': 'InnoDB',
967 965 'mysql_charset': 'utf8'},
968 966 )
969 967 repository_id = Column('repository_id', String(250), primary_key=True)
970 968 repository_path = Column('repository_path', Text)
971 969 version = Column('version', Integer)
@@ -1,992 +1,990 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20
22 21 import os
23 22 import time
24 23 import logging
25 24 import datetime
26 25 import traceback
27 26 import hashlib
28 27 import collections
29 28
30 29 from sqlalchemy import *
31 30 from sqlalchemy.ext.hybrid import hybrid_property
32 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
33 32 from sqlalchemy.exc import DatabaseError
34 33 from beaker.cache import cache_region, region_invalidate
35 34 from webob.exc import HTTPNotFound
36 35
37 36 from rhodecode.translation import _
38 37
39 38 from rhodecode.lib.vcs import get_backend
40 39 from rhodecode.lib.vcs.utils.helpers import get_scm
41 40 from rhodecode.lib.vcs.exceptions import VCSError
42 41 from zope.cachedescriptors.property import Lazy as LazyProperty
43 42
44 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 safe_unicode, remove_suffix, remove_prefix
43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, remove_suffix, remove_prefix
46 44 from rhodecode.lib.ext_json import json
47 45 from rhodecode.lib.caching_query import FromCache
48 46
49 47 from rhodecode.model.meta import Base, Session
50 48
51 49 URL_SEP = '/'
52 50 log = logging.getLogger(__name__)
53 51
54 52 #==============================================================================
55 53 # BASE CLASSES
56 54 #==============================================================================
57 55
58 56 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 57
60 58
61 59 class BaseModel(object):
62 60 """
63 61 Base Model for all classes
64 62 """
65 63
66 64 @classmethod
67 65 def _get_keys(cls):
68 66 """return column names for this model """
69 67 return class_mapper(cls).c.keys()
70 68
71 69 def get_dict(self):
72 70 """
73 71 return dict with keys and values corresponding
74 72 to this model data """
75 73
76 74 d = {}
77 75 for k in self._get_keys():
78 76 d[k] = getattr(self, k)
79 77
80 78 # also use __json__() if present to get additional fields
81 79 _json_attr = getattr(self, '__json__', None)
82 80 if _json_attr:
83 81 # update with attributes from __json__
84 82 if callable(_json_attr):
85 83 _json_attr = _json_attr()
86 84 for k, val in _json_attr.items():
87 85 d[k] = val
88 86 return d
89 87
90 88 def get_appstruct(self):
91 89 """return list with keys and values tupples corresponding
92 90 to this model data """
93 91
94 92 l = []
95 93 for k in self._get_keys():
96 94 l.append((k, getattr(self, k),))
97 95 return l
98 96
99 97 def populate_obj(self, populate_dict):
100 98 """populate model with data from given populate_dict"""
101 99
102 100 for k in self._get_keys():
103 101 if k in populate_dict:
104 102 setattr(self, k, populate_dict[k])
105 103
106 104 @classmethod
107 105 def query(cls):
108 106 return Session().query(cls)
109 107
110 108 @classmethod
111 109 def get(cls, id_):
112 110 if id_:
113 111 return cls.query().get(id_)
114 112
115 113 @classmethod
116 114 def get_or_404(cls, id_):
117 115 try:
118 116 id_ = int(id_)
119 117 except (TypeError, ValueError):
120 118 raise HTTPNotFound
121 119
122 120 res = cls.query().get(id_)
123 121 if not res:
124 122 raise HTTPNotFound
125 123 return res
126 124
127 125 @classmethod
128 126 def getAll(cls):
129 127 # deprecated and left for backward compatibility
130 128 return cls.get_all()
131 129
132 130 @classmethod
133 131 def get_all(cls):
134 132 return cls.query().all()
135 133
136 134 @classmethod
137 135 def delete(cls, id_):
138 136 obj = cls.query().get(id_)
139 137 Session().delete(obj)
140 138
141 139 def __repr__(self):
142 140 if hasattr(self, '__unicode__'):
143 141 # python repr needs to return str
144 142 return safe_str(self.__unicode__())
145 143 return '<DB:%s>' % (self.__class__.__name__)
146 144
147 145
148 146 class RhodeCodeSetting(Base, BaseModel):
149 147 __tablename__ = 'rhodecode_settings'
150 148 __table_args__ = (
151 149 UniqueConstraint('app_settings_name'),
152 150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 151 'mysql_charset': 'utf8'}
154 152 )
155 153 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 154 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
157 155 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
158 156
159 157 def __init__(self, k='', v=''):
160 158 self.app_settings_name = k
161 159 self.app_settings_value = v
162 160
163 161 @validates('_app_settings_value')
164 162 def validate_settings_value(self, key, val):
165 assert type(val) == unicode
163 assert type(val) == str
166 164 return val
167 165
168 166 @hybrid_property
169 167 def app_settings_value(self):
170 168 v = self._app_settings_value
171 169 if self.app_settings_name in ["ldap_active",
172 170 "default_repo_enable_statistics",
173 171 "default_repo_enable_locking",
174 172 "default_repo_private",
175 173 "default_repo_enable_downloads"]:
176 174 v = str2bool(v)
177 175 return v
178 176
179 177 @app_settings_value.setter
180 178 def app_settings_value(self, val):
181 179 """
182 180 Setter that will always make sure we use unicode in app_settings_value
183 181
184 182 :param val:
185 183 """
186 self._app_settings_value = safe_unicode(val)
184 self._app_settings_value = safe_str(val)
187 185
188 186 def __unicode__(self):
189 187 return u"<%s('%s:%s')>" % (
190 188 self.__class__.__name__,
191 189 self.app_settings_name, self.app_settings_value
192 190 )
193 191
194 192
195 193 class RhodeCodeUi(Base, BaseModel):
196 194 __tablename__ = 'rhodecode_ui'
197 195 __table_args__ = (
198 196 UniqueConstraint('ui_key'),
199 197 {'extend_existing': True, 'mysql_engine': 'InnoDB',
200 198 'mysql_charset': 'utf8'}
201 199 )
202 200
203 201 HOOK_REPO_SIZE = 'changegroup.repo_size'
204 202 HOOK_PUSH = 'changegroup.push_logger'
205 203 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
206 204 HOOK_PULL = 'outgoing.pull_logger'
207 205 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
208 206
209 207 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
210 208 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
211 209 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
212 210 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
213 211 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
214 212
215 213
216 214
217 215 class User(Base, BaseModel):
218 216 __tablename__ = 'users'
219 217 __table_args__ = (
220 218 UniqueConstraint('username'), UniqueConstraint('email'),
221 219 Index('u_username_idx', 'username'),
222 220 Index('u_email_idx', 'email'),
223 221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
224 222 'mysql_charset': 'utf8'}
225 223 )
226 224 DEFAULT_USER = 'default'
227 225 DEFAULT_PERMISSIONS = [
228 226 'hg.register.manual_activate', 'hg.create.repository',
229 227 'hg.fork.repository', 'repository.read', 'group.read'
230 228 ]
231 229 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
232 230 username = Column("username", String(255), nullable=True, unique=None, default=None)
233 231 password = Column("password", String(255), nullable=True, unique=None, default=None)
234 232 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
235 233 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
236 234 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
237 235 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
238 236 _email = Column("email", String(255), nullable=True, unique=None, default=None)
239 237 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
240 238 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
241 239 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
242 240 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
243 241
244 242 user_log = relationship('UserLog')
245 243 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
246 244
247 245 repositories = relationship('Repository')
248 246 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
249 247 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
250 248
251 249 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
252 250 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
253 251
254 252 group_member = relationship('UserGroupMember', cascade='all')
255 253
256 254 notifications = relationship('UserNotification', cascade='all')
257 255 # notifications assigned to this user
258 256 user_created_notifications = relationship('Notification', cascade='all')
259 257 # comments created by this user
260 258 user_comments = relationship('ChangesetComment', cascade='all')
261 259 user_emails = relationship('UserEmailMap', cascade='all')
262 260
263 261 @hybrid_property
264 262 def email(self):
265 263 return self._email
266 264
267 265 @email.setter
268 266 def email(self, val):
269 267 self._email = val.lower() if val else None
270 268
271 269 @property
272 270 def firstname(self):
273 271 # alias for future
274 272 return self.name
275 273
276 274 @property
277 275 def username_and_name(self):
278 276 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
279 277
280 278 @property
281 279 def full_name(self):
282 280 return '%s %s' % (self.firstname, self.lastname)
283 281
284 282 @property
285 283 def full_contact(self):
286 284 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
287 285
288 286 @property
289 287 def short_contact(self):
290 288 return '%s %s' % (self.firstname, self.lastname)
291 289
292 290 @property
293 291 def is_admin(self):
294 292 return self.admin
295 293
296 294 @classmethod
297 295 def get_by_username(cls, username, case_insensitive=False, cache=False):
298 296 if case_insensitive:
299 297 q = cls.query().filter(cls.username.ilike(username))
300 298 else:
301 299 q = cls.query().filter(cls.username == username)
302 300
303 301 if cache:
304 302 q = q.options(FromCache(
305 303 "sql_cache_short",
306 304 "get_user_%s" % _hash_key(username)
307 305 )
308 306 )
309 307 return q.scalar()
310 308
311 309 @classmethod
312 310 def get_by_auth_token(cls, auth_token, cache=False):
313 311 q = cls.query().filter(cls.api_key == auth_token)
314 312
315 313 if cache:
316 314 q = q.options(FromCache("sql_cache_short",
317 315 "get_auth_token_%s" % auth_token))
318 316 return q.scalar()
319 317
320 318 @classmethod
321 319 def get_by_email(cls, email, case_insensitive=False, cache=False):
322 320 if case_insensitive:
323 321 q = cls.query().filter(cls.email.ilike(email))
324 322 else:
325 323 q = cls.query().filter(cls.email == email)
326 324
327 325 if cache:
328 326 q = q.options(FromCache("sql_cache_short",
329 327 "get_email_key_%s" % email))
330 328
331 329 ret = q.scalar()
332 330 if ret is None:
333 331 q = UserEmailMap.query()
334 332 # try fetching in alternate email map
335 333 if case_insensitive:
336 334 q = q.filter(UserEmailMap.email.ilike(email))
337 335 else:
338 336 q = q.filter(UserEmailMap.email == email)
339 337 q = q.options(joinedload(UserEmailMap.user))
340 338 if cache:
341 339 q = q.options(FromCache("sql_cache_short",
342 340 "get_email_map_key_%s" % email))
343 341 ret = getattr(q.scalar(), 'user', None)
344 342
345 343 return ret
346 344
347 345
348 346 class UserEmailMap(Base, BaseModel):
349 347 __tablename__ = 'user_email_map'
350 348 __table_args__ = (
351 349 Index('uem_email_idx', 'email'),
352 350 UniqueConstraint('email'),
353 351 {'extend_existing': True, 'mysql_engine': 'InnoDB',
354 352 'mysql_charset': 'utf8'}
355 353 )
356 354 __mapper_args__ = {}
357 355
358 356 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
359 357 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
360 358 _email = Column("email", String(255), nullable=True, unique=False, default=None)
361 359 user = relationship('User', lazy='joined')
362 360
363 361 @validates('_email')
364 362 def validate_email(self, key, email):
365 363 # check if this email is not main one
366 364 main_email = Session().query(User).filter(User.email == email).scalar()
367 365 if main_email is not None:
368 366 raise AttributeError('email %s is present is user table' % email)
369 367 return email
370 368
371 369 @hybrid_property
372 370 def email(self):
373 371 return self._email
374 372
375 373 @email.setter
376 374 def email(self, val):
377 375 self._email = val.lower() if val else None
378 376
379 377
380 378 class UserLog(Base, BaseModel):
381 379 __tablename__ = 'user_logs'
382 380 __table_args__ = (
383 381 {'extend_existing': True, 'mysql_engine': 'InnoDB',
384 382 'mysql_charset': 'utf8'},
385 383 )
386 384 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
387 385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
388 386 username = Column("username", String(255), nullable=True, unique=None, default=None)
389 387 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
390 388 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
391 389 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
392 390 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
393 391 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
394 392
395 393
396 394 user = relationship('User')
397 395 repository = relationship('Repository', cascade='')
398 396
399 397
400 398 class UserGroup(Base, BaseModel):
401 399 __tablename__ = 'users_groups'
402 400 __table_args__ = (
403 401 {'extend_existing': True, 'mysql_engine': 'InnoDB',
404 402 'mysql_charset': 'utf8'},
405 403 )
406 404
407 405 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
408 406 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
409 407 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
410 408 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
411 409
412 410 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
413 411 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
414 412 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
415 413
416 414 def __unicode__(self):
417 415 return u'<userGroup(%s)>' % (self.users_group_name)
418 416
419 417 @classmethod
420 418 def get_by_group_name(cls, group_name, cache=False,
421 419 case_insensitive=False):
422 420 if case_insensitive:
423 421 q = cls.query().filter(cls.users_group_name.ilike(group_name))
424 422 else:
425 423 q = cls.query().filter(cls.users_group_name == group_name)
426 424 if cache:
427 425 q = q.options(FromCache(
428 426 "sql_cache_short",
429 427 "get_user_%s" % _hash_key(group_name)
430 428 )
431 429 )
432 430 return q.scalar()
433 431
434 432 @classmethod
435 433 def get(cls, users_group_id, cache=False):
436 434 user_group = cls.query()
437 435 if cache:
438 436 user_group = user_group.options(FromCache("sql_cache_short",
439 437 "get_users_group_%s" % users_group_id))
440 438 return user_group.get(users_group_id)
441 439
442 440
443 441 class UserGroupMember(Base, BaseModel):
444 442 __tablename__ = 'users_groups_members'
445 443 __table_args__ = (
446 444 {'extend_existing': True, 'mysql_engine': 'InnoDB',
447 445 'mysql_charset': 'utf8'},
448 446 )
449 447
450 448 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
451 449 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
452 450 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
453 451
454 452 user = relationship('User', lazy='joined')
455 453 users_group = relationship('UserGroup')
456 454
457 455 def __init__(self, gr_id='', u_id=''):
458 456 self.users_group_id = gr_id
459 457 self.user_id = u_id
460 458
461 459
462 460 class Repository(Base, BaseModel):
463 461 __tablename__ = 'repositories'
464 462 __table_args__ = (
465 463 UniqueConstraint('repo_name'),
466 464 Index('r_repo_name_idx', 'repo_name'),
467 465 {'extend_existing': True, 'mysql_engine': 'InnoDB',
468 466 'mysql_charset': 'utf8'},
469 467 )
470 468
471 469 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
472 470 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
473 471 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
474 472 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
475 473 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
476 474 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
477 475 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
478 476 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
479 477 description = Column("description", String(10000), nullable=True, unique=None, default=None)
480 478 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
481 479 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
482 480 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
483 481 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
484 482 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
485 483
486 484 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
487 485 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
488 486
489 487 user = relationship('User')
490 488 fork = relationship('Repository', remote_side=repo_id)
491 489 group = relationship('RepoGroup')
492 490 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
493 491 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
494 492 stats = relationship('Statistics', cascade='all', uselist=False)
495 493
496 494 followers = relationship('UserFollowing',
497 495 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
498 496 cascade='all')
499 497
500 498 logs = relationship('UserLog')
501 499 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
502 500
503 501 pull_requests_org = relationship('PullRequest',
504 502 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
505 503 cascade="all, delete, delete-orphan")
506 504
507 505 pull_requests_other = relationship('PullRequest',
508 506 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
509 507 cascade="all, delete, delete-orphan")
510 508
511 509 def __unicode__(self):
512 510 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
513 511 self.repo_name)
514 512
515 513
516 514 @classmethod
517 515 def get_by_repo_name(cls, repo_name):
518 516 q = Session().query(cls).filter(cls.repo_name == repo_name)
519 517 q = q.options(joinedload(Repository.fork))\
520 518 .options(joinedload(Repository.user))\
521 519 .options(joinedload(Repository.group))
522 520 return q.scalar()
523 521
524 522
525 523 class RepoGroup(Base, BaseModel):
526 524 __tablename__ = 'groups'
527 525 __table_args__ = (
528 526 UniqueConstraint('group_name', 'group_parent_id'),
529 527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
530 528 'mysql_charset': 'utf8'},
531 529 )
532 530 __mapper_args__ = {'order_by': 'group_name'}
533 531
534 532 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
535 533 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
536 534 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
537 535 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
538 536 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
539 537
540 538 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
541 539 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
542 540 parent_group = relationship('RepoGroup', remote_side=group_id)
543 541
544 542 def __init__(self, group_name='', parent_group=None):
545 543 self.group_name = group_name
546 544 self.parent_group = parent_group
547 545
548 546 def __unicode__(self):
549 547 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
550 548 self.group_name)
551 549
552 550 @classmethod
553 551 def url_sep(cls):
554 552 return URL_SEP
555 553
556 554 @classmethod
557 555 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
558 556 if case_insensitive:
559 557 gr = cls.query()\
560 558 .filter(cls.group_name.ilike(group_name))
561 559 else:
562 560 gr = cls.query()\
563 561 .filter(cls.group_name == group_name)
564 562 if cache:
565 563 gr = gr.options(FromCache(
566 564 "sql_cache_short",
567 565 "get_group_%s" % _hash_key(group_name)
568 566 )
569 567 )
570 568 return gr.scalar()
571 569
572 570
573 571 class Permission(Base, BaseModel):
574 572 __tablename__ = 'permissions'
575 573 __table_args__ = (
576 574 Index('p_perm_name_idx', 'permission_name'),
577 575 {'extend_existing': True, 'mysql_engine': 'InnoDB',
578 576 'mysql_charset': 'utf8'},
579 577 )
580 578 PERMS = [
581 579 ('repository.none', _('Repository no access')),
582 580 ('repository.read', _('Repository read access')),
583 581 ('repository.write', _('Repository write access')),
584 582 ('repository.admin', _('Repository admin access')),
585 583
586 584 ('group.none', _('Repositories Group no access')),
587 585 ('group.read', _('Repositories Group read access')),
588 586 ('group.write', _('Repositories Group write access')),
589 587 ('group.admin', _('Repositories Group admin access')),
590 588
591 589 ('hg.admin', _('RhodeCode Administrator')),
592 590 ('hg.create.none', _('Repository creation disabled')),
593 591 ('hg.create.repository', _('Repository creation enabled')),
594 592 ('hg.fork.none', _('Repository forking disabled')),
595 593 ('hg.fork.repository', _('Repository forking enabled')),
596 594 ('hg.register.none', _('Register disabled')),
597 595 ('hg.register.manual_activate', _('Register new user with RhodeCode '
598 596 'with manual activation')),
599 597
600 598 ('hg.register.auto_activate', _('Register new user with RhodeCode '
601 599 'with auto activation')),
602 600 ]
603 601
604 602 # defines which permissions are more important higher the more important
605 603 PERM_WEIGHTS = {
606 604 'repository.none': 0,
607 605 'repository.read': 1,
608 606 'repository.write': 3,
609 607 'repository.admin': 4,
610 608
611 609 'group.none': 0,
612 610 'group.read': 1,
613 611 'group.write': 3,
614 612 'group.admin': 4,
615 613
616 614 'hg.fork.none': 0,
617 615 'hg.fork.repository': 1,
618 616 'hg.create.none': 0,
619 617 'hg.create.repository':1
620 618 }
621 619
622 620 DEFAULT_USER_PERMISSIONS = [
623 621 'repository.read',
624 622 'group.read',
625 623 'hg.create.repository',
626 624 'hg.fork.repository',
627 625 'hg.register.manual_activate',
628 626 ]
629 627
630 628 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
631 629 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
632 630 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
633 631
634 632 def __unicode__(self):
635 633 return u"<%s('%s:%s')>" % (
636 634 self.__class__.__name__, self.permission_id, self.permission_name
637 635 )
638 636
639 637 @classmethod
640 638 def get_by_key(cls, key):
641 639 return cls.query().filter(cls.permission_name == key).scalar()
642 640
643 641
644 642 class UserRepoToPerm(Base, BaseModel):
645 643 __tablename__ = 'repo_to_perm'
646 644 __table_args__ = (
647 645 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
648 646 {'extend_existing': True, 'mysql_engine': 'InnoDB',
649 647 'mysql_charset': 'utf8'}
650 648 )
651 649 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
652 650 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
653 651 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
654 652 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
655 653
656 654 user = relationship('User')
657 655 repository = relationship('Repository')
658 656 permission = relationship('Permission')
659 657
660 658 def __unicode__(self):
661 659 return u'<user:%s => %s >' % (self.user, self.repository)
662 660
663 661
664 662 class UserToPerm(Base, BaseModel):
665 663 __tablename__ = 'user_to_perm'
666 664 __table_args__ = (
667 665 UniqueConstraint('user_id', 'permission_id'),
668 666 {'extend_existing': True, 'mysql_engine': 'InnoDB',
669 667 'mysql_charset': 'utf8'}
670 668 )
671 669 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
672 670 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
673 671 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
674 672
675 673 user = relationship('User')
676 674 permission = relationship('Permission', lazy='joined')
677 675
678 676
679 677 class UserGroupRepoToPerm(Base, BaseModel):
680 678 __tablename__ = 'users_group_repo_to_perm'
681 679 __table_args__ = (
682 680 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
683 681 {'extend_existing': True, 'mysql_engine': 'InnoDB',
684 682 'mysql_charset': 'utf8'}
685 683 )
686 684 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
687 685 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
688 686 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
689 687 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
690 688
691 689 users_group = relationship('UserGroup')
692 690 permission = relationship('Permission')
693 691 repository = relationship('Repository')
694 692
695 693 def __unicode__(self):
696 694 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
697 695
698 696
699 697 class UserGroupToPerm(Base, BaseModel):
700 698 __tablename__ = 'users_group_to_perm'
701 699 __table_args__ = (
702 700 UniqueConstraint('users_group_id', 'permission_id',),
703 701 {'extend_existing': True, 'mysql_engine': 'InnoDB',
704 702 'mysql_charset': 'utf8'}
705 703 )
706 704 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
707 705 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
708 706 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
709 707
710 708 users_group = relationship('UserGroup')
711 709 permission = relationship('Permission')
712 710
713 711
714 712 class UserRepoGroupToPerm(Base, BaseModel):
715 713 __tablename__ = 'user_repo_group_to_perm'
716 714 __table_args__ = (
717 715 UniqueConstraint('user_id', 'group_id', 'permission_id'),
718 716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
719 717 'mysql_charset': 'utf8'}
720 718 )
721 719
722 720 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
723 721 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
724 722 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
725 723 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
726 724
727 725 user = relationship('User')
728 726 group = relationship('RepoGroup')
729 727 permission = relationship('Permission')
730 728
731 729
732 730 class UserGroupRepoGroupToPerm(Base, BaseModel):
733 731 __tablename__ = 'users_group_repo_group_to_perm'
734 732 __table_args__ = (
735 733 UniqueConstraint('users_group_id', 'group_id'),
736 734 {'extend_existing': True, 'mysql_engine': 'InnoDB',
737 735 'mysql_charset': 'utf8'}
738 736 )
739 737
740 738 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)
741 739 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
742 740 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
743 741 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
744 742
745 743 users_group = relationship('UserGroup')
746 744 permission = relationship('Permission')
747 745 group = relationship('RepoGroup')
748 746
749 747
750 748 class Statistics(Base, BaseModel):
751 749 __tablename__ = 'statistics'
752 750 __table_args__ = (
753 751 UniqueConstraint('repository_id'),
754 752 {'extend_existing': True, 'mysql_engine': 'InnoDB',
755 753 'mysql_charset': 'utf8'}
756 754 )
757 755 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
758 756 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
759 757 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
760 758 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
761 759 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
762 760 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
763 761
764 762 repository = relationship('Repository', single_parent=True)
765 763
766 764
767 765 class UserFollowing(Base, BaseModel):
768 766 __tablename__ = 'user_followings'
769 767 __table_args__ = (
770 768 UniqueConstraint('user_id', 'follows_repository_id'),
771 769 UniqueConstraint('user_id', 'follows_user_id'),
772 770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
773 771 'mysql_charset': 'utf8'}
774 772 )
775 773
776 774 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
777 775 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
778 776 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
779 777 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
780 778 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
781 779
782 780 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
783 781
784 782 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
785 783 follows_repository = relationship('Repository', order_by='Repository.repo_name')
786 784
787 785
788 786 class CacheInvalidation(Base, BaseModel):
789 787 __tablename__ = 'cache_invalidation'
790 788 __table_args__ = (
791 789 UniqueConstraint('cache_key'),
792 790 Index('key_idx', 'cache_key'),
793 791 {'extend_existing': True, 'mysql_engine': 'InnoDB',
794 792 'mysql_charset': 'utf8'},
795 793 )
796 794 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
797 795 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
798 796 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
799 797 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
800 798
801 799 def __init__(self, cache_key, cache_args=''):
802 800 self.cache_key = cache_key
803 801 self.cache_args = cache_args
804 802 self.cache_active = False
805 803
806 804
807 805 class ChangesetComment(Base, BaseModel):
808 806 __tablename__ = 'changeset_comments'
809 807 __table_args__ = (
810 808 Index('cc_revision_idx', 'revision'),
811 809 {'extend_existing': True, 'mysql_engine': 'InnoDB',
812 810 'mysql_charset': 'utf8'},
813 811 )
814 812 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
815 813 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
816 814 revision = Column('revision', String(40), nullable=True)
817 815 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
818 816 line_no = Column('line_no', Unicode(10), nullable=True)
819 817 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
820 818 f_path = Column('f_path', Unicode(1000), nullable=True)
821 819 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
822 820 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
823 821 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
824 822 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
825 823
826 824 author = relationship('User', lazy='joined')
827 825 repo = relationship('Repository')
828 826 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
829 827 pull_request = relationship('PullRequest', lazy='joined')
830 828
831 829 @classmethod
832 830 def get_users(cls, revision=None, pull_request_id=None):
833 831 """
834 832 Returns user associated with this ChangesetComment. ie those
835 833 who actually commented
836 834
837 835 :param cls:
838 836 :param revision:
839 837 """
840 838 q = Session().query(User)\
841 839 .join(ChangesetComment.author)
842 840 if revision:
843 841 q = q.filter(cls.revision == revision)
844 842 elif pull_request_id:
845 843 q = q.filter(cls.pull_request_id == pull_request_id)
846 844 return q.all()
847 845
848 846
849 847 class ChangesetStatus(Base, BaseModel):
850 848 __tablename__ = 'changeset_statuses'
851 849 __table_args__ = (
852 850 Index('cs_revision_idx', 'revision'),
853 851 Index('cs_version_idx', 'version'),
854 852 UniqueConstraint('repo_id', 'revision', 'version'),
855 853 {'extend_existing': True, 'mysql_engine': 'InnoDB',
856 854 'mysql_charset': 'utf8'}
857 855 )
858 856 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
859 857 STATUS_APPROVED = 'approved'
860 858 STATUS_REJECTED = 'rejected'
861 859 STATUS_UNDER_REVIEW = 'under_review'
862 860
863 861 STATUSES = [
864 862 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
865 863 (STATUS_APPROVED, _("Approved")),
866 864 (STATUS_REJECTED, _("Rejected")),
867 865 (STATUS_UNDER_REVIEW, _("Under Review")),
868 866 ]
869 867
870 868 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
871 869 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
872 870 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
873 871 revision = Column('revision', String(40), nullable=False)
874 872 status = Column('status', String(128), nullable=False, default=DEFAULT)
875 873 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
876 874 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
877 875 version = Column('version', Integer(), nullable=False, default=0)
878 876 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
879 877
880 878 author = relationship('User', lazy='joined')
881 879 repo = relationship('Repository')
882 880 comment = relationship('ChangesetComment', lazy='joined')
883 881 pull_request = relationship('PullRequest', lazy='joined')
884 882
885 883
886 884
887 885 class PullRequest(Base, BaseModel):
888 886 __tablename__ = 'pull_requests'
889 887 __table_args__ = (
890 888 {'extend_existing': True, 'mysql_engine': 'InnoDB',
891 889 'mysql_charset': 'utf8'},
892 890 )
893 891
894 892 STATUS_NEW = u'new'
895 893 STATUS_OPEN = u'open'
896 894 STATUS_CLOSED = u'closed'
897 895
898 896 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
899 897 title = Column('title', Unicode(256), nullable=True)
900 898 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
901 899 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
902 900 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
903 901 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
904 902 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
905 903 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
906 904 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
907 905 org_ref = Column('org_ref', Unicode(256), nullable=False)
908 906 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
909 907 other_ref = Column('other_ref', Unicode(256), nullable=False)
910 908
911 909 author = relationship('User', lazy='joined')
912 910 reviewers = relationship('PullRequestReviewers',
913 911 cascade="all, delete, delete-orphan")
914 912 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
915 913 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
916 914 statuses = relationship('ChangesetStatus')
917 915 comments = relationship('ChangesetComment',
918 916 cascade="all, delete, delete-orphan")
919 917
920 918
921 919 class PullRequestReviewers(Base, BaseModel):
922 920 __tablename__ = 'pull_request_reviewers'
923 921 __table_args__ = (
924 922 {'extend_existing': True, 'mysql_engine': 'InnoDB',
925 923 'mysql_charset': 'utf8'},
926 924 )
927 925
928 926 def __init__(self, user=None, pull_request=None):
929 927 self.user = user
930 928 self.pull_request = pull_request
931 929
932 930 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
933 931 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
934 932 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
935 933
936 934 user = relationship('User')
937 935 pull_request = relationship('PullRequest')
938 936
939 937
940 938 class Notification(Base, BaseModel):
941 939 __tablename__ = 'notifications'
942 940 __table_args__ = (
943 941 Index('notification_type_idx', 'type'),
944 942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
945 943 'mysql_charset': 'utf8'},
946 944 )
947 945
948 946 TYPE_CHANGESET_COMMENT = u'cs_comment'
949 947 TYPE_MESSAGE = u'message'
950 948 TYPE_MENTION = u'mention'
951 949 TYPE_REGISTRATION = u'registration'
952 950 TYPE_PULL_REQUEST = u'pull_request'
953 951 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
954 952
955 953 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
956 954 subject = Column('subject', Unicode(512), nullable=True)
957 955 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
958 956 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
959 957 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
960 958 type_ = Column('type', Unicode(256))
961 959
962 960 created_by_user = relationship('User')
963 961 notifications_to_users = relationship('UserNotification', lazy='joined',
964 962 cascade="all, delete, delete-orphan")
965 963
966 964
967 965 class UserNotification(Base, BaseModel):
968 966 __tablename__ = 'user_to_notification'
969 967 __table_args__ = (
970 968 UniqueConstraint('user_id', 'notification_id'),
971 969 {'extend_existing': True, 'mysql_engine': 'InnoDB',
972 970 'mysql_charset': 'utf8'}
973 971 )
974 972 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
975 973 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
976 974 read = Column('read', Boolean, default=False)
977 975 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
978 976
979 977 user = relationship('User', lazy="joined")
980 978 notification = relationship('Notification', lazy="joined",
981 979 order_by=lambda: Notification.created_on.desc(),)
982 980
983 981
984 982 class DbMigrateVersion(Base, BaseModel):
985 983 __tablename__ = 'db_migrate_version'
986 984 __table_args__ = (
987 985 {'extend_existing': True, 'mysql_engine': 'InnoDB',
988 986 'mysql_charset': 'utf8'},
989 987 )
990 988 repository_id = Column('repository_id', String(250), primary_key=True)
991 989 repository_path = Column('repository_path', Text)
992 990 version = Column('version', Integer)
@@ -1,1001 +1,999 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 import os
22 21 import time
23 22 import logging
24 23 import datetime
25 24 import traceback
26 25 import hashlib
27 26 import collections
28 27
29 28 from sqlalchemy import *
30 29 from sqlalchemy.ext.hybrid import hybrid_property
31 30 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 31 from sqlalchemy.exc import DatabaseError
33 32 from beaker.cache import cache_region, region_invalidate
34 33 from webob.exc import HTTPNotFound
35 34
36 35 from rhodecode.translation import _
37 36
38 37 from rhodecode.lib.vcs import get_backend
39 38 from rhodecode.lib.vcs.utils.helpers import get_scm
40 39 from rhodecode.lib.vcs.exceptions import VCSError
41 40 from zope.cachedescriptors.property import Lazy as LazyProperty
42 41 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 42
44 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 safe_unicode, remove_suffix, remove_prefix
43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, remove_suffix, remove_prefix
46 44 from rhodecode.lib.ext_json import json
47 45 from rhodecode.lib.caching_query import FromCache
48 46
49 47 from rhodecode.model.meta import Base, Session
50 48
51 49 URL_SEP = '/'
52 50 log = logging.getLogger(__name__)
53 51
54 52 #==============================================================================
55 53 # BASE CLASSES
56 54 #==============================================================================
57 55
58 56 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 57
60 58
61 59 class BaseModel(object):
62 60 """
63 61 Base Model for all classes
64 62 """
65 63
66 64 @classmethod
67 65 def _get_keys(cls):
68 66 """return column names for this model """
69 67 return class_mapper(cls).c.keys()
70 68
71 69 def get_dict(self):
72 70 """
73 71 return dict with keys and values corresponding
74 72 to this model data """
75 73
76 74 d = {}
77 75 for k in self._get_keys():
78 76 d[k] = getattr(self, k)
79 77
80 78 # also use __json__() if present to get additional fields
81 79 _json_attr = getattr(self, '__json__', None)
82 80 if _json_attr:
83 81 # update with attributes from __json__
84 82 if callable(_json_attr):
85 83 _json_attr = _json_attr()
86 84 for k, val in _json_attr.items():
87 85 d[k] = val
88 86 return d
89 87
90 88 def get_appstruct(self):
91 89 """return list with keys and values tupples corresponding
92 90 to this model data """
93 91
94 92 l = []
95 93 for k in self._get_keys():
96 94 l.append((k, getattr(self, k),))
97 95 return l
98 96
99 97 def populate_obj(self, populate_dict):
100 98 """populate model with data from given populate_dict"""
101 99
102 100 for k in self._get_keys():
103 101 if k in populate_dict:
104 102 setattr(self, k, populate_dict[k])
105 103
106 104 @classmethod
107 105 def query(cls):
108 106 return Session().query(cls)
109 107
110 108 @classmethod
111 109 def get(cls, id_):
112 110 if id_:
113 111 return cls.query().get(id_)
114 112
115 113 @classmethod
116 114 def get_or_404(cls, id_):
117 115 try:
118 116 id_ = int(id_)
119 117 except (TypeError, ValueError):
120 118 raise HTTPNotFound
121 119
122 120 res = cls.query().get(id_)
123 121 if not res:
124 122 raise HTTPNotFound
125 123 return res
126 124
127 125 @classmethod
128 126 def getAll(cls):
129 127 # deprecated and left for backward compatibility
130 128 return cls.get_all()
131 129
132 130 @classmethod
133 131 def get_all(cls):
134 132 return cls.query().all()
135 133
136 134 @classmethod
137 135 def delete(cls, id_):
138 136 obj = cls.query().get(id_)
139 137 Session().delete(obj)
140 138
141 139 def __repr__(self):
142 140 if hasattr(self, '__unicode__'):
143 141 # python repr needs to return str
144 142 return safe_str(self.__unicode__())
145 143 return '<DB:%s>' % (self.__class__.__name__)
146 144
147 145
148 146 class RhodeCodeSetting(Base, BaseModel):
149 147 __tablename__ = 'rhodecode_settings'
150 148 __table_args__ = (
151 149 UniqueConstraint('app_settings_name'),
152 150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 151 'mysql_charset': 'utf8'}
154 152 )
155 153 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 154 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
157 155 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
158 156
159 157 def __init__(self, k='', v=''):
160 158 self.app_settings_name = k
161 159 self.app_settings_value = v
162 160
163 161 @validates('_app_settings_value')
164 162 def validate_settings_value(self, key, val):
165 assert type(val) == unicode
163 assert type(val) == str
166 164 return val
167 165
168 166 @hybrid_property
169 167 def app_settings_value(self):
170 168 v = self._app_settings_value
171 169 if self.app_settings_name in ["ldap_active",
172 170 "default_repo_enable_statistics",
173 171 "default_repo_enable_locking",
174 172 "default_repo_private",
175 173 "default_repo_enable_downloads"]:
176 174 v = str2bool(v)
177 175 return v
178 176
179 177 @app_settings_value.setter
180 178 def app_settings_value(self, val):
181 179 """
182 180 Setter that will always make sure we use unicode in app_settings_value
183 181
184 182 :param val:
185 183 """
186 self._app_settings_value = safe_unicode(val)
184 self._app_settings_value = safe_str(val)
187 185
188 186 def __unicode__(self):
189 187 return u"<%s('%s:%s')>" % (
190 188 self.__class__.__name__,
191 189 self.app_settings_name, self.app_settings_value
192 190 )
193 191
194 192
195 193 class RhodeCodeUi(Base, BaseModel):
196 194 __tablename__ = 'rhodecode_ui'
197 195 __table_args__ = (
198 196 UniqueConstraint('ui_key'),
199 197 {'extend_existing': True, 'mysql_engine': 'InnoDB',
200 198 'mysql_charset': 'utf8'}
201 199 )
202 200
203 201 HOOK_REPO_SIZE = 'changegroup.repo_size'
204 202 HOOK_PUSH = 'changegroup.push_logger'
205 203 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
206 204 HOOK_PULL = 'outgoing.pull_logger'
207 205 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
208 206
209 207 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
210 208 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
211 209 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
212 210 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
213 211 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
214 212
215 213
216 214
217 215 class User(Base, BaseModel):
218 216 __tablename__ = 'users'
219 217 __table_args__ = (
220 218 UniqueConstraint('username'), UniqueConstraint('email'),
221 219 Index('u_username_idx', 'username'),
222 220 Index('u_email_idx', 'email'),
223 221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
224 222 'mysql_charset': 'utf8'}
225 223 )
226 224 DEFAULT_USER = 'default'
227 225 DEFAULT_PERMISSIONS = [
228 226 'hg.register.manual_activate', 'hg.create.repository',
229 227 'hg.fork.repository', 'repository.read', 'group.read'
230 228 ]
231 229 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
232 230 username = Column("username", String(255), nullable=True, unique=None, default=None)
233 231 password = Column("password", String(255), nullable=True, unique=None, default=None)
234 232 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
235 233 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
236 234 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
237 235 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
238 236 _email = Column("email", String(255), nullable=True, unique=None, default=None)
239 237 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
240 238 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
241 239 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
242 240 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
243 241
244 242 user_log = relationship('UserLog')
245 243 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
246 244
247 245 repositories = relationship('Repository')
248 246 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
249 247 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
250 248
251 249 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
252 250 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
253 251
254 252 group_member = relationship('UserGroupMember', cascade='all')
255 253
256 254 notifications = relationship('UserNotification', cascade='all')
257 255 # notifications assigned to this user
258 256 user_created_notifications = relationship('Notification', cascade='all')
259 257 # comments created by this user
260 258 user_comments = relationship('ChangesetComment', cascade='all')
261 259 user_emails = relationship('UserEmailMap', cascade='all')
262 260
263 261 @hybrid_property
264 262 def email(self):
265 263 return self._email
266 264
267 265 @email.setter
268 266 def email(self, val):
269 267 self._email = val.lower() if val else None
270 268
271 269 @property
272 270 def firstname(self):
273 271 # alias for future
274 272 return self.name
275 273
276 274 @property
277 275 def username_and_name(self):
278 276 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
279 277
280 278 @property
281 279 def full_name(self):
282 280 return '%s %s' % (self.firstname, self.lastname)
283 281
284 282 @property
285 283 def full_contact(self):
286 284 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
287 285
288 286 @property
289 287 def short_contact(self):
290 288 return '%s %s' % (self.firstname, self.lastname)
291 289
292 290 @property
293 291 def is_admin(self):
294 292 return self.admin
295 293
296 294 @classmethod
297 295 def get_by_username(cls, username, case_insensitive=False, cache=False):
298 296 if case_insensitive:
299 297 q = cls.query().filter(cls.username.ilike(username))
300 298 else:
301 299 q = cls.query().filter(cls.username == username)
302 300
303 301 if cache:
304 302 q = q.options(FromCache(
305 303 "sql_cache_short",
306 304 "get_user_%s" % _hash_key(username)
307 305 )
308 306 )
309 307 return q.scalar()
310 308
311 309 @classmethod
312 310 def get_by_auth_token(cls, auth_token, cache=False):
313 311 q = cls.query().filter(cls.api_key == auth_token)
314 312
315 313 if cache:
316 314 q = q.options(FromCache("sql_cache_short",
317 315 "get_auth_token_%s" % auth_token))
318 316 return q.scalar()
319 317
320 318 @classmethod
321 319 def get_by_email(cls, email, case_insensitive=False, cache=False):
322 320 if case_insensitive:
323 321 q = cls.query().filter(cls.email.ilike(email))
324 322 else:
325 323 q = cls.query().filter(cls.email == email)
326 324
327 325 if cache:
328 326 q = q.options(FromCache("sql_cache_short",
329 327 "get_email_key_%s" % email))
330 328
331 329 ret = q.scalar()
332 330 if ret is None:
333 331 q = UserEmailMap.query()
334 332 # try fetching in alternate email map
335 333 if case_insensitive:
336 334 q = q.filter(UserEmailMap.email.ilike(email))
337 335 else:
338 336 q = q.filter(UserEmailMap.email == email)
339 337 q = q.options(joinedload(UserEmailMap.user))
340 338 if cache:
341 339 q = q.options(FromCache("sql_cache_short",
342 340 "get_email_map_key_%s" % email))
343 341 ret = getattr(q.scalar(), 'user', None)
344 342
345 343 return ret
346 344
347 345
348 346 class UserEmailMap(Base, BaseModel):
349 347 __tablename__ = 'user_email_map'
350 348 __table_args__ = (
351 349 Index('uem_email_idx', 'email'),
352 350 UniqueConstraint('email'),
353 351 {'extend_existing': True, 'mysql_engine': 'InnoDB',
354 352 'mysql_charset': 'utf8'}
355 353 )
356 354 __mapper_args__ = {}
357 355
358 356 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
359 357 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
360 358 _email = Column("email", String(255), nullable=True, unique=False, default=None)
361 359 user = relationship('User', lazy='joined')
362 360
363 361 @validates('_email')
364 362 def validate_email(self, key, email):
365 363 # check if this email is not main one
366 364 main_email = Session().query(User).filter(User.email == email).scalar()
367 365 if main_email is not None:
368 366 raise AttributeError('email %s is present is user table' % email)
369 367 return email
370 368
371 369 @hybrid_property
372 370 def email(self):
373 371 return self._email
374 372
375 373 @email.setter
376 374 def email(self, val):
377 375 self._email = val.lower() if val else None
378 376
379 377
380 378 class UserIpMap(Base, BaseModel):
381 379 __tablename__ = 'user_ip_map'
382 380 __table_args__ = (
383 381 UniqueConstraint('user_id', 'ip_addr'),
384 382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
385 383 'mysql_charset': 'utf8'}
386 384 )
387 385 __mapper_args__ = {}
388 386
389 387 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
390 388 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
391 389 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
392 390 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
393 391 user = relationship('User', lazy='joined')
394 392
395 393
396 394 class UserLog(Base, BaseModel):
397 395 __tablename__ = 'user_logs'
398 396 __table_args__ = (
399 397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
400 398 'mysql_charset': 'utf8'},
401 399 )
402 400 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
403 401 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
404 402 username = Column("username", String(255), nullable=True, unique=None, default=None)
405 403 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
406 404 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
407 405 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
408 406 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
409 407 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
410 408
411 409
412 410 user = relationship('User')
413 411 repository = relationship('Repository', cascade='')
414 412
415 413
416 414 class UserGroup(Base, BaseModel):
417 415 __tablename__ = 'users_groups'
418 416 __table_args__ = (
419 417 {'extend_existing': True, 'mysql_engine': 'InnoDB',
420 418 'mysql_charset': 'utf8'},
421 419 )
422 420
423 421 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
424 422 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
425 423 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
426 424 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
427 425
428 426 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
429 427 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
430 428 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
431 429
432 430 def __unicode__(self):
433 431 return u'<userGroup(%s)>' % (self.users_group_name)
434 432
435 433 @classmethod
436 434 def get_by_group_name(cls, group_name, cache=False,
437 435 case_insensitive=False):
438 436 if case_insensitive:
439 437 q = cls.query().filter(cls.users_group_name.ilike(group_name))
440 438 else:
441 439 q = cls.query().filter(cls.users_group_name == group_name)
442 440 if cache:
443 441 q = q.options(FromCache(
444 442 "sql_cache_short",
445 443 "get_user_%s" % _hash_key(group_name)
446 444 )
447 445 )
448 446 return q.scalar()
449 447
450 448 @classmethod
451 449 def get(cls, users_group_id, cache=False):
452 450 user_group = cls.query()
453 451 if cache:
454 452 user_group = user_group.options(FromCache("sql_cache_short",
455 453 "get_users_group_%s" % users_group_id))
456 454 return user_group.get(users_group_id)
457 455
458 456
459 457 class UserGroupMember(Base, BaseModel):
460 458 __tablename__ = 'users_groups_members'
461 459 __table_args__ = (
462 460 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 461 'mysql_charset': 'utf8'},
464 462 )
465 463
466 464 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
467 465 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
468 466 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
469 467
470 468 user = relationship('User', lazy='joined')
471 469 users_group = relationship('UserGroup')
472 470
473 471 def __init__(self, gr_id='', u_id=''):
474 472 self.users_group_id = gr_id
475 473 self.user_id = u_id
476 474
477 475
478 476 class Repository(Base, BaseModel):
479 477 __tablename__ = 'repositories'
480 478 __table_args__ = (
481 479 UniqueConstraint('repo_name'),
482 480 Index('r_repo_name_idx', 'repo_name'),
483 481 {'extend_existing': True, 'mysql_engine': 'InnoDB',
484 482 'mysql_charset': 'utf8'},
485 483 )
486 484
487 485 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
488 486 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
489 487 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
490 488 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
491 489 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
492 490 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
493 491 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
494 492 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
495 493 description = Column("description", String(10000), nullable=True, unique=None, default=None)
496 494 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
497 495 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
498 496 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
499 497 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
500 498 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
501 499 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
502 500
503 501 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
504 502 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
505 503
506 504 user = relationship('User')
507 505 fork = relationship('Repository', remote_side=repo_id)
508 506 group = relationship('RepoGroup')
509 507 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
510 508 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
511 509 stats = relationship('Statistics', cascade='all', uselist=False)
512 510
513 511 followers = relationship('UserFollowing',
514 512 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
515 513 cascade='all')
516 514
517 515 logs = relationship('UserLog')
518 516 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
519 517
520 518 pull_requests_org = relationship('PullRequest',
521 519 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
522 520 cascade="all, delete, delete-orphan")
523 521
524 522 pull_requests_other = relationship('PullRequest',
525 523 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
526 524 cascade="all, delete, delete-orphan")
527 525
528 526 def __unicode__(self):
529 527 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
530 safe_unicode(self.repo_name))
528 safe_str(self.repo_name))
531 529
532 530
533 531 @classmethod
534 532 def get_by_repo_name(cls, repo_name):
535 533 q = Session().query(cls).filter(cls.repo_name == repo_name)
536 534 q = q.options(joinedload(Repository.fork))\
537 535 .options(joinedload(Repository.user))\
538 536 .options(joinedload(Repository.group))
539 537 return q.scalar()
540 538
541 539
542 540 class RepoGroup(Base, BaseModel):
543 541 __tablename__ = 'groups'
544 542 __table_args__ = (
545 543 UniqueConstraint('group_name', 'group_parent_id'),
546 544 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 545 'mysql_charset': 'utf8'},
548 546 )
549 547 __mapper_args__ = {'order_by': 'group_name'}
550 548
551 549 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
552 550 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
553 551 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
554 552 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
555 553 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
556 554
557 555 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
558 556 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
559 557 parent_group = relationship('RepoGroup', remote_side=group_id)
560 558
561 559 def __init__(self, group_name='', parent_group=None):
562 560 self.group_name = group_name
563 561 self.parent_group = parent_group
564 562
565 563 def __unicode__(self):
566 564 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
567 565 self.group_name)
568 566
569 567 @classmethod
570 568 def url_sep(cls):
571 569 return URL_SEP
572 570
573 571 @classmethod
574 572 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
575 573 if case_insensitive:
576 574 gr = cls.query()\
577 575 .filter(cls.group_name.ilike(group_name))
578 576 else:
579 577 gr = cls.query()\
580 578 .filter(cls.group_name == group_name)
581 579 if cache:
582 580 gr = gr.options(FromCache(
583 581 "sql_cache_short",
584 582 "get_group_%s" % _hash_key(group_name)
585 583 )
586 584 )
587 585 return gr.scalar()
588 586
589 587
590 588 class Permission(Base, BaseModel):
591 589 __tablename__ = 'permissions'
592 590 __table_args__ = (
593 591 Index('p_perm_name_idx', 'permission_name'),
594 592 {'extend_existing': True, 'mysql_engine': 'InnoDB',
595 593 'mysql_charset': 'utf8'},
596 594 )
597 595 PERMS = [
598 596 ('repository.none', _('Repository no access')),
599 597 ('repository.read', _('Repository read access')),
600 598 ('repository.write', _('Repository write access')),
601 599 ('repository.admin', _('Repository admin access')),
602 600
603 601 ('group.none', _('Repositories Group no access')),
604 602 ('group.read', _('Repositories Group read access')),
605 603 ('group.write', _('Repositories Group write access')),
606 604 ('group.admin', _('Repositories Group admin access')),
607 605
608 606 ('hg.admin', _('RhodeCode Administrator')),
609 607 ('hg.create.none', _('Repository creation disabled')),
610 608 ('hg.create.repository', _('Repository creation enabled')),
611 609 ('hg.fork.none', _('Repository forking disabled')),
612 610 ('hg.fork.repository', _('Repository forking enabled')),
613 611 ('hg.register.none', _('Register disabled')),
614 612 ('hg.register.manual_activate', _('Register new user with RhodeCode '
615 613 'with manual activation')),
616 614
617 615 ('hg.register.auto_activate', _('Register new user with RhodeCode '
618 616 'with auto activation')),
619 617 ]
620 618
621 619 # defines which permissions are more important higher the more important
622 620 PERM_WEIGHTS = {
623 621 'repository.none': 0,
624 622 'repository.read': 1,
625 623 'repository.write': 3,
626 624 'repository.admin': 4,
627 625
628 626 'group.none': 0,
629 627 'group.read': 1,
630 628 'group.write': 3,
631 629 'group.admin': 4,
632 630
633 631 'hg.fork.none': 0,
634 632 'hg.fork.repository': 1,
635 633 'hg.create.none': 0,
636 634 'hg.create.repository':1
637 635 }
638 636
639 637 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
640 638 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
641 639 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
642 640
643 641 def __unicode__(self):
644 642 return u"<%s('%s:%s')>" % (
645 643 self.__class__.__name__, self.permission_id, self.permission_name
646 644 )
647 645
648 646 @classmethod
649 647 def get_by_key(cls, key):
650 648 return cls.query().filter(cls.permission_name == key).scalar()
651 649
652 650
653 651 class UserRepoToPerm(Base, BaseModel):
654 652 __tablename__ = 'repo_to_perm'
655 653 __table_args__ = (
656 654 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
657 655 {'extend_existing': True, 'mysql_engine': 'InnoDB',
658 656 'mysql_charset': 'utf8'}
659 657 )
660 658 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
661 659 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
662 660 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
663 661 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
664 662
665 663 user = relationship('User')
666 664 repository = relationship('Repository')
667 665 permission = relationship('Permission')
668 666
669 667 def __unicode__(self):
670 668 return u'<user:%s => %s >' % (self.user, self.repository)
671 669
672 670
673 671 class UserToPerm(Base, BaseModel):
674 672 __tablename__ = 'user_to_perm'
675 673 __table_args__ = (
676 674 UniqueConstraint('user_id', 'permission_id'),
677 675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
678 676 'mysql_charset': 'utf8'}
679 677 )
680 678 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
681 679 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
682 680 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
683 681
684 682 user = relationship('User')
685 683 permission = relationship('Permission', lazy='joined')
686 684
687 685
688 686 class UserGroupRepoToPerm(Base, BaseModel):
689 687 __tablename__ = 'users_group_repo_to_perm'
690 688 __table_args__ = (
691 689 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
692 690 {'extend_existing': True, 'mysql_engine': 'InnoDB',
693 691 'mysql_charset': 'utf8'}
694 692 )
695 693 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
696 694 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
697 695 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
698 696 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
699 697
700 698 users_group = relationship('UserGroup')
701 699 permission = relationship('Permission')
702 700 repository = relationship('Repository')
703 701
704 702 def __unicode__(self):
705 703 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
706 704
707 705
708 706 class UserGroupToPerm(Base, BaseModel):
709 707 __tablename__ = 'users_group_to_perm'
710 708 __table_args__ = (
711 709 UniqueConstraint('users_group_id', 'permission_id',),
712 710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
713 711 'mysql_charset': 'utf8'}
714 712 )
715 713 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
716 714 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
717 715 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
718 716
719 717 users_group = relationship('UserGroup')
720 718 permission = relationship('Permission')
721 719
722 720
723 721 class UserRepoGroupToPerm(Base, BaseModel):
724 722 __tablename__ = 'user_repo_group_to_perm'
725 723 __table_args__ = (
726 724 UniqueConstraint('user_id', 'group_id', 'permission_id'),
727 725 {'extend_existing': True, 'mysql_engine': 'InnoDB',
728 726 'mysql_charset': 'utf8'}
729 727 )
730 728
731 729 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
732 730 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
733 731 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
734 732 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
735 733
736 734 user = relationship('User')
737 735 group = relationship('RepoGroup')
738 736 permission = relationship('Permission')
739 737
740 738
741 739 class UserGroupRepoGroupToPerm(Base, BaseModel):
742 740 __tablename__ = 'users_group_repo_group_to_perm'
743 741 __table_args__ = (
744 742 UniqueConstraint('users_group_id', 'group_id'),
745 743 {'extend_existing': True, 'mysql_engine': 'InnoDB',
746 744 'mysql_charset': 'utf8'}
747 745 )
748 746
749 747 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)
750 748 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
751 749 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
752 750 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
753 751
754 752 users_group = relationship('UserGroup')
755 753 permission = relationship('Permission')
756 754 group = relationship('RepoGroup')
757 755
758 756
759 757 class Statistics(Base, BaseModel):
760 758 __tablename__ = 'statistics'
761 759 __table_args__ = (
762 760 UniqueConstraint('repository_id'),
763 761 {'extend_existing': True, 'mysql_engine': 'InnoDB',
764 762 'mysql_charset': 'utf8'}
765 763 )
766 764 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
767 765 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
768 766 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
769 767 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
770 768 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
771 769 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
772 770
773 771 repository = relationship('Repository', single_parent=True)
774 772
775 773
776 774 class UserFollowing(Base, BaseModel):
777 775 __tablename__ = 'user_followings'
778 776 __table_args__ = (
779 777 UniqueConstraint('user_id', 'follows_repository_id'),
780 778 UniqueConstraint('user_id', 'follows_user_id'),
781 779 {'extend_existing': True, 'mysql_engine': 'InnoDB',
782 780 'mysql_charset': 'utf8'}
783 781 )
784 782
785 783 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
786 784 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
787 785 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
788 786 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
789 787 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
790 788
791 789 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
792 790
793 791 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
794 792 follows_repository = relationship('Repository', order_by='Repository.repo_name')
795 793
796 794
797 795 class CacheInvalidation(Base, BaseModel):
798 796 __tablename__ = 'cache_invalidation'
799 797 __table_args__ = (
800 798 UniqueConstraint('cache_key'),
801 799 Index('key_idx', 'cache_key'),
802 800 {'extend_existing': True, 'mysql_engine': 'InnoDB',
803 801 'mysql_charset': 'utf8'},
804 802 )
805 803 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
806 804 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
807 805 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
808 806 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
809 807
810 808 def __init__(self, cache_key, cache_args=''):
811 809 self.cache_key = cache_key
812 810 self.cache_args = cache_args
813 811 self.cache_active = False
814 812
815 813
816 814 class ChangesetComment(Base, BaseModel):
817 815 __tablename__ = 'changeset_comments'
818 816 __table_args__ = (
819 817 Index('cc_revision_idx', 'revision'),
820 818 {'extend_existing': True, 'mysql_engine': 'InnoDB',
821 819 'mysql_charset': 'utf8'},
822 820 )
823 821 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
824 822 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
825 823 revision = Column('revision', String(40), nullable=True)
826 824 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
827 825 line_no = Column('line_no', Unicode(10), nullable=True)
828 826 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
829 827 f_path = Column('f_path', Unicode(1000), nullable=True)
830 828 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
831 829 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
832 830 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
833 831 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
834 832
835 833 author = relationship('User', lazy='joined')
836 834 repo = relationship('Repository')
837 835 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
838 836 pull_request = relationship('PullRequest', lazy='joined')
839 837
840 838 @classmethod
841 839 def get_users(cls, revision=None, pull_request_id=None):
842 840 """
843 841 Returns user associated with this ChangesetComment. ie those
844 842 who actually commented
845 843
846 844 :param cls:
847 845 :param revision:
848 846 """
849 847 q = Session().query(User)\
850 848 .join(ChangesetComment.author)
851 849 if revision:
852 850 q = q.filter(cls.revision == revision)
853 851 elif pull_request_id:
854 852 q = q.filter(cls.pull_request_id == pull_request_id)
855 853 return q.all()
856 854
857 855
858 856 class ChangesetStatus(Base, BaseModel):
859 857 __tablename__ = 'changeset_statuses'
860 858 __table_args__ = (
861 859 Index('cs_revision_idx', 'revision'),
862 860 Index('cs_version_idx', 'version'),
863 861 UniqueConstraint('repo_id', 'revision', 'version'),
864 862 {'extend_existing': True, 'mysql_engine': 'InnoDB',
865 863 'mysql_charset': 'utf8'}
866 864 )
867 865 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
868 866 STATUS_APPROVED = 'approved'
869 867 STATUS_REJECTED = 'rejected'
870 868 STATUS_UNDER_REVIEW = 'under_review'
871 869
872 870 STATUSES = [
873 871 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
874 872 (STATUS_APPROVED, _("Approved")),
875 873 (STATUS_REJECTED, _("Rejected")),
876 874 (STATUS_UNDER_REVIEW, _("Under Review")),
877 875 ]
878 876
879 877 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
880 878 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
881 879 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
882 880 revision = Column('revision', String(40), nullable=False)
883 881 status = Column('status', String(128), nullable=False, default=DEFAULT)
884 882 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
885 883 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
886 884 version = Column('version', Integer(), nullable=False, default=0)
887 885 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
888 886
889 887 author = relationship('User', lazy='joined')
890 888 repo = relationship('Repository')
891 889 comment = relationship('ChangesetComment', lazy='joined')
892 890 pull_request = relationship('PullRequest', lazy='joined')
893 891
894 892
895 893
896 894 class PullRequest(Base, BaseModel):
897 895 __tablename__ = 'pull_requests'
898 896 __table_args__ = (
899 897 {'extend_existing': True, 'mysql_engine': 'InnoDB',
900 898 'mysql_charset': 'utf8'},
901 899 )
902 900
903 901 STATUS_NEW = u'new'
904 902 STATUS_OPEN = u'open'
905 903 STATUS_CLOSED = u'closed'
906 904
907 905 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
908 906 title = Column('title', Unicode(256), nullable=True)
909 907 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
910 908 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
911 909 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
912 910 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
913 911 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
914 912 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
915 913 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
916 914 org_ref = Column('org_ref', Unicode(256), nullable=False)
917 915 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
918 916 other_ref = Column('other_ref', Unicode(256), nullable=False)
919 917
920 918 author = relationship('User', lazy='joined')
921 919 reviewers = relationship('PullRequestReviewers',
922 920 cascade="all, delete, delete-orphan")
923 921 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
924 922 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
925 923 statuses = relationship('ChangesetStatus')
926 924 comments = relationship('ChangesetComment',
927 925 cascade="all, delete, delete-orphan")
928 926
929 927
930 928 class PullRequestReviewers(Base, BaseModel):
931 929 __tablename__ = 'pull_request_reviewers'
932 930 __table_args__ = (
933 931 {'extend_existing': True, 'mysql_engine': 'InnoDB',
934 932 'mysql_charset': 'utf8'},
935 933 )
936 934
937 935 def __init__(self, user=None, pull_request=None):
938 936 self.user = user
939 937 self.pull_request = pull_request
940 938
941 939 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
942 940 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
943 941 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
944 942
945 943 user = relationship('User')
946 944 pull_request = relationship('PullRequest')
947 945
948 946
949 947 class Notification(Base, BaseModel):
950 948 __tablename__ = 'notifications'
951 949 __table_args__ = (
952 950 Index('notification_type_idx', 'type'),
953 951 {'extend_existing': True, 'mysql_engine': 'InnoDB',
954 952 'mysql_charset': 'utf8'},
955 953 )
956 954
957 955 TYPE_CHANGESET_COMMENT = u'cs_comment'
958 956 TYPE_MESSAGE = u'message'
959 957 TYPE_MENTION = u'mention'
960 958 TYPE_REGISTRATION = u'registration'
961 959 TYPE_PULL_REQUEST = u'pull_request'
962 960 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
963 961
964 962 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
965 963 subject = Column('subject', Unicode(512), nullable=True)
966 964 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
967 965 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
968 966 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
969 967 type_ = Column('type', Unicode(256))
970 968
971 969 created_by_user = relationship('User')
972 970 notifications_to_users = relationship('UserNotification', lazy='joined',
973 971 cascade="all, delete, delete-orphan")
974 972
975 973
976 974 class UserNotification(Base, BaseModel):
977 975 __tablename__ = 'user_to_notification'
978 976 __table_args__ = (
979 977 UniqueConstraint('user_id', 'notification_id'),
980 978 {'extend_existing': True, 'mysql_engine': 'InnoDB',
981 979 'mysql_charset': 'utf8'}
982 980 )
983 981 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
984 982 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
985 983 read = Column('read', Boolean, default=False)
986 984 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
987 985
988 986 user = relationship('User', lazy="joined")
989 987 notification = relationship('Notification', lazy="joined",
990 988 order_by=lambda: Notification.created_on.desc(),)
991 989
992 990
993 991 class DbMigrateVersion(Base, BaseModel):
994 992 __tablename__ = 'db_migrate_version'
995 993 __table_args__ = (
996 994 {'extend_existing': True, 'mysql_engine': 'InnoDB',
997 995 'mysql_charset': 'utf8'},
998 996 )
999 997 repository_id = Column('repository_id', String(250), primary_key=True)
1000 998 repository_path = Column('repository_path', Text)
1001 999 version = Column('version', Integer)
@@ -1,1084 +1,1083 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 import os
22 21 import time
23 22 import logging
24 23 import datetime
25 24 import traceback
26 25 import hashlib
27 26 import collections
28 27
29 28 from sqlalchemy import *
30 29 from sqlalchemy.ext.hybrid import hybrid_property
31 30 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 31 from sqlalchemy.exc import DatabaseError
33 32 from beaker.cache import cache_region, region_invalidate
34 33 from webob.exc import HTTPNotFound
35 34
36 35 from rhodecode.translation import _
37 36
38 37 from rhodecode.lib.vcs import get_backend
39 38 from rhodecode.lib.vcs.utils.helpers import get_scm
40 39 from rhodecode.lib.vcs.exceptions import VCSError
41 40 from zope.cachedescriptors.property import Lazy as LazyProperty
42 41 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 42
44 43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 safe_unicode, remove_suffix, remove_prefix, time_to_datetime
44 remove_suffix, remove_prefix, time_to_datetime
46 45 from rhodecode.lib.ext_json import json
47 46 from rhodecode.lib.caching_query import FromCache
48 47
49 48 from rhodecode.model.meta import Base, Session
50 49
51 50 URL_SEP = '/'
52 51 log = logging.getLogger(__name__)
53 52
54 53 #==============================================================================
55 54 # BASE CLASSES
56 55 #==============================================================================
57 56
58 57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 58
60 59
61 60 class BaseModel(object):
62 61 """
63 62 Base Model for all classes
64 63 """
65 64
66 65 @classmethod
67 66 def _get_keys(cls):
68 67 """return column names for this model """
69 68 return class_mapper(cls).c.keys()
70 69
71 70 def get_dict(self):
72 71 """
73 72 return dict with keys and values corresponding
74 73 to this model data """
75 74
76 75 d = {}
77 76 for k in self._get_keys():
78 77 d[k] = getattr(self, k)
79 78
80 79 # also use __json__() if present to get additional fields
81 80 _json_attr = getattr(self, '__json__', None)
82 81 if _json_attr:
83 82 # update with attributes from __json__
84 83 if callable(_json_attr):
85 84 _json_attr = _json_attr()
86 85 for k, val in _json_attr.items():
87 86 d[k] = val
88 87 return d
89 88
90 89 def get_appstruct(self):
91 90 """return list with keys and values tupples corresponding
92 91 to this model data """
93 92
94 93 l = []
95 94 for k in self._get_keys():
96 95 l.append((k, getattr(self, k),))
97 96 return l
98 97
99 98 def populate_obj(self, populate_dict):
100 99 """populate model with data from given populate_dict"""
101 100
102 101 for k in self._get_keys():
103 102 if k in populate_dict:
104 103 setattr(self, k, populate_dict[k])
105 104
106 105 @classmethod
107 106 def query(cls):
108 107 return Session().query(cls)
109 108
110 109 @classmethod
111 110 def get(cls, id_):
112 111 if id_:
113 112 return cls.query().get(id_)
114 113
115 114 @classmethod
116 115 def get_or_404(cls, id_):
117 116 try:
118 117 id_ = int(id_)
119 118 except (TypeError, ValueError):
120 119 raise HTTPNotFound
121 120
122 121 res = cls.query().get(id_)
123 122 if not res:
124 123 raise HTTPNotFound
125 124 return res
126 125
127 126 @classmethod
128 127 def getAll(cls):
129 128 # deprecated and left for backward compatibility
130 129 return cls.get_all()
131 130
132 131 @classmethod
133 132 def get_all(cls):
134 133 return cls.query().all()
135 134
136 135 @classmethod
137 136 def delete(cls, id_):
138 137 obj = cls.query().get(id_)
139 138 Session().delete(obj)
140 139
141 140 def __repr__(self):
142 141 if hasattr(self, '__unicode__'):
143 142 # python repr needs to return str
144 143 return safe_str(self.__unicode__())
145 144 return '<DB:%s>' % (self.__class__.__name__)
146 145
147 146
148 147 class RhodeCodeSetting(Base, BaseModel):
149 148 __tablename__ = 'rhodecode_settings'
150 149 __table_args__ = (
151 150 UniqueConstraint('app_settings_name'),
152 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 152 'mysql_charset': 'utf8'}
154 153 )
155 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 155 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
157 156 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
158 157
159 158 def __init__(self, k='', v=''):
160 159 self.app_settings_name = k
161 160 self.app_settings_value = v
162 161
163 162 @validates('_app_settings_value')
164 163 def validate_settings_value(self, key, val):
165 assert type(val) == unicode
164 assert type(val) == str
166 165 return val
167 166
168 167 @hybrid_property
169 168 def app_settings_value(self):
170 169 v = self._app_settings_value
171 170 if self.app_settings_name in ["ldap_active",
172 171 "default_repo_enable_statistics",
173 172 "default_repo_enable_locking",
174 173 "default_repo_private",
175 174 "default_repo_enable_downloads"]:
176 175 v = str2bool(v)
177 176 return v
178 177
179 178 @app_settings_value.setter
180 179 def app_settings_value(self, val):
181 180 """
182 181 Setter that will always make sure we use unicode in app_settings_value
183 182
184 183 :param val:
185 184 """
186 self._app_settings_value = safe_unicode(val)
185 self._app_settings_value = safe_str(val)
187 186
188 187 def __unicode__(self):
189 188 return u"<%s('%s:%s')>" % (
190 189 self.__class__.__name__,
191 190 self.app_settings_name, self.app_settings_value
192 191 )
193 192
194 193
195 194 class RhodeCodeUi(Base, BaseModel):
196 195 __tablename__ = 'rhodecode_ui'
197 196 __table_args__ = (
198 197 UniqueConstraint('ui_key'),
199 198 {'extend_existing': True, 'mysql_engine': 'InnoDB',
200 199 'mysql_charset': 'utf8'}
201 200 )
202 201
203 202 HOOK_REPO_SIZE = 'changegroup.repo_size'
204 203 HOOK_PUSH = 'changegroup.push_logger'
205 204 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
206 205 HOOK_PULL = 'outgoing.pull_logger'
207 206 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
208 207
209 208 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
210 209 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
211 210 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
212 211 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
213 212 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
214 213
215 214
216 215
217 216 class User(Base, BaseModel):
218 217 __tablename__ = 'users'
219 218 __table_args__ = (
220 219 UniqueConstraint('username'), UniqueConstraint('email'),
221 220 Index('u_username_idx', 'username'),
222 221 Index('u_email_idx', 'email'),
223 222 {'extend_existing': True, 'mysql_engine': 'InnoDB',
224 223 'mysql_charset': 'utf8'}
225 224 )
226 225 DEFAULT_USER = 'default'
227 226 DEFAULT_PERMISSIONS = [
228 227 'hg.register.manual_activate', 'hg.create.repository',
229 228 'hg.fork.repository', 'repository.read', 'group.read'
230 229 ]
231 230 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
232 231 username = Column("username", String(255), nullable=True, unique=None, default=None)
233 232 password = Column("password", String(255), nullable=True, unique=None, default=None)
234 233 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
235 234 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
236 235 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
237 236 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
238 237 _email = Column("email", String(255), nullable=True, unique=None, default=None)
239 238 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
240 239 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
241 240 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
242 241 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
243 242
244 243 user_log = relationship('UserLog')
245 244 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
246 245
247 246 repositories = relationship('Repository')
248 247 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
249 248 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
250 249
251 250 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
252 251 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
253 252
254 253 group_member = relationship('UserGroupMember', cascade='all')
255 254
256 255 notifications = relationship('UserNotification', cascade='all')
257 256 # notifications assigned to this user
258 257 user_created_notifications = relationship('Notification', cascade='all')
259 258 # comments created by this user
260 259 user_comments = relationship('ChangesetComment', cascade='all')
261 260 user_emails = relationship('UserEmailMap', cascade='all')
262 261
263 262 @hybrid_property
264 263 def email(self):
265 264 return self._email
266 265
267 266 @email.setter
268 267 def email(self, val):
269 268 self._email = val.lower() if val else None
270 269
271 270 @property
272 271 def firstname(self):
273 272 # alias for future
274 273 return self.name
275 274
276 275 @property
277 276 def username_and_name(self):
278 277 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
279 278
280 279 @property
281 280 def full_name(self):
282 281 return '%s %s' % (self.firstname, self.lastname)
283 282
284 283 @property
285 284 def full_contact(self):
286 285 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
287 286
288 287 @property
289 288 def short_contact(self):
290 289 return '%s %s' % (self.firstname, self.lastname)
291 290
292 291 @property
293 292 def is_admin(self):
294 293 return self.admin
295 294
296 295 @classmethod
297 296 def get_by_username(cls, username, case_insensitive=False, cache=False):
298 297 if case_insensitive:
299 298 q = cls.query().filter(cls.username.ilike(username))
300 299 else:
301 300 q = cls.query().filter(cls.username == username)
302 301
303 302 if cache:
304 303 q = q.options(FromCache(
305 304 "sql_cache_short",
306 305 "get_user_%s" % _hash_key(username)
307 306 )
308 307 )
309 308 return q.scalar()
310 309
311 310 @classmethod
312 311 def get_by_auth_token(cls, auth_token, cache=False):
313 312 q = cls.query().filter(cls.api_key == auth_token)
314 313
315 314 if cache:
316 315 q = q.options(FromCache("sql_cache_short",
317 316 "get_auth_token_%s" % auth_token))
318 317 return q.scalar()
319 318
320 319 @classmethod
321 320 def get_by_email(cls, email, case_insensitive=False, cache=False):
322 321 if case_insensitive:
323 322 q = cls.query().filter(cls.email.ilike(email))
324 323 else:
325 324 q = cls.query().filter(cls.email == email)
326 325
327 326 if cache:
328 327 q = q.options(FromCache("sql_cache_short",
329 328 "get_email_key_%s" % email))
330 329
331 330 ret = q.scalar()
332 331 if ret is None:
333 332 q = UserEmailMap.query()
334 333 # try fetching in alternate email map
335 334 if case_insensitive:
336 335 q = q.filter(UserEmailMap.email.ilike(email))
337 336 else:
338 337 q = q.filter(UserEmailMap.email == email)
339 338 q = q.options(joinedload(UserEmailMap.user))
340 339 if cache:
341 340 q = q.options(FromCache("sql_cache_short",
342 341 "get_email_map_key_%s" % email))
343 342 ret = getattr(q.scalar(), 'user', None)
344 343
345 344 return ret
346 345
347 346
348 347 class UserEmailMap(Base, BaseModel):
349 348 __tablename__ = 'user_email_map'
350 349 __table_args__ = (
351 350 Index('uem_email_idx', 'email'),
352 351 UniqueConstraint('email'),
353 352 {'extend_existing': True, 'mysql_engine': 'InnoDB',
354 353 'mysql_charset': 'utf8'}
355 354 )
356 355 __mapper_args__ = {}
357 356
358 357 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
359 358 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
360 359 _email = Column("email", String(255), nullable=True, unique=False, default=None)
361 360 user = relationship('User', lazy='joined')
362 361
363 362 @validates('_email')
364 363 def validate_email(self, key, email):
365 364 # check if this email is not main one
366 365 main_email = Session().query(User).filter(User.email == email).scalar()
367 366 if main_email is not None:
368 367 raise AttributeError('email %s is present is user table' % email)
369 368 return email
370 369
371 370 @hybrid_property
372 371 def email(self):
373 372 return self._email
374 373
375 374 @email.setter
376 375 def email(self, val):
377 376 self._email = val.lower() if val else None
378 377
379 378
380 379 class UserIpMap(Base, BaseModel):
381 380 __tablename__ = 'user_ip_map'
382 381 __table_args__ = (
383 382 UniqueConstraint('user_id', 'ip_addr'),
384 383 {'extend_existing': True, 'mysql_engine': 'InnoDB',
385 384 'mysql_charset': 'utf8'}
386 385 )
387 386 __mapper_args__ = {}
388 387
389 388 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
390 389 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
391 390 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
392 391 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
393 392 user = relationship('User', lazy='joined')
394 393
395 394
396 395 class UserLog(Base, BaseModel):
397 396 __tablename__ = 'user_logs'
398 397 __table_args__ = (
399 398 {'extend_existing': True, 'mysql_engine': 'InnoDB',
400 399 'mysql_charset': 'utf8'},
401 400 )
402 401 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
403 402 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
404 403 username = Column("username", String(255), nullable=True, unique=None, default=None)
405 404 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
406 405 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
407 406 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
408 407 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
409 408 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
410 409
411 410
412 411 user = relationship('User')
413 412 repository = relationship('Repository', cascade='')
414 413
415 414
416 415 class UserGroup(Base, BaseModel):
417 416 __tablename__ = 'users_groups'
418 417 __table_args__ = (
419 418 {'extend_existing': True, 'mysql_engine': 'InnoDB',
420 419 'mysql_charset': 'utf8'},
421 420 )
422 421
423 422 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
424 423 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
425 424 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
426 425 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
427 426
428 427 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
429 428 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
430 429 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
431 430
432 431 def __unicode__(self):
433 432 return u'<userGroup(%s)>' % (self.users_group_name)
434 433
435 434 @classmethod
436 435 def get_by_group_name(cls, group_name, cache=False,
437 436 case_insensitive=False):
438 437 if case_insensitive:
439 438 q = cls.query().filter(cls.users_group_name.ilike(group_name))
440 439 else:
441 440 q = cls.query().filter(cls.users_group_name == group_name)
442 441 if cache:
443 442 q = q.options(FromCache(
444 443 "sql_cache_short",
445 444 "get_user_%s" % _hash_key(group_name)
446 445 )
447 446 )
448 447 return q.scalar()
449 448
450 449 @classmethod
451 450 def get(cls, users_group_id, cache=False):
452 451 user_group = cls.query()
453 452 if cache:
454 453 user_group = user_group.options(FromCache("sql_cache_short",
455 454 "get_users_group_%s" % users_group_id))
456 455 return user_group.get(users_group_id)
457 456
458 457
459 458 class UserGroupMember(Base, BaseModel):
460 459 __tablename__ = 'users_groups_members'
461 460 __table_args__ = (
462 461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 462 'mysql_charset': 'utf8'},
464 463 )
465 464
466 465 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
467 466 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
468 467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
469 468
470 469 user = relationship('User', lazy='joined')
471 470 users_group = relationship('UserGroup')
472 471
473 472 def __init__(self, gr_id='', u_id=''):
474 473 self.users_group_id = gr_id
475 474 self.user_id = u_id
476 475
477 476
478 477 class RepositoryField(Base, BaseModel):
479 478 __tablename__ = 'repositories_fields'
480 479 __table_args__ = (
481 480 UniqueConstraint('repository_id', 'field_key'), # no-multi field
482 481 {'extend_existing': True, 'mysql_engine': 'InnoDB',
483 482 'mysql_charset': 'utf8'},
484 483 )
485 484 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
486 485
487 486 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
488 487 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
489 488 field_key = Column("field_key", String(250))
490 489 field_label = Column("field_label", String(1024), nullable=False)
491 490 field_value = Column("field_value", String(10000), nullable=False)
492 491 field_desc = Column("field_desc", String(1024), nullable=False)
493 492 field_type = Column("field_type", String(256), nullable=False, unique=None)
494 493 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
495 494
496 495 repository = relationship('Repository')
497 496
498 497 @classmethod
499 498 def get_by_key_name(cls, key, repo):
500 499 row = cls.query()\
501 500 .filter(cls.repository == repo)\
502 501 .filter(cls.field_key == key).scalar()
503 502 return row
504 503
505 504
506 505 class Repository(Base, BaseModel):
507 506 __tablename__ = 'repositories'
508 507 __table_args__ = (
509 508 UniqueConstraint('repo_name'),
510 509 Index('r_repo_name_idx', 'repo_name'),
511 510 {'extend_existing': True, 'mysql_engine': 'InnoDB',
512 511 'mysql_charset': 'utf8'},
513 512 )
514 513
515 514 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
516 515 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
517 516 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
518 517 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
519 518 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
520 519 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
521 520 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
522 521 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
523 522 description = Column("description", String(10000), nullable=True, unique=None, default=None)
524 523 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
525 524 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
526 525 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
527 526 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
528 527 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
529 528 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
530 529
531 530 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
532 531 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
533 532
534 533 user = relationship('User')
535 534 fork = relationship('Repository', remote_side=repo_id)
536 535 group = relationship('RepoGroup')
537 536 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
538 537 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
539 538 stats = relationship('Statistics', cascade='all', uselist=False)
540 539
541 540 followers = relationship('UserFollowing',
542 541 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
543 542 cascade='all')
544 543 extra_fields = relationship('RepositoryField',
545 544 cascade="all, delete, delete-orphan")
546 545
547 546 logs = relationship('UserLog')
548 547 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
549 548
550 549 pull_requests_org = relationship('PullRequest',
551 550 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
552 551 cascade="all, delete, delete-orphan")
553 552
554 553 pull_requests_other = relationship('PullRequest',
555 554 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
556 555 cascade="all, delete, delete-orphan")
557 556
558 557 def __unicode__(self):
559 558 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
560 safe_unicode(self.repo_name))
559 safe_str(self.repo_name))
561 560
562 561 #NOTE for this migration we are required tio have it
563 562 @hybrid_property
564 563 def changeset_cache(self):
565 564 from rhodecode.lib.vcs.backends.base import EmptyCommit
566 565 dummy = EmptyCommit().__json__()
567 566 if not self._changeset_cache:
568 567 return dummy
569 568 try:
570 569 return json.loads(self._changeset_cache)
571 570 except TypeError:
572 571 return dummy
573 572
574 573 @changeset_cache.setter
575 574 def changeset_cache(self, val):
576 575 try:
577 576 self._changeset_cache = json.dumps(val)
578 577 except Exception:
579 578 log.error(traceback.format_exc())
580 579
581 580 @classmethod
582 581 def get_by_repo_name(cls, repo_name):
583 582 q = Session().query(cls).filter(cls.repo_name == repo_name)
584 583 q = q.options(joinedload(Repository.fork))\
585 584 .options(joinedload(Repository.user))\
586 585 .options(joinedload(Repository.group))
587 586 return q.scalar()
588 587
589 588 #NOTE this is required for this migration to work
590 589 def update_commit_cache(self, cs_cache=None):
591 590 """
592 591 Update cache of last changeset for repository, keys should be::
593 592
594 593 short_id
595 594 raw_id
596 595 revision
597 596 message
598 597 date
599 598 author
600 599
601 600 :param cs_cache:
602 601 """
603 602 from rhodecode.lib.vcs.backends.base import BaseChangeset
604 603 if cs_cache is None:
605 604 cs_cache = EmptyCommit()
606 605 # Note: Using always the empty commit here in case we are
607 606 # upgrading towards version 3.0 and above. Reason is that in this
608 607 # case the vcsclient connection is not available and things
609 608 # would explode here.
610 609
611 610 if isinstance(cs_cache, BaseChangeset):
612 611 cs_cache = cs_cache.__json__()
613 612
614 613 if (cs_cache != self.changeset_cache or not self.changeset_cache):
615 614 _default = datetime.datetime.fromtimestamp(0)
616 615 last_change = cs_cache.get('date') or _default
617 616 log.debug('updated repo %s with new commit cache %s', self.repo_name, cs_cache)
618 617 self.updated_on = last_change
619 618 self.changeset_cache = cs_cache
620 619 Session().add(self)
621 620 Session().commit()
622 621 else:
623 622 log.debug('Skipping repo:%s already with latest changes', self.repo_name)
624 623
625 624 class RepoGroup(Base, BaseModel):
626 625 __tablename__ = 'groups'
627 626 __table_args__ = (
628 627 UniqueConstraint('group_name', 'group_parent_id'),
629 628 {'extend_existing': True, 'mysql_engine': 'InnoDB',
630 629 'mysql_charset': 'utf8'},
631 630 )
632 631 __mapper_args__ = {'order_by': 'group_name'}
633 632
634 633 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
635 634 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
636 635 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
637 636 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
638 637 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
639 638
640 639 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
641 640 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
642 641 parent_group = relationship('RepoGroup', remote_side=group_id)
643 642
644 643 def __init__(self, group_name='', parent_group=None):
645 644 self.group_name = group_name
646 645 self.parent_group = parent_group
647 646
648 647 def __unicode__(self):
649 648 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
650 649 self.group_name)
651 650
652 651 @classmethod
653 652 def url_sep(cls):
654 653 return URL_SEP
655 654
656 655 @classmethod
657 656 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
658 657 if case_insensitive:
659 658 gr = cls.query()\
660 659 .filter(cls.group_name.ilike(group_name))
661 660 else:
662 661 gr = cls.query()\
663 662 .filter(cls.group_name == group_name)
664 663 if cache:
665 664 gr = gr.options(FromCache(
666 665 "sql_cache_short",
667 666 "get_group_%s" % _hash_key(group_name)
668 667 )
669 668 )
670 669 return gr.scalar()
671 670
672 671
673 672 class Permission(Base, BaseModel):
674 673 __tablename__ = 'permissions'
675 674 __table_args__ = (
676 675 Index('p_perm_name_idx', 'permission_name'),
677 676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
678 677 'mysql_charset': 'utf8'},
679 678 )
680 679 PERMS = [
681 680 ('repository.none', _('Repository no access')),
682 681 ('repository.read', _('Repository read access')),
683 682 ('repository.write', _('Repository write access')),
684 683 ('repository.admin', _('Repository admin access')),
685 684
686 685 ('group.none', _('Repository group no access')),
687 686 ('group.read', _('Repository group read access')),
688 687 ('group.write', _('Repository group write access')),
689 688 ('group.admin', _('Repository group admin access')),
690 689
691 690 ('hg.admin', _('RhodeCode Administrator')),
692 691 ('hg.create.none', _('Repository creation disabled')),
693 692 ('hg.create.repository', _('Repository creation enabled')),
694 693 ('hg.fork.none', _('Repository forking disabled')),
695 694 ('hg.fork.repository', _('Repository forking enabled')),
696 695 ('hg.register.none', _('Register disabled')),
697 696 ('hg.register.manual_activate', _('Register new user with RhodeCode '
698 697 'with manual activation')),
699 698
700 699 ('hg.register.auto_activate', _('Register new user with RhodeCode '
701 700 'with auto activation')),
702 701 ]
703 702
704 703 # defines which permissions are more important higher the more important
705 704 PERM_WEIGHTS = {
706 705 'repository.none': 0,
707 706 'repository.read': 1,
708 707 'repository.write': 3,
709 708 'repository.admin': 4,
710 709
711 710 'group.none': 0,
712 711 'group.read': 1,
713 712 'group.write': 3,
714 713 'group.admin': 4,
715 714
716 715 'hg.fork.none': 0,
717 716 'hg.fork.repository': 1,
718 717 'hg.create.none': 0,
719 718 'hg.create.repository':1
720 719 }
721 720
722 721 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
723 722 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
724 723 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
725 724
726 725 def __unicode__(self):
727 726 return u"<%s('%s:%s')>" % (
728 727 self.__class__.__name__, self.permission_id, self.permission_name
729 728 )
730 729
731 730 @classmethod
732 731 def get_by_key(cls, key):
733 732 return cls.query().filter(cls.permission_name == key).scalar()
734 733
735 734
736 735 class UserRepoToPerm(Base, BaseModel):
737 736 __tablename__ = 'repo_to_perm'
738 737 __table_args__ = (
739 738 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
740 739 {'extend_existing': True, 'mysql_engine': 'InnoDB',
741 740 'mysql_charset': 'utf8'}
742 741 )
743 742 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
744 743 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
745 744 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
746 745 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
747 746
748 747 user = relationship('User')
749 748 repository = relationship('Repository')
750 749 permission = relationship('Permission')
751 750
752 751 def __unicode__(self):
753 752 return u'<user:%s => %s >' % (self.user, self.repository)
754 753
755 754
756 755 class UserToPerm(Base, BaseModel):
757 756 __tablename__ = 'user_to_perm'
758 757 __table_args__ = (
759 758 UniqueConstraint('user_id', 'permission_id'),
760 759 {'extend_existing': True, 'mysql_engine': 'InnoDB',
761 760 'mysql_charset': 'utf8'}
762 761 )
763 762 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
764 763 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
765 764 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
766 765
767 766 user = relationship('User')
768 767 permission = relationship('Permission', lazy='joined')
769 768
770 769
771 770 class UserGroupRepoToPerm(Base, BaseModel):
772 771 __tablename__ = 'users_group_repo_to_perm'
773 772 __table_args__ = (
774 773 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
775 774 {'extend_existing': True, 'mysql_engine': 'InnoDB',
776 775 'mysql_charset': 'utf8'}
777 776 )
778 777 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
779 778 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
780 779 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
781 780 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
782 781
783 782 users_group = relationship('UserGroup')
784 783 permission = relationship('Permission')
785 784 repository = relationship('Repository')
786 785
787 786 def __unicode__(self):
788 787 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
789 788
790 789
791 790 class UserGroupToPerm(Base, BaseModel):
792 791 __tablename__ = 'users_group_to_perm'
793 792 __table_args__ = (
794 793 UniqueConstraint('users_group_id', 'permission_id',),
795 794 {'extend_existing': True, 'mysql_engine': 'InnoDB',
796 795 'mysql_charset': 'utf8'}
797 796 )
798 797 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
799 798 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
800 799 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
801 800
802 801 users_group = relationship('UserGroup')
803 802 permission = relationship('Permission')
804 803
805 804
806 805 class UserRepoGroupToPerm(Base, BaseModel):
807 806 __tablename__ = 'user_repo_group_to_perm'
808 807 __table_args__ = (
809 808 UniqueConstraint('user_id', 'group_id', 'permission_id'),
810 809 {'extend_existing': True, 'mysql_engine': 'InnoDB',
811 810 'mysql_charset': 'utf8'}
812 811 )
813 812
814 813 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
815 814 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
816 815 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
817 816 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
818 817
819 818 user = relationship('User')
820 819 group = relationship('RepoGroup')
821 820 permission = relationship('Permission')
822 821
823 822
824 823 class UserGroupRepoGroupToPerm(Base, BaseModel):
825 824 __tablename__ = 'users_group_repo_group_to_perm'
826 825 __table_args__ = (
827 826 UniqueConstraint('users_group_id', 'group_id'),
828 827 {'extend_existing': True, 'mysql_engine': 'InnoDB',
829 828 'mysql_charset': 'utf8'}
830 829 )
831 830
832 831 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)
833 832 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
834 833 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
835 834 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
836 835
837 836 users_group = relationship('UserGroup')
838 837 permission = relationship('Permission')
839 838 group = relationship('RepoGroup')
840 839
841 840
842 841 class Statistics(Base, BaseModel):
843 842 __tablename__ = 'statistics'
844 843 __table_args__ = (
845 844 UniqueConstraint('repository_id'),
846 845 {'extend_existing': True, 'mysql_engine': 'InnoDB',
847 846 'mysql_charset': 'utf8'}
848 847 )
849 848 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
850 849 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
851 850 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
852 851 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
853 852 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
854 853 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
855 854
856 855 repository = relationship('Repository', single_parent=True)
857 856
858 857
859 858 class UserFollowing(Base, BaseModel):
860 859 __tablename__ = 'user_followings'
861 860 __table_args__ = (
862 861 UniqueConstraint('user_id', 'follows_repository_id'),
863 862 UniqueConstraint('user_id', 'follows_user_id'),
864 863 {'extend_existing': True, 'mysql_engine': 'InnoDB',
865 864 'mysql_charset': 'utf8'}
866 865 )
867 866
868 867 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
869 868 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
870 869 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
871 870 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
872 871 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
873 872
874 873 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
875 874
876 875 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
877 876 follows_repository = relationship('Repository', order_by='Repository.repo_name')
878 877
879 878
880 879 class CacheInvalidation(Base, BaseModel):
881 880 __tablename__ = 'cache_invalidation'
882 881 __table_args__ = (
883 882 UniqueConstraint('cache_key'),
884 883 Index('key_idx', 'cache_key'),
885 884 {'extend_existing': True, 'mysql_engine': 'InnoDB',
886 885 'mysql_charset': 'utf8'},
887 886 )
888 887 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
889 888 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
890 889 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
891 890 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
892 891
893 892 def __init__(self, cache_key, cache_args=''):
894 893 self.cache_key = cache_key
895 894 self.cache_args = cache_args
896 895 self.cache_active = False
897 896
898 897
899 898 class ChangesetComment(Base, BaseModel):
900 899 __tablename__ = 'changeset_comments'
901 900 __table_args__ = (
902 901 Index('cc_revision_idx', 'revision'),
903 902 {'extend_existing': True, 'mysql_engine': 'InnoDB',
904 903 'mysql_charset': 'utf8'},
905 904 )
906 905 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
907 906 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
908 907 revision = Column('revision', String(40), nullable=True)
909 908 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
910 909 line_no = Column('line_no', Unicode(10), nullable=True)
911 910 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
912 911 f_path = Column('f_path', Unicode(1000), nullable=True)
913 912 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
914 913 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
915 914 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
916 915 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
917 916
918 917 author = relationship('User', lazy='joined')
919 918 repo = relationship('Repository')
920 919 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
921 920 pull_request = relationship('PullRequest', lazy='joined')
922 921
923 922 @classmethod
924 923 def get_users(cls, revision=None, pull_request_id=None):
925 924 """
926 925 Returns user associated with this ChangesetComment. ie those
927 926 who actually commented
928 927
929 928 :param cls:
930 929 :param revision:
931 930 """
932 931 q = Session().query(User)\
933 932 .join(ChangesetComment.author)
934 933 if revision:
935 934 q = q.filter(cls.revision == revision)
936 935 elif pull_request_id:
937 936 q = q.filter(cls.pull_request_id == pull_request_id)
938 937 return q.all()
939 938
940 939
941 940 class ChangesetStatus(Base, BaseModel):
942 941 __tablename__ = 'changeset_statuses'
943 942 __table_args__ = (
944 943 Index('cs_revision_idx', 'revision'),
945 944 Index('cs_version_idx', 'version'),
946 945 UniqueConstraint('repo_id', 'revision', 'version'),
947 946 {'extend_existing': True, 'mysql_engine': 'InnoDB',
948 947 'mysql_charset': 'utf8'}
949 948 )
950 949 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
951 950 STATUS_APPROVED = 'approved'
952 951 STATUS_REJECTED = 'rejected'
953 952 STATUS_UNDER_REVIEW = 'under_review'
954 953
955 954 STATUSES = [
956 955 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
957 956 (STATUS_APPROVED, _("Approved")),
958 957 (STATUS_REJECTED, _("Rejected")),
959 958 (STATUS_UNDER_REVIEW, _("Under Review")),
960 959 ]
961 960
962 961 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
963 962 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
964 963 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
965 964 revision = Column('revision', String(40), nullable=False)
966 965 status = Column('status', String(128), nullable=False, default=DEFAULT)
967 966 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
968 967 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
969 968 version = Column('version', Integer(), nullable=False, default=0)
970 969 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
971 970
972 971 author = relationship('User', lazy='joined')
973 972 repo = relationship('Repository')
974 973 comment = relationship('ChangesetComment', lazy='joined')
975 974 pull_request = relationship('PullRequest', lazy='joined')
976 975
977 976
978 977
979 978 class PullRequest(Base, BaseModel):
980 979 __tablename__ = 'pull_requests'
981 980 __table_args__ = (
982 981 {'extend_existing': True, 'mysql_engine': 'InnoDB',
983 982 'mysql_charset': 'utf8'},
984 983 )
985 984
986 985 STATUS_NEW = u'new'
987 986 STATUS_OPEN = u'open'
988 987 STATUS_CLOSED = u'closed'
989 988
990 989 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
991 990 title = Column('title', Unicode(256), nullable=True)
992 991 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
993 992 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
994 993 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
995 994 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
996 995 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
997 996 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
998 997 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
999 998 org_ref = Column('org_ref', Unicode(256), nullable=False)
1000 999 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1001 1000 other_ref = Column('other_ref', Unicode(256), nullable=False)
1002 1001
1003 1002 author = relationship('User', lazy='joined')
1004 1003 reviewers = relationship('PullRequestReviewers',
1005 1004 cascade="all, delete, delete-orphan")
1006 1005 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1007 1006 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1008 1007 statuses = relationship('ChangesetStatus')
1009 1008 comments = relationship('ChangesetComment',
1010 1009 cascade="all, delete, delete-orphan")
1011 1010
1012 1011
1013 1012 class PullRequestReviewers(Base, BaseModel):
1014 1013 __tablename__ = 'pull_request_reviewers'
1015 1014 __table_args__ = (
1016 1015 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1017 1016 'mysql_charset': 'utf8'},
1018 1017 )
1019 1018
1020 1019 def __init__(self, user=None, pull_request=None):
1021 1020 self.user = user
1022 1021 self.pull_request = pull_request
1023 1022
1024 1023 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1025 1024 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1026 1025 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1027 1026
1028 1027 user = relationship('User')
1029 1028 pull_request = relationship('PullRequest')
1030 1029
1031 1030
1032 1031 class Notification(Base, BaseModel):
1033 1032 __tablename__ = 'notifications'
1034 1033 __table_args__ = (
1035 1034 Index('notification_type_idx', 'type'),
1036 1035 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1037 1036 'mysql_charset': 'utf8'},
1038 1037 )
1039 1038
1040 1039 TYPE_CHANGESET_COMMENT = u'cs_comment'
1041 1040 TYPE_MESSAGE = u'message'
1042 1041 TYPE_MENTION = u'mention'
1043 1042 TYPE_REGISTRATION = u'registration'
1044 1043 TYPE_PULL_REQUEST = u'pull_request'
1045 1044 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1046 1045
1047 1046 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1048 1047 subject = Column('subject', Unicode(512), nullable=True)
1049 1048 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1050 1049 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1051 1050 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1052 1051 type_ = Column('type', Unicode(256))
1053 1052
1054 1053 created_by_user = relationship('User')
1055 1054 notifications_to_users = relationship('UserNotification', lazy='joined',
1056 1055 cascade="all, delete, delete-orphan")
1057 1056
1058 1057
1059 1058 class UserNotification(Base, BaseModel):
1060 1059 __tablename__ = 'user_to_notification'
1061 1060 __table_args__ = (
1062 1061 UniqueConstraint('user_id', 'notification_id'),
1063 1062 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1064 1063 'mysql_charset': 'utf8'}
1065 1064 )
1066 1065 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1067 1066 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1068 1067 read = Column('read', Boolean, default=False)
1069 1068 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1070 1069
1071 1070 user = relationship('User', lazy="joined")
1072 1071 notification = relationship('Notification', lazy="joined",
1073 1072 order_by=lambda: Notification.created_on.desc(),)
1074 1073
1075 1074
1076 1075 class DbMigrateVersion(Base, BaseModel):
1077 1076 __tablename__ = 'db_migrate_version'
1078 1077 __table_args__ = (
1079 1078 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1080 1079 'mysql_charset': 'utf8'},
1081 1080 )
1082 1081 repository_id = Column('repository_id', String(250), primary_key=True)
1083 1082 repository_path = Column('repository_path', Text)
1084 1083 version = Column('version', Integer)
@@ -1,1145 +1,1144 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 import os
22 21 import time
23 22 import logging
24 23 import datetime
25 24 import traceback
26 25 import hashlib
27 26 import collections
28 27
29 28 from sqlalchemy import *
30 29 from sqlalchemy.ext.hybrid import hybrid_property
31 30 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 31 from sqlalchemy.exc import DatabaseError
33 32 from beaker.cache import cache_region, region_invalidate
34 33 from webob.exc import HTTPNotFound
35 34
36 35 from rhodecode.translation import _
37 36
38 37 from rhodecode.lib.vcs import get_backend
39 38 from rhodecode.lib.vcs.utils.helpers import get_scm
40 39 from rhodecode.lib.vcs.exceptions import VCSError
41 40 from zope.cachedescriptors.property import Lazy as LazyProperty
42 41 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 42
44 43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 safe_unicode, remove_suffix, remove_prefix, time_to_datetime
44 remove_suffix, remove_prefix, time_to_datetime
46 45 from rhodecode.lib.ext_json import json
47 46 from rhodecode.lib.caching_query import FromCache
48 47
49 48 from rhodecode.model.meta import Base, Session
50 49
51 50 URL_SEP = '/'
52 51 log = logging.getLogger(__name__)
53 52
54 53 #==============================================================================
55 54 # BASE CLASSES
56 55 #==============================================================================
57 56
58 57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 58
60 59
61 60 class BaseModel(object):
62 61 """
63 62 Base Model for all classes
64 63 """
65 64
66 65 @classmethod
67 66 def _get_keys(cls):
68 67 """return column names for this model """
69 68 return class_mapper(cls).c.keys()
70 69
71 70 def get_dict(self):
72 71 """
73 72 return dict with keys and values corresponding
74 73 to this model data """
75 74
76 75 d = {}
77 76 for k in self._get_keys():
78 77 d[k] = getattr(self, k)
79 78
80 79 # also use __json__() if present to get additional fields
81 80 _json_attr = getattr(self, '__json__', None)
82 81 if _json_attr:
83 82 # update with attributes from __json__
84 83 if callable(_json_attr):
85 84 _json_attr = _json_attr()
86 85 for k, val in _json_attr.items():
87 86 d[k] = val
88 87 return d
89 88
90 89 def get_appstruct(self):
91 90 """return list with keys and values tupples corresponding
92 91 to this model data """
93 92
94 93 l = []
95 94 for k in self._get_keys():
96 95 l.append((k, getattr(self, k),))
97 96 return l
98 97
99 98 def populate_obj(self, populate_dict):
100 99 """populate model with data from given populate_dict"""
101 100
102 101 for k in self._get_keys():
103 102 if k in populate_dict:
104 103 setattr(self, k, populate_dict[k])
105 104
106 105 @classmethod
107 106 def query(cls):
108 107 return Session().query(cls)
109 108
110 109 @classmethod
111 110 def get(cls, id_):
112 111 if id_:
113 112 return cls.query().get(id_)
114 113
115 114 @classmethod
116 115 def get_or_404(cls, id_):
117 116 try:
118 117 id_ = int(id_)
119 118 except (TypeError, ValueError):
120 119 raise HTTPNotFound
121 120
122 121 res = cls.query().get(id_)
123 122 if not res:
124 123 raise HTTPNotFound
125 124 return res
126 125
127 126 @classmethod
128 127 def getAll(cls):
129 128 # deprecated and left for backward compatibility
130 129 return cls.get_all()
131 130
132 131 @classmethod
133 132 def get_all(cls):
134 133 return cls.query().all()
135 134
136 135 @classmethod
137 136 def delete(cls, id_):
138 137 obj = cls.query().get(id_)
139 138 Session().delete(obj)
140 139
141 140 def __repr__(self):
142 141 if hasattr(self, '__unicode__'):
143 142 # python repr needs to return str
144 143 return safe_str(self.__unicode__())
145 144 return '<DB:%s>' % (self.__class__.__name__)
146 145
147 146
148 147 class RhodeCodeSetting(Base, BaseModel):
149 148 __tablename__ = 'rhodecode_settings'
150 149 __table_args__ = (
151 150 UniqueConstraint('app_settings_name'),
152 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 152 'mysql_charset': 'utf8'}
154 153 )
155 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 155 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
157 156 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
158 157
159 158 def __init__(self, k='', v=''):
160 159 self.app_settings_name = k
161 160 self.app_settings_value = v
162 161
163 162 @validates('_app_settings_value')
164 163 def validate_settings_value(self, key, val):
165 assert type(val) == unicode
164 assert type(val) == str
166 165 return val
167 166
168 167 @hybrid_property
169 168 def app_settings_value(self):
170 169 v = self._app_settings_value
171 170 if self.app_settings_name in ["ldap_active",
172 171 "default_repo_enable_statistics",
173 172 "default_repo_enable_locking",
174 173 "default_repo_private",
175 174 "default_repo_enable_downloads"]:
176 175 v = str2bool(v)
177 176 return v
178 177
179 178 @app_settings_value.setter
180 179 def app_settings_value(self, val):
181 180 """
182 181 Setter that will always make sure we use unicode in app_settings_value
183 182
184 183 :param val:
185 184 """
186 self._app_settings_value = safe_unicode(val)
185 self._app_settings_value = safe_str(val)
187 186
188 187 def __unicode__(self):
189 188 return u"<%s('%s:%s')>" % (
190 189 self.__class__.__name__,
191 190 self.app_settings_name, self.app_settings_value
192 191 )
193 192
194 193
195 194 class RhodeCodeUi(Base, BaseModel):
196 195 __tablename__ = 'rhodecode_ui'
197 196 __table_args__ = (
198 197 UniqueConstraint('ui_key'),
199 198 {'extend_existing': True, 'mysql_engine': 'InnoDB',
200 199 'mysql_charset': 'utf8'}
201 200 )
202 201
203 202 HOOK_REPO_SIZE = 'changegroup.repo_size'
204 203 HOOK_PUSH = 'changegroup.push_logger'
205 204 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
206 205 HOOK_PULL = 'outgoing.pull_logger'
207 206 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
208 207
209 208 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
210 209 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
211 210 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
212 211 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
213 212 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
214 213
215 214
216 215
217 216 class User(Base, BaseModel):
218 217 __tablename__ = 'users'
219 218 __table_args__ = (
220 219 UniqueConstraint('username'), UniqueConstraint('email'),
221 220 Index('u_username_idx', 'username'),
222 221 Index('u_email_idx', 'email'),
223 222 {'extend_existing': True, 'mysql_engine': 'InnoDB',
224 223 'mysql_charset': 'utf8'}
225 224 )
226 225 DEFAULT_USER = 'default'
227 226
228 227 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
229 228 username = Column("username", String(255), nullable=True, unique=None, default=None)
230 229 password = Column("password", String(255), nullable=True, unique=None, default=None)
231 230 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
232 231 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
233 232 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
234 233 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
235 234 _email = Column("email", String(255), nullable=True, unique=None, default=None)
236 235 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
237 236 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
238 237 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
239 238 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
240 239
241 240 user_log = relationship('UserLog')
242 241 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
243 242
244 243 repositories = relationship('Repository')
245 244 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
246 245 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
247 246
248 247 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
249 248 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
250 249
251 250 group_member = relationship('UserGroupMember', cascade='all')
252 251
253 252 notifications = relationship('UserNotification', cascade='all')
254 253 # notifications assigned to this user
255 254 user_created_notifications = relationship('Notification', cascade='all')
256 255 # comments created by this user
257 256 user_comments = relationship('ChangesetComment', cascade='all')
258 257 user_emails = relationship('UserEmailMap', cascade='all')
259 258
260 259 @hybrid_property
261 260 def email(self):
262 261 return self._email
263 262
264 263 @email.setter
265 264 def email(self, val):
266 265 self._email = val.lower() if val else None
267 266
268 267 @property
269 268 def firstname(self):
270 269 # alias for future
271 270 return self.name
272 271
273 272 @property
274 273 def username_and_name(self):
275 274 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
276 275
277 276 @property
278 277 def full_name(self):
279 278 return '%s %s' % (self.firstname, self.lastname)
280 279
281 280 @property
282 281 def full_contact(self):
283 282 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
284 283
285 284 @property
286 285 def short_contact(self):
287 286 return '%s %s' % (self.firstname, self.lastname)
288 287
289 288 @property
290 289 def is_admin(self):
291 290 return self.admin
292 291
293 292 @classmethod
294 293 def get_by_username(cls, username, case_insensitive=False, cache=False):
295 294 if case_insensitive:
296 295 q = cls.query().filter(cls.username.ilike(username))
297 296 else:
298 297 q = cls.query().filter(cls.username == username)
299 298
300 299 if cache:
301 300 q = q.options(FromCache(
302 301 "sql_cache_short",
303 302 "get_user_%s" % _hash_key(username)
304 303 )
305 304 )
306 305 return q.scalar()
307 306
308 307 @classmethod
309 308 def get_by_auth_token(cls, auth_token, cache=False):
310 309 q = cls.query().filter(cls.api_key == auth_token)
311 310
312 311 if cache:
313 312 q = q.options(FromCache("sql_cache_short",
314 313 "get_auth_token_%s" % auth_token))
315 314 return q.scalar()
316 315
317 316 @classmethod
318 317 def get_by_email(cls, email, case_insensitive=False, cache=False):
319 318 if case_insensitive:
320 319 q = cls.query().filter(cls.email.ilike(email))
321 320 else:
322 321 q = cls.query().filter(cls.email == email)
323 322
324 323 if cache:
325 324 q = q.options(FromCache("sql_cache_short",
326 325 "get_email_key_%s" % email))
327 326
328 327 ret = q.scalar()
329 328 if ret is None:
330 329 q = UserEmailMap.query()
331 330 # try fetching in alternate email map
332 331 if case_insensitive:
333 332 q = q.filter(UserEmailMap.email.ilike(email))
334 333 else:
335 334 q = q.filter(UserEmailMap.email == email)
336 335 q = q.options(joinedload(UserEmailMap.user))
337 336 if cache:
338 337 q = q.options(FromCache("sql_cache_short",
339 338 "get_email_map_key_%s" % email))
340 339 ret = getattr(q.scalar(), 'user', None)
341 340
342 341 return ret
343 342
344 343 @classmethod
345 344 def get_first_admin(cls):
346 345 user = User.query().filter(User.admin == True).first()
347 346 if user is None:
348 347 raise Exception('Missing administrative account!')
349 348 return user
350 349
351 350 @classmethod
352 351 def get_default_user(cls, cache=False):
353 352 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
354 353 if user is None:
355 354 raise Exception('Missing default account!')
356 355 return user
357 356
358 357
359 358
360 359
361 360 class UserEmailMap(Base, BaseModel):
362 361 __tablename__ = 'user_email_map'
363 362 __table_args__ = (
364 363 Index('uem_email_idx', 'email'),
365 364 UniqueConstraint('email'),
366 365 {'extend_existing': True, 'mysql_engine': 'InnoDB',
367 366 'mysql_charset': 'utf8'}
368 367 )
369 368 __mapper_args__ = {}
370 369
371 370 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
372 371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
373 372 _email = Column("email", String(255), nullable=True, unique=False, default=None)
374 373 user = relationship('User', lazy='joined')
375 374
376 375 @validates('_email')
377 376 def validate_email(self, key, email):
378 377 # check if this email is not main one
379 378 main_email = Session().query(User).filter(User.email == email).scalar()
380 379 if main_email is not None:
381 380 raise AttributeError('email %s is present is user table' % email)
382 381 return email
383 382
384 383 @hybrid_property
385 384 def email(self):
386 385 return self._email
387 386
388 387 @email.setter
389 388 def email(self, val):
390 389 self._email = val.lower() if val else None
391 390
392 391
393 392 class UserIpMap(Base, BaseModel):
394 393 __tablename__ = 'user_ip_map'
395 394 __table_args__ = (
396 395 UniqueConstraint('user_id', 'ip_addr'),
397 396 {'extend_existing': True, 'mysql_engine': 'InnoDB',
398 397 'mysql_charset': 'utf8'}
399 398 )
400 399 __mapper_args__ = {}
401 400
402 401 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
403 402 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
404 403 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
405 404 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
406 405 user = relationship('User', lazy='joined')
407 406
408 407
409 408 class UserLog(Base, BaseModel):
410 409 __tablename__ = 'user_logs'
411 410 __table_args__ = (
412 411 {'extend_existing': True, 'mysql_engine': 'InnoDB',
413 412 'mysql_charset': 'utf8'},
414 413 )
415 414 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
416 415 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
417 416 username = Column("username", String(255), nullable=True, unique=None, default=None)
418 417 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
419 418 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
420 419 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
421 420 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
422 421 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
423 422
424 423 def __unicode__(self):
425 424 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
426 425 self.repository_name,
427 426 self.action)
428 427
429 428 user = relationship('User')
430 429 repository = relationship('Repository', cascade='')
431 430
432 431
433 432 class UserGroup(Base, BaseModel):
434 433 __tablename__ = 'users_groups'
435 434 __table_args__ = (
436 435 {'extend_existing': True, 'mysql_engine': 'InnoDB',
437 436 'mysql_charset': 'utf8'},
438 437 )
439 438
440 439 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
441 440 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
442 441 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
443 442 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
444 443 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
445 444
446 445 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
447 446 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
448 447 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
449 448 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
450 449 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
451 450 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
452 451
453 452 user = relationship('User')
454 453
455 454 def __unicode__(self):
456 455 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
457 456 self.users_group_id,
458 457 self.users_group_name)
459 458
460 459 @classmethod
461 460 def get_by_group_name(cls, group_name, cache=False,
462 461 case_insensitive=False):
463 462 if case_insensitive:
464 463 q = cls.query().filter(cls.users_group_name.ilike(group_name))
465 464 else:
466 465 q = cls.query().filter(cls.users_group_name == group_name)
467 466 if cache:
468 467 q = q.options(FromCache(
469 468 "sql_cache_short",
470 469 "get_user_%s" % _hash_key(group_name)
471 470 )
472 471 )
473 472 return q.scalar()
474 473
475 474 @classmethod
476 475 def get(cls, users_group_id, cache=False):
477 476 user_group = cls.query()
478 477 if cache:
479 478 user_group = user_group.options(FromCache("sql_cache_short",
480 479 "get_users_group_%s" % users_group_id))
481 480 return user_group.get(users_group_id)
482 481
483 482
484 483 class UserGroupMember(Base, BaseModel):
485 484 __tablename__ = 'users_groups_members'
486 485 __table_args__ = (
487 486 {'extend_existing': True, 'mysql_engine': 'InnoDB',
488 487 'mysql_charset': 'utf8'},
489 488 )
490 489
491 490 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
492 491 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
493 492 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
494 493
495 494 user = relationship('User', lazy='joined')
496 495 users_group = relationship('UserGroup')
497 496
498 497 def __init__(self, gr_id='', u_id=''):
499 498 self.users_group_id = gr_id
500 499 self.user_id = u_id
501 500
502 501
503 502 class RepositoryField(Base, BaseModel):
504 503 __tablename__ = 'repositories_fields'
505 504 __table_args__ = (
506 505 UniqueConstraint('repository_id', 'field_key'), # no-multi field
507 506 {'extend_existing': True, 'mysql_engine': 'InnoDB',
508 507 'mysql_charset': 'utf8'},
509 508 )
510 509 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
511 510
512 511 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
513 512 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
514 513 field_key = Column("field_key", String(250))
515 514 field_label = Column("field_label", String(1024), nullable=False)
516 515 field_value = Column("field_value", String(10000), nullable=False)
517 516 field_desc = Column("field_desc", String(1024), nullable=False)
518 517 field_type = Column("field_type", String(256), nullable=False, unique=None)
519 518 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
520 519
521 520 repository = relationship('Repository')
522 521
523 522 @classmethod
524 523 def get_by_key_name(cls, key, repo):
525 524 row = cls.query()\
526 525 .filter(cls.repository == repo)\
527 526 .filter(cls.field_key == key).scalar()
528 527 return row
529 528
530 529
531 530 class Repository(Base, BaseModel):
532 531 __tablename__ = 'repositories'
533 532 __table_args__ = (
534 533 UniqueConstraint('repo_name'),
535 534 Index('r_repo_name_idx', 'repo_name'),
536 535 {'extend_existing': True, 'mysql_engine': 'InnoDB',
537 536 'mysql_charset': 'utf8'},
538 537 )
539 538
540 539 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
541 540 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
542 541 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
543 542 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
544 543 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
545 544 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
546 545 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
547 546 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
548 547 description = Column("description", String(10000), nullable=True, unique=None, default=None)
549 548 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
550 549 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
551 550 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
552 551 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
553 552 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
554 553 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
555 554
556 555 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
557 556 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
558 557
559 558 user = relationship('User')
560 559 fork = relationship('Repository', remote_side=repo_id)
561 560 group = relationship('RepoGroup')
562 561 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
563 562 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
564 563 stats = relationship('Statistics', cascade='all', uselist=False)
565 564
566 565 followers = relationship('UserFollowing',
567 566 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
568 567 cascade='all')
569 568 extra_fields = relationship('RepositoryField',
570 569 cascade="all, delete, delete-orphan")
571 570
572 571 logs = relationship('UserLog')
573 572 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
574 573
575 574 pull_requests_org = relationship('PullRequest',
576 575 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
577 576 cascade="all, delete, delete-orphan")
578 577
579 578 pull_requests_other = relationship('PullRequest',
580 579 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
581 580 cascade="all, delete, delete-orphan")
582 581
583 582 def __unicode__(self):
584 583 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
585 safe_unicode(self.repo_name))
584 safe_str(self.repo_name))
586 585
587 586 @classmethod
588 587 def get_by_repo_name(cls, repo_name):
589 588 q = Session().query(cls).filter(cls.repo_name == repo_name)
590 589 q = q.options(joinedload(Repository.fork))\
591 590 .options(joinedload(Repository.user))\
592 591 .options(joinedload(Repository.group))
593 592 return q.scalar()
594 593
595 594
596 595 class RepoGroup(Base, BaseModel):
597 596 __tablename__ = 'groups'
598 597 __table_args__ = (
599 598 UniqueConstraint('group_name', 'group_parent_id'),
600 599 {'extend_existing': True, 'mysql_engine': 'InnoDB',
601 600 'mysql_charset': 'utf8'},
602 601 )
603 602 __mapper_args__ = {'order_by': 'group_name'}
604 603
605 604 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
606 605 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
607 606 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
608 607 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
609 608 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
610 609 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
611 610
612 611 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
613 612 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
614 613 parent_group = relationship('RepoGroup', remote_side=group_id)
615 614 user = relationship('User')
616 615
617 616 def __init__(self, group_name='', parent_group=None):
618 617 self.group_name = group_name
619 618 self.parent_group = parent_group
620 619
621 620 def __unicode__(self):
622 621 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
623 622 self.group_name)
624 623
625 624 @classmethod
626 625 def url_sep(cls):
627 626 return URL_SEP
628 627
629 628 @classmethod
630 629 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
631 630 if case_insensitive:
632 631 gr = cls.query()\
633 632 .filter(cls.group_name.ilike(group_name))
634 633 else:
635 634 gr = cls.query()\
636 635 .filter(cls.group_name == group_name)
637 636 if cache:
638 637 gr = gr.options(FromCache(
639 638 "sql_cache_short",
640 639 "get_group_%s" % _hash_key(group_name)
641 640 )
642 641 )
643 642 return gr.scalar()
644 643
645 644
646 645 class Permission(Base, BaseModel):
647 646 __tablename__ = 'permissions'
648 647 __table_args__ = (
649 648 Index('p_perm_name_idx', 'permission_name'),
650 649 {'extend_existing': True, 'mysql_engine': 'InnoDB',
651 650 'mysql_charset': 'utf8'},
652 651 )
653 652 PERMS = [
654 653 ('hg.admin', _('RhodeCode Administrator')),
655 654
656 655 ('repository.none', _('Repository no access')),
657 656 ('repository.read', _('Repository read access')),
658 657 ('repository.write', _('Repository write access')),
659 658 ('repository.admin', _('Repository admin access')),
660 659
661 660 ('group.none', _('Repository group no access')),
662 661 ('group.read', _('Repository group read access')),
663 662 ('group.write', _('Repository group write access')),
664 663 ('group.admin', _('Repository group admin access')),
665 664
666 665 ('usergroup.none', _('User group no access')),
667 666 ('usergroup.read', _('User group read access')),
668 667 ('usergroup.write', _('User group write access')),
669 668 ('usergroup.admin', _('User group admin access')),
670 669
671 670 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
672 671 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
673 672
674 673 ('hg.usergroup.create.false', _('User Group creation disabled')),
675 674 ('hg.usergroup.create.true', _('User Group creation enabled')),
676 675
677 676 ('hg.create.none', _('Repository creation disabled')),
678 677 ('hg.create.repository', _('Repository creation enabled')),
679 678
680 679 ('hg.fork.none', _('Repository forking disabled')),
681 680 ('hg.fork.repository', _('Repository forking enabled')),
682 681
683 682 ('hg.register.none', _('Registration disabled')),
684 683 ('hg.register.manual_activate', _('User Registration with manual account activation')),
685 684 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
686 685
687 686 ('hg.extern_activate.manual', _('Manual activation of external account')),
688 687 ('hg.extern_activate.auto', _('Automatic activation of external account')),
689 688
690 689 ]
691 690
692 691 #definition of system default permissions for DEFAULT user
693 692 DEFAULT_USER_PERMISSIONS = [
694 693 'repository.read',
695 694 'group.read',
696 695 'usergroup.read',
697 696 'hg.create.repository',
698 697 'hg.fork.repository',
699 698 'hg.register.manual_activate',
700 699 'hg.extern_activate.auto',
701 700 ]
702 701
703 702 # defines which permissions are more important higher the more important
704 703 # Weight defines which permissions are more important.
705 704 # The higher number the more important.
706 705 PERM_WEIGHTS = {
707 706 'repository.none': 0,
708 707 'repository.read': 1,
709 708 'repository.write': 3,
710 709 'repository.admin': 4,
711 710
712 711 'group.none': 0,
713 712 'group.read': 1,
714 713 'group.write': 3,
715 714 'group.admin': 4,
716 715
717 716 'usergroup.none': 0,
718 717 'usergroup.read': 1,
719 718 'usergroup.write': 3,
720 719 'usergroup.admin': 4,
721 720 'hg.repogroup.create.false': 0,
722 721 'hg.repogroup.create.true': 1,
723 722
724 723 'hg.usergroup.create.false': 0,
725 724 'hg.usergroup.create.true': 1,
726 725
727 726 'hg.fork.none': 0,
728 727 'hg.fork.repository': 1,
729 728 'hg.create.none': 0,
730 729 'hg.create.repository': 1
731 730 }
732 731
733 732 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
734 733 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
735 734 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
736 735
737 736 def __unicode__(self):
738 737 return u"<%s('%s:%s')>" % (
739 738 self.__class__.__name__, self.permission_id, self.permission_name
740 739 )
741 740
742 741 @classmethod
743 742 def get_by_key(cls, key):
744 743 return cls.query().filter(cls.permission_name == key).scalar()
745 744
746 745
747 746 class UserRepoToPerm(Base, BaseModel):
748 747 __tablename__ = 'repo_to_perm'
749 748 __table_args__ = (
750 749 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
751 750 {'extend_existing': True, 'mysql_engine': 'InnoDB',
752 751 'mysql_charset': 'utf8'}
753 752 )
754 753 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
755 754 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
756 755 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
757 756 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
758 757
759 758 user = relationship('User')
760 759 repository = relationship('Repository')
761 760 permission = relationship('Permission')
762 761
763 762 def __unicode__(self):
764 763 return u'<%s => %s >' % (self.user, self.repository)
765 764
766 765
767 766 class UserUserGroupToPerm(Base, BaseModel):
768 767 __tablename__ = 'user_user_group_to_perm'
769 768 __table_args__ = (
770 769 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
771 770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
772 771 'mysql_charset': 'utf8'}
773 772 )
774 773 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
775 774 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
776 775 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
777 776 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
778 777
779 778 user = relationship('User')
780 779 user_group = relationship('UserGroup')
781 780 permission = relationship('Permission')
782 781
783 782 def __unicode__(self):
784 783 return u'<%s => %s >' % (self.user, self.user_group)
785 784
786 785
787 786 class UserToPerm(Base, BaseModel):
788 787 __tablename__ = 'user_to_perm'
789 788 __table_args__ = (
790 789 UniqueConstraint('user_id', 'permission_id'),
791 790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
792 791 'mysql_charset': 'utf8'}
793 792 )
794 793 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
795 794 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
796 795 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
797 796
798 797 user = relationship('User')
799 798 permission = relationship('Permission', lazy='joined')
800 799
801 800 def __unicode__(self):
802 801 return u'<%s => %s >' % (self.user, self.permission)
803 802
804 803
805 804 class UserGroupRepoToPerm(Base, BaseModel):
806 805 __tablename__ = 'users_group_repo_to_perm'
807 806 __table_args__ = (
808 807 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
809 808 {'extend_existing': True, 'mysql_engine': 'InnoDB',
810 809 'mysql_charset': 'utf8'}
811 810 )
812 811 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
813 812 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
814 813 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
815 814 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
816 815
817 816 users_group = relationship('UserGroup')
818 817 permission = relationship('Permission')
819 818 repository = relationship('Repository')
820 819
821 820 def __unicode__(self):
822 821 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
823 822
824 823
825 824 class UserGroupUserGroupToPerm(Base, BaseModel):
826 825 __tablename__ = 'user_group_user_group_to_perm'
827 826 __table_args__ = (
828 827 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
829 828 CheckConstraint('target_user_group_id != user_group_id'),
830 829 {'extend_existing': True, 'mysql_engine': 'InnoDB',
831 830 'mysql_charset': 'utf8'}
832 831 )
833 832 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)
834 833 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
835 834 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
836 835 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
837 836
838 837 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
839 838 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
840 839 permission = relationship('Permission')
841 840
842 841 def __unicode__(self):
843 842 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
844 843
845 844
846 845 class UserGroupToPerm(Base, BaseModel):
847 846 __tablename__ = 'users_group_to_perm'
848 847 __table_args__ = (
849 848 UniqueConstraint('users_group_id', 'permission_id',),
850 849 {'extend_existing': True, 'mysql_engine': 'InnoDB',
851 850 'mysql_charset': 'utf8'}
852 851 )
853 852 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
854 853 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
855 854 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
856 855
857 856 users_group = relationship('UserGroup')
858 857 permission = relationship('Permission')
859 858
860 859
861 860 class UserRepoGroupToPerm(Base, BaseModel):
862 861 __tablename__ = 'user_repo_group_to_perm'
863 862 __table_args__ = (
864 863 UniqueConstraint('user_id', 'group_id', 'permission_id'),
865 864 {'extend_existing': True, 'mysql_engine': 'InnoDB',
866 865 'mysql_charset': 'utf8'}
867 866 )
868 867
869 868 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
870 869 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
871 870 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
872 871 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
873 872
874 873 user = relationship('User')
875 874 group = relationship('RepoGroup')
876 875 permission = relationship('Permission')
877 876
878 877
879 878 class UserGroupRepoGroupToPerm(Base, BaseModel):
880 879 __tablename__ = 'users_group_repo_group_to_perm'
881 880 __table_args__ = (
882 881 UniqueConstraint('users_group_id', 'group_id'),
883 882 {'extend_existing': True, 'mysql_engine': 'InnoDB',
884 883 'mysql_charset': 'utf8'}
885 884 )
886 885
887 886 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)
888 887 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
889 888 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
890 889 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
891 890
892 891 users_group = relationship('UserGroup')
893 892 permission = relationship('Permission')
894 893 group = relationship('RepoGroup')
895 894
896 895
897 896 class Statistics(Base, BaseModel):
898 897 __tablename__ = 'statistics'
899 898 __table_args__ = (
900 899 UniqueConstraint('repository_id'),
901 900 {'extend_existing': True, 'mysql_engine': 'InnoDB',
902 901 'mysql_charset': 'utf8'}
903 902 )
904 903 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
905 904 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
906 905 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
907 906 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
908 907 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
909 908 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
910 909
911 910 repository = relationship('Repository', single_parent=True)
912 911
913 912
914 913 class UserFollowing(Base, BaseModel):
915 914 __tablename__ = 'user_followings'
916 915 __table_args__ = (
917 916 UniqueConstraint('user_id', 'follows_repository_id'),
918 917 UniqueConstraint('user_id', 'follows_user_id'),
919 918 {'extend_existing': True, 'mysql_engine': 'InnoDB',
920 919 'mysql_charset': 'utf8'}
921 920 )
922 921
923 922 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
924 923 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
925 924 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
926 925 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
927 926 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
928 927
929 928 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
930 929
931 930 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
932 931 follows_repository = relationship('Repository', order_by='Repository.repo_name')
933 932
934 933
935 934 class CacheInvalidation(Base, BaseModel):
936 935 __tablename__ = 'cache_invalidation'
937 936 __table_args__ = (
938 937 UniqueConstraint('cache_key'),
939 938 Index('key_idx', 'cache_key'),
940 939 {'extend_existing': True, 'mysql_engine': 'InnoDB',
941 940 'mysql_charset': 'utf8'},
942 941 )
943 942 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
944 943 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
945 944 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
946 945 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
947 946
948 947 def __init__(self, cache_key, cache_args=''):
949 948 self.cache_key = cache_key
950 949 self.cache_args = cache_args
951 950 self.cache_active = False
952 951
953 952
954 953 class ChangesetComment(Base, BaseModel):
955 954 __tablename__ = 'changeset_comments'
956 955 __table_args__ = (
957 956 Index('cc_revision_idx', 'revision'),
958 957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
959 958 'mysql_charset': 'utf8'},
960 959 )
961 960 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
962 961 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
963 962 revision = Column('revision', String(40), nullable=True)
964 963 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
965 964 line_no = Column('line_no', Unicode(10), nullable=True)
966 965 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
967 966 f_path = Column('f_path', Unicode(1000), nullable=True)
968 967 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
969 968 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
970 969 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
971 970 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
972 971
973 972 author = relationship('User', lazy='joined')
974 973 repo = relationship('Repository')
975 974 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
976 975 pull_request = relationship('PullRequest', lazy='joined')
977 976
978 977
979 978 class ChangesetStatus(Base, BaseModel):
980 979 __tablename__ = 'changeset_statuses'
981 980 __table_args__ = (
982 981 Index('cs_revision_idx', 'revision'),
983 982 Index('cs_version_idx', 'version'),
984 983 UniqueConstraint('repo_id', 'revision', 'version'),
985 984 {'extend_existing': True, 'mysql_engine': 'InnoDB',
986 985 'mysql_charset': 'utf8'}
987 986 )
988 987 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
989 988 STATUS_APPROVED = 'approved'
990 989 STATUS_REJECTED = 'rejected'
991 990 STATUS_UNDER_REVIEW = 'under_review'
992 991
993 992 STATUSES = [
994 993 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
995 994 (STATUS_APPROVED, _("Approved")),
996 995 (STATUS_REJECTED, _("Rejected")),
997 996 (STATUS_UNDER_REVIEW, _("Under Review")),
998 997 ]
999 998
1000 999 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1001 1000 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1002 1001 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1003 1002 revision = Column('revision', String(40), nullable=False)
1004 1003 status = Column('status', String(128), nullable=False, default=DEFAULT)
1005 1004 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1006 1005 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1007 1006 version = Column('version', Integer(), nullable=False, default=0)
1008 1007 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1009 1008
1010 1009 author = relationship('User', lazy='joined')
1011 1010 repo = relationship('Repository')
1012 1011 comment = relationship('ChangesetComment', lazy='joined')
1013 1012 pull_request = relationship('PullRequest', lazy='joined')
1014 1013
1015 1014
1016 1015
1017 1016 class PullRequest(Base, BaseModel):
1018 1017 __tablename__ = 'pull_requests'
1019 1018 __table_args__ = (
1020 1019 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1021 1020 'mysql_charset': 'utf8'},
1022 1021 )
1023 1022
1024 1023 STATUS_NEW = u'new'
1025 1024 STATUS_OPEN = u'open'
1026 1025 STATUS_CLOSED = u'closed'
1027 1026
1028 1027 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1029 1028 title = Column('title', Unicode(256), nullable=True)
1030 1029 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1031 1030 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1032 1031 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1033 1032 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1034 1033 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1035 1034 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
1036 1035 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1037 1036 org_ref = Column('org_ref', Unicode(256), nullable=False)
1038 1037 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1039 1038 other_ref = Column('other_ref', Unicode(256), nullable=False)
1040 1039
1041 1040 author = relationship('User', lazy='joined')
1042 1041 reviewers = relationship('PullRequestReviewers',
1043 1042 cascade="all, delete, delete-orphan")
1044 1043 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1045 1044 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1046 1045 statuses = relationship('ChangesetStatus')
1047 1046 comments = relationship('ChangesetComment',
1048 1047 cascade="all, delete, delete-orphan")
1049 1048
1050 1049
1051 1050 class PullRequestReviewers(Base, BaseModel):
1052 1051 __tablename__ = 'pull_request_reviewers'
1053 1052 __table_args__ = (
1054 1053 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1055 1054 'mysql_charset': 'utf8'},
1056 1055 )
1057 1056
1058 1057 def __init__(self, user=None, pull_request=None):
1059 1058 self.user = user
1060 1059 self.pull_request = pull_request
1061 1060
1062 1061 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1063 1062 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1064 1063 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1065 1064
1066 1065 user = relationship('User')
1067 1066 pull_request = relationship('PullRequest')
1068 1067
1069 1068
1070 1069 class Notification(Base, BaseModel):
1071 1070 __tablename__ = 'notifications'
1072 1071 __table_args__ = (
1073 1072 Index('notification_type_idx', 'type'),
1074 1073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1075 1074 'mysql_charset': 'utf8'},
1076 1075 )
1077 1076
1078 1077 TYPE_CHANGESET_COMMENT = u'cs_comment'
1079 1078 TYPE_MESSAGE = u'message'
1080 1079 TYPE_MENTION = u'mention'
1081 1080 TYPE_REGISTRATION = u'registration'
1082 1081 TYPE_PULL_REQUEST = u'pull_request'
1083 1082 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1084 1083
1085 1084 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1086 1085 subject = Column('subject', Unicode(512), nullable=True)
1087 1086 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1088 1087 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1089 1088 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1090 1089 type_ = Column('type', Unicode(256))
1091 1090
1092 1091 created_by_user = relationship('User')
1093 1092 notifications_to_users = relationship('UserNotification', lazy='joined',
1094 1093 cascade="all, delete, delete-orphan")
1095 1094
1096 1095
1097 1096 class UserNotification(Base, BaseModel):
1098 1097 __tablename__ = 'user_to_notification'
1099 1098 __table_args__ = (
1100 1099 UniqueConstraint('user_id', 'notification_id'),
1101 1100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1102 1101 'mysql_charset': 'utf8'}
1103 1102 )
1104 1103 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1105 1104 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1106 1105 read = Column('read', Boolean, default=False)
1107 1106 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1108 1107
1109 1108 user = relationship('User', lazy="joined")
1110 1109 notification = relationship('Notification', lazy="joined",
1111 1110 order_by=lambda: Notification.created_on.desc(),)
1112 1111
1113 1112
1114 1113 class Gist(Base, BaseModel):
1115 1114 __tablename__ = 'gists'
1116 1115 __table_args__ = (
1117 1116 Index('g_gist_access_id_idx', 'gist_access_id'),
1118 1117 Index('g_created_on_idx', 'created_on'),
1119 1118 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1120 1119 'mysql_charset': 'utf8'}
1121 1120 )
1122 1121 GIST_PUBLIC = u'public'
1123 1122 GIST_PRIVATE = u'private'
1124 1123
1125 1124 gist_id = Column('gist_id', Integer(), primary_key=True)
1126 1125 gist_access_id = Column('gist_access_id', Unicode(250))
1127 1126 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1128 1127 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
1129 1128 gist_expires = Column('gist_expires', Float(), nullable=False)
1130 1129 gist_type = Column('gist_type', Unicode(128), nullable=False)
1131 1130 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1132 1131 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1133 1132
1134 1133 owner = relationship('User')
1135 1134
1136 1135
1137 1136 class DbMigrateVersion(Base, BaseModel):
1138 1137 __tablename__ = 'db_migrate_version'
1139 1138 __table_args__ = (
1140 1139 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1141 1140 'mysql_charset': 'utf8'},
1142 1141 )
1143 1142 repository_id = Column('repository_id', String(250), primary_key=True)
1144 1143 repository_path = Column('repository_path', Text)
1145 1144 version = Column('version', Integer)
@@ -1,1147 +1,1146 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 import os
22 21 import time
23 22 import logging
24 23 import datetime
25 24 import traceback
26 25 import hashlib
27 26 import collections
28 27
29 28 from sqlalchemy import *
30 29 from sqlalchemy.ext.hybrid import hybrid_property
31 30 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 31 from sqlalchemy.exc import DatabaseError
33 32 from beaker.cache import cache_region, region_invalidate
34 33 from webob.exc import HTTPNotFound
35 34
36 35 from rhodecode.translation import _
37 36
38 37 from rhodecode.lib.vcs import get_backend
39 38 from rhodecode.lib.vcs.utils.helpers import get_scm
40 39 from rhodecode.lib.vcs.exceptions import VCSError
41 40 from zope.cachedescriptors.property import Lazy as LazyProperty
42 41 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 42
44 43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 safe_unicode, remove_suffix, remove_prefix, time_to_datetime
44 remove_suffix, remove_prefix, time_to_datetime
46 45 from rhodecode.lib.ext_json import json
47 46 from rhodecode.lib.caching_query import FromCache
48 47
49 48 from rhodecode.model.meta import Base, Session
50 49
51 50 URL_SEP = '/'
52 51 log = logging.getLogger(__name__)
53 52
54 53 #==============================================================================
55 54 # BASE CLASSES
56 55 #==============================================================================
57 56
58 57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 58
60 59
61 60 class BaseModel(object):
62 61 """
63 62 Base Model for all classes
64 63 """
65 64
66 65 @classmethod
67 66 def _get_keys(cls):
68 67 """return column names for this model """
69 68 return class_mapper(cls).c.keys()
70 69
71 70 def get_dict(self):
72 71 """
73 72 return dict with keys and values corresponding
74 73 to this model data """
75 74
76 75 d = {}
77 76 for k in self._get_keys():
78 77 d[k] = getattr(self, k)
79 78
80 79 # also use __json__() if present to get additional fields
81 80 _json_attr = getattr(self, '__json__', None)
82 81 if _json_attr:
83 82 # update with attributes from __json__
84 83 if callable(_json_attr):
85 84 _json_attr = _json_attr()
86 85 for k, val in _json_attr.items():
87 86 d[k] = val
88 87 return d
89 88
90 89 def get_appstruct(self):
91 90 """return list with keys and values tupples corresponding
92 91 to this model data """
93 92
94 93 l = []
95 94 for k in self._get_keys():
96 95 l.append((k, getattr(self, k),))
97 96 return l
98 97
99 98 def populate_obj(self, populate_dict):
100 99 """populate model with data from given populate_dict"""
101 100
102 101 for k in self._get_keys():
103 102 if k in populate_dict:
104 103 setattr(self, k, populate_dict[k])
105 104
106 105 @classmethod
107 106 def query(cls):
108 107 return Session().query(cls)
109 108
110 109 @classmethod
111 110 def get(cls, id_):
112 111 if id_:
113 112 return cls.query().get(id_)
114 113
115 114 @classmethod
116 115 def get_or_404(cls, id_):
117 116 try:
118 117 id_ = int(id_)
119 118 except (TypeError, ValueError):
120 119 raise HTTPNotFound
121 120
122 121 res = cls.query().get(id_)
123 122 if not res:
124 123 raise HTTPNotFound
125 124 return res
126 125
127 126 @classmethod
128 127 def getAll(cls):
129 128 # deprecated and left for backward compatibility
130 129 return cls.get_all()
131 130
132 131 @classmethod
133 132 def get_all(cls):
134 133 return cls.query().all()
135 134
136 135 @classmethod
137 136 def delete(cls, id_):
138 137 obj = cls.query().get(id_)
139 138 Session().delete(obj)
140 139
141 140 def __repr__(self):
142 141 if hasattr(self, '__unicode__'):
143 142 # python repr needs to return str
144 143 return safe_str(self.__unicode__())
145 144 return '<DB:%s>' % (self.__class__.__name__)
146 145
147 146
148 147 class RhodeCodeSetting(Base, BaseModel):
149 148 __tablename__ = 'rhodecode_settings'
150 149 __table_args__ = (
151 150 UniqueConstraint('app_settings_name'),
152 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 152 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
154 153 )
155 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 155 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
157 156 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
158 157 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
159 158
160 159 def __init__(self, key='', val='', type='unicode'):
161 160 self.app_settings_name = key
162 161 self.app_settings_value = val
163 162 self.app_settings_type = type
164 163
165 164 @validates('_app_settings_value')
166 165 def validate_settings_value(self, key, val):
167 assert type(val) == unicode
166 assert type(val) == str
168 167 return val
169 168
170 169 @hybrid_property
171 170 def app_settings_value(self):
172 171 v = self._app_settings_value
173 172 if self.app_settings_name in ["ldap_active",
174 173 "default_repo_enable_statistics",
175 174 "default_repo_enable_locking",
176 175 "default_repo_private",
177 176 "default_repo_enable_downloads"]:
178 177 v = str2bool(v)
179 178 return v
180 179
181 180 @app_settings_value.setter
182 181 def app_settings_value(self, val):
183 182 """
184 183 Setter that will always make sure we use unicode in app_settings_value
185 184
186 185 :param val:
187 186 """
188 self._app_settings_value = safe_unicode(val)
187 self._app_settings_value = safe_str(val)
189 188
190 189 def __unicode__(self):
191 190 return u"<%s('%s:%s')>" % (
192 191 self.__class__.__name__,
193 192 self.app_settings_name, self.app_settings_value
194 193 )
195 194
196 195
197 196 class RhodeCodeUi(Base, BaseModel):
198 197 __tablename__ = 'rhodecode_ui'
199 198 __table_args__ = (
200 199 UniqueConstraint('ui_key'),
201 200 {'extend_existing': True, 'mysql_engine': 'InnoDB',
202 201 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
203 202 )
204 203
205 204 HOOK_REPO_SIZE = 'changegroup.repo_size'
206 205 HOOK_PUSH = 'changegroup.push_logger'
207 206 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
208 207 HOOK_PULL = 'outgoing.pull_logger'
209 208 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
210 209
211 210 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
212 211 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
213 212 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
214 213 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
215 214 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
216 215
217 216
218 217
219 218 class User(Base, BaseModel):
220 219 __tablename__ = 'users'
221 220 __table_args__ = (
222 221 UniqueConstraint('username'), UniqueConstraint('email'),
223 222 Index('u_username_idx', 'username'),
224 223 Index('u_email_idx', 'email'),
225 224 {'extend_existing': True, 'mysql_engine': 'InnoDB',
226 225 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
227 226 )
228 227 DEFAULT_USER = 'default'
229 228
230 229 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
231 230 username = Column("username", String(255), nullable=True, unique=None, default=None)
232 231 password = Column("password", String(255), nullable=True, unique=None, default=None)
233 232 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
234 233 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
235 234 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
236 235 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
237 236 _email = Column("email", String(255), nullable=True, unique=None, default=None)
238 237 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
239 238 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
240 239 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
241 240 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
242 241
243 242 user_log = relationship('UserLog')
244 243 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
245 244
246 245 repositories = relationship('Repository')
247 246 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
248 247 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
249 248
250 249 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
251 250 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
252 251
253 252 group_member = relationship('UserGroupMember', cascade='all')
254 253
255 254 notifications = relationship('UserNotification', cascade='all')
256 255 # notifications assigned to this user
257 256 user_created_notifications = relationship('Notification', cascade='all')
258 257 # comments created by this user
259 258 user_comments = relationship('ChangesetComment', cascade='all')
260 259 user_emails = relationship('UserEmailMap', cascade='all')
261 260
262 261 @hybrid_property
263 262 def email(self):
264 263 return self._email
265 264
266 265 @email.setter
267 266 def email(self, val):
268 267 self._email = val.lower() if val else None
269 268
270 269 @property
271 270 def firstname(self):
272 271 # alias for future
273 272 return self.name
274 273
275 274 @property
276 275 def username_and_name(self):
277 276 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
278 277
279 278 @property
280 279 def full_name(self):
281 280 return '%s %s' % (self.firstname, self.lastname)
282 281
283 282 @property
284 283 def full_contact(self):
285 284 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
286 285
287 286 @property
288 287 def short_contact(self):
289 288 return '%s %s' % (self.firstname, self.lastname)
290 289
291 290 @property
292 291 def is_admin(self):
293 292 return self.admin
294 293
295 294 @classmethod
296 295 def get_by_username(cls, username, case_insensitive=False, cache=False):
297 296 if case_insensitive:
298 297 q = cls.query().filter(cls.username.ilike(username))
299 298 else:
300 299 q = cls.query().filter(cls.username == username)
301 300
302 301 if cache:
303 302 q = q.options(FromCache(
304 303 "sql_cache_short",
305 304 "get_user_%s" % _hash_key(username)
306 305 )
307 306 )
308 307 return q.scalar()
309 308
310 309 @classmethod
311 310 def get_by_auth_token(cls, auth_token, cache=False):
312 311 q = cls.query().filter(cls.api_key == auth_token)
313 312
314 313 if cache:
315 314 q = q.options(FromCache("sql_cache_short",
316 315 "get_auth_token_%s" % auth_token))
317 316 return q.scalar()
318 317
319 318 @classmethod
320 319 def get_by_email(cls, email, case_insensitive=False, cache=False):
321 320 if case_insensitive:
322 321 q = cls.query().filter(cls.email.ilike(email))
323 322 else:
324 323 q = cls.query().filter(cls.email == email)
325 324
326 325 if cache:
327 326 q = q.options(FromCache("sql_cache_short",
328 327 "get_email_key_%s" % email))
329 328
330 329 ret = q.scalar()
331 330 if ret is None:
332 331 q = UserEmailMap.query()
333 332 # try fetching in alternate email map
334 333 if case_insensitive:
335 334 q = q.filter(UserEmailMap.email.ilike(email))
336 335 else:
337 336 q = q.filter(UserEmailMap.email == email)
338 337 q = q.options(joinedload(UserEmailMap.user))
339 338 if cache:
340 339 q = q.options(FromCache("sql_cache_short",
341 340 "get_email_map_key_%s" % email))
342 341 ret = getattr(q.scalar(), 'user', None)
343 342
344 343 return ret
345 344
346 345 @classmethod
347 346 def get_first_admin(cls):
348 347 user = User.query().filter(User.admin == True).first()
349 348 if user is None:
350 349 raise Exception('Missing administrative account!')
351 350 return user
352 351
353 352 @classmethod
354 353 def get_default_user(cls, cache=False):
355 354 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
356 355 if user is None:
357 356 raise Exception('Missing default account!')
358 357 return user
359 358
360 359
361 360
362 361
363 362 class UserEmailMap(Base, BaseModel):
364 363 __tablename__ = 'user_email_map'
365 364 __table_args__ = (
366 365 Index('uem_email_idx', 'email'),
367 366 UniqueConstraint('email'),
368 367 {'extend_existing': True, 'mysql_engine': 'InnoDB',
369 368 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
370 369 )
371 370 __mapper_args__ = {}
372 371
373 372 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
374 373 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
375 374 _email = Column("email", String(255), nullable=True, unique=False, default=None)
376 375 user = relationship('User', lazy='joined')
377 376
378 377 @validates('_email')
379 378 def validate_email(self, key, email):
380 379 # check if this email is not main one
381 380 main_email = Session().query(User).filter(User.email == email).scalar()
382 381 if main_email is not None:
383 382 raise AttributeError('email %s is present is user table' % email)
384 383 return email
385 384
386 385 @hybrid_property
387 386 def email(self):
388 387 return self._email
389 388
390 389 @email.setter
391 390 def email(self, val):
392 391 self._email = val.lower() if val else None
393 392
394 393
395 394 class UserIpMap(Base, BaseModel):
396 395 __tablename__ = 'user_ip_map'
397 396 __table_args__ = (
398 397 UniqueConstraint('user_id', 'ip_addr'),
399 398 {'extend_existing': True, 'mysql_engine': 'InnoDB',
400 399 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
401 400 )
402 401 __mapper_args__ = {}
403 402
404 403 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
405 404 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
406 405 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
407 406 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
408 407 user = relationship('User', lazy='joined')
409 408
410 409
411 410 class UserLog(Base, BaseModel):
412 411 __tablename__ = 'user_logs'
413 412 __table_args__ = (
414 413 {'extend_existing': True, 'mysql_engine': 'InnoDB',
415 414 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
416 415 )
417 416 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
418 417 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
419 418 username = Column("username", String(255), nullable=True, unique=None, default=None)
420 419 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
421 420 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
422 421 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
423 422 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
424 423 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
425 424
426 425 def __unicode__(self):
427 426 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
428 427 self.repository_name,
429 428 self.action)
430 429
431 430 user = relationship('User')
432 431 repository = relationship('Repository', cascade='')
433 432
434 433
435 434 class UserGroup(Base, BaseModel):
436 435 __tablename__ = 'users_groups'
437 436 __table_args__ = (
438 437 {'extend_existing': True, 'mysql_engine': 'InnoDB',
439 438 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
440 439 )
441 440
442 441 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
443 442 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
444 443 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
445 444 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
446 445 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
447 446
448 447 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
449 448 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
450 449 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
451 450 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
452 451 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
453 452 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
454 453
455 454 user = relationship('User')
456 455
457 456 def __unicode__(self):
458 457 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
459 458 self.users_group_id,
460 459 self.users_group_name)
461 460
462 461 @classmethod
463 462 def get_by_group_name(cls, group_name, cache=False,
464 463 case_insensitive=False):
465 464 if case_insensitive:
466 465 q = cls.query().filter(cls.users_group_name.ilike(group_name))
467 466 else:
468 467 q = cls.query().filter(cls.users_group_name == group_name)
469 468 if cache:
470 469 q = q.options(FromCache(
471 470 "sql_cache_short",
472 471 "get_user_%s" % _hash_key(group_name)
473 472 )
474 473 )
475 474 return q.scalar()
476 475
477 476 @classmethod
478 477 def get(cls, user_group_id, cache=False):
479 478 user_group = cls.query()
480 479 if cache:
481 480 user_group = user_group.options(FromCache("sql_cache_short",
482 481 "get_users_group_%s" % user_group_id))
483 482 return user_group.get(user_group_id)
484 483
485 484
486 485 class UserGroupMember(Base, BaseModel):
487 486 __tablename__ = 'users_groups_members'
488 487 __table_args__ = (
489 488 {'extend_existing': True, 'mysql_engine': 'InnoDB',
490 489 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
491 490 )
492 491
493 492 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
494 493 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
495 494 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
496 495
497 496 user = relationship('User', lazy='joined')
498 497 users_group = relationship('UserGroup')
499 498
500 499 def __init__(self, gr_id='', u_id=''):
501 500 self.users_group_id = gr_id
502 501 self.user_id = u_id
503 502
504 503
505 504 class RepositoryField(Base, BaseModel):
506 505 __tablename__ = 'repositories_fields'
507 506 __table_args__ = (
508 507 UniqueConstraint('repository_id', 'field_key'), # no-multi field
509 508 {'extend_existing': True, 'mysql_engine': 'InnoDB',
510 509 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
511 510 )
512 511 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
513 512
514 513 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
515 514 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
516 515 field_key = Column("field_key", String(250))
517 516 field_label = Column("field_label", String(1024), nullable=False)
518 517 field_value = Column("field_value", String(10000), nullable=False)
519 518 field_desc = Column("field_desc", String(1024), nullable=False)
520 519 field_type = Column("field_type", String(256), nullable=False, unique=None)
521 520 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
522 521
523 522 repository = relationship('Repository')
524 523
525 524 @classmethod
526 525 def get_by_key_name(cls, key, repo):
527 526 row = cls.query()\
528 527 .filter(cls.repository == repo)\
529 528 .filter(cls.field_key == key).scalar()
530 529 return row
531 530
532 531
533 532 class Repository(Base, BaseModel):
534 533 __tablename__ = 'repositories'
535 534 __table_args__ = (
536 535 UniqueConstraint('repo_name'),
537 536 Index('r_repo_name_idx', 'repo_name'),
538 537 {'extend_existing': True, 'mysql_engine': 'InnoDB',
539 538 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
540 539 )
541 540
542 541 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
543 542 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
544 543 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
545 544 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
546 545 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
547 546 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
548 547 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
549 548 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
550 549 description = Column("description", String(10000), nullable=True, unique=None, default=None)
551 550 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
552 551 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
553 552 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
554 553 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
555 554 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
556 555 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
557 556
558 557 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
559 558 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
560 559
561 560 user = relationship('User')
562 561 fork = relationship('Repository', remote_side=repo_id)
563 562 group = relationship('RepoGroup')
564 563 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
565 564 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
566 565 stats = relationship('Statistics', cascade='all', uselist=False)
567 566
568 567 followers = relationship('UserFollowing',
569 568 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
570 569 cascade='all')
571 570 extra_fields = relationship('RepositoryField',
572 571 cascade="all, delete, delete-orphan")
573 572
574 573 logs = relationship('UserLog')
575 574 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
576 575
577 576 pull_requests_org = relationship('PullRequest',
578 577 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
579 578 cascade="all, delete, delete-orphan")
580 579
581 580 pull_requests_other = relationship('PullRequest',
582 581 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
583 582 cascade="all, delete, delete-orphan")
584 583
585 584 def __unicode__(self):
586 585 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
587 safe_unicode(self.repo_name))
586 safe_str(self.repo_name))
588 587
589 588 @classmethod
590 589 def get_by_repo_name(cls, repo_name):
591 590 q = Session().query(cls).filter(cls.repo_name == repo_name)
592 591 q = q.options(joinedload(Repository.fork))\
593 592 .options(joinedload(Repository.user))\
594 593 .options(joinedload(Repository.group))
595 594 return q.scalar()
596 595
597 596
598 597 class RepoGroup(Base, BaseModel):
599 598 __tablename__ = 'groups'
600 599 __table_args__ = (
601 600 UniqueConstraint('group_name', 'group_parent_id'),
602 601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
603 602 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
604 603 )
605 604 __mapper_args__ = {'order_by': 'group_name'}
606 605
607 606 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
608 607 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
609 608 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
610 609 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
611 610 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
612 611 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
613 612
614 613 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
615 614 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
616 615 parent_group = relationship('RepoGroup', remote_side=group_id)
617 616 user = relationship('User')
618 617
619 618 def __init__(self, group_name='', parent_group=None):
620 619 self.group_name = group_name
621 620 self.parent_group = parent_group
622 621
623 622 def __unicode__(self):
624 623 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
625 624 self.group_name)
626 625
627 626 @classmethod
628 627 def url_sep(cls):
629 628 return URL_SEP
630 629
631 630 @classmethod
632 631 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
633 632 if case_insensitive:
634 633 gr = cls.query()\
635 634 .filter(cls.group_name.ilike(group_name))
636 635 else:
637 636 gr = cls.query()\
638 637 .filter(cls.group_name == group_name)
639 638 if cache:
640 639 gr = gr.options(FromCache(
641 640 "sql_cache_short",
642 641 "get_group_%s" % _hash_key(group_name)
643 642 )
644 643 )
645 644 return gr.scalar()
646 645
647 646
648 647 class Permission(Base, BaseModel):
649 648 __tablename__ = 'permissions'
650 649 __table_args__ = (
651 650 Index('p_perm_name_idx', 'permission_name'),
652 651 {'extend_existing': True, 'mysql_engine': 'InnoDB',
653 652 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
654 653 )
655 654 PERMS = [
656 655 ('hg.admin', _('RhodeCode Administrator')),
657 656
658 657 ('repository.none', _('Repository no access')),
659 658 ('repository.read', _('Repository read access')),
660 659 ('repository.write', _('Repository write access')),
661 660 ('repository.admin', _('Repository admin access')),
662 661
663 662 ('group.none', _('Repository group no access')),
664 663 ('group.read', _('Repository group read access')),
665 664 ('group.write', _('Repository group write access')),
666 665 ('group.admin', _('Repository group admin access')),
667 666
668 667 ('usergroup.none', _('User group no access')),
669 668 ('usergroup.read', _('User group read access')),
670 669 ('usergroup.write', _('User group write access')),
671 670 ('usergroup.admin', _('User group admin access')),
672 671
673 672 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
674 673 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
675 674
676 675 ('hg.usergroup.create.false', _('User Group creation disabled')),
677 676 ('hg.usergroup.create.true', _('User Group creation enabled')),
678 677
679 678 ('hg.create.none', _('Repository creation disabled')),
680 679 ('hg.create.repository', _('Repository creation enabled')),
681 680
682 681 ('hg.fork.none', _('Repository forking disabled')),
683 682 ('hg.fork.repository', _('Repository forking enabled')),
684 683
685 684 ('hg.register.none', _('Registration disabled')),
686 685 ('hg.register.manual_activate', _('User Registration with manual account activation')),
687 686 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
688 687
689 688 ('hg.extern_activate.manual', _('Manual activation of external account')),
690 689 ('hg.extern_activate.auto', _('Automatic activation of external account')),
691 690
692 691 ]
693 692
694 693 #definition of system default permissions for DEFAULT user
695 694 DEFAULT_USER_PERMISSIONS = [
696 695 'repository.read',
697 696 'group.read',
698 697 'usergroup.read',
699 698 'hg.create.repository',
700 699 'hg.fork.repository',
701 700 'hg.register.manual_activate',
702 701 'hg.extern_activate.auto',
703 702 ]
704 703
705 704 # defines which permissions are more important higher the more important
706 705 # Weight defines which permissions are more important.
707 706 # The higher number the more important.
708 707 PERM_WEIGHTS = {
709 708 'repository.none': 0,
710 709 'repository.read': 1,
711 710 'repository.write': 3,
712 711 'repository.admin': 4,
713 712
714 713 'group.none': 0,
715 714 'group.read': 1,
716 715 'group.write': 3,
717 716 'group.admin': 4,
718 717
719 718 'usergroup.none': 0,
720 719 'usergroup.read': 1,
721 720 'usergroup.write': 3,
722 721 'usergroup.admin': 4,
723 722 'hg.repogroup.create.false': 0,
724 723 'hg.repogroup.create.true': 1,
725 724
726 725 'hg.usergroup.create.false': 0,
727 726 'hg.usergroup.create.true': 1,
728 727
729 728 'hg.fork.none': 0,
730 729 'hg.fork.repository': 1,
731 730 'hg.create.none': 0,
732 731 'hg.create.repository': 1
733 732 }
734 733
735 734 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
736 735 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
737 736 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
738 737
739 738 def __unicode__(self):
740 739 return u"<%s('%s:%s')>" % (
741 740 self.__class__.__name__, self.permission_id, self.permission_name
742 741 )
743 742
744 743 @classmethod
745 744 def get_by_key(cls, key):
746 745 return cls.query().filter(cls.permission_name == key).scalar()
747 746
748 747
749 748 class UserRepoToPerm(Base, BaseModel):
750 749 __tablename__ = 'repo_to_perm'
751 750 __table_args__ = (
752 751 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
753 752 {'extend_existing': True, 'mysql_engine': 'InnoDB',
754 753 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
755 754 )
756 755 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
757 756 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
758 757 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
759 758 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
760 759
761 760 user = relationship('User')
762 761 repository = relationship('Repository')
763 762 permission = relationship('Permission')
764 763
765 764 def __unicode__(self):
766 765 return u'<%s => %s >' % (self.user, self.repository)
767 766
768 767
769 768 class UserUserGroupToPerm(Base, BaseModel):
770 769 __tablename__ = 'user_user_group_to_perm'
771 770 __table_args__ = (
772 771 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
773 772 {'extend_existing': True, 'mysql_engine': 'InnoDB',
774 773 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
775 774 )
776 775 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
777 776 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
778 777 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
779 778 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
780 779
781 780 user = relationship('User')
782 781 user_group = relationship('UserGroup')
783 782 permission = relationship('Permission')
784 783
785 784 def __unicode__(self):
786 785 return u'<%s => %s >' % (self.user, self.user_group)
787 786
788 787
789 788 class UserToPerm(Base, BaseModel):
790 789 __tablename__ = 'user_to_perm'
791 790 __table_args__ = (
792 791 UniqueConstraint('user_id', 'permission_id'),
793 792 {'extend_existing': True, 'mysql_engine': 'InnoDB',
794 793 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
795 794 )
796 795 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
797 796 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
798 797 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
799 798
800 799 user = relationship('User')
801 800 permission = relationship('Permission', lazy='joined')
802 801
803 802 def __unicode__(self):
804 803 return u'<%s => %s >' % (self.user, self.permission)
805 804
806 805
807 806 class UserGroupRepoToPerm(Base, BaseModel):
808 807 __tablename__ = 'users_group_repo_to_perm'
809 808 __table_args__ = (
810 809 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
811 810 {'extend_existing': True, 'mysql_engine': 'InnoDB',
812 811 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
813 812 )
814 813 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
815 814 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
816 815 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
817 816 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
818 817
819 818 users_group = relationship('UserGroup')
820 819 permission = relationship('Permission')
821 820 repository = relationship('Repository')
822 821
823 822 def __unicode__(self):
824 823 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
825 824
826 825
827 826 class UserGroupUserGroupToPerm(Base, BaseModel):
828 827 __tablename__ = 'user_group_user_group_to_perm'
829 828 __table_args__ = (
830 829 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
831 830 CheckConstraint('target_user_group_id != user_group_id'),
832 831 {'extend_existing': True, 'mysql_engine': 'InnoDB',
833 832 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
834 833 )
835 834 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)
836 835 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
837 836 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
838 837 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
839 838
840 839 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
841 840 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
842 841 permission = relationship('Permission')
843 842
844 843 def __unicode__(self):
845 844 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
846 845
847 846
848 847 class UserGroupToPerm(Base, BaseModel):
849 848 __tablename__ = 'users_group_to_perm'
850 849 __table_args__ = (
851 850 UniqueConstraint('users_group_id', 'permission_id',),
852 851 {'extend_existing': True, 'mysql_engine': 'InnoDB',
853 852 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
854 853 )
855 854 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
856 855 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
857 856 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
858 857
859 858 users_group = relationship('UserGroup')
860 859 permission = relationship('Permission')
861 860
862 861
863 862 class UserRepoGroupToPerm(Base, BaseModel):
864 863 __tablename__ = 'user_repo_group_to_perm'
865 864 __table_args__ = (
866 865 UniqueConstraint('user_id', 'group_id', 'permission_id'),
867 866 {'extend_existing': True, 'mysql_engine': 'InnoDB',
868 867 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
869 868 )
870 869
871 870 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
872 871 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
873 872 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
874 873 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
875 874
876 875 user = relationship('User')
877 876 group = relationship('RepoGroup')
878 877 permission = relationship('Permission')
879 878
880 879
881 880 class UserGroupRepoGroupToPerm(Base, BaseModel):
882 881 __tablename__ = 'users_group_repo_group_to_perm'
883 882 __table_args__ = (
884 883 UniqueConstraint('users_group_id', 'group_id'),
885 884 {'extend_existing': True, 'mysql_engine': 'InnoDB',
886 885 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
887 886 )
888 887
889 888 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)
890 889 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
891 890 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
892 891 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
893 892
894 893 users_group = relationship('UserGroup')
895 894 permission = relationship('Permission')
896 895 group = relationship('RepoGroup')
897 896
898 897
899 898 class Statistics(Base, BaseModel):
900 899 __tablename__ = 'statistics'
901 900 __table_args__ = (
902 901 UniqueConstraint('repository_id'),
903 902 {'extend_existing': True, 'mysql_engine': 'InnoDB',
904 903 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
905 904 )
906 905 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
907 906 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
908 907 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
909 908 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
910 909 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
911 910 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
912 911
913 912 repository = relationship('Repository', single_parent=True)
914 913
915 914
916 915 class UserFollowing(Base, BaseModel):
917 916 __tablename__ = 'user_followings'
918 917 __table_args__ = (
919 918 UniqueConstraint('user_id', 'follows_repository_id'),
920 919 UniqueConstraint('user_id', 'follows_user_id'),
921 920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
922 921 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
923 922 )
924 923
925 924 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
926 925 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
927 926 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
928 927 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
929 928 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
930 929
931 930 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
932 931
933 932 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
934 933 follows_repository = relationship('Repository', order_by='Repository.repo_name')
935 934
936 935
937 936 class CacheInvalidation(Base, BaseModel):
938 937 __tablename__ = 'cache_invalidation'
939 938 __table_args__ = (
940 939 UniqueConstraint('cache_key'),
941 940 Index('key_idx', 'cache_key'),
942 941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
943 942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
944 943 )
945 944 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
946 945 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
947 946 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
948 947 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
949 948
950 949 def __init__(self, cache_key, cache_args=''):
951 950 self.cache_key = cache_key
952 951 self.cache_args = cache_args
953 952 self.cache_active = False
954 953
955 954
956 955 class ChangesetComment(Base, BaseModel):
957 956 __tablename__ = 'changeset_comments'
958 957 __table_args__ = (
959 958 Index('cc_revision_idx', 'revision'),
960 959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
961 960 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
962 961 )
963 962 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
964 963 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
965 964 revision = Column('revision', String(40), nullable=True)
966 965 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
967 966 line_no = Column('line_no', Unicode(10), nullable=True)
968 967 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
969 968 f_path = Column('f_path', Unicode(1000), nullable=True)
970 969 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
971 970 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
972 971 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
973 972 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
974 973
975 974 author = relationship('User', lazy='joined')
976 975 repo = relationship('Repository')
977 976 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
978 977 pull_request = relationship('PullRequest', lazy='joined')
979 978
980 979
981 980 class ChangesetStatus(Base, BaseModel):
982 981 __tablename__ = 'changeset_statuses'
983 982 __table_args__ = (
984 983 Index('cs_revision_idx', 'revision'),
985 984 Index('cs_version_idx', 'version'),
986 985 UniqueConstraint('repo_id', 'revision', 'version'),
987 986 {'extend_existing': True, 'mysql_engine': 'InnoDB',
988 987 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
989 988 )
990 989 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
991 990 STATUS_APPROVED = 'approved'
992 991 STATUS_REJECTED = 'rejected'
993 992 STATUS_UNDER_REVIEW = 'under_review'
994 993
995 994 STATUSES = [
996 995 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
997 996 (STATUS_APPROVED, _("Approved")),
998 997 (STATUS_REJECTED, _("Rejected")),
999 998 (STATUS_UNDER_REVIEW, _("Under Review")),
1000 999 ]
1001 1000
1002 1001 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1003 1002 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1004 1003 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1005 1004 revision = Column('revision', String(40), nullable=False)
1006 1005 status = Column('status', String(128), nullable=False, default=DEFAULT)
1007 1006 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1008 1007 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1009 1008 version = Column('version', Integer(), nullable=False, default=0)
1010 1009 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1011 1010
1012 1011 author = relationship('User', lazy='joined')
1013 1012 repo = relationship('Repository')
1014 1013 comment = relationship('ChangesetComment', lazy='joined')
1015 1014 pull_request = relationship('PullRequest', lazy='joined')
1016 1015
1017 1016
1018 1017
1019 1018 class PullRequest(Base, BaseModel):
1020 1019 __tablename__ = 'pull_requests'
1021 1020 __table_args__ = (
1022 1021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1023 1022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1024 1023 )
1025 1024
1026 1025 STATUS_NEW = u'new'
1027 1026 STATUS_OPEN = u'open'
1028 1027 STATUS_CLOSED = u'closed'
1029 1028
1030 1029 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1031 1030 title = Column('title', Unicode(256), nullable=True)
1032 1031 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1033 1032 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1034 1033 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1035 1034 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1036 1035 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1037 1036 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
1038 1037 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1039 1038 org_ref = Column('org_ref', Unicode(256), nullable=False)
1040 1039 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1041 1040 other_ref = Column('other_ref', Unicode(256), nullable=False)
1042 1041
1043 1042 author = relationship('User', lazy='joined')
1044 1043 reviewers = relationship('PullRequestReviewers',
1045 1044 cascade="all, delete, delete-orphan")
1046 1045 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1047 1046 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1048 1047 statuses = relationship('ChangesetStatus')
1049 1048 comments = relationship('ChangesetComment',
1050 1049 cascade="all, delete, delete-orphan")
1051 1050
1052 1051
1053 1052 class PullRequestReviewers(Base, BaseModel):
1054 1053 __tablename__ = 'pull_request_reviewers'
1055 1054 __table_args__ = (
1056 1055 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1057 1056 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1058 1057 )
1059 1058
1060 1059 def __init__(self, user=None, pull_request=None):
1061 1060 self.user = user
1062 1061 self.pull_request = pull_request
1063 1062
1064 1063 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1065 1064 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1066 1065 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1067 1066
1068 1067 user = relationship('User')
1069 1068 pull_request = relationship('PullRequest')
1070 1069
1071 1070
1072 1071 class Notification(Base, BaseModel):
1073 1072 __tablename__ = 'notifications'
1074 1073 __table_args__ = (
1075 1074 Index('notification_type_idx', 'type'),
1076 1075 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1077 1076 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1078 1077 )
1079 1078
1080 1079 TYPE_CHANGESET_COMMENT = u'cs_comment'
1081 1080 TYPE_MESSAGE = u'message'
1082 1081 TYPE_MENTION = u'mention'
1083 1082 TYPE_REGISTRATION = u'registration'
1084 1083 TYPE_PULL_REQUEST = u'pull_request'
1085 1084 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1086 1085
1087 1086 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1088 1087 subject = Column('subject', Unicode(512), nullable=True)
1089 1088 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1090 1089 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1091 1090 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1092 1091 type_ = Column('type', Unicode(256))
1093 1092
1094 1093 created_by_user = relationship('User')
1095 1094 notifications_to_users = relationship('UserNotification', lazy='joined',
1096 1095 cascade="all, delete, delete-orphan")
1097 1096
1098 1097
1099 1098 class UserNotification(Base, BaseModel):
1100 1099 __tablename__ = 'user_to_notification'
1101 1100 __table_args__ = (
1102 1101 UniqueConstraint('user_id', 'notification_id'),
1103 1102 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1104 1103 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1105 1104 )
1106 1105 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1107 1106 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1108 1107 read = Column('read', Boolean, default=False)
1109 1108 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1110 1109
1111 1110 user = relationship('User', lazy="joined")
1112 1111 notification = relationship('Notification', lazy="joined",
1113 1112 order_by=lambda: Notification.created_on.desc(),)
1114 1113
1115 1114
1116 1115 class Gist(Base, BaseModel):
1117 1116 __tablename__ = 'gists'
1118 1117 __table_args__ = (
1119 1118 Index('g_gist_access_id_idx', 'gist_access_id'),
1120 1119 Index('g_created_on_idx', 'created_on'),
1121 1120 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1122 1121 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1123 1122 )
1124 1123 GIST_PUBLIC = u'public'
1125 1124 GIST_PRIVATE = u'private'
1126 1125
1127 1126 gist_id = Column('gist_id', Integer(), primary_key=True)
1128 1127 gist_access_id = Column('gist_access_id', Unicode(250))
1129 1128 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1130 1129 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
1131 1130 gist_expires = Column('gist_expires', Float(53), nullable=False)
1132 1131 gist_type = Column('gist_type', Unicode(128), nullable=False)
1133 1132 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1134 1133 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1135 1134
1136 1135 owner = relationship('User')
1137 1136
1138 1137
1139 1138 class DbMigrateVersion(Base, BaseModel):
1140 1139 __tablename__ = 'db_migrate_version'
1141 1140 __table_args__ = (
1142 1141 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1143 1142 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1144 1143 )
1145 1144 repository_id = Column('repository_id', String(250), primary_key=True)
1146 1145 repository_path = Column('repository_path', Text)
1147 1146 version = Column('version', Integer)
@@ -1,1170 +1,1169 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 import os
22 21 import time
23 22 import logging
24 23 import datetime
25 24 import traceback
26 25 import hashlib
27 26 import collections
28 27 import functools
29 28
30 29 from sqlalchemy import *
31 30 from sqlalchemy.ext.hybrid import hybrid_property
32 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
33 32 from sqlalchemy.exc import DatabaseError
34 33 from beaker.cache import cache_region, region_invalidate
35 34 from webob.exc import HTTPNotFound
36 35
37 36 from rhodecode.translation import _
38 37
39 38 from rhodecode.lib.vcs import get_backend
40 39 from rhodecode.lib.vcs.utils.helpers import get_scm
41 40 from rhodecode.lib.vcs.exceptions import VCSError
42 41 from zope.cachedescriptors.property import Lazy as LazyProperty
43 42 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 43
45 44 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
46 safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int
45 remove_prefix, time_to_datetime, aslist, Optional, safe_int
47 46 from rhodecode.lib.ext_json import json
48 47 from rhodecode.lib.caching_query import FromCache
49 48
50 49 from rhodecode.model.meta import Base, Session
51 50
52 51 URL_SEP = '/'
53 52 log = logging.getLogger(__name__)
54 53
55 54 #==============================================================================
56 55 # BASE CLASSES
57 56 #==============================================================================
58 57
59 58 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
60 59
61 60
62 61 class BaseModel(object):
63 62 """
64 63 Base Model for all classes
65 64 """
66 65
67 66 @classmethod
68 67 def _get_keys(cls):
69 68 """return column names for this model """
70 69 return class_mapper(cls).c.keys()
71 70
72 71 def get_dict(self):
73 72 """
74 73 return dict with keys and values corresponding
75 74 to this model data """
76 75
77 76 d = {}
78 77 for k in self._get_keys():
79 78 d[k] = getattr(self, k)
80 79
81 80 # also use __json__() if present to get additional fields
82 81 _json_attr = getattr(self, '__json__', None)
83 82 if _json_attr:
84 83 # update with attributes from __json__
85 84 if callable(_json_attr):
86 85 _json_attr = _json_attr()
87 86 for k, val in _json_attr.items():
88 87 d[k] = val
89 88 return d
90 89
91 90 def get_appstruct(self):
92 91 """return list with keys and values tupples corresponding
93 92 to this model data """
94 93
95 94 l = []
96 95 for k in self._get_keys():
97 96 l.append((k, getattr(self, k),))
98 97 return l
99 98
100 99 def populate_obj(self, populate_dict):
101 100 """populate model with data from given populate_dict"""
102 101
103 102 for k in self._get_keys():
104 103 if k in populate_dict:
105 104 setattr(self, k, populate_dict[k])
106 105
107 106 @classmethod
108 107 def query(cls):
109 108 return Session().query(cls)
110 109
111 110 @classmethod
112 111 def get(cls, id_):
113 112 if id_:
114 113 return cls.query().get(id_)
115 114
116 115 @classmethod
117 116 def get_or_404(cls, id_):
118 117 try:
119 118 id_ = int(id_)
120 119 except (TypeError, ValueError):
121 120 raise HTTPNotFound
122 121
123 122 res = cls.query().get(id_)
124 123 if not res:
125 124 raise HTTPNotFound
126 125 return res
127 126
128 127 @classmethod
129 128 def getAll(cls):
130 129 # deprecated and left for backward compatibility
131 130 return cls.get_all()
132 131
133 132 @classmethod
134 133 def get_all(cls):
135 134 return cls.query().all()
136 135
137 136 @classmethod
138 137 def delete(cls, id_):
139 138 obj = cls.query().get(id_)
140 139 Session().delete(obj)
141 140
142 141 def __repr__(self):
143 142 if hasattr(self, '__unicode__'):
144 143 # python repr needs to return str
145 144 return safe_str(self.__unicode__())
146 145 return '<DB:%s>' % (self.__class__.__name__)
147 146
148 147
149 148 class RhodeCodeSetting(Base, BaseModel):
150 149 SETTINGS_TYPES = {
151 150 'str': safe_str,
152 151 'int': safe_int,
153 'unicode': safe_unicode,
152 'unicode': safe_str,
154 153 'bool': str2bool,
155 154 'list': functools.partial(aslist, sep=',')
156 155 }
157 156 __tablename__ = 'rhodecode_settings'
158 157 __table_args__ = (
159 158 UniqueConstraint('app_settings_name'),
160 159 {'extend_existing': True, 'mysql_engine': 'InnoDB',
161 160 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
162 161 )
163 162 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
164 163 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
165 164 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
166 165 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
167 166
168 167 def __init__(self, key='', val='', type='unicode'):
169 168 self.app_settings_name = key
170 169 self.app_settings_value = val
171 170 self.app_settings_type = type
172 171
173 172 @validates('_app_settings_value')
174 173 def validate_settings_value(self, key, val):
175 assert type(val) == unicode
174 assert type(val) == str
176 175 return val
177 176
178 177 @hybrid_property
179 178 def app_settings_value(self):
180 179 v = self._app_settings_value
181 180 _type = self.app_settings_type
182 181 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
183 182 return converter(v)
184 183
185 184 @app_settings_value.setter
186 185 def app_settings_value(self, val):
187 186 """
188 187 Setter that will always make sure we use unicode in app_settings_value
189 188
190 189 :param val:
191 190 """
192 self._app_settings_value = safe_unicode(val)
191 self._app_settings_value = safe_str(val)
193 192
194 193 @hybrid_property
195 194 def app_settings_type(self):
196 195 return self._app_settings_type
197 196
198 197 @app_settings_type.setter
199 198 def app_settings_type(self, val):
200 199 if val not in self.SETTINGS_TYPES:
201 200 raise Exception('type must be one of %s got %s'
202 201 % (self.SETTINGS_TYPES.keys(), val))
203 202 self._app_settings_type = val
204 203
205 204 def __unicode__(self):
206 205 return u"<%s('%s:%s[%s]')>" % (
207 206 self.__class__.__name__,
208 207 self.app_settings_name, self.app_settings_value, self.app_settings_type
209 208 )
210 209
211 210
212 211 class RhodeCodeUi(Base, BaseModel):
213 212 __tablename__ = 'rhodecode_ui'
214 213 __table_args__ = (
215 214 UniqueConstraint('ui_key'),
216 215 {'extend_existing': True, 'mysql_engine': 'InnoDB',
217 216 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
218 217 )
219 218
220 219 HOOK_REPO_SIZE = 'changegroup.repo_size'
221 220 HOOK_PUSH = 'changegroup.push_logger'
222 221 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
223 222 HOOK_PULL = 'outgoing.pull_logger'
224 223 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
225 224
226 225 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
227 226 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
228 227 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
229 228 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
230 229 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
231 230
232 231
233 232
234 233 class User(Base, BaseModel):
235 234 __tablename__ = 'users'
236 235 __table_args__ = (
237 236 UniqueConstraint('username'), UniqueConstraint('email'),
238 237 Index('u_username_idx', 'username'),
239 238 Index('u_email_idx', 'email'),
240 239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
241 240 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
242 241 )
243 242 DEFAULT_USER = 'default'
244 243
245 244 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
246 245 username = Column("username", String(255), nullable=True, unique=None, default=None)
247 246 password = Column("password", String(255), nullable=True, unique=None, default=None)
248 247 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
249 248 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
250 249 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
251 250 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
252 251 _email = Column("email", String(255), nullable=True, unique=None, default=None)
253 252 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
254 253 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
255 254 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
256 255 #for migration reasons, this is going to be later deleted
257 256 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
258 257
259 258 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
260 259 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
261 260 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
262 261
263 262 user_log = relationship('UserLog')
264 263 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
265 264
266 265 repositories = relationship('Repository')
267 266 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
268 267 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
269 268
270 269 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
271 270 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
272 271
273 272 group_member = relationship('UserGroupMember', cascade='all')
274 273
275 274 notifications = relationship('UserNotification', cascade='all')
276 275 # notifications assigned to this user
277 276 user_created_notifications = relationship('Notification', cascade='all')
278 277 # comments created by this user
279 278 user_comments = relationship('ChangesetComment', cascade='all')
280 279 user_emails = relationship('UserEmailMap', cascade='all')
281 280
282 281 @hybrid_property
283 282 def email(self):
284 283 return self._email
285 284
286 285 @email.setter
287 286 def email(self, val):
288 287 self._email = val.lower() if val else None
289 288
290 289 @property
291 290 def firstname(self):
292 291 # alias for future
293 292 return self.name
294 293
295 294 @property
296 295 def username_and_name(self):
297 296 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
298 297
299 298 @property
300 299 def full_name(self):
301 300 return '%s %s' % (self.firstname, self.lastname)
302 301
303 302 @property
304 303 def full_contact(self):
305 304 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
306 305
307 306 @property
308 307 def short_contact(self):
309 308 return '%s %s' % (self.firstname, self.lastname)
310 309
311 310 @property
312 311 def is_admin(self):
313 312 return self.admin
314 313
315 314 @classmethod
316 315 def get_by_username(cls, username, case_insensitive=False, cache=False):
317 316 if case_insensitive:
318 317 q = cls.query().filter(cls.username.ilike(username))
319 318 else:
320 319 q = cls.query().filter(cls.username == username)
321 320
322 321 if cache:
323 322 q = q.options(FromCache(
324 323 "sql_cache_short",
325 324 "get_user_%s" % _hash_key(username)
326 325 )
327 326 )
328 327 return q.scalar()
329 328
330 329 @classmethod
331 330 def get_by_auth_token(cls, auth_token, cache=False):
332 331 q = cls.query().filter(cls.api_key == auth_token)
333 332
334 333 if cache:
335 334 q = q.options(FromCache("sql_cache_short",
336 335 "get_auth_token_%s" % auth_token))
337 336 return q.scalar()
338 337
339 338 @classmethod
340 339 def get_by_email(cls, email, case_insensitive=False, cache=False):
341 340 if case_insensitive:
342 341 q = cls.query().filter(cls.email.ilike(email))
343 342 else:
344 343 q = cls.query().filter(cls.email == email)
345 344
346 345 if cache:
347 346 q = q.options(FromCache("sql_cache_short",
348 347 "get_email_key_%s" % email))
349 348
350 349 ret = q.scalar()
351 350 if ret is None:
352 351 q = UserEmailMap.query()
353 352 # try fetching in alternate email map
354 353 if case_insensitive:
355 354 q = q.filter(UserEmailMap.email.ilike(email))
356 355 else:
357 356 q = q.filter(UserEmailMap.email == email)
358 357 q = q.options(joinedload(UserEmailMap.user))
359 358 if cache:
360 359 q = q.options(FromCache("sql_cache_short",
361 360 "get_email_map_key_%s" % email))
362 361 ret = getattr(q.scalar(), 'user', None)
363 362
364 363 return ret
365 364
366 365 @classmethod
367 366 def get_first_admin(cls):
368 367 user = User.query().filter(User.admin == True).first()
369 368 if user is None:
370 369 raise Exception('Missing administrative account!')
371 370 return user
372 371
373 372 @classmethod
374 373 def get_default_user(cls, cache=False):
375 374 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
376 375 if user is None:
377 376 raise Exception('Missing default account!')
378 377 return user
379 378
380 379
381 380
382 381
383 382 class UserEmailMap(Base, BaseModel):
384 383 __tablename__ = 'user_email_map'
385 384 __table_args__ = (
386 385 Index('uem_email_idx', 'email'),
387 386 UniqueConstraint('email'),
388 387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
389 388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
390 389 )
391 390 __mapper_args__ = {}
392 391
393 392 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
394 393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
395 394 _email = Column("email", String(255), nullable=True, unique=False, default=None)
396 395 user = relationship('User', lazy='joined')
397 396
398 397 @validates('_email')
399 398 def validate_email(self, key, email):
400 399 # check if this email is not main one
401 400 main_email = Session().query(User).filter(User.email == email).scalar()
402 401 if main_email is not None:
403 402 raise AttributeError('email %s is present is user table' % email)
404 403 return email
405 404
406 405 @hybrid_property
407 406 def email(self):
408 407 return self._email
409 408
410 409 @email.setter
411 410 def email(self, val):
412 411 self._email = val.lower() if val else None
413 412
414 413
415 414 class UserIpMap(Base, BaseModel):
416 415 __tablename__ = 'user_ip_map'
417 416 __table_args__ = (
418 417 UniqueConstraint('user_id', 'ip_addr'),
419 418 {'extend_existing': True, 'mysql_engine': 'InnoDB',
420 419 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
421 420 )
422 421 __mapper_args__ = {}
423 422
424 423 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
425 424 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
426 425 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
427 426 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
428 427 user = relationship('User', lazy='joined')
429 428
430 429
431 430 class UserLog(Base, BaseModel):
432 431 __tablename__ = 'user_logs'
433 432 __table_args__ = (
434 433 {'extend_existing': True, 'mysql_engine': 'InnoDB',
435 434 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
436 435 )
437 436 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
438 437 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
439 438 username = Column("username", String(255), nullable=True, unique=None, default=None)
440 439 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
441 440 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
442 441 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
443 442 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
444 443 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
445 444
446 445 def __unicode__(self):
447 446 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
448 447 self.repository_name,
449 448 self.action)
450 449
451 450 user = relationship('User')
452 451 repository = relationship('Repository', cascade='')
453 452
454 453
455 454 class UserGroup(Base, BaseModel):
456 455 __tablename__ = 'users_groups'
457 456 __table_args__ = (
458 457 {'extend_existing': True, 'mysql_engine': 'InnoDB',
459 458 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
460 459 )
461 460
462 461 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
463 462 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
464 463 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
465 464 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
466 465 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
467 466 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
468 467 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
469 468
470 469 # don't trigger lazy load for migrations
471 470 #members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
472 471 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
473 472 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
474 473 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
475 474 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
476 475 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
477 476
478 477 user = relationship('User')
479 478
480 479 def __unicode__(self):
481 480 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
482 481 self.users_group_id,
483 482 self.users_group_name)
484 483
485 484 @classmethod
486 485 def get_by_group_name(cls, group_name, cache=False,
487 486 case_insensitive=False):
488 487 if case_insensitive:
489 488 q = cls.query().filter(cls.users_group_name.ilike(group_name))
490 489 else:
491 490 q = cls.query().filter(cls.users_group_name == group_name)
492 491 if cache:
493 492 q = q.options(FromCache(
494 493 "sql_cache_short",
495 494 "get_user_%s" % _hash_key(group_name)
496 495 )
497 496 )
498 497 return q.scalar()
499 498
500 499 @classmethod
501 500 def get(cls, user_group_id, cache=False):
502 501 user_group = cls.query()
503 502 if cache:
504 503 user_group = user_group.options(FromCache("sql_cache_short",
505 504 "get_users_group_%s" % user_group_id))
506 505 return user_group.get(user_group_id)
507 506
508 507
509 508 class UserGroupMember(Base, BaseModel):
510 509 __tablename__ = 'users_groups_members'
511 510 __table_args__ = (
512 511 {'extend_existing': True, 'mysql_engine': 'InnoDB',
513 512 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
514 513 )
515 514
516 515 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
517 516 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
518 517 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
519 518
520 519 user = relationship('User', lazy='joined')
521 520 users_group = relationship('UserGroup')
522 521
523 522 def __init__(self, gr_id='', u_id=''):
524 523 self.users_group_id = gr_id
525 524 self.user_id = u_id
526 525
527 526
528 527 class RepositoryField(Base, BaseModel):
529 528 __tablename__ = 'repositories_fields'
530 529 __table_args__ = (
531 530 UniqueConstraint('repository_id', 'field_key'), # no-multi field
532 531 {'extend_existing': True, 'mysql_engine': 'InnoDB',
533 532 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
534 533 )
535 534 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
536 535
537 536 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
538 537 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
539 538 field_key = Column("field_key", String(250))
540 539 field_label = Column("field_label", String(1024), nullable=False)
541 540 field_value = Column("field_value", String(10000), nullable=False)
542 541 field_desc = Column("field_desc", String(1024), nullable=False)
543 542 field_type = Column("field_type", String(256), nullable=False, unique=None)
544 543 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
545 544
546 545 repository = relationship('Repository')
547 546
548 547 @classmethod
549 548 def get_by_key_name(cls, key, repo):
550 549 row = cls.query()\
551 550 .filter(cls.repository == repo)\
552 551 .filter(cls.field_key == key).scalar()
553 552 return row
554 553
555 554
556 555 class Repository(Base, BaseModel):
557 556 __tablename__ = 'repositories'
558 557 __table_args__ = (
559 558 UniqueConstraint('repo_name'),
560 559 Index('r_repo_name_idx', 'repo_name'),
561 560 {'extend_existing': True, 'mysql_engine': 'InnoDB',
562 561 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
563 562 )
564 563
565 564 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
566 565 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
567 566 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
568 567 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
569 568 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
570 569 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
571 570 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
572 571 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
573 572 description = Column("description", String(10000), nullable=True, unique=None, default=None)
574 573 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
575 574 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
576 575 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
577 576 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
578 577 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
579 578 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
580 579
581 580 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
582 581 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
583 582
584 583 user = relationship('User')
585 584 fork = relationship('Repository', remote_side=repo_id)
586 585 group = relationship('RepoGroup')
587 586 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
588 587 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
589 588 stats = relationship('Statistics', cascade='all', uselist=False)
590 589
591 590 followers = relationship('UserFollowing',
592 591 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
593 592 cascade='all')
594 593 extra_fields = relationship('RepositoryField',
595 594 cascade="all, delete, delete-orphan")
596 595
597 596 logs = relationship('UserLog')
598 597 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
599 598
600 599 pull_requests_org = relationship('PullRequest',
601 600 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
602 601 cascade="all, delete, delete-orphan")
603 602
604 603 pull_requests_other = relationship('PullRequest',
605 604 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
606 605 cascade="all, delete, delete-orphan")
607 606
608 607 def __unicode__(self):
609 608 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
610 safe_unicode(self.repo_name))
609 safe_str(self.repo_name))
611 610
612 611 @classmethod
613 612 def get_by_repo_name(cls, repo_name):
614 613 q = Session().query(cls).filter(cls.repo_name == repo_name)
615 614 q = q.options(joinedload(Repository.fork))\
616 615 .options(joinedload(Repository.user))\
617 616 .options(joinedload(Repository.group))
618 617 return q.scalar()
619 618
620 619
621 620 class RepoGroup(Base, BaseModel):
622 621 __tablename__ = 'groups'
623 622 __table_args__ = (
624 623 UniqueConstraint('group_name', 'group_parent_id'),
625 624 {'extend_existing': True, 'mysql_engine': 'InnoDB',
626 625 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
627 626 )
628 627 __mapper_args__ = {'order_by': 'group_name'}
629 628
630 629 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
631 630 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
632 631 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
633 632 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
634 633 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
635 634 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
636 635
637 636 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
638 637 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
639 638 parent_group = relationship('RepoGroup', remote_side=group_id)
640 639 user = relationship('User')
641 640
642 641 def __init__(self, group_name='', parent_group=None):
643 642 self.group_name = group_name
644 643 self.parent_group = parent_group
645 644
646 645 def __unicode__(self):
647 646 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
648 647 self.group_name)
649 648
650 649 @classmethod
651 650 def url_sep(cls):
652 651 return URL_SEP
653 652
654 653 @classmethod
655 654 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
656 655 if case_insensitive:
657 656 gr = cls.query()\
658 657 .filter(cls.group_name.ilike(group_name))
659 658 else:
660 659 gr = cls.query()\
661 660 .filter(cls.group_name == group_name)
662 661 if cache:
663 662 gr = gr.options(FromCache(
664 663 "sql_cache_short",
665 664 "get_group_%s" % _hash_key(group_name)
666 665 )
667 666 )
668 667 return gr.scalar()
669 668
670 669
671 670 class Permission(Base, BaseModel):
672 671 __tablename__ = 'permissions'
673 672 __table_args__ = (
674 673 Index('p_perm_name_idx', 'permission_name'),
675 674 {'extend_existing': True, 'mysql_engine': 'InnoDB',
676 675 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
677 676 )
678 677 PERMS = [
679 678 ('hg.admin', _('RhodeCode Administrator')),
680 679
681 680 ('repository.none', _('Repository no access')),
682 681 ('repository.read', _('Repository read access')),
683 682 ('repository.write', _('Repository write access')),
684 683 ('repository.admin', _('Repository admin access')),
685 684
686 685 ('group.none', _('Repository group no access')),
687 686 ('group.read', _('Repository group read access')),
688 687 ('group.write', _('Repository group write access')),
689 688 ('group.admin', _('Repository group admin access')),
690 689
691 690 ('usergroup.none', _('User group no access')),
692 691 ('usergroup.read', _('User group read access')),
693 692 ('usergroup.write', _('User group write access')),
694 693 ('usergroup.admin', _('User group admin access')),
695 694
696 695 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
697 696 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
698 697
699 698 ('hg.usergroup.create.false', _('User Group creation disabled')),
700 699 ('hg.usergroup.create.true', _('User Group creation enabled')),
701 700
702 701 ('hg.create.none', _('Repository creation disabled')),
703 702 ('hg.create.repository', _('Repository creation enabled')),
704 703
705 704 ('hg.fork.none', _('Repository forking disabled')),
706 705 ('hg.fork.repository', _('Repository forking enabled')),
707 706
708 707 ('hg.register.none', _('Registration disabled')),
709 708 ('hg.register.manual_activate', _('User Registration with manual account activation')),
710 709 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
711 710
712 711 ('hg.extern_activate.manual', _('Manual activation of external account')),
713 712 ('hg.extern_activate.auto', _('Automatic activation of external account')),
714 713
715 714 ]
716 715
717 716 #definition of system default permissions for DEFAULT user
718 717 DEFAULT_USER_PERMISSIONS = [
719 718 'repository.read',
720 719 'group.read',
721 720 'usergroup.read',
722 721 'hg.create.repository',
723 722 'hg.fork.repository',
724 723 'hg.register.manual_activate',
725 724 'hg.extern_activate.auto',
726 725 ]
727 726
728 727 # defines which permissions are more important higher the more important
729 728 # Weight defines which permissions are more important.
730 729 # The higher number the more important.
731 730 PERM_WEIGHTS = {
732 731 'repository.none': 0,
733 732 'repository.read': 1,
734 733 'repository.write': 3,
735 734 'repository.admin': 4,
736 735
737 736 'group.none': 0,
738 737 'group.read': 1,
739 738 'group.write': 3,
740 739 'group.admin': 4,
741 740
742 741 'usergroup.none': 0,
743 742 'usergroup.read': 1,
744 743 'usergroup.write': 3,
745 744 'usergroup.admin': 4,
746 745 'hg.repogroup.create.false': 0,
747 746 'hg.repogroup.create.true': 1,
748 747
749 748 'hg.usergroup.create.false': 0,
750 749 'hg.usergroup.create.true': 1,
751 750
752 751 'hg.fork.none': 0,
753 752 'hg.fork.repository': 1,
754 753 'hg.create.none': 0,
755 754 'hg.create.repository': 1
756 755 }
757 756
758 757 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
759 758 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
760 759 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
761 760
762 761 def __unicode__(self):
763 762 return u"<%s('%s:%s')>" % (
764 763 self.__class__.__name__, self.permission_id, self.permission_name
765 764 )
766 765
767 766 @classmethod
768 767 def get_by_key(cls, key):
769 768 return cls.query().filter(cls.permission_name == key).scalar()
770 769
771 770
772 771 class UserRepoToPerm(Base, BaseModel):
773 772 __tablename__ = 'repo_to_perm'
774 773 __table_args__ = (
775 774 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
776 775 {'extend_existing': True, 'mysql_engine': 'InnoDB',
777 776 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
778 777 )
779 778 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
780 779 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
781 780 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
782 781 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
783 782
784 783 user = relationship('User')
785 784 repository = relationship('Repository')
786 785 permission = relationship('Permission')
787 786
788 787 def __unicode__(self):
789 788 return u'<%s => %s >' % (self.user, self.repository)
790 789
791 790
792 791 class UserUserGroupToPerm(Base, BaseModel):
793 792 __tablename__ = 'user_user_group_to_perm'
794 793 __table_args__ = (
795 794 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
796 795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
797 796 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
798 797 )
799 798 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
800 799 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
801 800 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
802 801 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
803 802
804 803 user = relationship('User')
805 804 user_group = relationship('UserGroup')
806 805 permission = relationship('Permission')
807 806
808 807 def __unicode__(self):
809 808 return u'<%s => %s >' % (self.user, self.user_group)
810 809
811 810
812 811 class UserToPerm(Base, BaseModel):
813 812 __tablename__ = 'user_to_perm'
814 813 __table_args__ = (
815 814 UniqueConstraint('user_id', 'permission_id'),
816 815 {'extend_existing': True, 'mysql_engine': 'InnoDB',
817 816 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
818 817 )
819 818 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
820 819 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
821 820 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
822 821
823 822 user = relationship('User')
824 823 permission = relationship('Permission', lazy='joined')
825 824
826 825 def __unicode__(self):
827 826 return u'<%s => %s >' % (self.user, self.permission)
828 827
829 828
830 829 class UserGroupRepoToPerm(Base, BaseModel):
831 830 __tablename__ = 'users_group_repo_to_perm'
832 831 __table_args__ = (
833 832 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
834 833 {'extend_existing': True, 'mysql_engine': 'InnoDB',
835 834 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
836 835 )
837 836 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
838 837 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
839 838 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
840 839 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
841 840
842 841 users_group = relationship('UserGroup')
843 842 permission = relationship('Permission')
844 843 repository = relationship('Repository')
845 844
846 845 def __unicode__(self):
847 846 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
848 847
849 848
850 849 class UserGroupUserGroupToPerm(Base, BaseModel):
851 850 __tablename__ = 'user_group_user_group_to_perm'
852 851 __table_args__ = (
853 852 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
854 853 CheckConstraint('target_user_group_id != user_group_id'),
855 854 {'extend_existing': True, 'mysql_engine': 'InnoDB',
856 855 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
857 856 )
858 857 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)
859 858 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
860 859 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
861 860 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
862 861
863 862 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
864 863 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
865 864 permission = relationship('Permission')
866 865
867 866 def __unicode__(self):
868 867 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
869 868
870 869
871 870 class UserGroupToPerm(Base, BaseModel):
872 871 __tablename__ = 'users_group_to_perm'
873 872 __table_args__ = (
874 873 UniqueConstraint('users_group_id', 'permission_id',),
875 874 {'extend_existing': True, 'mysql_engine': 'InnoDB',
876 875 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
877 876 )
878 877 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
879 878 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
880 879 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
881 880
882 881 users_group = relationship('UserGroup')
883 882 permission = relationship('Permission')
884 883
885 884
886 885 class UserRepoGroupToPerm(Base, BaseModel):
887 886 __tablename__ = 'user_repo_group_to_perm'
888 887 __table_args__ = (
889 888 UniqueConstraint('user_id', 'group_id', 'permission_id'),
890 889 {'extend_existing': True, 'mysql_engine': 'InnoDB',
891 890 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
892 891 )
893 892
894 893 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
895 894 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
896 895 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
897 896 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
898 897
899 898 user = relationship('User')
900 899 group = relationship('RepoGroup')
901 900 permission = relationship('Permission')
902 901
903 902
904 903 class UserGroupRepoGroupToPerm(Base, BaseModel):
905 904 __tablename__ = 'users_group_repo_group_to_perm'
906 905 __table_args__ = (
907 906 UniqueConstraint('users_group_id', 'group_id'),
908 907 {'extend_existing': True, 'mysql_engine': 'InnoDB',
909 908 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
910 909 )
911 910
912 911 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)
913 912 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
914 913 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
915 914 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
916 915
917 916 users_group = relationship('UserGroup')
918 917 permission = relationship('Permission')
919 918 group = relationship('RepoGroup')
920 919
921 920
922 921 class Statistics(Base, BaseModel):
923 922 __tablename__ = 'statistics'
924 923 __table_args__ = (
925 924 UniqueConstraint('repository_id'),
926 925 {'extend_existing': True, 'mysql_engine': 'InnoDB',
927 926 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
928 927 )
929 928 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
930 929 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
931 930 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
932 931 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
933 932 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
934 933 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
935 934
936 935 repository = relationship('Repository', single_parent=True)
937 936
938 937
939 938 class UserFollowing(Base, BaseModel):
940 939 __tablename__ = 'user_followings'
941 940 __table_args__ = (
942 941 UniqueConstraint('user_id', 'follows_repository_id'),
943 942 UniqueConstraint('user_id', 'follows_user_id'),
944 943 {'extend_existing': True, 'mysql_engine': 'InnoDB',
945 944 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
946 945 )
947 946
948 947 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
949 948 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
950 949 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
951 950 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
952 951 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
953 952
954 953 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
955 954
956 955 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
957 956 follows_repository = relationship('Repository', order_by='Repository.repo_name')
958 957
959 958
960 959 class CacheInvalidation(Base, BaseModel):
961 960 __tablename__ = 'cache_invalidation'
962 961 __table_args__ = (
963 962 UniqueConstraint('cache_key'),
964 963 Index('key_idx', 'cache_key'),
965 964 {'extend_existing': True, 'mysql_engine': 'InnoDB',
966 965 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
967 966 )
968 967 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
969 968 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
970 969 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
971 970 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
972 971
973 972 def __init__(self, cache_key, cache_args=''):
974 973 self.cache_key = cache_key
975 974 self.cache_args = cache_args
976 975 self.cache_active = False
977 976
978 977
979 978 class ChangesetComment(Base, BaseModel):
980 979 __tablename__ = 'changeset_comments'
981 980 __table_args__ = (
982 981 Index('cc_revision_idx', 'revision'),
983 982 {'extend_existing': True, 'mysql_engine': 'InnoDB',
984 983 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
985 984 )
986 985 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
987 986 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
988 987 revision = Column('revision', String(40), nullable=True)
989 988 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
990 989 line_no = Column('line_no', Unicode(10), nullable=True)
991 990 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
992 991 f_path = Column('f_path', Unicode(1000), nullable=True)
993 992 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
994 993 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
995 994 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
996 995 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
997 996
998 997 author = relationship('User', lazy='joined')
999 998 repo = relationship('Repository')
1000 999 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1001 1000 pull_request = relationship('PullRequest', lazy='joined')
1002 1001
1003 1002
1004 1003 class ChangesetStatus(Base, BaseModel):
1005 1004 __tablename__ = 'changeset_statuses'
1006 1005 __table_args__ = (
1007 1006 Index('cs_revision_idx', 'revision'),
1008 1007 Index('cs_version_idx', 'version'),
1009 1008 UniqueConstraint('repo_id', 'revision', 'version'),
1010 1009 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1011 1010 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1012 1011 )
1013 1012 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1014 1013 STATUS_APPROVED = 'approved'
1015 1014 STATUS_REJECTED = 'rejected'
1016 1015 STATUS_UNDER_REVIEW = 'under_review'
1017 1016
1018 1017 STATUSES = [
1019 1018 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1020 1019 (STATUS_APPROVED, _("Approved")),
1021 1020 (STATUS_REJECTED, _("Rejected")),
1022 1021 (STATUS_UNDER_REVIEW, _("Under Review")),
1023 1022 ]
1024 1023
1025 1024 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1026 1025 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1027 1026 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1028 1027 revision = Column('revision', String(40), nullable=False)
1029 1028 status = Column('status', String(128), nullable=False, default=DEFAULT)
1030 1029 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1031 1030 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1032 1031 version = Column('version', Integer(), nullable=False, default=0)
1033 1032 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1034 1033
1035 1034 author = relationship('User', lazy='joined')
1036 1035 repo = relationship('Repository')
1037 1036 comment = relationship('ChangesetComment', lazy='joined')
1038 1037 pull_request = relationship('PullRequest', lazy='joined')
1039 1038
1040 1039
1041 1040
1042 1041 class PullRequest(Base, BaseModel):
1043 1042 __tablename__ = 'pull_requests'
1044 1043 __table_args__ = (
1045 1044 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1046 1045 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1047 1046 )
1048 1047
1049 1048 STATUS_NEW = u'new'
1050 1049 STATUS_OPEN = u'open'
1051 1050 STATUS_CLOSED = u'closed'
1052 1051
1053 1052 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1054 1053 title = Column('title', Unicode(256), nullable=True)
1055 1054 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1056 1055 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1057 1056 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1058 1057 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1059 1058 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1060 1059 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
1061 1060 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1062 1061 org_ref = Column('org_ref', Unicode(256), nullable=False)
1063 1062 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1064 1063 other_ref = Column('other_ref', Unicode(256), nullable=False)
1065 1064
1066 1065 author = relationship('User', lazy='joined')
1067 1066 reviewers = relationship('PullRequestReviewers',
1068 1067 cascade="all, delete, delete-orphan")
1069 1068 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1070 1069 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1071 1070 statuses = relationship('ChangesetStatus')
1072 1071 comments = relationship('ChangesetComment',
1073 1072 cascade="all, delete, delete-orphan")
1074 1073
1075 1074
1076 1075 class PullRequestReviewers(Base, BaseModel):
1077 1076 __tablename__ = 'pull_request_reviewers'
1078 1077 __table_args__ = (
1079 1078 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1080 1079 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1081 1080 )
1082 1081
1083 1082 def __init__(self, user=None, pull_request=None):
1084 1083 self.user = user
1085 1084 self.pull_request = pull_request
1086 1085
1087 1086 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1088 1087 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1089 1088 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1090 1089
1091 1090 user = relationship('User')
1092 1091 pull_request = relationship('PullRequest')
1093 1092
1094 1093
1095 1094 class Notification(Base, BaseModel):
1096 1095 __tablename__ = 'notifications'
1097 1096 __table_args__ = (
1098 1097 Index('notification_type_idx', 'type'),
1099 1098 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1100 1099 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1101 1100 )
1102 1101
1103 1102 TYPE_CHANGESET_COMMENT = u'cs_comment'
1104 1103 TYPE_MESSAGE = u'message'
1105 1104 TYPE_MENTION = u'mention'
1106 1105 TYPE_REGISTRATION = u'registration'
1107 1106 TYPE_PULL_REQUEST = u'pull_request'
1108 1107 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1109 1108
1110 1109 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1111 1110 subject = Column('subject', Unicode(512), nullable=True)
1112 1111 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1113 1112 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1114 1113 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1115 1114 type_ = Column('type', Unicode(256))
1116 1115
1117 1116 created_by_user = relationship('User')
1118 1117 notifications_to_users = relationship('UserNotification', lazy='joined',
1119 1118 cascade="all, delete, delete-orphan")
1120 1119
1121 1120
1122 1121 class UserNotification(Base, BaseModel):
1123 1122 __tablename__ = 'user_to_notification'
1124 1123 __table_args__ = (
1125 1124 UniqueConstraint('user_id', 'notification_id'),
1126 1125 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1127 1126 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1128 1127 )
1129 1128 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1130 1129 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1131 1130 read = Column('read', Boolean, default=False)
1132 1131 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1133 1132
1134 1133 user = relationship('User', lazy="joined")
1135 1134 notification = relationship('Notification', lazy="joined",
1136 1135 order_by=lambda: Notification.created_on.desc(),)
1137 1136
1138 1137
1139 1138 class Gist(Base, BaseModel):
1140 1139 __tablename__ = 'gists'
1141 1140 __table_args__ = (
1142 1141 Index('g_gist_access_id_idx', 'gist_access_id'),
1143 1142 Index('g_created_on_idx', 'created_on'),
1144 1143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1145 1144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1146 1145 )
1147 1146 GIST_PUBLIC = u'public'
1148 1147 GIST_PRIVATE = u'private'
1149 1148
1150 1149 gist_id = Column('gist_id', Integer(), primary_key=True)
1151 1150 gist_access_id = Column('gist_access_id', Unicode(250))
1152 1151 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1153 1152 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
1154 1153 gist_expires = Column('gist_expires', Float(53), nullable=False)
1155 1154 gist_type = Column('gist_type', Unicode(128), nullable=False)
1156 1155 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1157 1156 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1158 1157
1159 1158 owner = relationship('User')
1160 1159
1161 1160
1162 1161 class DbMigrateVersion(Base, BaseModel):
1163 1162 __tablename__ = 'db_migrate_version'
1164 1163 __table_args__ = (
1165 1164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1166 1165 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1167 1166 )
1168 1167 repository_id = Column('repository_id', String(250), primary_key=True)
1169 1168 repository_path = Column('repository_path', Text)
1170 1169 version = Column('version', Integer)
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now