Set up virtualenv to use py2
[mediagoblin.git] / mediagoblin / db / migrations.py
CommitLineData
70b44584 1# GNU MediaGoblin -- federated, autonomous media hosting
b781c3c9 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
70b44584
CAW
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
29fdd3bb 17import datetime
34d8bc98 18import uuid
29fdd3bb 19
88a9662b 20from sqlalchemy import (MetaData, Table, Column, Boolean, SmallInteger,
316e1dfd 21 Integer, Unicode, UnicodeText, DateTime,
892eed59 22 ForeignKey, Date, Index)
0f14c362 23from sqlalchemy.exc import ProgrammingError
316e1dfd 24from sqlalchemy.ext.declarative import declarative_base
ab1f65e6 25from sqlalchemy.sql import and_
0f14c362 26from migrate.changeset.constraint import UniqueConstraint
b781c3c9 27
42dbb26a 28from mediagoblin.db.extratypes import JSONEncoded, MutationDict
0c875e1e
CAW
29from mediagoblin.db.migration_tools import (
30 RegisterMigration, inspect_table, replace_table_hack)
2b7b9de3 31from mediagoblin.db.models import (MediaEntry, Collection, MediaComment, User,
d70b7a51 32 Privilege)
2dd966b5 33from mediagoblin.db.extratypes import JSONEncoded, MutationDict
b781c3c9 34
d70b7a51 35
3ea1cf36 36MIGRATIONS = {}
b781c3c9
JK
37
38
39@RegisterMigration(1, MIGRATIONS)
40def ogg_to_webm_audio(db_conn):
41 metadata = MetaData(bind=db_conn.bind)
42
43 file_keynames = Table('core__file_keynames', metadata, autoload=True,
44 autoload_with=db_conn.bind)
45
46 db_conn.execute(
38c6d441 47 file_keynames.update().where(file_keynames.c.name == 'ogg').
b781c3c9
JK
48 values(name='webm_audio')
49 )
b1055401 50 db_conn.commit()
38c6d441
JW
51
52
53@RegisterMigration(2, MIGRATIONS)
54def add_wants_notification_column(db_conn):
55 metadata = MetaData(bind=db_conn.bind)
56
57 users = Table('core__users', metadata, autoload=True,
58 autoload_with=db_conn.bind)
59
60 col = Column('wants_comment_notification', Boolean,
c4869eff 61 default=True, nullable=True)
38c6d441 62 col.create(users, populate_defaults=True)
b1055401 63 db_conn.commit()
64712915
JW
64
65
66@RegisterMigration(3, MIGRATIONS)
67def add_transcoding_progress(db_conn):
68 metadata = MetaData(bind=db_conn.bind)
69
c4466cb4 70 media_entry = inspect_table(metadata, 'core__media_entries')
64712915
JW
71
72 col = Column('transcoding_progress', SmallInteger)
73 col.create(media_entry)
74 db_conn.commit()
be5be115 75
88a9662b 76
316e1dfd
E
77class Collection_v0(declarative_base()):
78 __tablename__ = "core__collections"
79
80 id = Column(Integer, primary_key=True)
81 title = Column(Unicode, nullable=False)
82 slug = Column(Unicode)
83 created = Column(DateTime, nullable=False, default=datetime.datetime.now,
84 index=True)
85 description = Column(UnicodeText)
86 creator = Column(Integer, ForeignKey(User.id), nullable=False)
87 items = Column(Integer, default=0)
88
89class CollectionItem_v0(declarative_base()):
90 __tablename__ = "core__collection_items"
91
92 id = Column(Integer, primary_key=True)
93 media_entry = Column(
94 Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
95 collection = Column(Integer, ForeignKey(Collection.id), nullable=False)
96 note = Column(UnicodeText, nullable=True)
97 added = Column(DateTime, nullable=False, default=datetime.datetime.now)
98 position = Column(Integer)
99
100 ## This should be activated, normally.
101 ## But this would change the way the next migration used to work.
102 ## So it's commented for now.
0f14c362
E
103 __table_args__ = (
104 UniqueConstraint('collection', 'media_entry'),
105 {})
106
107collectionitem_unique_constraint_done = False
316e1dfd 108
be5be115 109@RegisterMigration(4, MIGRATIONS)
29fdd3bb 110def add_collection_tables(db_conn):
316e1dfd
E
111 Collection_v0.__table__.create(db_conn.bind)
112 CollectionItem_v0.__table__.create(db_conn.bind)
29fdd3bb 113
0f14c362
E
114 global collectionitem_unique_constraint_done
115 collectionitem_unique_constraint_done = True
116
29fdd3bb
AW
117 db_conn.commit()
118
88a9662b 119
29fdd3bb 120@RegisterMigration(5, MIGRATIONS)
59fb87c9 121def add_mediaentry_collected(db_conn):
be5be115
AW
122 metadata = MetaData(bind=db_conn.bind)
123
c4466cb4 124 media_entry = inspect_table(metadata, 'core__media_entries')
be5be115 125
d8984df8 126 col = Column('collected', Integer, default=0)
be5be115
AW
127 col.create(media_entry)
128 db_conn.commit()
5354f954
JW
129
130
316e1dfd
E
131class ProcessingMetaData_v0(declarative_base()):
132 __tablename__ = 'core__processing_metadata'
939d57a0 133
316e1dfd
E
134 id = Column(Integer, primary_key=True)
135 media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False,
136 index=True)
137 callback_url = Column(Unicode)
939d57a0 138
316e1dfd
E
139@RegisterMigration(6, MIGRATIONS)
140def create_processing_metadata_table(db):
141 ProcessingMetaData_v0.__table__.create(db.bind)
5354f954 142 db.commit()
0f14c362 143
ea91c183
E
144
145# Okay, problem being:
146# Migration #4 forgot to add the uniqueconstraint for the
147# new tables. While creating the tables from scratch had
148# the constraint enabled.
149#
a64abbb1 150# So we have four situations that should end up at the same
ea91c183
E
151# db layout:
152#
153# 1. Fresh install.
154# Well, easy. Just uses the tables in models.py
155# 2. Fresh install using a git version just before this migration
156# The tables are all there, the unique constraint is also there.
157# This migration should do nothing.
158# But as we can't detect the uniqueconstraint easily,
159# this migration just adds the constraint again.
160# And possibly fails very loud. But ignores the failure.
161# 3. old install, not using git, just releases.
162# This one will get the new tables in #4 (now with constraint!)
163# And this migration is just skipped silently.
164# 4. old install, always on latest git.
165# This one has the tables, but lacks the constraint.
a64abbb1 166# So this migration adds the constraint.
0f14c362
E
167@RegisterMigration(7, MIGRATIONS)
168def fix_CollectionItem_v0_constraint(db_conn):
169 """Add the forgotten Constraint on CollectionItem"""
170
171 global collectionitem_unique_constraint_done
172 if collectionitem_unique_constraint_done:
0f14c362
E
173 # Reset it. Maybe the whole thing gets run again
174 # For a different db?
175 collectionitem_unique_constraint_done = False
176 return
177
178 metadata = MetaData(bind=db_conn.bind)
179
c4466cb4 180 CollectionItem_table = inspect_table(metadata, 'core__collection_items')
0f14c362
E
181
182 constraint = UniqueConstraint('collection', 'media_entry',
183 name='core__collection_items_collection_media_entry_key',
184 table=CollectionItem_table)
185
186 try:
187 constraint.create()
78fd5581
CAW
188 except ProgrammingError:
189 # User probably has an install that was run since the
190 # collection tables were added, so we don't need to run this migration.
191 pass
192
0f14c362 193 db_conn.commit()
dc4dfbde
MH
194
195
196@RegisterMigration(8, MIGRATIONS)
197def add_license_preference(db):
198 metadata = MetaData(bind=db.bind)
199
0c871f81 200 user_table = inspect_table(metadata, 'core__users')
dc4dfbde 201
0c871f81 202 col = Column('license_preference', Unicode)
dc4dfbde
MH
203 col.create(user_table)
204 db.commit()
e66431f4
CAW
205
206
207@RegisterMigration(9, MIGRATIONS)
208def mediaentry_new_slug_era(db):
209 """
210 Update for the new era for media type slugs.
211
212 Entries without slugs now display differently in the url like:
213 /u/cwebber/m/id=251/
214
215 ... because of this, we should back-convert:
216 - entries without slugs should be converted to use the id, if possible, to
217 make old urls still work
218 - slugs with = (or also : which is now also not allowed) to have those
219 stripped out (small possibility of breakage here sadly)
220 """
e66431f4
CAW
221
222 def slug_and_user_combo_exists(slug, uploader):
e66431f4
CAW
223 return db.execute(
224 media_table.select(
ab1f65e6 225 and_(media_table.c.uploader==uploader,
aecd65b7 226 media_table.c.slug==slug))).first() is not None
e66431f4
CAW
227
228 def append_garbage_till_unique(row, new_slug):
229 """
230 Attach junk to this row until it's unique, then save it
231 """
232 if slug_and_user_combo_exists(new_slug, row.uploader):
233 # okay, still no success;
234 # let's whack junk on there till it's unique.
235 new_slug += '-' + uuid.uuid4().hex[:4]
236 # keep going if necessary!
237 while slug_and_user_combo_exists(new_slug, row.uploader):
238 new_slug += uuid.uuid4().hex[:4]
239
240 db.execute(
241 media_table.update(). \
242 where(media_table.c.id==row.id). \
243 values(slug=new_slug))
244
245 metadata = MetaData(bind=db.bind)
246
247 media_table = inspect_table(metadata, 'core__media_entries')
0b7cdb6f 248
e66431f4
CAW
249 for row in db.execute(media_table.select()):
250 # no slug, try setting to an id
251 if not row.slug:
252 append_garbage_till_unique(row, unicode(row.id))
253 # has "=" or ":" in it... we're getting rid of those
254 elif u"=" in row.slug or u":" in row.slug:
255 append_garbage_till_unique(
256 row, row.slug.replace(u"=", u"-").replace(u":", u"-"))
0b7cdb6f
CAW
257
258 db.commit()
34d8bc98
RE
259
260
261@RegisterMigration(10, MIGRATIONS)
262def unique_collections_slug(db):
263 """Add unique constraint to collection slug"""
264 metadata = MetaData(bind=db.bind)
265 collection_table = inspect_table(metadata, "core__collections")
266 existing_slugs = {}
267 slugs_to_change = []
268
269 for row in db.execute(collection_table.select()):
270 # if duplicate slug, generate a unique slug
271 if row.creator in existing_slugs and row.slug in \
272 existing_slugs[row.creator]:
273 slugs_to_change.append(row.id)
274 else:
275 if not row.creator in existing_slugs:
276 existing_slugs[row.creator] = [row.slug]
277 else:
278 existing_slugs[row.creator].append(row.slug)
279
280 for row_id in slugs_to_change:
f96c284e 281 new_slug = unicode(uuid.uuid4())
34d8bc98
RE
282 db.execute(collection_table.update().
283 where(collection_table.c.id == row_id).
284 values(slug=new_slug))
285 # sqlite does not like to change the schema when a transaction(update) is
286 # not yet completed
287 db.commit()
288
289 constraint = UniqueConstraint('creator', 'slug',
290 name='core__collection_creator_slug_key',
291 table=collection_table)
292 constraint.create()
293
294 db.commit()
8ad734af 295
8ad734af 296@RegisterMigration(11, MIGRATIONS)
342f06f7
RE
297def drop_token_related_User_columns(db):
298 """
299 Drop unneeded columns from the User table after switching to using
300 itsdangerous tokens for email and forgot password verification.
301 """
302 metadata = MetaData(bind=db.bind)
303 user_table = inspect_table(metadata, 'core__users')
304
305 verification_key = user_table.columns['verification_key']
306 fp_verification_key = user_table.columns['fp_verification_key']
307 fp_token_expire = user_table.columns['fp_token_expire']
308
309 verification_key.drop()
310 fp_verification_key.drop()
311 fp_token_expire.drop()
312
313 db.commit()
257b8ab6 314
5adb906a 315
2d7b6bde
JW
316class CommentSubscription_v0(declarative_base()):
317 __tablename__ = 'core__comment_subscriptions'
318 id = Column(Integer, primary_key=True)
319
320 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
321
322 media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
323
324 user_id = Column(Integer, ForeignKey(User.id), nullable=False)
325
326 notify = Column(Boolean, nullable=False, default=True)
327 send_email = Column(Boolean, nullable=False, default=True)
328
329
330class Notification_v0(declarative_base()):
331 __tablename__ = 'core__notifications'
332 id = Column(Integer, primary_key=True)
333 type = Column(Unicode)
334
335 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
336
337 user_id = Column(Integer, ForeignKey(User.id), nullable=False,
338 index=True)
339 seen = Column(Boolean, default=lambda: False, index=True)
340
341
342class CommentNotification_v0(Notification_v0):
343 __tablename__ = 'core__comment_notifications'
344 id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
345
346 subject_id = Column(Integer, ForeignKey(MediaComment.id))
347
348
349class ProcessingNotification_v0(Notification_v0):
350 __tablename__ = 'core__processing_notifications'
351
352 id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
353
354 subject_id = Column(Integer, ForeignKey(MediaEntry.id))
355
356
257b8ab6 357@RegisterMigration(12, MIGRATIONS)
2d7b6bde
JW
358def add_new_notification_tables(db):
359 metadata = MetaData(bind=db.bind)
360
361 user_table = inspect_table(metadata, 'core__users')
362 mediaentry_table = inspect_table(metadata, 'core__media_entries')
363 mediacomment_table = inspect_table(metadata, 'core__media_comments')
364
365 CommentSubscription_v0.__table__.create(db.bind)
366
367 Notification_v0.__table__.create(db.bind)
368 CommentNotification_v0.__table__.create(db.bind)
369 ProcessingNotification_v0.__table__.create(db.bind)
af4414a8 370
e8eec575
CAW
371 db.commit()
372
af4414a8
RE
373
374@RegisterMigration(13, MIGRATIONS)
8ad734af
RE
375def pw_hash_nullable(db):
376 """Make pw_hash column nullable"""
377 metadata = MetaData(bind=db.bind)
378 user_table = inspect_table(metadata, "core__users")
379
380 user_table.c.pw_hash.alter(nullable=True)
381
15db1831
CAW
382 # sqlite+sqlalchemy seems to drop this constraint during the
383 # migration, so we add it back here for now a bit manually.
5a1be074 384 if db.bind.url.drivername == 'sqlite':
e4deacd9
RE
385 constraint = UniqueConstraint('username', table=user_table)
386 constraint.create()
387
8ad734af 388 db.commit()
8ddd7769 389
390
7271b062 391# oauth1 migrations
8e3bf978 392class Client_v0(declarative_base()):
7271b062 393 """
394 Model representing a client - Used for API Auth
395 """
396 __tablename__ = "core__clients"
397
398 id = Column(Unicode, nullable=True, primary_key=True)
399 secret = Column(Unicode, nullable=False)
400 expirey = Column(DateTime, nullable=True)
401 application_type = Column(Unicode, nullable=False)
402 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
403 updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
404
405 # optional stuff
406 redirect_uri = Column(JSONEncoded, nullable=True)
407 logo_url = Column(Unicode, nullable=True)
408 application_name = Column(Unicode, nullable=True)
409 contacts = Column(JSONEncoded, nullable=True)
410
411 def __repr__(self):
412 if self.application_name:
413 return "<Client {0} - {1}>".format(self.application_name, self.id)
414 else:
415 return "<Client {0}>".format(self.id)
416
8e3bf978 417class RequestToken_v0(declarative_base()):
7271b062 418 """
419 Model for representing the request tokens
420 """
421 __tablename__ = "core__request_tokens"
422
423 token = Column(Unicode, primary_key=True)
424 secret = Column(Unicode, nullable=False)
8e3bf978 425 client = Column(Unicode, ForeignKey(Client_v0.id))
7271b062 426 user = Column(Integer, ForeignKey(User.id), nullable=True)
427 used = Column(Boolean, default=False)
428 authenticated = Column(Boolean, default=False)
429 verifier = Column(Unicode, nullable=True)
430 callback = Column(Unicode, nullable=False, default=u"oob")
431 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
432 updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
93d805ad 433
8e3bf978 434class AccessToken_v0(declarative_base()):
7271b062 435 """
436 Model for representing the access tokens
437 """
438 __tablename__ = "core__access_tokens"
439
440 token = Column(Unicode, nullable=False, primary_key=True)
441 secret = Column(Unicode, nullable=False)
442 user = Column(Integer, ForeignKey(User.id))
8e3bf978 443 request_token = Column(Unicode, ForeignKey(RequestToken_v0.token))
7271b062 444 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
445 updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
93d805ad 446
7271b062 447
8e3bf978 448class NonceTimestamp_v0(declarative_base()):
7271b062 449 """
450 A place the timestamp and nonce can be stored - this is for OAuth1
451 """
452 __tablename__ = "core__nonce_timestamps"
453
454 nonce = Column(Unicode, nullable=False, primary_key=True)
455 timestamp = Column(DateTime, nullable=False, primary_key=True)
456
457
8ddd7769 458@RegisterMigration(14, MIGRATIONS)
459def create_oauth1_tables(db):
460 """ Creates the OAuth1 tables """
461
7271b062 462 Client_v0.__table__.create(db.bind)
463 RequestToken_v0.__table__.create(db.bind)
464 AccessToken_v0.__table__.create(db.bind)
465 NonceTimestamp_v0.__table__.create(db.bind)
8ddd7769 466
467 db.commit()
93d805ad 468
93d805ad
RE
469@RegisterMigration(15, MIGRATIONS)
470def wants_notifications(db):
471 """Add a wants_notifications field to User model"""
472 metadata = MetaData(bind=db.bind)
473 user_table = inspect_table(metadata, "core__users")
93d805ad
RE
474 col = Column('wants_notifications', Boolean, default=True)
475 col.create(user_table)
045fe0ee 476 db.commit()
477
63866d80
CAW
478
479
480@RegisterMigration(16, MIGRATIONS)
481def upload_limits(db):
482 """Add user upload limit columns"""
483 metadata = MetaData(bind=db.bind)
484
485 user_table = inspect_table(metadata, 'core__users')
486 media_entry_table = inspect_table(metadata, 'core__media_entries')
487
488 col = Column('uploaded', Integer, default=0)
489 col.create(user_table)
490
491 col = Column('upload_limit', Integer)
492 col.create(user_table)
493
494 col = Column('file_size', Integer, default=0)
495 col.create(media_entry_table)
496
497 db.commit()
498
499
500@RegisterMigration(17, MIGRATIONS)
501def add_file_metadata(db):
502 """Add file_metadata to MediaFile"""
503 metadata = MetaData(bind=db.bind)
504 media_file_table = inspect_table(metadata, "core__mediafiles")
505
506 col = Column('file_metadata', MutationDict.as_mutable(JSONEncoded))
507 col.create(media_file_table)
508
509 db.commit()
510
511###################
512# Moderation tables
513###################
514
2c901db0 515class ReportBase_v0(declarative_base()):
516 __tablename__ = 'core__reports'
517 id = Column(Integer, primary_key=True)
518 reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
519 report_content = Column(UnicodeText)
520 reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
dfd66b78 521 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
2c901db0 522 discriminator = Column('type', Unicode(50))
c9068870 523 resolver_id = Column(Integer, ForeignKey(User.id))
524 resolved = Column(DateTime)
525 result = Column(UnicodeText)
2c901db0 526 __mapper_args__ = {'polymorphic_on': discriminator}
527
63866d80 528
2c901db0 529class CommentReport_v0(ReportBase_v0):
530 __tablename__ = 'core__reports_on_comments'
531 __mapper_args__ = {'polymorphic_identity': 'comment_report'}
532
533 id = Column('id',Integer, ForeignKey('core__reports.id'),
534 primary_key=True)
6483b370 535 comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=True)
2c901db0 536
045fe0ee 537
2c901db0 538class MediaReport_v0(ReportBase_v0):
539 __tablename__ = 'core__reports_on_media'
540 __mapper_args__ = {'polymorphic_identity': 'media_report'}
541
542 id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True)
6483b370 543 media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=True)
2c901db0 544
63866d80 545
2c901db0 546class UserBan_v0(declarative_base()):
547 __tablename__ = 'core__user_bans'
0a24db84 548 user_id = Column(Integer, ForeignKey(User.id), nullable=False,
2c901db0 549 primary_key=True)
1bb367f6 550 expiration_date = Column(Date)
2c901db0 551 reason = Column(UnicodeText, nullable=False)
552
63866d80 553
2c901db0 554class Privilege_v0(declarative_base()):
555 __tablename__ = 'core__privileges'
556 id = Column(Integer, nullable=False, primary_key=True, unique=True)
557 privilege_name = Column(Unicode, nullable=False, unique=True)
558
63866d80 559
2c901db0 560class PrivilegeUserAssociation_v0(declarative_base()):
561 __tablename__ = 'core__privileges_users'
9519c0a9 562 privilege_id = Column(
dfd66b78 563 'core__privilege_id',
564 Integer,
565 ForeignKey(User.id),
2c901db0 566 primary_key=True)
567 user_id = Column(
dfd66b78 568 'core__user_id',
569 Integer,
570 ForeignKey(Privilege.id),
2c901db0 571 primary_key=True)
572
63866d80 573
9519c0a9 574PRIVILEGE_FOUNDATIONS_v0 = [{'privilege_name':u'admin'},
63866d80
CAW
575 {'privilege_name':u'moderator'},
576 {'privilege_name':u'uploader'},
577 {'privilege_name':u'reporter'},
578 {'privilege_name':u'commenter'},
579 {'privilege_name':u'active'}]
9519c0a9 580
581
8ac78593
CAW
582# vR1 stands for "version Rename 1". This only exists because we need
583# to deal with dropping some booleans and it's otherwise impossible
584# with sqlite.
585
9519c0a9 586class User_vR1(declarative_base()):
587 __tablename__ = 'rename__users'
588 id = Column(Integer, primary_key=True)
589 username = Column(Unicode, nullable=False, unique=True)
590 email = Column(Unicode, nullable=False)
591 pw_hash = Column(Unicode)
592 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
593 wants_comment_notification = Column(Boolean, default=True)
594 wants_notifications = Column(Boolean, default=True)
595 license_preference = Column(Unicode)
596 url = Column(Unicode)
597 bio = Column(UnicodeText) # ??
a4609dd3
CAW
598 uploaded = Column(Integer, default=0)
599 upload_limit = Column(Integer)
9519c0a9 600
63866d80 601
9519c0a9 602@RegisterMigration(18, MIGRATIONS)
2c901db0 603def create_moderation_tables(db):
9519c0a9 604
605 # First, we will create the new tables in the database.
606 #--------------------------------------------------------------------------
2c901db0 607 ReportBase_v0.__table__.create(db.bind)
608 CommentReport_v0.__table__.create(db.bind)
609 MediaReport_v0.__table__.create(db.bind)
2c901db0 610 UserBan_v0.__table__.create(db.bind)
611 Privilege_v0.__table__.create(db.bind)
612 PrivilegeUserAssociation_v0.__table__.create(db.bind)
25625107 613
2c901db0 614 db.commit()
615
9519c0a9 616 # Then initialize the tables that we will later use
617 #--------------------------------------------------------------------------
6acf4ee6 618 metadata = MetaData(bind=db.bind)
9519c0a9 619 privileges_table= inspect_table(metadata, "core__privileges")
6acf4ee6 620 user_table = inspect_table(metadata, 'core__users')
9519c0a9 621 user_privilege_assoc = inspect_table(
622 metadata, 'core__privileges_users')
623
624 # This section initializes the default Privilege foundations, that
625 # would be created through the FOUNDATIONS system in a new instance
626 #--------------------------------------------------------------------------
627 for parameters in PRIVILEGE_FOUNDATIONS_v0:
628 db.execute(privileges_table.insert().values(**parameters))
629
6acf4ee6 630 db.commit()
631
9519c0a9 632 # This next section takes the information from the old is_admin and status
633 # columns and converts those to the new privilege system
634 #--------------------------------------------------------------------------
635 admin_users_ids, active_users_ids, inactive_users_ids = (
636 db.execute(
637 user_table.select().where(
f1318b59 638 user_table.c.is_admin==True)).fetchall(),
9519c0a9 639 db.execute(
640 user_table.select().where(
f1318b59 641 user_table.c.is_admin==False).where(
9519c0a9 642 user_table.c.status==u"active")).fetchall(),
643 db.execute(
644 user_table.select().where(
f1318b59 645 user_table.c.is_admin==False).where(
9519c0a9 646 user_table.c.status!=u"active")).fetchall())
647
648 # Get the ids for each of the privileges so we can reference them ~~~~~~~~~
649 (admin_privilege_id, uploader_privilege_id,
650 reporter_privilege_id, commenter_privilege_id,
651 active_privilege_id) = [
652 db.execute(privileges_table.select().where(
653 privileges_table.c.privilege_name==privilege_name)).first()['id']
654 for privilege_name in
655 [u"admin",u"uploader",u"reporter",u"commenter",u"active"]
656 ]
657
658 # Give each user the appopriate privileges depending whether they are an
0b1fcaeb 659 # admin, an active user or an inactive user ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9519c0a9 660 for admin_user in admin_users_ids:
661 admin_user_id = admin_user['id']
2b7b9de3
JT
662 for privilege_id in [admin_privilege_id, uploader_privilege_id,
663 reporter_privilege_id, commenter_privilege_id,
0b1fcaeb 664 active_privilege_id]:
9519c0a9 665 db.execute(user_privilege_assoc.insert().values(
666 core__privilege_id=admin_user_id,
667 core__user_id=privilege_id))
668
669 for active_user in active_users_ids:
670 active_user_id = active_user['id']
2b7b9de3 671 for privilege_id in [uploader_privilege_id, reporter_privilege_id,
0b1fcaeb 672 commenter_privilege_id, active_privilege_id]:
9519c0a9 673 db.execute(user_privilege_assoc.insert().values(
674 core__privilege_id=active_user_id,
675 core__user_id=privilege_id))
676
677 for inactive_user in inactive_users_ids:
678 inactive_user_id = inactive_user['id']
2b7b9de3 679 for privilege_id in [uploader_privilege_id, reporter_privilege_id,
0b1fcaeb 680 commenter_privilege_id]:
9519c0a9 681 db.execute(user_privilege_assoc.insert().values(
682 core__privilege_id=inactive_user_id,
683 core__user_id=privilege_id))
684
685 db.commit()
686
e5196ff0 687 # And then, once the information is taken from is_admin & status columns
9519c0a9 688 # we drop all of the vestigial columns from the User table.
689 #--------------------------------------------------------------------------
690 if db.bind.url.drivername == 'sqlite':
691 # SQLite has some issues that make it *impossible* to drop boolean
692 # columns. So, the following code is a very hacky workaround which
693 # makes it possible. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
694
695 User_vR1.__table__.create(db.bind)
696 db.commit()
697 new_user_table = inspect_table(metadata, 'rename__users')
0c875e1e 698 replace_table_hack(db, user_table, new_user_table)
9519c0a9 699 else:
0b1fcaeb 700 # If the db is not run using SQLite, this process is much simpler ~~~~~
9519c0a9 701
702 status = user_table.columns['status']
703 email_verified = user_table.columns['email_verified']
704 is_admin = user_table.columns['is_admin']
705 status.drop()
706 email_verified.drop()
707 is_admin.drop()
93d805ad 708
7dfcc538 709 db.commit()
70bceff8 710
c56a88b4 711
7dfcc538
J
712@RegisterMigration(19, MIGRATIONS)
713def drop_MediaEntry_collected(db):
714 """
715 Drop unused MediaEntry.collected column
716 """
717 metadata = MetaData(bind=db.bind)
7dfcc538 718
9412fffe 719 media_collected= inspect_table(metadata, 'core__media_entries')
7dfcc538
J
720 media_collected = media_collected.columns['collected']
721
7dfcc538 722 media_collected.drop()
9412fffe 723
93d805ad 724 db.commit()
2dd966b5 725
70bceff8 726
2dd966b5 727@RegisterMigration(20, MIGRATIONS)
9f3dc83a 728def add_metadata_column(db):
2dd966b5 729 metadata = MetaData(bind=db.bind)
730
9f3dc83a 731 media_entry = inspect_table(metadata, 'core__media_entries')
2dd966b5 732
c8abeb58 733 col = Column('media_metadata', MutationDict.as_mutable(JSONEncoded),
734 default=MutationDict())
9f3dc83a 735 col.create(media_entry)
2dd966b5 736
737 db.commit()
70bceff8
CAW
738
739
7918f86a 740class PrivilegeUserAssociation_R1(declarative_base()):
741 __tablename__ = 'rename__privileges_users'
c56a88b4 742 user = Column(
987a6351 743 "user",
7918f86a 744 Integer,
745 ForeignKey(User.id),
746 primary_key=True)
c56a88b4 747 privilege = Column(
987a6351 748 "privilege",
7918f86a 749 Integer,
750 ForeignKey(Privilege.id),
751 primary_key=True)
752
70bceff8 753@RegisterMigration(21, MIGRATIONS)
7918f86a 754def fix_privilege_user_association_table(db):
755 """
756 There was an error in the PrivilegeUserAssociation table that allowed for a
757 dangerous sql error. We need to the change the name of the columns to be
9adef07e 758 unique, and properly referenced.
7918f86a 759 """
760 metadata = MetaData(bind=db.bind)
761
762 privilege_user_assoc = inspect_table(
763 metadata, 'core__privileges_users')
7918f86a 764
713dde5b 765 # This whole process is more complex if we're dealing with sqlite
c56a88b4
CAW
766 if db.bind.url.drivername == 'sqlite':
767 PrivilegeUserAssociation_R1.__table__.create(db.bind)
768 db.commit()
769
770 new_privilege_user_assoc = inspect_table(
771 metadata, 'rename__privileges_users')
772 result = db.execute(privilege_user_assoc.select())
773 for row in result:
774 # The columns were improperly named before, so we switch the columns
775 user_id, priv_id = row['core__privilege_id'], row['core__user_id']
776 db.execute(new_privilege_user_assoc.insert().values(
777 user=user_id,
778 privilege=priv_id))
7918f86a 779
c56a88b4
CAW
780 db.commit()
781
782 privilege_user_assoc.drop()
783 new_privilege_user_assoc.rename('core__privileges_users')
7918f86a 784
713dde5b 785 # much simpler if postgres though!
c56a88b4 786 else:
987a6351
CAW
787 privilege_user_assoc.c.core__user_id.alter(name="privilege")
788 privilege_user_assoc.c.core__privilege_id.alter(name="user")
7918f86a 789
790 db.commit()
892eed59
JT
791
792@RegisterMigration(22, MIGRATIONS)
793def add_index_username_field(db):
794 """
795 This indexes the User.username field which is frequently queried
796 for example a user logging in. This solves the issue #894
797 """
798 metadata = MetaData(bind=db.bind)
799 user_table = inspect_table(metadata, "core__users")
800
801 new_index = Index("ix_core__users_uploader", user_table.c.username)
802 new_index.create()
803
804 db.commit()