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__ = |
|
|
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( |
|
|
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 = |
|
|
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 |
|
|
34 |
script as script_ |
|
|
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. |
|
|
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 |
|
|
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= |
|
|
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= |
|
|
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= |
|
|
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