##// END OF EJS Templates
dbmigrations:...
marcink -
r836:28a4bb11 beta
parent child Browse files
Show More
@@ -1,48 +1,48
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.__init__
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode, a web based repository management based on pylons
7 7 versioning implementation: http://semver.org/
8 8
9 9 :created_on: Apr 9, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software; you can redistribute it and/or
15 15 # modify it under the terms of the GNU General Public License
16 16 # as published by the Free Software Foundation; version 2
17 17 # of the License or (at your opinion) any later version of the license.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program; if not, write to the Free Software
26 26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 27 # MA 02110-1301, USA.
28 28
29 29
30 30 VERSION = (1, 1, 0, 'beta')
31 31 __version__ = '.'.join((str(each) for each in VERSION[:4]))
32 __dbversion__ = 1 #defines current db version for migrations
32 __dbversion__ = 2 #defines current db version for migrations
33 33
34 34 from rhodecode.lib.utils import get_current_revision
35 35 _rev = get_current_revision()
36 36
37 37 if len(VERSION) > 3 and _rev:
38 38 __version__ += ' [rev:%s]' % _rev[0]
39 39
40 40 def get_version():
41 41 """Returns shorter version (digit parts only) as string."""
42 42
43 43 return '.'.join((str(each) for each in VERSION[:3]))
44 44
45 45 BACKENDS = {
46 46 'hg': 'Mercurial repository',
47 47 #'git': 'Git repository',
48 48 }
@@ -1,77 +1,86
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.dbmigrate.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database migration modules
7 7
8 8 :created_on: Dec 11, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import logging
29 29 from sqlalchemy import engine_from_config
30 30
31 from rhodecode import __dbversion__
32 from rhodecode.lib.dbmigrate.migrate.versioning import api
31 33 from rhodecode.lib.dbmigrate.migrate.exceptions import \
32 34 DatabaseNotControlledError
33 35 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
34 36
35 37 log = logging.getLogger(__name__)
36 38
37 39 class UpgradeDb(BasePasterCommand):
38 40 """Command used for paster to upgrade our database to newer version
39 41 """
40 42
41 43 max_args = 1
42 44 min_args = 1
43 45
44 46 usage = "CONFIG_FILE"
45 47 summary = "Upgrades current db to newer version given configuration file"
46 48 group_name = "RhodeCode"
47 49
48 50 parser = Command.standard_parser(verbose=True)
49 51
50 52 def command(self):
51 53 from pylons import config
54
52 55 add_cache(config)
53 #engine = engine_from_config(config, 'sqlalchemy.db1.')
54 #rint engine
55 56
56 from rhodecode.lib.dbmigrate.migrate.versioning import api
57 path = 'rhodecode/lib/dbmigrate'
57 #engine = engine_from_config(config, 'sqlalchemy.db1.')
58 58
59 repository_path = 'rhodecode/lib/dbmigrate'
60 db_uri = config['sqlalchemy.db1.url']
59 61
60 62 try:
61 curr_version = api.db_version(config['sqlalchemy.db1.url'], path)
63 curr_version = api.db_version(db_uri, repository_path)
62 64 msg = ('Found current database under version'
63 65 ' control with version %s' % curr_version)
64 66
65 67 except (RuntimeError, DatabaseNotControlledError), e:
66 curr_version = 0
68 curr_version = 1
67 69 msg = ('Current database is not under version control setting'
68 70 ' as version %s' % curr_version)
71 api.version_control(db_uri, repository_path, curr_version)
72
69 73
70 74 print msg
75 #now we have our dbversion we can do upgrade
76
77 msg = 'attempting to do database upgrade to version %s' % __dbversion__
78 print msg
79 api.upgrade(db_uri, repository_path, __dbversion__)
71 80
72 81 def update_parser(self):
73 82 self.parser.add_option('--sql',
74 83 action='store_true',
75 84 dest='just_sql',
76 85 help="Prints upgrade sql for further investigation",
77 86 default=False)
@@ -1,669 +1,669
1 1 """
2 2 Schema module providing common schema operations.
3 3 """
4 4 import warnings
5 5
6 6 from UserDict import DictMixin
7 7
8 8 import sqlalchemy
9 9
10 10 from sqlalchemy.schema import ForeignKeyConstraint
11 11 from sqlalchemy.schema import UniqueConstraint
12 12
13 13 from rhodecode.lib.dbmigrate.migrate.exceptions import *
14 14 from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06
15 15 from rhodecode.lib.dbmigrate.migrate.changeset.databases.visitor import (get_engine_visitor,
16 16 run_single_visitor)
17 17
18 18
19 19 __all__ = [
20 20 'create_column',
21 21 'drop_column',
22 22 'alter_column',
23 23 'rename_table',
24 24 'rename_index',
25 25 'ChangesetTable',
26 26 'ChangesetColumn',
27 27 'ChangesetIndex',
28 28 'ChangesetDefaultClause',
29 29 'ColumnDelta',
30 30 ]
31 31
32 32 DEFAULT_ALTER_METADATA = True
33 33
34 34
35 35 def create_column(column, table=None, *p, **kw):
36 36 """Create a column, given the table.
37 37
38 38 API to :meth:`ChangesetColumn.create`.
39 39 """
40 40 if table is not None:
41 41 return table.create_column(column, *p, **kw)
42 42 return column.create(*p, **kw)
43 43
44 44
45 45 def drop_column(column, table=None, *p, **kw):
46 46 """Drop a column, given the table.
47 47
48 48 API to :meth:`ChangesetColumn.drop`.
49 49 """
50 50 if table is not None:
51 51 return table.drop_column(column, *p, **kw)
52 52 return column.drop(*p, **kw)
53 53
54 54
55 55 def rename_table(table, name, engine=None, **kw):
56 56 """Rename a table.
57 57
58 58 If Table instance is given, engine is not used.
59 59
60 60 API to :meth:`ChangesetTable.rename`.
61 61
62 62 :param table: Table to be renamed.
63 63 :param name: New name for Table.
64 64 :param engine: Engine instance.
65 65 :type table: string or Table instance
66 66 :type name: string
67 67 :type engine: obj
68 68 """
69 69 table = _to_table(table, engine)
70 70 table.rename(name, **kw)
71 71
72 72
73 73 def rename_index(index, name, table=None, engine=None, **kw):
74 74 """Rename an index.
75 75
76 76 If Index instance is given,
77 77 table and engine are not used.
78 78
79 79 API to :meth:`ChangesetIndex.rename`.
80 80
81 81 :param index: Index to be renamed.
82 82 :param name: New name for index.
83 83 :param table: Table to which Index is reffered.
84 84 :param engine: Engine instance.
85 85 :type index: string or Index instance
86 86 :type name: string
87 87 :type table: string or Table instance
88 88 :type engine: obj
89 89 """
90 90 index = _to_index(index, table, engine)
91 91 index.rename(name, **kw)
92 92
93 93
94 94 def alter_column(*p, **k):
95 95 """Alter a column.
96 96
97 97 This is a helper function that creates a :class:`ColumnDelta` and
98 98 runs it.
99 99
100 100 :argument column:
101 101 The name of the column to be altered or a
102 102 :class:`ChangesetColumn` column representing it.
103 103
104 104 :param table:
105 105 A :class:`~sqlalchemy.schema.Table` or table name to
106 106 for the table where the column will be changed.
107 107
108 108 :param engine:
109 109 The :class:`~sqlalchemy.engine.base.Engine` to use for table
110 110 reflection and schema alterations.
111 111
112 112 :param alter_metadata:
113 113 If `True`, which is the default, the
114 114 :class:`~sqlalchemy.schema.Column` will also modified.
115 115 If `False`, the :class:`~sqlalchemy.schema.Column` will be left
116 116 as it was.
117 117
118 118 :returns: A :class:`ColumnDelta` instance representing the change.
119 119
120 120
121 121 """
122
122
123 123 k.setdefault('alter_metadata', DEFAULT_ALTER_METADATA)
124 124
125 125 if 'table' not in k and isinstance(p[0], sqlalchemy.Column):
126 126 k['table'] = p[0].table
127 127 if 'engine' not in k:
128 128 k['engine'] = k['table'].bind
129 129
130 130 # deprecation
131 131 if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
132 132 warnings.warn(
133 133 "Passing a Column object to alter_column is deprecated."
134 134 " Just pass in keyword parameters instead.",
135 135 MigrateDeprecationWarning
136 136 )
137 137 engine = k['engine']
138 138 delta = ColumnDelta(*p, **k)
139 139
140 140 visitorcallable = get_engine_visitor(engine, 'schemachanger')
141 141 engine._run_visitor(visitorcallable, delta)
142 142
143 143 return delta
144 144
145 145
146 146 def _to_table(table, engine=None):
147 147 """Return if instance of Table, else construct new with metadata"""
148 148 if isinstance(table, sqlalchemy.Table):
149 149 return table
150 150
151 151 # Given: table name, maybe an engine
152 152 meta = sqlalchemy.MetaData()
153 153 if engine is not None:
154 154 meta.bind = engine
155 155 return sqlalchemy.Table(table, meta)
156 156
157 157
158 158 def _to_index(index, table=None, engine=None):
159 159 """Return if instance of Index, else construct new with metadata"""
160 160 if isinstance(index, sqlalchemy.Index):
161 161 return index
162 162
163 163 # Given: index name; table name required
164 164 table = _to_table(table, engine)
165 165 ret = sqlalchemy.Index(index)
166 166 ret.table = table
167 167 return ret
168 168
169 169
170 170 class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem):
171 171 """Extracts the differences between two columns/column-parameters
172 172
173 173 May receive parameters arranged in several different ways:
174 174
175 175 * **current_column, new_column, \*p, \*\*kw**
176 176 Additional parameters can be specified to override column
177 177 differences.
178 178
179 179 * **current_column, \*p, \*\*kw**
180 180 Additional parameters alter current_column. Table name is extracted
181 181 from current_column object.
182 182 Name is changed to current_column.name from current_name,
183 183 if current_name is specified.
184 184
185 185 * **current_col_name, \*p, \*\*kw**
186 186 Table kw must specified.
187 187
188 188 :param table: Table at which current Column should be bound to.\
189 189 If table name is given, reflection will be used.
190 190 :type table: string or Table instance
191 191 :param alter_metadata: If True, it will apply changes to metadata.
192 192 :type alter_metadata: bool
193 193 :param metadata: If `alter_metadata` is true, \
194 194 metadata is used to reflect table names into
195 195 :type metadata: :class:`MetaData` instance
196 196 :param engine: When reflecting tables, either engine or metadata must \
197 197 be specified to acquire engine object.
198 198 :type engine: :class:`Engine` instance
199 199 :returns: :class:`ColumnDelta` instance provides interface for altered attributes to \
200 200 `result_column` through :func:`dict` alike object.
201 201
202 202 * :class:`ColumnDelta`.result_column is altered column with new attributes
203 203
204 204 * :class:`ColumnDelta`.current_name is current name of column in db
205 205
206 206
207 207 """
208 208
209 209 # Column attributes that can be altered
210 210 diff_keys = ('name', 'type', 'primary_key', 'nullable',
211 211 'server_onupdate', 'server_default', 'autoincrement')
212 212 diffs = dict()
213 213 __visit_name__ = 'column'
214 214
215 215 def __init__(self, *p, **kw):
216 216 self.alter_metadata = kw.pop("alter_metadata", False)
217 217 self.meta = kw.pop("metadata", None)
218 218 self.engine = kw.pop("engine", None)
219 219
220 220 # Things are initialized differently depending on how many column
221 221 # parameters are given. Figure out how many and call the appropriate
222 222 # method.
223 223 if len(p) >= 1 and isinstance(p[0], sqlalchemy.Column):
224 224 # At least one column specified
225 225 if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
226 226 # Two columns specified
227 227 diffs = self.compare_2_columns(*p, **kw)
228 228 else:
229 229 # Exactly one column specified
230 230 diffs = self.compare_1_column(*p, **kw)
231 231 else:
232 232 # Zero columns specified
233 233 if not len(p) or not isinstance(p[0], basestring):
234 234 raise ValueError("First argument must be column name")
235 235 diffs = self.compare_parameters(*p, **kw)
236 236
237 237 self.apply_diffs(diffs)
238 238
239 239 def __repr__(self):
240 240 return '<ColumnDelta altermetadata=%r, %s>' % (self.alter_metadata,
241 241 super(ColumnDelta, self).__repr__())
242 242
243 243 def __getitem__(self, key):
244 244 if key not in self.keys():
245 245 raise KeyError("No such diff key, available: %s" % self.diffs)
246 246 return getattr(self.result_column, key)
247 247
248 248 def __setitem__(self, key, value):
249 249 if key not in self.keys():
250 250 raise KeyError("No such diff key, available: %s" % self.diffs)
251 251 setattr(self.result_column, key, value)
252 252
253 253 def __delitem__(self, key):
254 254 raise NotImplementedError
255 255
256 256 def keys(self):
257 257 return self.diffs.keys()
258 258
259 259 def compare_parameters(self, current_name, *p, **k):
260 260 """Compares Column objects with reflection"""
261 261 self.table = k.pop('table')
262 262 self.result_column = self._table.c.get(current_name)
263 263 if len(p):
264 264 k = self._extract_parameters(p, k, self.result_column)
265 265 return k
266 266
267 267 def compare_1_column(self, col, *p, **k):
268 268 """Compares one Column object"""
269 269 self.table = k.pop('table', None)
270 270 if self.table is None:
271 271 self.table = col.table
272 272 self.result_column = col
273 273 if len(p):
274 274 k = self._extract_parameters(p, k, self.result_column)
275 275 return k
276 276
277 277 def compare_2_columns(self, old_col, new_col, *p, **k):
278 278 """Compares two Column objects"""
279 279 self.process_column(new_col)
280 280 self.table = k.pop('table', None)
281 281 # we cannot use bool() on table in SA06
282 282 if self.table is None:
283 283 self.table = old_col.table
284 284 if self.table is None:
285 285 new_col.table
286 286 self.result_column = old_col
287 287
288 288 # set differences
289 289 # leave out some stuff for later comp
290 290 for key in (set(self.diff_keys) - set(('type',))):
291 291 val = getattr(new_col, key, None)
292 292 if getattr(self.result_column, key, None) != val:
293 293 k.setdefault(key, val)
294 294
295 295 # inspect types
296 296 if not self.are_column_types_eq(self.result_column.type, new_col.type):
297 297 k.setdefault('type', new_col.type)
298 298
299 299 if len(p):
300 300 k = self._extract_parameters(p, k, self.result_column)
301 301 return k
302 302
303 303 def apply_diffs(self, diffs):
304 304 """Populate dict and column object with new values"""
305 305 self.diffs = diffs
306 306 for key in self.diff_keys:
307 307 if key in diffs:
308 308 setattr(self.result_column, key, diffs[key])
309 309
310 310 self.process_column(self.result_column)
311 311
312 312 # create an instance of class type if not yet
313 313 if 'type' in diffs and callable(self.result_column.type):
314 314 self.result_column.type = self.result_column.type()
315 315
316 316 # add column to the table
317 317 if self.table is not None and self.alter_metadata:
318 318 self.result_column.add_to_table(self.table)
319 319
320 320 def are_column_types_eq(self, old_type, new_type):
321 321 """Compares two types to be equal"""
322 322 ret = old_type.__class__ == new_type.__class__
323 323
324 324 # String length is a special case
325 325 if ret and isinstance(new_type, sqlalchemy.types.String):
326 326 ret = (getattr(old_type, 'length', None) == \
327 327 getattr(new_type, 'length', None))
328 328 return ret
329 329
330 330 def _extract_parameters(self, p, k, column):
331 331 """Extracts data from p and modifies diffs"""
332 332 p = list(p)
333 333 while len(p):
334 334 if isinstance(p[0], basestring):
335 335 k.setdefault('name', p.pop(0))
336 336 elif isinstance(p[0], sqlalchemy.types.AbstractType):
337 337 k.setdefault('type', p.pop(0))
338 338 elif callable(p[0]):
339 339 p[0] = p[0]()
340 340 else:
341 341 break
342 342
343 343 if len(p):
344 344 new_col = column.copy_fixed()
345 345 new_col._init_items(*p)
346 346 k = self.compare_2_columns(column, new_col, **k)
347 347 return k
348 348
349 349 def process_column(self, column):
350 350 """Processes default values for column"""
351 351 # XXX: this is a snippet from SA processing of positional parameters
352 352 if not SQLA_06 and column.args:
353 353 toinit = list(column.args)
354 354 else:
355 355 toinit = list()
356 356
357 357 if column.server_default is not None:
358 358 if isinstance(column.server_default, sqlalchemy.FetchedValue):
359 359 toinit.append(column.server_default)
360 360 else:
361 361 toinit.append(sqlalchemy.DefaultClause(column.server_default))
362 362 if column.server_onupdate is not None:
363 363 if isinstance(column.server_onupdate, FetchedValue):
364 364 toinit.append(column.server_default)
365 365 else:
366 366 toinit.append(sqlalchemy.DefaultClause(column.server_onupdate,
367 367 for_update=True))
368 368 if toinit:
369 369 column._init_items(*toinit)
370
370
371 371 if not SQLA_06:
372 372 column.args = []
373 373
374 374 def _get_table(self):
375 375 return getattr(self, '_table', None)
376 376
377 377 def _set_table(self, table):
378 378 if isinstance(table, basestring):
379 379 if self.alter_metadata:
380 380 if not self.meta:
381 381 raise ValueError("metadata must be specified for table"
382 382 " reflection when using alter_metadata")
383 383 meta = self.meta
384 384 if self.engine:
385 385 meta.bind = self.engine
386 386 else:
387 387 if not self.engine and not self.meta:
388 388 raise ValueError("engine or metadata must be specified"
389 389 " to reflect tables")
390 390 if not self.engine:
391 391 self.engine = self.meta.bind
392 392 meta = sqlalchemy.MetaData(bind=self.engine)
393 393 self._table = sqlalchemy.Table(table, meta, autoload=True)
394 394 elif isinstance(table, sqlalchemy.Table):
395 395 self._table = table
396 396 if not self.alter_metadata:
397 397 self._table.meta = sqlalchemy.MetaData(bind=self._table.bind)
398 398
399 399 def _get_result_column(self):
400 400 return getattr(self, '_result_column', None)
401 401
402 402 def _set_result_column(self, column):
403 403 """Set Column to Table based on alter_metadata evaluation."""
404 404 self.process_column(column)
405 405 if not hasattr(self, 'current_name'):
406 406 self.current_name = column.name
407 407 if self.alter_metadata:
408 408 self._result_column = column
409 409 else:
410 410 self._result_column = column.copy_fixed()
411 411
412 412 table = property(_get_table, _set_table)
413 413 result_column = property(_get_result_column, _set_result_column)
414 414
415 415
416 416 class ChangesetTable(object):
417 417 """Changeset extensions to SQLAlchemy tables."""
418 418
419 419 def create_column(self, column, *p, **kw):
420 420 """Creates a column.
421 421
422 422 The column parameter may be a column definition or the name of
423 423 a column in this table.
424 424
425 425 API to :meth:`ChangesetColumn.create`
426 426
427 427 :param column: Column to be created
428 428 :type column: Column instance or string
429 429 """
430 430 if not isinstance(column, sqlalchemy.Column):
431 431 # It's a column name
432 432 column = getattr(self.c, str(column))
433 433 column.create(table=self, *p, **kw)
434 434
435 435 def drop_column(self, column, *p, **kw):
436 436 """Drop a column, given its name or definition.
437 437
438 438 API to :meth:`ChangesetColumn.drop`
439 439
440 440 :param column: Column to be droped
441 441 :type column: Column instance or string
442 442 """
443 443 if not isinstance(column, sqlalchemy.Column):
444 444 # It's a column name
445 445 try:
446 446 column = getattr(self.c, str(column))
447 447 except AttributeError:
448 448 # That column isn't part of the table. We don't need
449 449 # its entire definition to drop the column, just its
450 450 # name, so create a dummy column with the same name.
451 451 column = sqlalchemy.Column(str(column), sqlalchemy.Integer())
452 452 column.drop(table=self, *p, **kw)
453 453
454 454 def rename(self, name, connection=None, **kwargs):
455 455 """Rename this table.
456 456
457 457 :param name: New name of the table.
458 458 :type name: string
459 459 :param alter_metadata: If True, table will be removed from metadata
460 460 :type alter_metadata: bool
461 461 :param connection: reuse connection istead of creating new one.
462 462 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
463 463 """
464 464 self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA)
465 465 engine = self.bind
466 466 self.new_name = name
467 467 visitorcallable = get_engine_visitor(engine, 'schemachanger')
468 468 run_single_visitor(engine, visitorcallable, self, connection, **kwargs)
469 469
470 470 # Fix metadata registration
471 471 if self.alter_metadata:
472 472 self.name = name
473 473 self.deregister()
474 474 self._set_parent(self.metadata)
475 475
476 476 def _meta_key(self):
477 477 return sqlalchemy.schema._get_table_key(self.name, self.schema)
478 478
479 479 def deregister(self):
480 480 """Remove this table from its metadata"""
481 481 key = self._meta_key()
482 482 meta = self.metadata
483 483 if key in meta.tables:
484 484 del meta.tables[key]
485 485
486 486
487 487 class ChangesetColumn(object):
488 488 """Changeset extensions to SQLAlchemy columns."""
489 489
490 490 def alter(self, *p, **k):
491 491 """Makes a call to :func:`alter_column` for the column this
492 492 method is called on.
493 493 """
494 494 if 'table' not in k:
495 495 k['table'] = self.table
496 496 if 'engine' not in k:
497 497 k['engine'] = k['table'].bind
498 498 return alter_column(self, *p, **k)
499 499
500 500 def create(self, table=None, index_name=None, unique_name=None,
501 501 primary_key_name=None, populate_default=True, connection=None, **kwargs):
502 502 """Create this column in the database.
503 503
504 504 Assumes the given table exists. ``ALTER TABLE ADD COLUMN``,
505 505 for most databases.
506 506
507 507 :param table: Table instance to create on.
508 508 :param index_name: Creates :class:`ChangesetIndex` on this column.
509 509 :param unique_name: Creates :class:\
510 510 `~migrate.changeset.constraint.UniqueConstraint` on this column.
511 511 :param primary_key_name: Creates :class:\
512 512 `~migrate.changeset.constraint.PrimaryKeyConstraint` on this column.
513 513 :param alter_metadata: If True, column will be added to table object.
514 514 :param populate_default: If True, created column will be \
515 515 populated with defaults
516 516 :param connection: reuse connection istead of creating new one.
517 517 :type table: Table instance
518 518 :type index_name: string
519 519 :type unique_name: string
520 520 :type primary_key_name: string
521 521 :type alter_metadata: bool
522 522 :type populate_default: bool
523 523 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
524 524
525 525 :returns: self
526 526 """
527 527 self.populate_default = populate_default
528 528 self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA)
529 529 self.index_name = index_name
530 530 self.unique_name = unique_name
531 531 self.primary_key_name = primary_key_name
532 532 for cons in ('index_name', 'unique_name', 'primary_key_name'):
533 533 self._check_sanity_constraints(cons)
534 534
535 535 if self.alter_metadata:
536 536 self.add_to_table(table)
537 537 engine = self.table.bind
538 538 visitorcallable = get_engine_visitor(engine, 'columngenerator')
539 539 engine._run_visitor(visitorcallable, self, connection, **kwargs)
540 540
541 541 # TODO: reuse existing connection
542 542 if self.populate_default and self.default is not None:
543 543 stmt = table.update().values({self: engine._execute_default(self.default)})
544 544 engine.execute(stmt)
545 545
546 546 return self
547 547
548 548 def drop(self, table=None, connection=None, **kwargs):
549 549 """Drop this column from the database, leaving its table intact.
550 550
551 551 ``ALTER TABLE DROP COLUMN``, for most databases.
552 552
553 553 :param alter_metadata: If True, column will be removed from table object.
554 554 :type alter_metadata: bool
555 555 :param connection: reuse connection istead of creating new one.
556 556 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
557 557 """
558 558 self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA)
559 559 if table is not None:
560 560 self.table = table
561 561 engine = self.table.bind
562 562 if self.alter_metadata:
563 563 self.remove_from_table(self.table, unset_table=False)
564 564 visitorcallable = get_engine_visitor(engine, 'columndropper')
565 565 engine._run_visitor(visitorcallable, self, connection, **kwargs)
566 566 if self.alter_metadata:
567 567 self.table = None
568 568 return self
569 569
570 570 def add_to_table(self, table):
571 571 if table is not None and self.table is None:
572 572 self._set_parent(table)
573 573
574 574 def _col_name_in_constraint(self, cons, name):
575 575 return False
576
576
577 577 def remove_from_table(self, table, unset_table=True):
578 578 # TODO: remove primary keys, constraints, etc
579 579 if unset_table:
580 580 self.table = None
581
581
582 582 to_drop = set()
583 583 for index in table.indexes:
584 584 columns = []
585 585 for col in index.columns:
586 586 if col.name != self.name:
587 587 columns.append(col)
588 588 if columns:
589 589 index.columns = columns
590 590 else:
591 591 to_drop.add(index)
592 592 table.indexes = table.indexes - to_drop
593
593
594 594 to_drop = set()
595 595 for cons in table.constraints:
596 596 # TODO: deal with other types of constraint
597 597 if isinstance(cons, (ForeignKeyConstraint,
598 598 UniqueConstraint)):
599 599 for col_name in cons.columns:
600 600 if not isinstance(col_name, basestring):
601 601 col_name = col_name.name
602 602 if self.name == col_name:
603 603 to_drop.add(cons)
604 604 table.constraints = table.constraints - to_drop
605
605
606 606 if table.c.contains_column(self):
607 607 table.c.remove(self)
608 608
609 609 # TODO: this is fixed in 0.6
610 610 def copy_fixed(self, **kw):
611 611 """Create a copy of this ``Column``, with all attributes."""
612 612 return sqlalchemy.Column(self.name, self.type, self.default,
613 613 key=self.key,
614 614 primary_key=self.primary_key,
615 615 nullable=self.nullable,
616 616 quote=self.quote,
617 617 index=self.index,
618 618 unique=self.unique,
619 619 onupdate=self.onupdate,
620 620 autoincrement=self.autoincrement,
621 621 server_default=self.server_default,
622 622 server_onupdate=self.server_onupdate,
623 623 *[c.copy(**kw) for c in self.constraints])
624 624
625 625 def _check_sanity_constraints(self, name):
626 626 """Check if constraints names are correct"""
627 627 obj = getattr(self, name)
628 628 if (getattr(self, name[:-5]) and not obj):
629 629 raise InvalidConstraintError("Column.create() accepts index_name,"
630 630 " primary_key_name and unique_name to generate constraints")
631 631 if not isinstance(obj, basestring) and obj is not None:
632 632 raise InvalidConstraintError(
633 633 "%s argument for column must be constraint name" % name)
634 634
635 635
636 636 class ChangesetIndex(object):
637 637 """Changeset extensions to SQLAlchemy Indexes."""
638 638
639 639 __visit_name__ = 'index'
640 640
641 641 def rename(self, name, connection=None, **kwargs):
642 642 """Change the name of an index.
643 643
644 644 :param name: New name of the Index.
645 645 :type name: string
646 646 :param alter_metadata: If True, Index object will be altered.
647 647 :type alter_metadata: bool
648 648 :param connection: reuse connection istead of creating new one.
649 649 :type connection: :class:`sqlalchemy.engine.base.Connection` instance
650 650 """
651 651 self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA)
652 652 engine = self.table.bind
653 653 self.new_name = name
654 654 visitorcallable = get_engine_visitor(engine, 'schemachanger')
655 655 engine._run_visitor(visitorcallable, self, connection, **kwargs)
656 656 if self.alter_metadata:
657 657 self.name = name
658 658
659 659
660 660 class ChangesetDefaultClause(object):
661 661 """Implements comparison between :class:`DefaultClause` instances"""
662 662
663 663 def __eq__(self, other):
664 664 if isinstance(other, self.__class__):
665 665 if self.arg == other.arg:
666 666 return True
667 667
668 668 def __ne__(self, other):
669 669 return not self.__eq__(other)
@@ -1,383 +1,383
1 1 """
2 2 This module provides an external API to the versioning system.
3 3
4 4 .. versionchanged:: 0.6.0
5 5 :func:`migrate.versioning.api.test` and schema diff functions
6 6 changed order of positional arguments so all accept `url` and `repository`
7 7 as first arguments.
8 8
9 9 .. versionchanged:: 0.5.4
10 10 ``--preview_sql`` displays source file when using SQL scripts.
11 11 If Python script is used, it runs the action with mocked engine and
12 12 returns captured SQL statements.
13 13
14 14 .. versionchanged:: 0.5.4
15 15 Deprecated ``--echo`` parameter in favour of new
16 16 :func:`migrate.versioning.util.construct_engine` behavior.
17 17 """
18 18
19 19 # Dear migrate developers,
20 20 #
21 21 # please do not comment this module using sphinx syntax because its
22 22 # docstrings are presented as user help and most users cannot
23 23 # interpret sphinx annotated ReStructuredText.
24 24 #
25 25 # Thanks,
26 26 # Jan Dittberner
27 27
28 28 import sys
29 29 import inspect
30 30 import logging
31 31
32 32 from rhodecode.lib.dbmigrate.migrate import exceptions
33 from rhodecode.lib.dbmigrate.migrate.versioning import (repository, schema, version,
34 script as script_) # command name conflict
33 from rhodecode.lib.dbmigrate.migrate.versioning import repository, schema, version, \
34 script as script_ # command name conflict
35 35 from rhodecode.lib.dbmigrate.migrate.versioning.util import catch_known_errors, with_engine
36 36
37 37
38 38 log = logging.getLogger(__name__)
39 39 command_desc = {
40 40 'help': 'displays help on a given command',
41 41 'create': 'create an empty repository at the specified path',
42 42 'script': 'create an empty change Python script',
43 43 'script_sql': 'create empty change SQL scripts for given database',
44 44 'version': 'display the latest version available in a repository',
45 45 'db_version': 'show the current version of the repository under version control',
46 46 'source': 'display the Python code for a particular version in this repository',
47 47 'version_control': 'mark a database as under this repository\'s version control',
48 48 'upgrade': 'upgrade a database to a later version',
49 49 'downgrade': 'downgrade a database to an earlier version',
50 50 'drop_version_control': 'removes version control from a database',
51 51 'manage': 'creates a Python script that runs Migrate with a set of default values',
52 52 'test': 'performs the upgrade and downgrade command on the given database',
53 53 'compare_model_to_db': 'compare MetaData against the current database state',
54 54 'create_model': 'dump the current database as a Python model to stdout',
55 55 'make_update_script_for_model': 'create a script changing the old MetaData to the new (current) MetaData',
56 56 'update_db_from_model': 'modify the database to match the structure of the current MetaData',
57 57 }
58 58 __all__ = command_desc.keys()
59 59
60 60 Repository = repository.Repository
61 61 ControlledSchema = schema.ControlledSchema
62 62 VerNum = version.VerNum
63 63 PythonScript = script_.PythonScript
64 64 SqlScript = script_.SqlScript
65 65
66 66
67 67 # deprecated
68 68 def help(cmd=None, **opts):
69 69 """%prog help COMMAND
70 70
71 71 Displays help on a given command.
72 72 """
73 73 if cmd is None:
74 74 raise exceptions.UsageError(None)
75 75 try:
76 76 func = globals()[cmd]
77 77 except:
78 78 raise exceptions.UsageError(
79 79 "'%s' isn't a valid command. Try 'help COMMAND'" % cmd)
80 80 ret = func.__doc__
81 81 if sys.argv[0]:
82 82 ret = ret.replace('%prog', sys.argv[0])
83 83 return ret
84 84
85 85 @catch_known_errors
86 86 def create(repository, name, **opts):
87 87 """%prog create REPOSITORY_PATH NAME [--table=TABLE]
88 88
89 89 Create an empty repository at the specified path.
90 90
91 91 You can specify the version_table to be used; by default, it is
92 92 'migrate_version'. This table is created in all version-controlled
93 93 databases.
94 94 """
95 95 repo_path = Repository.create(repository, name, **opts)
96 96
97 97
98 98 @catch_known_errors
99 99 def script(description, repository, **opts):
100 100 """%prog script DESCRIPTION REPOSITORY_PATH
101 101
102 102 Create an empty change script using the next unused version number
103 103 appended with the given description.
104 104
105 105 For instance, manage.py script "Add initial tables" creates:
106 106 repository/versions/001_Add_initial_tables.py
107 107 """
108 108 repo = Repository(repository)
109 109 repo.create_script(description, **opts)
110 110
111 111
112 112 @catch_known_errors
113 113 def script_sql(database, repository, **opts):
114 114 """%prog script_sql DATABASE REPOSITORY_PATH
115 115
116 116 Create empty change SQL scripts for given DATABASE, where DATABASE
117 117 is either specific ('postgres', 'mysql', 'oracle', 'sqlite', etc.)
118 118 or generic ('default').
119 119
120 120 For instance, manage.py script_sql postgres creates:
121 121 repository/versions/001_postgres_upgrade.sql and
122 122 repository/versions/001_postgres_postgres.sql
123 123 """
124 124 repo = Repository(repository)
125 125 repo.create_script_sql(database, **opts)
126 126
127 127
128 128 def version(repository, **opts):
129 129 """%prog version REPOSITORY_PATH
130 130
131 131 Display the latest version available in a repository.
132 132 """
133 133 repo = Repository(repository)
134 134 return repo.latest
135 135
136 136
137 137 @with_engine
138 138 def db_version(url, repository, **opts):
139 139 """%prog db_version URL REPOSITORY_PATH
140 140
141 141 Show the current version of the repository with the given
142 142 connection string, under version control of the specified
143 143 repository.
144 144
145 145 The url should be any valid SQLAlchemy connection string.
146 146 """
147 147 engine = opts.pop('engine')
148 148 schema = ControlledSchema(engine, repository)
149 149 return schema.version
150 150
151 151
152 152 def source(version, dest=None, repository=None, **opts):
153 153 """%prog source VERSION [DESTINATION] --repository=REPOSITORY_PATH
154 154
155 155 Display the Python code for a particular version in this
156 156 repository. Save it to the file at DESTINATION or, if omitted,
157 157 send to stdout.
158 158 """
159 159 if repository is None:
160 160 raise exceptions.UsageError("A repository must be specified")
161 161 repo = Repository(repository)
162 162 ret = repo.version(version).script().source()
163 163 if dest is not None:
164 164 dest = open(dest, 'w')
165 165 dest.write(ret)
166 166 dest.close()
167 167 ret = None
168 168 return ret
169 169
170 170
171 171 def upgrade(url, repository, version=None, **opts):
172 172 """%prog upgrade URL REPOSITORY_PATH [VERSION] [--preview_py|--preview_sql]
173 173
174 174 Upgrade a database to a later version.
175 175
176 176 This runs the upgrade() function defined in your change scripts.
177 177
178 178 By default, the database is updated to the latest available
179 179 version. You may specify a version instead, if you wish.
180 180
181 181 You may preview the Python or SQL code to be executed, rather than
182 182 actually executing it, using the appropriate 'preview' option.
183 183 """
184 184 err = "Cannot upgrade a database of version %s to version %s. "\
185 185 "Try 'downgrade' instead."
186 186 return _migrate(url, repository, version, upgrade=True, err=err, **opts)
187 187
188 188
189 189 def downgrade(url, repository, version, **opts):
190 190 """%prog downgrade URL REPOSITORY_PATH VERSION [--preview_py|--preview_sql]
191 191
192 192 Downgrade a database to an earlier version.
193 193
194 194 This is the reverse of upgrade; this runs the downgrade() function
195 195 defined in your change scripts.
196 196
197 197 You may preview the Python or SQL code to be executed, rather than
198 198 actually executing it, using the appropriate 'preview' option.
199 199 """
200 200 err = "Cannot downgrade a database of version %s to version %s. "\
201 201 "Try 'upgrade' instead."
202 202 return _migrate(url, repository, version, upgrade=False, err=err, **opts)
203 203
204 204 @with_engine
205 205 def test(url, repository, **opts):
206 206 """%prog test URL REPOSITORY_PATH [VERSION]
207 207
208 208 Performs the upgrade and downgrade option on the given
209 209 database. This is not a real test and may leave the database in a
210 210 bad state. You should therefore better run the test on a copy of
211 211 your database.
212 212 """
213 213 engine = opts.pop('engine')
214 214 repos = Repository(repository)
215 215 script = repos.version(None).script()
216 216
217 217 # Upgrade
218 218 log.info("Upgrading...")
219 219 script.run(engine, 1)
220 220 log.info("done")
221 221
222 222 log.info("Downgrading...")
223 223 script.run(engine, -1)
224 224 log.info("done")
225 225 log.info("Success")
226 226
227 227
228 228 @with_engine
229 229 def version_control(url, repository, version=None, **opts):
230 230 """%prog version_control URL REPOSITORY_PATH [VERSION]
231 231
232 232 Mark a database as under this repository's version control.
233 233
234 234 Once a database is under version control, schema changes should
235 235 only be done via change scripts in this repository.
236 236
237 237 This creates the table version_table in the database.
238 238
239 239 The url should be any valid SQLAlchemy connection string.
240 240
241 241 By default, the database begins at version 0 and is assumed to be
242 242 empty. If the database is not empty, you may specify a version at
243 243 which to begin instead. No attempt is made to verify this
244 244 version's correctness - the database schema is expected to be
245 245 identical to what it would be if the database were created from
246 246 scratch.
247 247 """
248 248 engine = opts.pop('engine')
249 249 ControlledSchema.create(engine, repository, version)
250 250
251 251
252 252 @with_engine
253 253 def drop_version_control(url, repository, **opts):
254 254 """%prog drop_version_control URL REPOSITORY_PATH
255 255
256 256 Removes version control from a database.
257 257 """
258 258 engine = opts.pop('engine')
259 259 schema = ControlledSchema(engine, repository)
260 260 schema.drop()
261 261
262 262
263 263 def manage(file, **opts):
264 264 """%prog manage FILENAME [VARIABLES...]
265 265
266 266 Creates a script that runs Migrate with a set of default values.
267 267
268 268 For example::
269 269
270 270 %prog manage manage.py --repository=/path/to/repository \
271 271 --url=sqlite:///project.db
272 272
273 273 would create the script manage.py. The following two commands
274 274 would then have exactly the same results::
275 275
276 276 python manage.py version
277 277 %prog version --repository=/path/to/repository
278 278 """
279 279 Repository.create_manage_file(file, **opts)
280 280
281 281
282 282 @with_engine
283 283 def compare_model_to_db(url, repository, model, **opts):
284 284 """%prog compare_model_to_db URL REPOSITORY_PATH MODEL
285 285
286 286 Compare the current model (assumed to be a module level variable
287 287 of type sqlalchemy.MetaData) against the current database.
288 288
289 289 NOTE: This is EXPERIMENTAL.
290 290 """ # TODO: get rid of EXPERIMENTAL label
291 291 engine = opts.pop('engine')
292 292 return ControlledSchema.compare_model_to_db(engine, model, repository)
293 293
294 294
295 295 @with_engine
296 296 def create_model(url, repository, **opts):
297 297 """%prog create_model URL REPOSITORY_PATH [DECLERATIVE=True]
298 298
299 299 Dump the current database as a Python model to stdout.
300 300
301 301 NOTE: This is EXPERIMENTAL.
302 302 """ # TODO: get rid of EXPERIMENTAL label
303 303 engine = opts.pop('engine')
304 304 declarative = opts.get('declarative', False)
305 305 return ControlledSchema.create_model(engine, repository, declarative)
306 306
307 307
308 308 @catch_known_errors
309 309 @with_engine
310 310 def make_update_script_for_model(url, repository, oldmodel, model, **opts):
311 311 """%prog make_update_script_for_model URL OLDMODEL MODEL REPOSITORY_PATH
312 312
313 313 Create a script changing the old Python model to the new (current)
314 314 Python model, sending to stdout.
315 315
316 316 NOTE: This is EXPERIMENTAL.
317 317 """ # TODO: get rid of EXPERIMENTAL label
318 318 engine = opts.pop('engine')
319 319 return PythonScript.make_update_script_for_model(
320 320 engine, oldmodel, model, repository, **opts)
321 321
322 322
323 323 @with_engine
324 324 def update_db_from_model(url, repository, model, **opts):
325 325 """%prog update_db_from_model URL REPOSITORY_PATH MODEL
326 326
327 327 Modify the database to match the structure of the current Python
328 328 model. This also sets the db_version number to the latest in the
329 329 repository.
330 330
331 331 NOTE: This is EXPERIMENTAL.
332 332 """ # TODO: get rid of EXPERIMENTAL label
333 333 engine = opts.pop('engine')
334 334 schema = ControlledSchema(engine, repository)
335 335 schema.update_db_from_model(model)
336 336
337 337 @with_engine
338 338 def _migrate(url, repository, version, upgrade, err, **opts):
339 339 engine = opts.pop('engine')
340 340 url = str(engine.url)
341 341 schema = ControlledSchema(engine, repository)
342 342 version = _migrate_version(schema, version, upgrade, err)
343 343
344 344 changeset = schema.changeset(version)
345 345 for ver, change in changeset:
346 346 nextver = ver + changeset.step
347 347 log.info('%s -> %s... ', ver, nextver)
348 348
349 349 if opts.get('preview_sql'):
350 350 if isinstance(change, PythonScript):
351 351 log.info(change.preview_sql(url, changeset.step, **opts))
352 352 elif isinstance(change, SqlScript):
353 353 log.info(change.source())
354 354
355 355 elif opts.get('preview_py'):
356 356 if not isinstance(change, PythonScript):
357 357 raise exceptions.UsageError("Python source can be only displayed"
358 358 " for python migration files")
359 359 source_ver = max(ver, nextver)
360 360 module = schema.repository.version(source_ver).script().module
361 361 funcname = upgrade and "upgrade" or "downgrade"
362 362 func = getattr(module, funcname)
363 363 log.info(inspect.getsource(func))
364 364 else:
365 365 schema.runchange(ver, change, changeset.step)
366 366 log.info('done')
367 367
368 368
369 369 def _migrate_version(schema, version, upgrade, err):
370 370 if version is None:
371 371 return version
372 372 # Version is specified: ensure we're upgrading in the right direction
373 373 # (current version < target version for upgrading; reverse for down)
374 374 version = VerNum(version)
375 375 cur = schema.version
376 376 if upgrade is not None:
377 377 if upgrade:
378 378 direction = cur <= version
379 379 else:
380 380 direction = cur >= version
381 381 if not direction:
382 382 raise exceptions.KnownError(err % (cur, version))
383 383 return version
@@ -1,49 +1,48
1 1 #!/usr/bin/env python
2 2 # -*- coding: utf-8 -*-
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
22 21 src = Template(opts.pop('templates_path', None)).get_sql_script(theme=opts.pop('templates_theme', None))
23 22 shutil.copy(src, path)
24 23 return cls(path)
25 24
26 25 # TODO: why is step parameter even here?
27 26 def run(self, engine, step=None, executemany=True):
28 27 """Runs SQL script through raw dbapi execute call"""
29 28 text = self.source()
30 29 # Don't rely on SA's autocommit here
31 30 # (SA uses .startswith to check if a commit is needed. What if script
32 31 # starts with a comment?)
33 32 conn = engine.connect()
34 33 try:
35 34 trans = conn.begin()
36 35 try:
37 36 # HACK: SQLite doesn't allow multiple statements through
38 37 # its execute() method, but it provides executescript() instead
39 38 dbapi = conn.engine.raw_connection()
40 39 if executemany and getattr(dbapi, 'executescript', None):
41 40 dbapi.executescript(text)
42 41 else:
43 42 conn.execute(text)
44 43 trans.commit()
45 44 except:
46 45 trans.rollback()
47 46 raise
48 47 finally:
49 48 conn.close()
@@ -1,94 +1,94
1 1 #!/usr/bin/env python
2 2 # -*- coding: utf-8 -*-
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 pkg = 'migrate.versioning.templates'
40 pkg = 'rhodecode.lib.dbmigrate.migrate.versioning.templates'
41 41 _manage = 'manage.py_tmpl'
42 42
43 43 def __new__(cls, path=None):
44 44 if path is None:
45 45 path = cls._find_path(cls.pkg)
46 46 return super(Template, cls).__new__(cls, path)
47 47
48 48 def __init__(self, path=None):
49 49 if path is None:
50 50 path = Template._find_path(self.pkg)
51 51 super(Template, self).__init__(path)
52 52 self.repository = RepositoryCollection(os.path.join(path, 'repository'))
53 53 self.script = ScriptCollection(os.path.join(path, 'script'))
54 54 self.manage = ManageCollection(os.path.join(path, 'manage'))
55 55 self.sql_script = SQLScriptCollection(os.path.join(path, 'sql_script'))
56 56
57 57 @classmethod
58 58 def _find_path(cls, pkg):
59 59 """Returns absolute path to dotted python package."""
60 60 tmp_pkg = pkg.rsplit('.', 1)
61 61
62 62 if len(tmp_pkg) != 1:
63 63 return resource_filename(tmp_pkg[0], tmp_pkg[1])
64 64 else:
65 65 return resource_filename(tmp_pkg[0], '')
66 66
67 67 def _get_item(self, collection, theme=None):
68 68 """Locates and returns collection.
69 69
70 70 :param collection: name of collection to locate
71 71 :param type_: type of subfolder in collection (defaults to "_default")
72 72 :returns: (package, source)
73 73 :rtype: str, str
74 74 """
75 75 item = getattr(self, collection)
76 76 theme_mask = getattr(item, '_mask')
77 77 theme = theme_mask % (theme or 'default')
78 78 return item.get_path(theme)
79 79
80 80 def get_repository(self, *a, **kw):
81 81 """Calls self._get_item('repository', *a, **kw)"""
82 82 return self._get_item('repository', *a, **kw)
83
83
84 84 def get_script(self, *a, **kw):
85 85 """Calls self._get_item('script', *a, **kw)"""
86 86 return self._get_item('script', *a, **kw)
87 87
88 88 def get_sql_script(self, *a, **kw):
89 89 """Calls self._get_item('sql_script', *a, **kw)"""
90 90 return self._get_item('sql_script', *a, **kw)
91 91
92 92 def get_manage(self, *a, **kw):
93 93 """Calls self._get_item('manage', *a, **kw)"""
94 94 return self._get_item('manage', *a, **kw)
@@ -1,238 +1,237
1 from migrate import *
2
3 1 #==============================================================================
4 2 # DB INITIAL MODEL
5 3 #==============================================================================
6 4 import logging
7 5 import datetime
8 6
9 7 from sqlalchemy import *
10 8 from sqlalchemy.exc import DatabaseError
11 9 from sqlalchemy.orm import relation, backref, class_mapper
12 10 from sqlalchemy.orm.session import Session
11 from rhodecode.model.meta import Base
13 12
14 from rhodecode.model.meta import Base
13 from rhodecode.lib.dbmigrate.migrate import *
15 14
16 15 log = logging.getLogger(__name__)
17 16
18 17 class BaseModel(object):
19 18
20 19 @classmethod
21 20 def _get_keys(cls):
22 21 """return column names for this model """
23 22 return class_mapper(cls).c.keys()
24 23
25 24 def get_dict(self):
26 25 """return dict with keys and values corresponding
27 26 to this model data """
28 27
29 28 d = {}
30 29 for k in self._get_keys():
31 30 d[k] = getattr(self, k)
32 31 return d
33 32
34 33 def get_appstruct(self):
35 34 """return list with keys and values tupples corresponding
36 35 to this model data """
37 36
38 37 l = []
39 38 for k in self._get_keys():
40 39 l.append((k, getattr(self, k),))
41 40 return l
42 41
43 42 def populate_obj(self, populate_dict):
44 43 """populate model with data from given populate_dict"""
45 44
46 45 for k in self._get_keys():
47 46 if k in populate_dict:
48 47 setattr(self, k, populate_dict[k])
49 48
50 49 class RhodeCodeSettings(Base, BaseModel):
51 50 __tablename__ = 'rhodecode_settings'
52 51 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
53 52 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
54 53 app_settings_name = Column("app_settings_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
55 54 app_settings_value = Column("app_settings_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
56 55
57 56 def __init__(self, k, v):
58 57 self.app_settings_name = k
59 58 self.app_settings_value = v
60 59
61 60 def __repr__(self):
62 61 return "<RhodeCodeSetting('%s:%s')>" % (self.app_settings_name,
63 62 self.app_settings_value)
64 63
65 64 class RhodeCodeUi(Base, BaseModel):
66 65 __tablename__ = 'rhodecode_ui'
67 66 __table_args__ = {'useexisting':True}
68 67 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
69 68 ui_section = Column("ui_section", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
70 69 ui_key = Column("ui_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
71 70 ui_value = Column("ui_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
72 71 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
73 72
74 73
75 74 class User(Base, BaseModel):
76 75 __tablename__ = 'users'
77 76 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
78 77 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
79 78 username = Column("username", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
80 79 password = Column("password", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
81 80 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
82 81 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
83 82 name = Column("name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
84 83 lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
85 84 email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
86 85 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
87 86 is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False)
88 87
89 88 user_log = relation('UserLog', cascade='all')
90 89 user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
91 90
92 91 repositories = relation('Repository')
93 92 user_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
94 93
95 94 @property
96 95 def full_contact(self):
97 96 return '%s %s <%s>' % (self.name, self.lastname, self.email)
98 97
99 98 def __repr__(self):
100 99 return "<User('id:%s:%s')>" % (self.user_id, self.username)
101 100
102 101 def update_lastlogin(self):
103 102 """Update user lastlogin"""
104 103
105 104 try:
106 105 session = Session.object_session(self)
107 106 self.last_login = datetime.datetime.now()
108 107 session.add(self)
109 108 session.commit()
110 109 log.debug('updated user %s lastlogin', self.username)
111 110 except (DatabaseError,):
112 111 session.rollback()
113 112
114 113
115 114 class UserLog(Base, BaseModel):
116 115 __tablename__ = 'user_logs'
117 116 __table_args__ = {'useexisting':True}
118 117 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
119 118 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
120 119 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
121 120 repository_name = Column("repository_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
122 121 user_ip = Column("user_ip", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
123 122 action = Column("action", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
124 123 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
125 124
126 125 user = relation('User')
127 126 repository = relation('Repository')
128 127
129 128 class Repository(Base, BaseModel):
130 129 __tablename__ = 'repositories'
131 130 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
132 131 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
133 132 repo_name = Column("repo_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
134 133 repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
135 134 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None)
136 135 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
137 136 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
138 137 description = Column("description", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
139 138 fork_id = Column("fork_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=False, default=None)
140 139
141 140 user = relation('User')
142 141 fork = relation('Repository', remote_side=repo_id)
143 142 repo_to_perm = relation('RepoToPerm', cascade='all')
144 143 stats = relation('Statistics', cascade='all', uselist=False)
145 144
146 145 repo_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
147 146
148 147
149 148 def __repr__(self):
150 149 return "<Repository('%s:%s')>" % (self.repo_id, self.repo_name)
151 150
152 151 class Permission(Base, BaseModel):
153 152 __tablename__ = 'permissions'
154 153 __table_args__ = {'useexisting':True}
155 154 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 155 permission_name = Column("permission_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 156 permission_longname = Column("permission_longname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
158 157
159 158 def __repr__(self):
160 159 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
161 160
162 161 class RepoToPerm(Base, BaseModel):
163 162 __tablename__ = 'repo_to_perm'
164 163 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
165 164 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
166 165 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
167 166 permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
168 167 repository_id = Column("repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
169 168
170 169 user = relation('User')
171 170 permission = relation('Permission')
172 171 repository = relation('Repository')
173 172
174 173 class UserToPerm(Base, BaseModel):
175 174 __tablename__ = 'user_to_perm'
176 175 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
177 176 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
178 177 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
179 178 permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
180 179
181 180 user = relation('User')
182 181 permission = relation('Permission')
183 182
184 183 class Statistics(Base, BaseModel):
185 184 __tablename__ = 'statistics'
186 185 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
187 186 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
188 187 repository_id = Column("repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=True, default=None)
189 188 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
190 189 commit_activity = Column("commit_activity", LargeBinary(), nullable=False)#JSON data
191 190 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
192 191 languages = Column("languages", LargeBinary(), nullable=False)#JSON data
193 192
194 193 repository = relation('Repository', single_parent=True)
195 194
196 195 class UserFollowing(Base, BaseModel):
197 196 __tablename__ = 'user_followings'
198 197 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
199 198 UniqueConstraint('user_id', 'follows_user_id')
200 199 , {'useexisting':True})
201 200
202 201 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
203 202 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
204 203 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
205 204 follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
206 205
207 206 user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
208 207
209 208 follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
210 209 follows_repository = relation('Repository')
211 210
212 211
213 212 class CacheInvalidation(Base, BaseModel):
214 213 __tablename__ = 'cache_invalidation'
215 214 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
216 215 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
217 216 cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
218 217 cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
219 218 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
220 219
221 220
222 221 def __init__(self, cache_key, cache_args=''):
223 222 self.cache_key = cache_key
224 223 self.cache_args = cache_args
225 224 self.cache_active = False
226 225
227 226 def __repr__(self):
228 227 return "<CacheInvalidation('%s:%s')>" % (self.cache_id, self.cache_key)
229 228
230 229
231 230 def upgrade(migrate_engine):
232 231 # Upgrade operations go here. Don't create your own engine; bind migrate_engine
233 232 # to your metadata
234 233 Base.metadata.create_all(bind=migrate_engine, checkfirst=False)
235 234
236 235 def downgrade(migrate_engine):
237 236 # Operations to reverse the above upgrade go here.
238 237 Base.metadata.drop_all(bind=migrate_engine, checkfirst=False)
@@ -1,118 +1,129
1 from sqlalchemy import *
2 from sqlalchemy.orm import relation
1 import logging
2 import datetime
3 3
4 from migrate import *
5 from migrate.changeset import *
6 from rhodecode.model.meta import Base, BaseModel
4 from sqlalchemy import *
5 from sqlalchemy.exc import DatabaseError
6 from sqlalchemy.orm import relation, backref, class_mapper
7 from sqlalchemy.orm.session import Session
8 from rhodecode.model.meta import Base
9 from rhodecode.model.db import BaseModel
10
11 from rhodecode.lib.dbmigrate.migrate import *
12
13 log = logging.getLogger(__name__)
7 14
8 15 def upgrade(migrate_engine):
9 16 """ Upgrade operations go here.
10 17 Don't create your own engine; bind migrate_engine to your metadata
11 18 """
12 19
13 20 #==========================================================================
14 21 # Upgrade of `users` table
15 22 #==========================================================================
16 23 tblname = 'users'
17 24 tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True,
18 25 autoload_with=migrate_engine)
19 26
20 27 #ADD is_ldap column
21 is_ldap = Column("is_ldap", Boolean(), nullable=False,
28 is_ldap = Column("is_ldap", Boolean(), nullable=True,
22 29 unique=None, default=False)
23 is_ldap.create(tbl)
24
30 is_ldap.create(tbl, populate_default=True)
31 is_ldap.alter(nullable=False)
25 32
26 33 #==========================================================================
27 34 # Upgrade of `user_logs` table
28 35 #==========================================================================
29 36
30 37 tblname = 'users'
31 38 tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True,
32 39 autoload_with=migrate_engine)
33 40
34 41 #ADD revision column
35 42 revision = Column('revision', TEXT(length=None, convert_unicode=False,
36 43 assert_unicode=None),
37 44 nullable=True, unique=None, default=None)
38 45 revision.create(tbl)
39 46
40 47
41 48
42 49 #==========================================================================
43 50 # Upgrade of `repositories` table
44 51 #==========================================================================
45 52 tblname = 'users'
46 53 tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True,
47 54 autoload_with=migrate_engine)
48 55
49 56 #ADD repo_type column
50 57 repo_type = Column("repo_type", String(length=None, convert_unicode=False,
51 58 assert_unicode=None),
52 nullable=False, unique=False, default=None)
53 repo_type.create(tbl)
59 nullable=True, unique=False, default='hg')
54 60
61 repo_type.create(tbl, populate_default=True)
62 repo_type.alter(nullable=False)
55 63
56 64 #ADD statistics column
57 65 enable_statistics = Column("statistics", Boolean(), nullable=True,
58 66 unique=None, default=True)
59 67 enable_statistics.create(tbl)
60 68
61 69
62 70
63 71 #==========================================================================
64 72 # Add table `user_followings`
65 73 #==========================================================================
66 74 tblname = 'user_followings'
75
67 76 class UserFollowing(Base, BaseModel):
68 77 __tablename__ = 'user_followings'
69 78 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
70 79 UniqueConstraint('user_id', 'follows_user_id')
71 80 , {'useexisting':True})
72 81
73 82 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
74 83 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
75 84 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
76 85 follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
77 86
78 87 user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
79 88
80 89 follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
81 90 follows_repository = relation('Repository')
82 91
83 92 Base.metadata.tables[tblname].create(migrate_engine)
84 93
85 94 #==========================================================================
86 95 # Add table `cache_invalidation`
87 96 #==========================================================================
97 tblname = 'cache_invalidation'
98
88 99 class CacheInvalidation(Base, BaseModel):
89 100 __tablename__ = 'cache_invalidation'
90 101 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
91 102 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
92 103 cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
93 104 cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
94 105 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
95 106
96 107
97 108 def __init__(self, cache_key, cache_args=''):
98 109 self.cache_key = cache_key
99 110 self.cache_args = cache_args
100 111 self.cache_active = False
101 112
102 113 def __repr__(self):
103 114 return "<CacheInvalidation('%s:%s')>" % (self.cache_id, self.cache_key)
104 115
105 116 Base.metadata.tables[tblname].create(migrate_engine)
106 117
107 118 return
108 119
109 120
110 121
111 122
112 123
113 124
114 125 def downgrade(migrate_engine):
115 126 meta = MetaData()
116 127 meta.bind = migrate_engine
117 128
118 129
@@ -1,257 +1,257
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27 import logging
28 28 import datetime
29 29
30 30 from sqlalchemy import *
31 31 from sqlalchemy.exc import DatabaseError
32 32 from sqlalchemy.orm import relation, backref, class_mapper
33 33 from sqlalchemy.orm.session import Session
34 34
35 35 from rhodecode.model.meta import Base
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39 class BaseModel(object):
40 40
41 41 @classmethod
42 42 def _get_keys(cls):
43 43 """return column names for this model """
44 44 return class_mapper(cls).c.keys()
45 45
46 46 def get_dict(self):
47 47 """return dict with keys and values corresponding
48 48 to this model data """
49 49
50 50 d = {}
51 51 for k in self._get_keys():
52 52 d[k] = getattr(self, k)
53 53 return d
54 54
55 55 def get_appstruct(self):
56 56 """return list with keys and values tupples corresponding
57 57 to this model data """
58 58
59 59 l = []
60 60 for k in self._get_keys():
61 61 l.append((k, getattr(self, k),))
62 62 return l
63 63
64 64 def populate_obj(self, populate_dict):
65 65 """populate model with data from given populate_dict"""
66 66
67 67 for k in self._get_keys():
68 68 if k in populate_dict:
69 69 setattr(self, k, populate_dict[k])
70 70
71 71 class RhodeCodeSettings(Base, BaseModel):
72 72 __tablename__ = 'rhodecode_settings'
73 73 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
74 74 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
75 75 app_settings_name = Column("app_settings_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
76 76 app_settings_value = Column("app_settings_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
77 77
78 78 def __init__(self, k, v):
79 79 self.app_settings_name = k
80 80 self.app_settings_value = v
81 81
82 82 def __repr__(self):
83 83 return "<RhodeCodeSetting('%s:%s')>" % (self.app_settings_name,
84 84 self.app_settings_value)
85 85
86 86 class RhodeCodeUi(Base, BaseModel):
87 87 __tablename__ = 'rhodecode_ui'
88 88 __table_args__ = {'useexisting':True}
89 89 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
90 90 ui_section = Column("ui_section", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
91 91 ui_key = Column("ui_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
92 92 ui_value = Column("ui_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
93 93 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
94 94
95 95
96 96 class User(Base, BaseModel):
97 97 __tablename__ = 'users'
98 98 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
99 99 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
100 100 username = Column("username", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
101 101 password = Column("password", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
102 102 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
103 103 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
104 104 name = Column("name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
105 105 lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
106 106 email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
107 107 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
108 108 is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False)
109 109
110 110 user_log = relation('UserLog', cascade='all')
111 111 user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
112 112
113 113 repositories = relation('Repository')
114 114 user_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
115 115
116 116 @property
117 117 def full_contact(self):
118 118 return '%s %s <%s>' % (self.name, self.lastname, self.email)
119 119
120 120 def __repr__(self):
121 121 return "<User('id:%s:%s')>" % (self.user_id, self.username)
122 122
123 123 def update_lastlogin(self):
124 124 """Update user lastlogin"""
125 125
126 126 try:
127 127 session = Session.object_session(self)
128 128 self.last_login = datetime.datetime.now()
129 129 session.add(self)
130 130 session.commit()
131 131 log.debug('updated user %s lastlogin', self.username)
132 132 except (DatabaseError,):
133 133 session.rollback()
134 134
135 135
136 136 class UserLog(Base, BaseModel):
137 137 __tablename__ = 'user_logs'
138 138 __table_args__ = {'useexisting':True}
139 139 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
140 140 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
141 141 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
142 142 repository_name = Column("repository_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143 143 user_ip = Column("user_ip", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
144 144 action = Column("action", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
145 145 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
146 146
147 147 user = relation('User')
148 148 repository = relation('Repository')
149 149
150 150 class Repository(Base, BaseModel):
151 151 __tablename__ = 'repositories'
152 152 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
153 153 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
154 154 repo_name = Column("repo_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
155 repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
155 repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
156 156 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None)
157 157 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
158 158 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
159 159 description = Column("description", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
160 160 fork_id = Column("fork_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=False, default=None)
161 161
162 162 user = relation('User')
163 163 fork = relation('Repository', remote_side=repo_id)
164 164 repo_to_perm = relation('RepoToPerm', cascade='all')
165 165 stats = relation('Statistics', cascade='all', uselist=False)
166 166
167 167 repo_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
168 168
169 169
170 170 def __repr__(self):
171 171 return "<Repository('%s:%s')>" % (self.repo_id, self.repo_name)
172 172
173 173 class Permission(Base, BaseModel):
174 174 __tablename__ = 'permissions'
175 175 __table_args__ = {'useexisting':True}
176 176 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
177 177 permission_name = Column("permission_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
178 178 permission_longname = Column("permission_longname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
179 179
180 180 def __repr__(self):
181 181 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
182 182
183 183 class RepoToPerm(Base, BaseModel):
184 184 __tablename__ = 'repo_to_perm'
185 185 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
186 186 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
187 187 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
188 188 permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
189 189 repository_id = Column("repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
190 190
191 191 user = relation('User')
192 192 permission = relation('Permission')
193 193 repository = relation('Repository')
194 194
195 195 class UserToPerm(Base, BaseModel):
196 196 __tablename__ = 'user_to_perm'
197 197 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
198 198 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
199 199 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
200 200 permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
201 201
202 202 user = relation('User')
203 203 permission = relation('Permission')
204 204
205 205 class Statistics(Base, BaseModel):
206 206 __tablename__ = 'statistics'
207 207 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
208 208 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
209 209 repository_id = Column("repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=True, default=None)
210 210 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
211 211 commit_activity = Column("commit_activity", LargeBinary(), nullable=False)#JSON data
212 212 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
213 213 languages = Column("languages", LargeBinary(), nullable=False)#JSON data
214 214
215 215 repository = relation('Repository', single_parent=True)
216 216
217 217 class UserFollowing(Base, BaseModel):
218 218 __tablename__ = 'user_followings'
219 219 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
220 220 UniqueConstraint('user_id', 'follows_user_id')
221 221 , {'useexisting':True})
222 222
223 223 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
224 224 user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
225 225 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
226 226 follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
227 227
228 228 user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
229 229
230 230 follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
231 231 follows_repository = relation('Repository')
232 232
233 233
234 234 class CacheInvalidation(Base, BaseModel):
235 235 __tablename__ = 'cache_invalidation'
236 236 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
237 237 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
238 238 cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
239 239 cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
240 240 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
241 241
242 242
243 243 def __init__(self, cache_key, cache_args=''):
244 244 self.cache_key = cache_key
245 245 self.cache_args = cache_args
246 246 self.cache_active = False
247 247
248 248 def __repr__(self):
249 249 return "<CacheInvalidation('%s:%s')>" % (self.cache_id, self.cache_key)
250 250
251 251 class DbMigrateVersion(Base, BaseModel):
252 252 __tablename__ = 'db_migrate_version'
253 253 __table_args__ = {'useexisting':True}
254 254 repository_id = Column('repository_id', String(250), primary_key=True)
255 255 repository_path = Column('repository_path', Text)
256 256 version = Column('version', Integer)
257 257
General Comments 0
You need to be logged in to leave comments. Login now