1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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.
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.
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/>.
20 from sqlalchemy import (MetaData, Table, Column, Boolean, SmallInteger,
21 Integer, Unicode, UnicodeText, DateTime,
23 from sqlalchemy.exc import ProgrammingError
24 from sqlalchemy.ext.declarative import declarative_base
25 from sqlalchemy.sql import and_
26 from migrate.changeset.constraint import UniqueConstraint
28 from mediagoblin.db.migration_tools import RegisterMigration, inspect_table
29 from mediagoblin.db.models import (MediaEntry, Collection, User,
30 MediaComment, Privilege, ReportBase)
35 @RegisterMigration(1, MIGRATIONS)
36 def ogg_to_webm_audio(db_conn):
37 metadata = MetaData(bind=db_conn.bind)
39 file_keynames = Table('core__file_keynames', metadata, autoload=True,
40 autoload_with=db_conn.bind)
43 file_keynames.update().where(file_keynames.c.name == 'ogg').
44 values(name='webm_audio')
49 @RegisterMigration(2, MIGRATIONS)
50 def add_wants_notification_column(db_conn):
51 metadata = MetaData(bind=db_conn.bind)
53 users = Table('core__users', metadata, autoload=True,
54 autoload_with=db_conn.bind)
56 col = Column('wants_comment_notification', Boolean,
57 default=True, nullable=True)
58 col.create(users, populate_defaults=True)
62 @RegisterMigration(3, MIGRATIONS)
63 def add_transcoding_progress(db_conn):
64 metadata = MetaData(bind=db_conn.bind)
66 media_entry = inspect_table(metadata, 'core__media_entries')
68 col = Column('transcoding_progress', SmallInteger)
69 col.create(media_entry)
73 class Collection_v0(declarative_base()):
74 __tablename__ = "core__collections"
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,
81 description = Column(UnicodeText)
82 creator = Column(Integer, ForeignKey(User.id), nullable=False)
83 items = Column(Integer, default=0)
85 class CollectionItem_v0(declarative_base()):
86 __tablename__ = "core__collection_items"
88 id = Column(Integer, primary_key=True)
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)
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.
100 UniqueConstraint('collection', 'media_entry'),
103 collectionitem_unique_constraint_done = False
105 @RegisterMigration(4, MIGRATIONS)
106 def add_collection_tables(db_conn):
107 Collection_v0.__table__.create(db_conn.bind)
108 CollectionItem_v0.__table__.create(db_conn.bind)
110 global collectionitem_unique_constraint_done
111 collectionitem_unique_constraint_done = True
116 @RegisterMigration(5, MIGRATIONS)
117 def add_mediaentry_collected(db_conn):
118 metadata = MetaData(bind=db_conn.bind)
120 media_entry = inspect_table(metadata, 'core__media_entries')
122 col = Column('collected', Integer, default=0)
123 col.create(media_entry)
127 class ProcessingMetaData_v0(declarative_base()):
128 __tablename__ = 'core__processing_metadata'
130 id = Column(Integer, primary_key=True)
131 media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False,
133 callback_url = Column(Unicode)
135 @RegisterMigration(6, MIGRATIONS)
136 def create_processing_metadata_table(db):
137 ProcessingMetaData_v0.__table__.create(db.bind)
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.
146 # So we have four situations that should end up at the same
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.
162 # So this migration adds the constraint.
163 @RegisterMigration(7, MIGRATIONS)
164 def fix_CollectionItem_v0_constraint(db_conn):
165 """Add the forgotten Constraint on CollectionItem"""
167 global collectionitem_unique_constraint_done
168 if collectionitem_unique_constraint_done:
169 # Reset it. Maybe the whole thing gets run again
170 # For a different db?
171 collectionitem_unique_constraint_done = False
174 metadata = MetaData(bind=db_conn.bind)
176 CollectionItem_table = inspect_table(metadata, 'core__collection_items')
178 constraint = UniqueConstraint('collection', 'media_entry',
179 name='core__collection_items_collection_media_entry_key',
180 table=CollectionItem_table)
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.
192 @RegisterMigration(8, MIGRATIONS)
193 def add_license_preference(db):
194 metadata = MetaData(bind=db.bind)
196 user_table = inspect_table(metadata, 'core__users')
198 col = Column('license_preference', Unicode)
199 col.create(user_table)
203 @RegisterMigration(9, MIGRATIONS)
204 def mediaentry_new_slug_era(db):
206 Update for the new era for media type slugs.
208 Entries without slugs now display differently in the url like:
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)
218 def slug_and_user_combo_exists(slug, uploader):
221 and_(media_table.c.uploader==uploader,
222 media_table.c.slug==slug))).first() is not None
224 def append_garbage_till_unique(row, new_slug):
226 Attach junk to this row until it's unique, then save it
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]
237 media_table.update(). \
238 where(media_table.c.id==row.id). \
239 values(slug=new_slug))
241 metadata = MetaData(bind=db.bind)
243 media_table = inspect_table(metadata, 'core__media_entries')
245 for row in db.execute(media_table.select()):
246 # no slug, try setting to an id
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"-"))
257 @RegisterMigration(10, MIGRATIONS)
258 def 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")
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)
271 if not row.creator in existing_slugs:
272 existing_slugs[row.creator] = [row.slug]
274 existing_slugs[row.creator].append(row.slug)
276 for row_id in slugs_to_change:
277 new_slug = unicode(uuid.uuid4())
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
285 constraint = UniqueConstraint('creator', 'slug',
286 name='core__collection_creator_slug_key',
287 table=collection_table)
292 class ReportBase_v0(declarative_base()):
296 __tablename__ = 'core__reports'
297 id = Column(Integer, primary_key=True)
298 reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
299 report_content = Column(UnicodeText)
300 reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
301 created = Column(DateTime, nullable=False, default=datetime.datetime.now)
302 resolved = Column(DateTime)
303 result = Column(UnicodeText)
304 discriminator = Column('type', Unicode(50))
305 __mapper_args__ = {'polymorphic_on': discriminator}
308 class CommentReport_v0(ReportBase_v0):
309 __tablename__ = 'core__reports_on_comments'
310 __mapper_args__ = {'polymorphic_identity': 'comment_report'}
312 id = Column('id',Integer, ForeignKey('core__reports.id'),
314 comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=False)
316 class MediaReport_v0(ReportBase_v0):
317 __tablename__ = 'core__reports_on_media'
318 __mapper_args__ = {'polymorphic_identity': 'media_report'}
323 ForeignKey('core__reports.id'),
325 media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
328 class UserBan_v0(declarative_base()):
329 __tablename__ = 'core__user_bans'
330 user_id = Column('id',Integer, ForeignKey(User.id), nullable=False,
332 expiration_date = Column(DateTime)
333 reason = Column(UnicodeText, nullable=False)
335 class Privilege_v0(declarative_base()):
336 __tablename__ = 'core__privileges'
337 id = Column(Integer, nullable=False, primary_key=True, unique=True)
338 privilege_name = Column(Unicode, nullable=False)
340 class PrivilegeUserAssociation_v0(declarative_base()):
341 __tablename__ = 'core__privileges_users'
344 'core__privilege_id',
351 ForeignKey(Privilege.id),
354 @RegisterMigration(11, MIGRATIONS)
355 def create_moderation_tables(db):
356 ReportBase_v0.__table__.create(db.bind)
357 CommentReport_v0.__table__.create(db.bind)
358 MediaReport_v0.__table__.create(db.bind)
359 UserBan_v0.__table__.create(db.bind)
360 Privilege_v0.__table__.create(db.bind)
361 PrivilegeUserAssociation_v0.__table__.create(db.bind)