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
29 from mediagoblin
.db
.extratypes
import JSONEncoded
, MutationDict
30 from mediagoblin
.db
.migration_tools
import (
31 RegisterMigration
, inspect_table
, replace_table_hack
)
32 from mediagoblin
.db
.models
import (MediaEntry
, Collection
, MediaComment
, User
,
38 @RegisterMigration(1, MIGRATIONS
)
39 def ogg_to_webm_audio(db_conn
):
40 metadata
= MetaData(bind
=db_conn
.bind
)
42 file_keynames
= Table('core__file_keynames', metadata
, autoload
=True,
43 autoload_with
=db_conn
.bind
)
46 file_keynames
.update().where(file_keynames
.c
.name
== 'ogg').
47 values(name
='webm_audio')
52 @RegisterMigration(2, MIGRATIONS
)
53 def add_wants_notification_column(db_conn
):
54 metadata
= MetaData(bind
=db_conn
.bind
)
56 users
= Table('core__users', metadata
, autoload
=True,
57 autoload_with
=db_conn
.bind
)
59 col
= Column('wants_comment_notification', Boolean
,
60 default
=True, nullable
=True)
61 col
.create(users
, populate_defaults
=True)
65 @RegisterMigration(3, MIGRATIONS
)
66 def add_transcoding_progress(db_conn
):
67 metadata
= MetaData(bind
=db_conn
.bind
)
69 media_entry
= inspect_table(metadata
, 'core__media_entries')
71 col
= Column('transcoding_progress', SmallInteger
)
72 col
.create(media_entry
)
76 class Collection_v0(declarative_base()):
77 __tablename__
= "core__collections"
79 id = Column(Integer
, primary_key
=True)
80 title
= Column(Unicode
, nullable
=False)
81 slug
= Column(Unicode
)
82 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
,
84 description
= Column(UnicodeText
)
85 creator
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
86 items
= Column(Integer
, default
=0)
88 class CollectionItem_v0(declarative_base()):
89 __tablename__
= "core__collection_items"
91 id = Column(Integer
, primary_key
=True)
93 Integer
, ForeignKey(MediaEntry
.id), nullable
=False, index
=True)
94 collection
= Column(Integer
, ForeignKey(Collection
.id), nullable
=False)
95 note
= Column(UnicodeText
, nullable
=True)
96 added
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
97 position
= Column(Integer
)
99 ## This should be activated, normally.
100 ## But this would change the way the next migration used to work.
101 ## So it's commented for now.
103 UniqueConstraint('collection', 'media_entry'),
106 collectionitem_unique_constraint_done
= False
108 @RegisterMigration(4, MIGRATIONS
)
109 def add_collection_tables(db_conn
):
110 Collection_v0
.__table__
.create(db_conn
.bind
)
111 CollectionItem_v0
.__table__
.create(db_conn
.bind
)
113 global collectionitem_unique_constraint_done
114 collectionitem_unique_constraint_done
= True
119 @RegisterMigration(5, MIGRATIONS
)
120 def add_mediaentry_collected(db_conn
):
121 metadata
= MetaData(bind
=db_conn
.bind
)
123 media_entry
= inspect_table(metadata
, 'core__media_entries')
125 col
= Column('collected', Integer
, default
=0)
126 col
.create(media_entry
)
130 class ProcessingMetaData_v0(declarative_base()):
131 __tablename__
= 'core__processing_metadata'
133 id = Column(Integer
, primary_key
=True)
134 media_entry_id
= Column(Integer
, ForeignKey(MediaEntry
.id), nullable
=False,
136 callback_url
= Column(Unicode
)
138 @RegisterMigration(6, MIGRATIONS
)
139 def create_processing_metadata_table(db
):
140 ProcessingMetaData_v0
.__table__
.create(db
.bind
)
144 # Okay, problem being:
145 # Migration #4 forgot to add the uniqueconstraint for the
146 # new tables. While creating the tables from scratch had
147 # the constraint enabled.
149 # So we have four situations that should end up at the same
153 # Well, easy. Just uses the tables in models.py
154 # 2. Fresh install using a git version just before this migration
155 # The tables are all there, the unique constraint is also there.
156 # This migration should do nothing.
157 # But as we can't detect the uniqueconstraint easily,
158 # this migration just adds the constraint again.
159 # And possibly fails very loud. But ignores the failure.
160 # 3. old install, not using git, just releases.
161 # This one will get the new tables in #4 (now with constraint!)
162 # And this migration is just skipped silently.
163 # 4. old install, always on latest git.
164 # This one has the tables, but lacks the constraint.
165 # So this migration adds the constraint.
166 @RegisterMigration(7, MIGRATIONS
)
167 def fix_CollectionItem_v0_constraint(db_conn
):
168 """Add the forgotten Constraint on CollectionItem"""
170 global collectionitem_unique_constraint_done
171 if collectionitem_unique_constraint_done
:
172 # Reset it. Maybe the whole thing gets run again
173 # For a different db?
174 collectionitem_unique_constraint_done
= False
177 metadata
= MetaData(bind
=db_conn
.bind
)
179 CollectionItem_table
= inspect_table(metadata
, 'core__collection_items')
181 constraint
= UniqueConstraint('collection', 'media_entry',
182 name
='core__collection_items_collection_media_entry_key',
183 table
=CollectionItem_table
)
187 except ProgrammingError
:
188 # User probably has an install that was run since the
189 # collection tables were added, so we don't need to run this migration.
195 @RegisterMigration(8, MIGRATIONS
)
196 def add_license_preference(db
):
197 metadata
= MetaData(bind
=db
.bind
)
199 user_table
= inspect_table(metadata
, 'core__users')
201 col
= Column('license_preference', Unicode
)
202 col
.create(user_table
)
206 @RegisterMigration(9, MIGRATIONS
)
207 def mediaentry_new_slug_era(db
):
209 Update for the new era for media type slugs.
211 Entries without slugs now display differently in the url like:
214 ... because of this, we should back-convert:
215 - entries without slugs should be converted to use the id, if possible, to
216 make old urls still work
217 - slugs with = (or also : which is now also not allowed) to have those
218 stripped out (small possibility of breakage here sadly)
221 def slug_and_user_combo_exists(slug
, uploader
):
224 and_(media_table
.c
.uploader
==uploader
,
225 media_table
.c
.slug
==slug
))).first() is not None
227 def append_garbage_till_unique(row
, new_slug
):
229 Attach junk to this row until it's unique, then save it
231 if slug_and_user_combo_exists(new_slug
, row
.uploader
):
232 # okay, still no success;
233 # let's whack junk on there till it's unique.
234 new_slug
+= '-' + uuid
.uuid4().hex[:4]
235 # keep going if necessary!
236 while slug_and_user_combo_exists(new_slug
, row
.uploader
):
237 new_slug
+= uuid
.uuid4().hex[:4]
240 media_table
.update(). \
241 where(media_table
.c
.id==row
.id). \
242 values(slug
=new_slug
))
244 metadata
= MetaData(bind
=db
.bind
)
246 media_table
= inspect_table(metadata
, 'core__media_entries')
248 for row
in db
.execute(media_table
.select()):
249 # no slug, try setting to an id
251 append_garbage_till_unique(row
, unicode(row
.id))
252 # has "=" or ":" in it... we're getting rid of those
253 elif u
"=" in row
.slug
or u
":" in row
.slug
:
254 append_garbage_till_unique(
255 row
, row
.slug
.replace(u
"=", u
"-").replace(u
":", u
"-"))
260 @RegisterMigration(10, MIGRATIONS
)
261 def unique_collections_slug(db
):
262 """Add unique constraint to collection slug"""
263 metadata
= MetaData(bind
=db
.bind
)
264 collection_table
= inspect_table(metadata
, "core__collections")
268 for row
in db
.execute(collection_table
.select()):
269 # if duplicate slug, generate a unique slug
270 if row
.creator
in existing_slugs
and row
.slug
in \
271 existing_slugs
[row
.creator
]:
272 slugs_to_change
.append(row
.id)
274 if not row
.creator
in existing_slugs
:
275 existing_slugs
[row
.creator
] = [row
.slug
]
277 existing_slugs
[row
.creator
].append(row
.slug
)
279 for row_id
in slugs_to_change
:
280 new_slug
= unicode(uuid
.uuid4())
281 db
.execute(collection_table
.update().
282 where(collection_table
.c
.id == row_id
).
283 values(slug
=new_slug
))
284 # sqlite does not like to change the schema when a transaction(update) is
288 constraint
= UniqueConstraint('creator', 'slug',
289 name
='core__collection_creator_slug_key',
290 table
=collection_table
)
295 @RegisterMigration(11, MIGRATIONS
)
296 def drop_token_related_User_columns(db
):
298 Drop unneeded columns from the User table after switching to using
299 itsdangerous tokens for email and forgot password verification.
301 metadata
= MetaData(bind
=db
.bind
)
302 user_table
= inspect_table(metadata
, 'core__users')
304 verification_key
= user_table
.columns
['verification_key']
305 fp_verification_key
= user_table
.columns
['fp_verification_key']
306 fp_token_expire
= user_table
.columns
['fp_token_expire']
308 verification_key
.drop()
309 fp_verification_key
.drop()
310 fp_token_expire
.drop()
315 class CommentSubscription_v0(declarative_base()):
316 __tablename__
= 'core__comment_subscriptions'
317 id = Column(Integer
, primary_key
=True)
319 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
321 media_entry_id
= Column(Integer
, ForeignKey(MediaEntry
.id), nullable
=False)
323 user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
325 notify
= Column(Boolean
, nullable
=False, default
=True)
326 send_email
= Column(Boolean
, nullable
=False, default
=True)
329 class Notification_v0(declarative_base()):
330 __tablename__
= 'core__notifications'
331 id = Column(Integer
, primary_key
=True)
332 type = Column(Unicode
)
334 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
336 user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False,
338 seen
= Column(Boolean
, default
=lambda: False, index
=True)
341 class CommentNotification_v0(Notification_v0
):
342 __tablename__
= 'core__comment_notifications'
343 id = Column(Integer
, ForeignKey(Notification_v0
.id), primary_key
=True)
345 subject_id
= Column(Integer
, ForeignKey(MediaComment
.id))
348 class ProcessingNotification_v0(Notification_v0
):
349 __tablename__
= 'core__processing_notifications'
351 id = Column(Integer
, ForeignKey(Notification_v0
.id), primary_key
=True)
353 subject_id
= Column(Integer
, ForeignKey(MediaEntry
.id))
356 @RegisterMigration(12, MIGRATIONS
)
357 def add_new_notification_tables(db
):
358 metadata
= MetaData(bind
=db
.bind
)
360 user_table
= inspect_table(metadata
, 'core__users')
361 mediaentry_table
= inspect_table(metadata
, 'core__media_entries')
362 mediacomment_table
= inspect_table(metadata
, 'core__media_comments')
364 CommentSubscription_v0
.__table__
.create(db
.bind
)
366 Notification_v0
.__table__
.create(db
.bind
)
367 CommentNotification_v0
.__table__
.create(db
.bind
)
368 ProcessingNotification_v0
.__table__
.create(db
.bind
)
373 @RegisterMigration(13, MIGRATIONS
)
374 def pw_hash_nullable(db
):
375 """Make pw_hash column nullable"""
376 metadata
= MetaData(bind
=db
.bind
)
377 user_table
= inspect_table(metadata
, "core__users")
379 user_table
.c
.pw_hash
.alter(nullable
=True)
381 # sqlite+sqlalchemy seems to drop this constraint during the
382 # migration, so we add it back here for now a bit manually.
383 if db
.bind
.url
.drivername
== 'sqlite':
384 constraint
= UniqueConstraint('username', table
=user_table
)
391 class Client_v0(declarative_base()):
393 Model representing a client - Used for API Auth
395 __tablename__
= "core__clients"
397 id = Column(Unicode
, nullable
=True, primary_key
=True)
398 secret
= Column(Unicode
, nullable
=False)
399 expirey
= Column(DateTime
, nullable
=True)
400 application_type
= Column(Unicode
, nullable
=False)
401 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
402 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
405 redirect_uri
= Column(JSONEncoded
, nullable
=True)
406 logo_url
= Column(Unicode
, nullable
=True)
407 application_name
= Column(Unicode
, nullable
=True)
408 contacts
= Column(JSONEncoded
, nullable
=True)
411 if self
.application_name
:
412 return "<Client {0} - {1}>".format(self
.application_name
, self
.id)
414 return "<Client {0}>".format(self
.id)
416 class RequestToken_v0(declarative_base()):
418 Model for representing the request tokens
420 __tablename__
= "core__request_tokens"
422 token
= Column(Unicode
, primary_key
=True)
423 secret
= Column(Unicode
, nullable
=False)
424 client
= Column(Unicode
, ForeignKey(Client_v0
.id))
425 user
= Column(Integer
, ForeignKey(User
.id), nullable
=True)
426 used
= Column(Boolean
, default
=False)
427 authenticated
= Column(Boolean
, default
=False)
428 verifier
= Column(Unicode
, nullable
=True)
429 callback
= Column(Unicode
, nullable
=False, default
=u
"oob")
430 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
431 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
433 class AccessToken_v0(declarative_base()):
435 Model for representing the access tokens
437 __tablename__
= "core__access_tokens"
439 token
= Column(Unicode
, nullable
=False, primary_key
=True)
440 secret
= Column(Unicode
, nullable
=False)
441 user
= Column(Integer
, ForeignKey(User
.id))
442 request_token
= Column(Unicode
, ForeignKey(RequestToken_v0
.token
))
443 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
444 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
447 class NonceTimestamp_v0(declarative_base()):
449 A place the timestamp and nonce can be stored - this is for OAuth1
451 __tablename__
= "core__nonce_timestamps"
453 nonce
= Column(Unicode
, nullable
=False, primary_key
=True)
454 timestamp
= Column(DateTime
, nullable
=False, primary_key
=True)
457 @RegisterMigration(14, MIGRATIONS
)
458 def create_oauth1_tables(db
):
459 """ Creates the OAuth1 tables """
461 Client_v0
.__table__
.create(db
.bind
)
462 RequestToken_v0
.__table__
.create(db
.bind
)
463 AccessToken_v0
.__table__
.create(db
.bind
)
464 NonceTimestamp_v0
.__table__
.create(db
.bind
)
469 @RegisterMigration(15, MIGRATIONS
)
470 def 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")
474 col
= Column('wants_notifications', Boolean
, default
=True)
475 col
.create(user_table
)
480 @RegisterMigration(16, MIGRATIONS
)
481 def upload_limits(db
):
482 """Add user upload limit columns"""
483 metadata
= MetaData(bind
=db
.bind
)
485 user_table
= inspect_table(metadata
, 'core__users')
486 media_entry_table
= inspect_table(metadata
, 'core__media_entries')
488 col
= Column('uploaded', Integer
, default
=0)
489 col
.create(user_table
)
491 col
= Column('upload_limit', Integer
)
492 col
.create(user_table
)
494 col
= Column('file_size', Integer
, default
=0)
495 col
.create(media_entry_table
)
500 @RegisterMigration(17, MIGRATIONS
)
501 def 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")
506 col
= Column('file_metadata', MutationDict
.as_mutable(JSONEncoded
))
507 col
.create(media_file_table
)
515 class 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)
521 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
522 discriminator
= Column('type', Unicode(50))
523 resolver_id
= Column(Integer
, ForeignKey(User
.id))
524 resolved
= Column(DateTime
)
525 result
= Column(UnicodeText
)
526 __mapper_args__
= {'polymorphic_on': discriminator
}
529 class CommentReport_v0(ReportBase_v0
):
530 __tablename__
= 'core__reports_on_comments'
531 __mapper_args__
= {'polymorphic_identity': 'comment_report'}
533 id = Column('id',Integer
, ForeignKey('core__reports.id'),
535 comment_id
= Column(Integer
, ForeignKey(MediaComment
.id), nullable
=True)
538 class MediaReport_v0(ReportBase_v0
):
539 __tablename__
= 'core__reports_on_media'
540 __mapper_args__
= {'polymorphic_identity': 'media_report'}
542 id = Column('id',Integer
, ForeignKey('core__reports.id'), primary_key
=True)
543 media_entry_id
= Column(Integer
, ForeignKey(MediaEntry
.id), nullable
=True)
546 class UserBan_v0(declarative_base()):
547 __tablename__
= 'core__user_bans'
548 user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False,
550 expiration_date
= Column(Date
)
551 reason
= Column(UnicodeText
, nullable
=False)
554 class 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)
560 class PrivilegeUserAssociation_v0(declarative_base()):
561 __tablename__
= 'core__privileges_users'
562 privilege_id
= Column(
563 'core__privilege_id',
570 ForeignKey(Privilege
.id),
574 PRIVILEGE_FOUNDATIONS_v0
= [{'privilege_name':u
'admin'},
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'}]
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
586 class 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
) # ??
598 uploaded
= Column(Integer
, default
=0)
599 upload_limit
= Column(Integer
)
602 @RegisterMigration(18, MIGRATIONS
)
603 def create_moderation_tables(db
):
605 # First, we will create the new tables in the database.
606 #--------------------------------------------------------------------------
607 ReportBase_v0
.__table__
.create(db
.bind
)
608 CommentReport_v0
.__table__
.create(db
.bind
)
609 MediaReport_v0
.__table__
.create(db
.bind
)
610 UserBan_v0
.__table__
.create(db
.bind
)
611 Privilege_v0
.__table__
.create(db
.bind
)
612 PrivilegeUserAssociation_v0
.__table__
.create(db
.bind
)
616 # Then initialize the tables that we will later use
617 #--------------------------------------------------------------------------
618 metadata
= MetaData(bind
=db
.bind
)
619 privileges_table
= inspect_table(metadata
, "core__privileges")
620 user_table
= inspect_table(metadata
, 'core__users')
621 user_privilege_assoc
= inspect_table(
622 metadata
, 'core__privileges_users')
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
))
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
= (
637 user_table
.select().where(
638 user_table
.c
.is_admin
==True)).fetchall(),
640 user_table
.select().where(
641 user_table
.c
.is_admin
==False).where(
642 user_table
.c
.status
==u
"active")).fetchall(),
644 user_table
.select().where(
645 user_table
.c
.is_admin
==False).where(
646 user_table
.c
.status
!=u
"active")).fetchall())
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"]
658 # Give each user the appopriate privileges depending whether they are an
659 # admin, an active user or an inactive user ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
660 for admin_user
in admin_users_ids
:
661 admin_user_id
= admin_user
['id']
662 for privilege_id
in [admin_privilege_id
, uploader_privilege_id
,
663 reporter_privilege_id
, commenter_privilege_id
,
664 active_privilege_id
]:
665 db
.execute(user_privilege_assoc
.insert().values(
666 core__privilege_id
=admin_user_id
,
667 core__user_id
=privilege_id
))
669 for active_user
in active_users_ids
:
670 active_user_id
= active_user
['id']
671 for privilege_id
in [uploader_privilege_id
, reporter_privilege_id
,
672 commenter_privilege_id
, active_privilege_id
]:
673 db
.execute(user_privilege_assoc
.insert().values(
674 core__privilege_id
=active_user_id
,
675 core__user_id
=privilege_id
))
677 for inactive_user
in inactive_users_ids
:
678 inactive_user_id
= inactive_user
['id']
679 for privilege_id
in [uploader_privilege_id
, reporter_privilege_id
,
680 commenter_privilege_id
]:
681 db
.execute(user_privilege_assoc
.insert().values(
682 core__privilege_id
=inactive_user_id
,
683 core__user_id
=privilege_id
))
687 # And then, once the information is taken from is_admin & status columns
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. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
695 User_vR1
.__table__
.create(db
.bind
)
697 new_user_table
= inspect_table(metadata
, 'rename__users')
698 replace_table_hack(db
, user_table
, new_user_table
)
700 # If the db is not run using SQLite, this process is much simpler ~~~~~
702 status
= user_table
.columns
['status']
703 email_verified
= user_table
.columns
['email_verified']
704 is_admin
= user_table
.columns
['is_admin']
706 email_verified
.drop()
710 @RegisterMigration(19, MIGRATIONS
)
711 def drop_MediaEntry_collected(db
):
713 Drop unused MediaEntry.collected column
715 metadata
= MetaData(bind
=db
.bind
)
717 media_collected
= inspect_table(metadata
, 'core__media_entries')
718 media_collected
= media_collected
.columns
['collected']
720 media_collected
.drop()