This was a very small update, I'm hoping to rebase after this to solve some
[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,
0f14c362
E
22 ForeignKey)
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
c130e3ee 28from mediagoblin.db.migration_tools import RegisterMigration, inspect_table
3ce0c611 29from mediagoblin.db.models import (MediaEntry, Collection, User,
30 MediaComment, Privilege, ReportBase)
b781c3c9 31
3ea1cf36 32MIGRATIONS = {}
b781c3c9
JK
33
34
35@RegisterMigration(1, MIGRATIONS)
36def ogg_to_webm_audio(db_conn):
37 metadata = MetaData(bind=db_conn.bind)
38
39 file_keynames = Table('core__file_keynames', metadata, autoload=True,
40 autoload_with=db_conn.bind)
41
42 db_conn.execute(
38c6d441 43 file_keynames.update().where(file_keynames.c.name == 'ogg').
b781c3c9
JK
44 values(name='webm_audio')
45 )
b1055401 46 db_conn.commit()
38c6d441
JW
47
48
49@RegisterMigration(2, MIGRATIONS)
50def add_wants_notification_column(db_conn):
51 metadata = MetaData(bind=db_conn.bind)
52
53 users = Table('core__users', metadata, autoload=True,
54 autoload_with=db_conn.bind)
55
56 col = Column('wants_comment_notification', Boolean,
c4869eff 57 default=True, nullable=True)
38c6d441 58 col.create(users, populate_defaults=True)
b1055401 59 db_conn.commit()
64712915
JW
60
61
62@RegisterMigration(3, MIGRATIONS)
63def add_transcoding_progress(db_conn):
64 metadata = MetaData(bind=db_conn.bind)
65
c4466cb4 66 media_entry = inspect_table(metadata, 'core__media_entries')
64712915
JW
67
68 col = Column('transcoding_progress', SmallInteger)
69 col.create(media_entry)
70 db_conn.commit()
be5be115 71
88a9662b 72
316e1dfd
E
73class Collection_v0(declarative_base()):
74 __tablename__ = "core__collections"
75
76 id = Column(Integer, primary_key=True)
77 title = Column(Unicode, nullable=False)
78 slug = Column(Unicode)
79 created = Column(DateTime, nullable=False, default=datetime.datetime.now,
80 index=True)
81 description = Column(UnicodeText)
82 creator = Column(Integer, ForeignKey(User.id), nullable=False)
83 items = Column(Integer, default=0)
84
85class CollectionItem_v0(declarative_base()):
86 __tablename__ = "core__collection_items"
87
88 id = Column(Integer, primary_key=True)
89 media_entry = Column(
90 Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
91 collection = Column(Integer, ForeignKey(Collection.id), nullable=False)
92 note = Column(UnicodeText, nullable=True)
93 added = Column(DateTime, nullable=False, default=datetime.datetime.now)
94 position = Column(Integer)
95
96 ## This should be activated, normally.
97 ## But this would change the way the next migration used to work.
98 ## So it's commented for now.
0f14c362
E
99 __table_args__ = (
100 UniqueConstraint('collection', 'media_entry'),
101 {})
102
103collectionitem_unique_constraint_done = False
316e1dfd 104
be5be115 105@RegisterMigration(4, MIGRATIONS)
29fdd3bb 106def add_collection_tables(db_conn):
316e1dfd
E
107 Collection_v0.__table__.create(db_conn.bind)
108 CollectionItem_v0.__table__.create(db_conn.bind)
29fdd3bb 109
0f14c362
E
110 global collectionitem_unique_constraint_done
111 collectionitem_unique_constraint_done = True
112
29fdd3bb
AW
113 db_conn.commit()
114
88a9662b 115
29fdd3bb 116@RegisterMigration(5, MIGRATIONS)
59fb87c9 117def add_mediaentry_collected(db_conn):
be5be115
AW
118 metadata = MetaData(bind=db_conn.bind)
119
c4466cb4 120 media_entry = inspect_table(metadata, 'core__media_entries')
be5be115 121
d8984df8 122 col = Column('collected', Integer, default=0)
be5be115
AW
123 col.create(media_entry)
124 db_conn.commit()
5354f954
JW
125
126
316e1dfd
E
127class ProcessingMetaData_v0(declarative_base()):
128 __tablename__ = 'core__processing_metadata'
939d57a0 129
316e1dfd
E
130 id = Column(Integer, primary_key=True)
131 media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False,
132 index=True)
133 callback_url = Column(Unicode)
939d57a0 134
316e1dfd
E
135@RegisterMigration(6, MIGRATIONS)
136def create_processing_metadata_table(db):
137 ProcessingMetaData_v0.__table__.create(db.bind)
5354f954 138 db.commit()
0f14c362 139
ea91c183
E
140
141# Okay, problem being:
142# Migration #4 forgot to add the uniqueconstraint for the
143# new tables. While creating the tables from scratch had
144# the constraint enabled.
145#
a64abbb1 146# So we have four situations that should end up at the same
ea91c183
E
147# db layout:
148#
149# 1. Fresh install.
150# Well, easy. Just uses the tables in models.py
151# 2. Fresh install using a git version just before this migration
152# The tables are all there, the unique constraint is also there.
153# This migration should do nothing.
154# But as we can't detect the uniqueconstraint easily,
155# this migration just adds the constraint again.
156# And possibly fails very loud. But ignores the failure.
157# 3. old install, not using git, just releases.
158# This one will get the new tables in #4 (now with constraint!)
159# And this migration is just skipped silently.
160# 4. old install, always on latest git.
161# This one has the tables, but lacks the constraint.
a64abbb1 162# So this migration adds the constraint.
0f14c362
E
163@RegisterMigration(7, MIGRATIONS)
164def fix_CollectionItem_v0_constraint(db_conn):
165 """Add the forgotten Constraint on CollectionItem"""
166
167 global collectionitem_unique_constraint_done
168 if collectionitem_unique_constraint_done:
0f14c362
E
169 # Reset it. Maybe the whole thing gets run again
170 # For a different db?
171 collectionitem_unique_constraint_done = False
172 return
173
174 metadata = MetaData(bind=db_conn.bind)
175
c4466cb4 176 CollectionItem_table = inspect_table(metadata, 'core__collection_items')
0f14c362
E
177
178 constraint = UniqueConstraint('collection', 'media_entry',
179 name='core__collection_items_collection_media_entry_key',
180 table=CollectionItem_table)
181
182 try:
183 constraint.create()
78fd5581
CAW
184 except ProgrammingError:
185 # User probably has an install that was run since the
186 # collection tables were added, so we don't need to run this migration.
187 pass
188
0f14c362 189 db_conn.commit()
dc4dfbde
MH
190
191
192@RegisterMigration(8, MIGRATIONS)
193def add_license_preference(db):
194 metadata = MetaData(bind=db.bind)
195
0c871f81 196 user_table = inspect_table(metadata, 'core__users')
dc4dfbde 197
0c871f81 198 col = Column('license_preference', Unicode)
dc4dfbde
MH
199 col.create(user_table)
200 db.commit()
e66431f4
CAW
201
202
203@RegisterMigration(9, MIGRATIONS)
204def mediaentry_new_slug_era(db):
205 """
206 Update for the new era for media type slugs.
207
208 Entries without slugs now display differently in the url like:
209 /u/cwebber/m/id=251/
210
211 ... because of this, we should back-convert:
212 - entries without slugs should be converted to use the id, if possible, to
213 make old urls still work
214 - slugs with = (or also : which is now also not allowed) to have those
215 stripped out (small possibility of breakage here sadly)
216 """
e66431f4
CAW
217
218 def slug_and_user_combo_exists(slug, uploader):
e66431f4
CAW
219 return db.execute(
220 media_table.select(
ab1f65e6 221 and_(media_table.c.uploader==uploader,
aecd65b7 222 media_table.c.slug==slug))).first() is not None
e66431f4
CAW
223
224 def append_garbage_till_unique(row, new_slug):
225 """
226 Attach junk to this row until it's unique, then save it
227 """
228 if slug_and_user_combo_exists(new_slug, row.uploader):
229 # okay, still no success;
230 # let's whack junk on there till it's unique.
231 new_slug += '-' + uuid.uuid4().hex[:4]
232 # keep going if necessary!
233 while slug_and_user_combo_exists(new_slug, row.uploader):
234 new_slug += uuid.uuid4().hex[:4]
235
236 db.execute(
237 media_table.update(). \
238 where(media_table.c.id==row.id). \
239 values(slug=new_slug))
240
241 metadata = MetaData(bind=db.bind)
242
243 media_table = inspect_table(metadata, 'core__media_entries')
0b7cdb6f 244
e66431f4
CAW
245 for row in db.execute(media_table.select()):
246 # no slug, try setting to an id
247 if not row.slug:
248 append_garbage_till_unique(row, unicode(row.id))
249 # has "=" or ":" in it... we're getting rid of those
250 elif u"=" in row.slug or u":" in row.slug:
251 append_garbage_till_unique(
252 row, row.slug.replace(u"=", u"-").replace(u":", u"-"))
0b7cdb6f
CAW
253
254 db.commit()
34d8bc98
RE
255
256
257@RegisterMigration(10, MIGRATIONS)
258def unique_collections_slug(db):
259 """Add unique constraint to collection slug"""
260 metadata = MetaData(bind=db.bind)
261 collection_table = inspect_table(metadata, "core__collections")
262 existing_slugs = {}
263 slugs_to_change = []
264
265 for row in db.execute(collection_table.select()):
266 # if duplicate slug, generate a unique slug
267 if row.creator in existing_slugs and row.slug in \
268 existing_slugs[row.creator]:
269 slugs_to_change.append(row.id)
270 else:
271 if not row.creator in existing_slugs:
272 existing_slugs[row.creator] = [row.slug]
273 else:
274 existing_slugs[row.creator].append(row.slug)
275
276 for row_id in slugs_to_change:
f96c284e 277 new_slug = unicode(uuid.uuid4())
34d8bc98
RE
278 db.execute(collection_table.update().
279 where(collection_table.c.id == row_id).
280 values(slug=new_slug))
281 # sqlite does not like to change the schema when a transaction(update) is
282 # not yet completed
283 db.commit()
284
285 constraint = UniqueConstraint('creator', 'slug',
286 name='core__collection_creator_slug_key',
287 table=collection_table)
288 constraint.create()
289
290 db.commit()
30a9fe7c 291
8ad734af 292@RegisterMigration(11, MIGRATIONS)
342f06f7 293def drop_token_related_User_columns(db):
30a9fe7c 294 """
342f06f7
RE
295 Drop unneeded columns from the User table after switching to using
296 itsdangerous tokens for email and forgot password verification.
30a9fe7c 297 """
342f06f7
RE
298 metadata = MetaData(bind=db.bind)
299 user_table = inspect_table(metadata, 'core__users')
300
52a355b2 301
342f06f7
RE
302 verification_key = user_table.columns['verification_key']
303 fp_verification_key = user_table.columns['fp_verification_key']
304 fp_token_expire = user_table.columns['fp_token_expire']
305
306 verification_key.drop()
307 fp_verification_key.drop()
308 fp_token_expire.drop()
309
310 db.commit()
257b8ab6 311
5adb906a 312
2d7b6bde
JW
313class CommentSubscription_v0(declarative_base()):
314 __tablename__ = 'core__comment_subscriptions'
315 id = Column(Integer, primary_key=True)
316
317 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
318
319 media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
320
321 user_id = Column(Integer, ForeignKey(User.id), nullable=False)
322
52a355b2 323
2d7b6bde
JW
324 notify = Column(Boolean, nullable=False, default=True)
325 send_email = Column(Boolean, nullable=False, default=True)
326
327
328class Notification_v0(declarative_base()):
329 __tablename__ = 'core__notifications'
330 id = Column(Integer, primary_key=True)
331 type = Column(Unicode)
332
333 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
334
335 user_id = Column(Integer, ForeignKey(User.id), nullable=False,
336 index=True)
337 seen = Column(Boolean, default=lambda: False, index=True)
338
339
340class CommentNotification_v0(Notification_v0):
341 __tablename__ = 'core__comment_notifications'
342 id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
343
344 subject_id = Column(Integer, ForeignKey(MediaComment.id))
345
346
347class ProcessingNotification_v0(Notification_v0):
348 __tablename__ = 'core__processing_notifications'
349
350 id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
351
352 subject_id = Column(Integer, ForeignKey(MediaEntry.id))
353
354
257b8ab6 355@RegisterMigration(12, MIGRATIONS)
2d7b6bde
JW
356def add_new_notification_tables(db):
357 metadata = MetaData(bind=db.bind)
358
359 user_table = inspect_table(metadata, 'core__users')
360 mediaentry_table = inspect_table(metadata, 'core__media_entries')
361 mediacomment_table = inspect_table(metadata, 'core__media_comments')
362
363 CommentSubscription_v0.__table__.create(db.bind)
364
365 Notification_v0.__table__.create(db.bind)
366 CommentNotification_v0.__table__.create(db.bind)
367 ProcessingNotification_v0.__table__.create(db.bind)
af4414a8
RE
368
369
370@RegisterMigration(13, MIGRATIONS)
8ad734af
RE
371def pw_hash_nullable(db):
372 """Make pw_hash column nullable"""
373 metadata = MetaData(bind=db.bind)
374 user_table = inspect_table(metadata, "core__users")
375
376 user_table.c.pw_hash.alter(nullable=True)
377
15db1831
CAW
378 # sqlite+sqlalchemy seems to drop this constraint during the
379 # migration, so we add it back here for now a bit manually.
5a1be074 380 if db.bind.url.drivername == 'sqlite':
e4deacd9
RE
381 constraint = UniqueConstraint('username', table=user_table)
382 constraint.create()
383
52a355b2 384class ReportBase_v0(declarative_base()):
30a9fe7c 385 __tablename__ = 'core__reports'
386 id = Column(Integer, primary_key=True)
387 reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
388 report_content = Column(UnicodeText)
3ce0c611 389 reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
30a9fe7c 390 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
30a9fe7c 391 discriminator = Column('type', Unicode(50))
392 __mapper_args__ = {'polymorphic_on': discriminator}
393
30a9fe7c 394class CommentReport_v0(ReportBase_v0):
395 __tablename__ = 'core__reports_on_comments'
396 __mapper_args__ = {'polymorphic_identity': 'comment_report'}
397
398 id = Column('id',Integer, ForeignKey('core__reports.id'),
399 primary_key=True)
400 comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=False)
401
402class MediaReport_v0(ReportBase_v0):
403 __tablename__ = 'core__reports_on_media'
404 __mapper_args__ = {'polymorphic_identity': 'media_report'}
405
3aa3871b 406 id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True)
9d6e453f 407 media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
30a9fe7c 408
3aa3871b 409class ArchivedReport_v0(ReportBase_v0):
410 __tablename__ = 'core__reports_archived'
411 __mapper_args__ = {'polymorphic_identity': 'archived_report'}
412
8394febb 413 id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True)
3aa3871b 414 media_entry_id = Column(Integer, ForeignKey(MediaEntry.id))
415 comment_id = Column(Integer, ForeignKey(MediaComment.id))
416 resolver_id = Column(Integer, ForeignKey(User.id), nullable=False)
417 resolved_time = Column(DateTime)
418 result = Column(UnicodeText)
30a9fe7c 419
420class UserBan_v0(declarative_base()):
421 __tablename__ = 'core__user_bans'
422 user_id = Column('id',Integer, ForeignKey(User.id), nullable=False,
423 primary_key=True)
424 expiration_date = Column(DateTime)
425 reason = Column(UnicodeText, nullable=False)
426
3fb96fc9 427class Privilege_v0(declarative_base()):
428 __tablename__ = 'core__privileges'
9b8ef022 429 id = Column(Integer, nullable=False, primary_key=True, unique=True)
e1561d04 430 privilege_name = Column(Unicode, nullable=False, unique=True)
30a9fe7c 431
3fb96fc9 432class PrivilegeUserAssociation_v0(declarative_base()):
433 __tablename__ = 'core__privileges_users'
9b8ef022 434 group_id = Column(
3fb96fc9 435 'core__privilege_id',
9b8ef022 436 Integer,
437 ForeignKey(User.id),
438 primary_key=True)
439 user_id = Column(
440 'core__user_id',
441 Integer,
3fb96fc9 442 ForeignKey(Privilege.id),
9b8ef022 443 primary_key=True)
30a9fe7c 444
52a355b2 445@RegisterMigration(14, MIGRATIONS)
9b8ef022 446def create_moderation_tables(db):
447 ReportBase_v0.__table__.create(db.bind)
448 CommentReport_v0.__table__.create(db.bind)
449 MediaReport_v0.__table__.create(db.bind)
3aa3871b 450 ArchivedReport_v0.__table__.create(db.bind)
30a9fe7c 451 UserBan_v0.__table__.create(db.bind)
3fb96fc9 452 Privilege_v0.__table__.create(db.bind)
453 PrivilegeUserAssociation_v0.__table__.create(db.bind)
30a9fe7c 454 db.commit()