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 RegisterMigration
, inspect_table
31 from mediagoblin
.db
.models
import (MediaEntry
, Collection
, MediaComment
, User
,
37 @RegisterMigration(1, MIGRATIONS
)
38 def ogg_to_webm_audio(db_conn
):
39 metadata
= MetaData(bind
=db_conn
.bind
)
41 file_keynames
= Table('core__file_keynames', metadata
, autoload
=True,
42 autoload_with
=db_conn
.bind
)
45 file_keynames
.update().where(file_keynames
.c
.name
== 'ogg').
46 values(name
='webm_audio')
51 @RegisterMigration(2, MIGRATIONS
)
52 def add_wants_notification_column(db_conn
):
53 metadata
= MetaData(bind
=db_conn
.bind
)
55 users
= Table('core__users', metadata
, autoload
=True,
56 autoload_with
=db_conn
.bind
)
58 col
= Column('wants_comment_notification', Boolean
,
59 default
=True, nullable
=True)
60 col
.create(users
, populate_defaults
=True)
64 @RegisterMigration(3, MIGRATIONS
)
65 def add_transcoding_progress(db_conn
):
66 metadata
= MetaData(bind
=db_conn
.bind
)
68 media_entry
= inspect_table(metadata
, 'core__media_entries')
70 col
= Column('transcoding_progress', SmallInteger
)
71 col
.create(media_entry
)
75 class Collection_v0(declarative_base()):
76 __tablename__
= "core__collections"
78 id = Column(Integer
, primary_key
=True)
79 title
= Column(Unicode
, nullable
=False)
80 slug
= Column(Unicode
)
81 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
,
83 description
= Column(UnicodeText
)
84 creator
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
85 items
= Column(Integer
, default
=0)
87 class CollectionItem_v0(declarative_base()):
88 __tablename__
= "core__collection_items"
90 id = Column(Integer
, primary_key
=True)
92 Integer
, ForeignKey(MediaEntry
.id), nullable
=False, index
=True)
93 collection
= Column(Integer
, ForeignKey(Collection
.id), nullable
=False)
94 note
= Column(UnicodeText
, nullable
=True)
95 added
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
96 position
= Column(Integer
)
98 ## This should be activated, normally.
99 ## But this would change the way the next migration used to work.
100 ## So it's commented for now.
102 UniqueConstraint('collection', 'media_entry'),
105 collectionitem_unique_constraint_done
= False
107 @RegisterMigration(4, MIGRATIONS
)
108 def add_collection_tables(db_conn
):
109 Collection_v0
.__table__
.create(db_conn
.bind
)
110 CollectionItem_v0
.__table__
.create(db_conn
.bind
)
112 global collectionitem_unique_constraint_done
113 collectionitem_unique_constraint_done
= True
118 @RegisterMigration(5, MIGRATIONS
)
119 def add_mediaentry_collected(db_conn
):
120 metadata
= MetaData(bind
=db_conn
.bind
)
122 media_entry
= inspect_table(metadata
, 'core__media_entries')
124 col
= Column('collected', Integer
, default
=0)
125 col
.create(media_entry
)
129 class ProcessingMetaData_v0(declarative_base()):
130 __tablename__
= 'core__processing_metadata'
132 id = Column(Integer
, primary_key
=True)
133 media_entry_id
= Column(Integer
, ForeignKey(MediaEntry
.id), nullable
=False,
135 callback_url
= Column(Unicode
)
137 @RegisterMigration(6, MIGRATIONS
)
138 def create_processing_metadata_table(db
):
139 ProcessingMetaData_v0
.__table__
.create(db
.bind
)
143 # Okay, problem being:
144 # Migration #4 forgot to add the uniqueconstraint for the
145 # new tables. While creating the tables from scratch had
146 # the constraint enabled.
148 # So we have four situations that should end up at the same
152 # Well, easy. Just uses the tables in models.py
153 # 2. Fresh install using a git version just before this migration
154 # The tables are all there, the unique constraint is also there.
155 # This migration should do nothing.
156 # But as we can't detect the uniqueconstraint easily,
157 # this migration just adds the constraint again.
158 # And possibly fails very loud. But ignores the failure.
159 # 3. old install, not using git, just releases.
160 # This one will get the new tables in #4 (now with constraint!)
161 # And this migration is just skipped silently.
162 # 4. old install, always on latest git.
163 # This one has the tables, but lacks the constraint.
164 # So this migration adds the constraint.
165 @RegisterMigration(7, MIGRATIONS
)
166 def fix_CollectionItem_v0_constraint(db_conn
):
167 """Add the forgotten Constraint on CollectionItem"""
169 global collectionitem_unique_constraint_done
170 if collectionitem_unique_constraint_done
:
171 # Reset it. Maybe the whole thing gets run again
172 # For a different db?
173 collectionitem_unique_constraint_done
= False
176 metadata
= MetaData(bind
=db_conn
.bind
)
178 CollectionItem_table
= inspect_table(metadata
, 'core__collection_items')
180 constraint
= UniqueConstraint('collection', 'media_entry',
181 name
='core__collection_items_collection_media_entry_key',
182 table
=CollectionItem_table
)
186 except ProgrammingError
:
187 # User probably has an install that was run since the
188 # collection tables were added, so we don't need to run this migration.
194 @RegisterMigration(8, MIGRATIONS
)
195 def add_license_preference(db
):
196 metadata
= MetaData(bind
=db
.bind
)
198 user_table
= inspect_table(metadata
, 'core__users')
200 col
= Column('license_preference', Unicode
)
201 col
.create(user_table
)
205 @RegisterMigration(9, MIGRATIONS
)
206 def mediaentry_new_slug_era(db
):
208 Update for the new era for media type slugs.
210 Entries without slugs now display differently in the url like:
213 ... because of this, we should back-convert:
214 - entries without slugs should be converted to use the id, if possible, to
215 make old urls still work
216 - slugs with = (or also : which is now also not allowed) to have those
217 stripped out (small possibility of breakage here sadly)
220 def slug_and_user_combo_exists(slug
, uploader
):
223 and_(media_table
.c
.uploader
==uploader
,
224 media_table
.c
.slug
==slug
))).first() is not None
226 def append_garbage_till_unique(row
, new_slug
):
228 Attach junk to this row until it's unique, then save it
230 if slug_and_user_combo_exists(new_slug
, row
.uploader
):
231 # okay, still no success;
232 # let's whack junk on there till it's unique.
233 new_slug
+= '-' + uuid
.uuid4().hex[:4]
234 # keep going if necessary!
235 while slug_and_user_combo_exists(new_slug
, row
.uploader
):
236 new_slug
+= uuid
.uuid4().hex[:4]
239 media_table
.update(). \
240 where(media_table
.c
.id==row
.id). \
241 values(slug
=new_slug
))
243 metadata
= MetaData(bind
=db
.bind
)
245 media_table
= inspect_table(metadata
, 'core__media_entries')
247 for row
in db
.execute(media_table
.select()):
248 # no slug, try setting to an id
250 append_garbage_till_unique(row
, unicode(row
.id))
251 # has "=" or ":" in it... we're getting rid of those
252 elif u
"=" in row
.slug
or u
":" in row
.slug
:
253 append_garbage_till_unique(
254 row
, row
.slug
.replace(u
"=", u
"-").replace(u
":", u
"-"))
259 @RegisterMigration(10, MIGRATIONS
)
260 def unique_collections_slug(db
):
261 """Add unique constraint to collection slug"""
262 metadata
= MetaData(bind
=db
.bind
)
263 collection_table
= inspect_table(metadata
, "core__collections")
267 for row
in db
.execute(collection_table
.select()):
268 # if duplicate slug, generate a unique slug
269 if row
.creator
in existing_slugs
and row
.slug
in \
270 existing_slugs
[row
.creator
]:
271 slugs_to_change
.append(row
.id)
273 if not row
.creator
in existing_slugs
:
274 existing_slugs
[row
.creator
] = [row
.slug
]
276 existing_slugs
[row
.creator
].append(row
.slug
)
278 for row_id
in slugs_to_change
:
279 new_slug
= unicode(uuid
.uuid4())
280 db
.execute(collection_table
.update().
281 where(collection_table
.c
.id == row_id
).
282 values(slug
=new_slug
))
283 # sqlite does not like to change the schema when a transaction(update) is
287 constraint
= UniqueConstraint('creator', 'slug',
288 name
='core__collection_creator_slug_key',
289 table
=collection_table
)
294 @RegisterMigration(11, MIGRATIONS
)
295 def drop_token_related_User_columns(db
):
297 Drop unneeded columns from the User table after switching to using
298 itsdangerous tokens for email and forgot password verification.
300 metadata
= MetaData(bind
=db
.bind
)
301 user_table
= inspect_table(metadata
, 'core__users')
303 verification_key
= user_table
.columns
['verification_key']
304 fp_verification_key
= user_table
.columns
['fp_verification_key']
305 fp_token_expire
= user_table
.columns
['fp_token_expire']
307 verification_key
.drop()
308 fp_verification_key
.drop()
309 fp_token_expire
.drop()
314 class CommentSubscription_v0(declarative_base()):
315 __tablename__
= 'core__comment_subscriptions'
316 id = Column(Integer
, primary_key
=True)
318 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
320 media_entry_id
= Column(Integer
, ForeignKey(MediaEntry
.id), nullable
=False)
322 user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
324 notify
= Column(Boolean
, nullable
=False, default
=True)
325 send_email
= Column(Boolean
, nullable
=False, default
=True)
328 class Notification_v0(declarative_base()):
329 __tablename__
= 'core__notifications'
330 id = Column(Integer
, primary_key
=True)
331 type = Column(Unicode
)
333 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
335 user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False,
337 seen
= Column(Boolean
, default
=lambda: False, index
=True)
340 class CommentNotification_v0(Notification_v0
):
341 __tablename__
= 'core__comment_notifications'
342 id = Column(Integer
, ForeignKey(Notification_v0
.id), primary_key
=True)
344 subject_id
= Column(Integer
, ForeignKey(MediaComment
.id))
347 class ProcessingNotification_v0(Notification_v0
):
348 __tablename__
= 'core__processing_notifications'
350 id = Column(Integer
, ForeignKey(Notification_v0
.id), primary_key
=True)
352 subject_id
= Column(Integer
, ForeignKey(MediaEntry
.id))
355 @RegisterMigration(12, MIGRATIONS
)
356 def add_new_notification_tables(db
):
357 metadata
= MetaData(bind
=db
.bind
)
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')
363 CommentSubscription_v0
.__table__
.create(db
.bind
)
365 Notification_v0
.__table__
.create(db
.bind
)
366 CommentNotification_v0
.__table__
.create(db
.bind
)
367 ProcessingNotification_v0
.__table__
.create(db
.bind
)
372 @RegisterMigration(13, MIGRATIONS
)
373 def pw_hash_nullable(db
):
374 """Make pw_hash column nullable"""
375 metadata
= MetaData(bind
=db
.bind
)
376 user_table
= inspect_table(metadata
, "core__users")
378 user_table
.c
.pw_hash
.alter(nullable
=True)
380 # sqlite+sqlalchemy seems to drop this constraint during the
381 # migration, so we add it back here for now a bit manually.
382 if db
.bind
.url
.drivername
== 'sqlite':
383 constraint
= UniqueConstraint('username', table
=user_table
)
390 class Client_v0(declarative_base()):
392 Model representing a client - Used for API Auth
394 __tablename__
= "core__clients"
396 id = Column(Unicode
, nullable
=True, primary_key
=True)
397 secret
= Column(Unicode
, nullable
=False)
398 expirey
= Column(DateTime
, nullable
=True)
399 application_type
= Column(Unicode
, nullable
=False)
400 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
401 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
404 redirect_uri
= Column(JSONEncoded
, nullable
=True)
405 logo_url
= Column(Unicode
, nullable
=True)
406 application_name
= Column(Unicode
, nullable
=True)
407 contacts
= Column(JSONEncoded
, nullable
=True)
410 if self
.application_name
:
411 return "<Client {0} - {1}>".format(self
.application_name
, self
.id)
413 return "<Client {0}>".format(self
.id)
415 class RequestToken_v0(declarative_base()):
417 Model for representing the request tokens
419 __tablename__
= "core__request_tokens"
421 token
= Column(Unicode
, primary_key
=True)
422 secret
= Column(Unicode
, nullable
=False)
423 client
= Column(Unicode
, ForeignKey(Client_v0
.id))
424 user
= Column(Integer
, ForeignKey(User
.id), nullable
=True)
425 used
= Column(Boolean
, default
=False)
426 authenticated
= Column(Boolean
, default
=False)
427 verifier
= Column(Unicode
, nullable
=True)
428 callback
= Column(Unicode
, nullable
=False, default
=u
"oob")
429 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
430 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
432 class AccessToken_v0(declarative_base()):
434 Model for representing the access tokens
436 __tablename__
= "core__access_tokens"
438 token
= Column(Unicode
, nullable
=False, primary_key
=True)
439 secret
= Column(Unicode
, nullable
=False)
440 user
= Column(Integer
, ForeignKey(User
.id))
441 request_token
= Column(Unicode
, ForeignKey(RequestToken_v0
.token
))
442 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
443 updated
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
446 class NonceTimestamp_v0(declarative_base()):
448 A place the timestamp and nonce can be stored - this is for OAuth1
450 __tablename__
= "core__nonce_timestamps"
452 nonce
= Column(Unicode
, nullable
=False, primary_key
=True)
453 timestamp
= Column(DateTime
, nullable
=False, primary_key
=True)
456 @RegisterMigration(14, MIGRATIONS
)
457 def create_oauth1_tables(db
):
458 """ Creates the OAuth1 tables """
460 Client_v0
.__table__
.create(db
.bind
)
461 RequestToken_v0
.__table__
.create(db
.bind
)
462 AccessToken_v0
.__table__
.create(db
.bind
)
463 NonceTimestamp_v0
.__table__
.create(db
.bind
)
468 @RegisterMigration(15, MIGRATIONS
)
469 def wants_notifications(db
):
470 """Add a wants_notifications field to User model"""
471 metadata
= MetaData(bind
=db
.bind
)
472 user_table
= inspect_table(metadata
, "core__users")
473 col
= Column('wants_notifications', Boolean
, default
=True)
474 col
.create(user_table
)
479 @RegisterMigration(16, MIGRATIONS
)
480 def upload_limits(db
):
481 """Add user upload limit columns"""
482 metadata
= MetaData(bind
=db
.bind
)
484 user_table
= inspect_table(metadata
, 'core__users')
485 media_entry_table
= inspect_table(metadata
, 'core__media_entries')
487 col
= Column('uploaded', Integer
, default
=0)
488 col
.create(user_table
)
490 col
= Column('upload_limit', Integer
)
491 col
.create(user_table
)
493 col
= Column('file_size', Integer
, default
=0)
494 col
.create(media_entry_table
)
499 @RegisterMigration(17, MIGRATIONS
)
500 def add_file_metadata(db
):
501 """Add file_metadata to MediaFile"""
502 metadata
= MetaData(bind
=db
.bind
)
503 media_file_table
= inspect_table(metadata
, "core__mediafiles")
505 col
= Column('file_metadata', MutationDict
.as_mutable(JSONEncoded
))
506 col
.create(media_file_table
)
514 class ReportBase_v0(declarative_base()):
515 __tablename__
= 'core__reports'
516 id = Column(Integer
, primary_key
=True)
517 reporter_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
518 report_content
= Column(UnicodeText
)
519 reported_user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False)
520 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
521 discriminator
= Column('type', Unicode(50))
522 resolver_id
= Column(Integer
, ForeignKey(User
.id))
523 resolved
= Column(DateTime
)
524 result
= Column(UnicodeText
)
525 __mapper_args__
= {'polymorphic_on': discriminator
}
528 class CommentReport_v0(ReportBase_v0
):
529 __tablename__
= 'core__reports_on_comments'
530 __mapper_args__
= {'polymorphic_identity': 'comment_report'}
532 id = Column('id',Integer
, ForeignKey('core__reports.id'),
534 comment_id
= Column(Integer
, ForeignKey(MediaComment
.id), nullable
=True)
537 class MediaReport_v0(ReportBase_v0
):
538 __tablename__
= 'core__reports_on_media'
539 __mapper_args__
= {'polymorphic_identity': 'media_report'}
541 id = Column('id',Integer
, ForeignKey('core__reports.id'), primary_key
=True)
542 media_entry_id
= Column(Integer
, ForeignKey(MediaEntry
.id), nullable
=True)
545 class UserBan_v0(declarative_base()):
546 __tablename__
= 'core__user_bans'
547 user_id
= Column(Integer
, ForeignKey(User
.id), nullable
=False,
549 expiration_date
= Column(Date
)
550 reason
= Column(UnicodeText
, nullable
=False)
553 class Privilege_v0(declarative_base()):
554 __tablename__
= 'core__privileges'
555 id = Column(Integer
, nullable
=False, primary_key
=True, unique
=True)
556 privilege_name
= Column(Unicode
, nullable
=False, unique
=True)
559 class PrivilegeUserAssociation_v0(declarative_base()):
560 __tablename__
= 'core__privileges_users'
561 privilege_id
= Column(
562 'core__privilege_id',
569 ForeignKey(Privilege
.id),
573 PRIVILEGE_FOUNDATIONS_v0
= [{'privilege_name':u
'admin'},
574 {'privilege_name':u
'moderator'},
575 {'privilege_name':u
'uploader'},
576 {'privilege_name':u
'reporter'},
577 {'privilege_name':u
'commenter'},
578 {'privilege_name':u
'active'}]
581 # vR1 stands for "version Rename 1". This only exists because we need
582 # to deal with dropping some booleans and it's otherwise impossible
585 class User_vR1(declarative_base()):
586 __tablename__
= 'rename__users'
587 id = Column(Integer
, primary_key
=True)
588 username
= Column(Unicode
, nullable
=False, unique
=True)
589 email
= Column(Unicode
, nullable
=False)
590 pw_hash
= Column(Unicode
)
591 created
= Column(DateTime
, nullable
=False, default
=datetime
.datetime
.now
)
592 wants_comment_notification
= Column(Boolean
, default
=True)
593 wants_notifications
= Column(Boolean
, default
=True)
594 license_preference
= Column(Unicode
)
595 url
= Column(Unicode
)
596 bio
= Column(UnicodeText
) # ??
597 uploaded
= Column(Integer
, default
=0)
598 upload_limit
= Column(Integer
)
601 @RegisterMigration(18, MIGRATIONS
)
602 def create_moderation_tables(db
):
604 # First, we will create the new tables in the database.
605 #--------------------------------------------------------------------------
606 ReportBase_v0
.__table__
.create(db
.bind
)
607 CommentReport_v0
.__table__
.create(db
.bind
)
608 MediaReport_v0
.__table__
.create(db
.bind
)
609 UserBan_v0
.__table__
.create(db
.bind
)
610 Privilege_v0
.__table__
.create(db
.bind
)
611 PrivilegeUserAssociation_v0
.__table__
.create(db
.bind
)
615 # Then initialize the tables that we will later use
616 #--------------------------------------------------------------------------
617 metadata
= MetaData(bind
=db
.bind
)
618 privileges_table
= inspect_table(metadata
, "core__privileges")
619 user_table
= inspect_table(metadata
, 'core__users')
620 user_privilege_assoc
= inspect_table(
621 metadata
, 'core__privileges_users')
623 # This section initializes the default Privilege foundations, that
624 # would be created through the FOUNDATIONS system in a new instance
625 #--------------------------------------------------------------------------
626 for parameters
in PRIVILEGE_FOUNDATIONS_v0
:
627 db
.execute(privileges_table
.insert().values(**parameters
))
631 # This next section takes the information from the old is_admin and status
632 # columns and converts those to the new privilege system
633 #--------------------------------------------------------------------------
634 admin_users_ids
, active_users_ids
, inactive_users_ids
= (
636 user_table
.select().where(
637 user_table
.c
.is_admin
==1)).fetchall(),
639 user_table
.select().where(
640 user_table
.c
.is_admin
==0).where(
641 user_table
.c
.status
==u
"active")).fetchall(),
643 user_table
.select().where(
644 user_table
.c
.is_admin
==0).where(
645 user_table
.c
.status
!=u
"active")).fetchall())
647 # Get the ids for each of the privileges so we can reference them ~~~~~~~~~
648 (admin_privilege_id
, uploader_privilege_id
,
649 reporter_privilege_id
, commenter_privilege_id
,
650 active_privilege_id
) = [
651 db
.execute(privileges_table
.select().where(
652 privileges_table
.c
.privilege_name
==privilege_name
)).first()['id']
653 for privilege_name
in
654 [u
"admin",u
"uploader",u
"reporter",u
"commenter",u
"active"]
657 # Give each user the appopriate privileges depending whether they are an
658 # admin, an active user or an inactive user ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
659 for admin_user
in admin_users_ids
:
660 admin_user_id
= admin_user
['id']
661 for privilege_id
in [admin_privilege_id
, uploader_privilege_id
,
662 reporter_privilege_id
, commenter_privilege_id
,
663 active_privilege_id
]:
664 db
.execute(user_privilege_assoc
.insert().values(
665 core__privilege_id
=admin_user_id
,
666 core__user_id
=privilege_id
))
668 for active_user
in active_users_ids
:
669 active_user_id
= active_user
['id']
670 for privilege_id
in [uploader_privilege_id
, reporter_privilege_id
,
671 commenter_privilege_id
, active_privilege_id
]:
672 db
.execute(user_privilege_assoc
.insert().values(
673 core__privilege_id
=active_user_id
,
674 core__user_id
=privilege_id
))
676 for inactive_user
in inactive_users_ids
:
677 inactive_user_id
= inactive_user
['id']
678 for privilege_id
in [uploader_privilege_id
, reporter_privilege_id
,
679 commenter_privilege_id
]:
680 db
.execute(user_privilege_assoc
.insert().values(
681 core__privilege_id
=inactive_user_id
,
682 core__user_id
=privilege_id
))
686 # And then, once the information is taken from the is_admin & status columns
687 # we drop all of the vestigial columns from the User table.
688 #--------------------------------------------------------------------------
689 if db
.bind
.url
.drivername
== 'sqlite':
690 # SQLite has some issues that make it *impossible* to drop boolean
691 # columns. So, the following code is a very hacky workaround which
692 # makes it possible. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
694 User_vR1
.__table__
.create(db
.bind
)
696 new_user_table
= inspect_table(metadata
, 'rename__users')
697 for row
in db
.execute(user_table
.select()):
698 db
.execute(new_user_table
.insert().values(
699 username
=row
.username
,
703 wants_comment_notification
=row
.wants_comment_notification
,
704 wants_notifications
=row
.wants_notifications
,
705 license_preference
=row
.license_preference
,
708 uploaded
=row
.uploaded
,
709 upload_limit
=row
.upload_limit
))
715 new_user_table
.rename("core__users")
717 # If the db is not run using SQLite, this process is much simpler ~~~~~
719 status
= user_table
.columns
['status']
720 email_verified
= user_table
.columns
['email_verified']
721 is_admin
= user_table
.columns
['is_admin']
723 email_verified
.drop()