migrations.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432
  1. # GNU MediaGoblin -- federated, autonomous media hosting
  2. # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
  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. import datetime
  17. import uuid
  18. import six
  19. if six.PY2:
  20. import migrate
  21. import pytz
  22. import dateutil.tz
  23. from sqlalchemy import (MetaData, Table, Column, Boolean, SmallInteger,
  24. Integer, Unicode, UnicodeText, DateTime,
  25. ForeignKey, Date, Index)
  26. from sqlalchemy.exc import ProgrammingError
  27. from sqlalchemy.ext.declarative import declarative_base
  28. from sqlalchemy.sql import and_
  29. from sqlalchemy.schema import UniqueConstraint
  30. from mediagoblin.db.extratypes import JSONEncoded, MutationDict
  31. from mediagoblin.db.migration_tools import (
  32. RegisterMigration, inspect_table, replace_table_hack)
  33. from mediagoblin.db.models import (MediaEntry, Collection, MediaComment, User,
  34. Privilege, Generator)
  35. from mediagoblin.db.extratypes import JSONEncoded, MutationDict
  36. MIGRATIONS = {}
  37. @RegisterMigration(1, MIGRATIONS)
  38. def ogg_to_webm_audio(db_conn):
  39. metadata = MetaData(bind=db_conn.bind)
  40. file_keynames = Table('core__file_keynames', metadata, autoload=True,
  41. autoload_with=db_conn.bind)
  42. db_conn.execute(
  43. file_keynames.update().where(file_keynames.c.name == 'ogg').
  44. values(name='webm_audio')
  45. )
  46. db_conn.commit()
  47. @RegisterMigration(2, MIGRATIONS)
  48. def add_wants_notification_column(db_conn):
  49. metadata = MetaData(bind=db_conn.bind)
  50. users = Table('core__users', metadata, autoload=True,
  51. autoload_with=db_conn.bind)
  52. col = Column('wants_comment_notification', Boolean,
  53. default=True, nullable=True)
  54. col.create(users, populate_defaults=True)
  55. db_conn.commit()
  56. @RegisterMigration(3, MIGRATIONS)
  57. def add_transcoding_progress(db_conn):
  58. metadata = MetaData(bind=db_conn.bind)
  59. media_entry = inspect_table(metadata, 'core__media_entries')
  60. col = Column('transcoding_progress', SmallInteger)
  61. col.create(media_entry)
  62. db_conn.commit()
  63. class Collection_v0(declarative_base()):
  64. __tablename__ = "core__collections"
  65. id = Column(Integer, primary_key=True)
  66. title = Column(Unicode, nullable=False)
  67. slug = Column(Unicode)
  68. created = Column(DateTime, nullable=False, default=datetime.datetime.now,
  69. index=True)
  70. description = Column(UnicodeText)
  71. creator = Column(Integer, ForeignKey(User.id), nullable=False)
  72. items = Column(Integer, default=0)
  73. class CollectionItem_v0(declarative_base()):
  74. __tablename__ = "core__collection_items"
  75. id = Column(Integer, primary_key=True)
  76. media_entry = Column(
  77. Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
  78. collection = Column(Integer, ForeignKey(Collection.id), nullable=False)
  79. note = Column(UnicodeText, nullable=True)
  80. added = Column(DateTime, nullable=False, default=datetime.datetime.now)
  81. position = Column(Integer)
  82. ## This should be activated, normally.
  83. ## But this would change the way the next migration used to work.
  84. ## So it's commented for now.
  85. __table_args__ = (
  86. UniqueConstraint('collection', 'media_entry'),
  87. {})
  88. collectionitem_unique_constraint_done = False
  89. @RegisterMigration(4, MIGRATIONS)
  90. def add_collection_tables(db_conn):
  91. Collection_v0.__table__.create(db_conn.bind)
  92. CollectionItem_v0.__table__.create(db_conn.bind)
  93. global collectionitem_unique_constraint_done
  94. collectionitem_unique_constraint_done = True
  95. db_conn.commit()
  96. @RegisterMigration(5, MIGRATIONS)
  97. def add_mediaentry_collected(db_conn):
  98. metadata = MetaData(bind=db_conn.bind)
  99. media_entry = inspect_table(metadata, 'core__media_entries')
  100. col = Column('collected', Integer, default=0)
  101. col.create(media_entry)
  102. db_conn.commit()
  103. class ProcessingMetaData_v0(declarative_base()):
  104. __tablename__ = 'core__processing_metadata'
  105. id = Column(Integer, primary_key=True)
  106. media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False,
  107. index=True)
  108. callback_url = Column(Unicode)
  109. @RegisterMigration(6, MIGRATIONS)
  110. def create_processing_metadata_table(db):
  111. ProcessingMetaData_v0.__table__.create(db.bind)
  112. db.commit()
  113. # Okay, problem being:
  114. # Migration #4 forgot to add the uniqueconstraint for the
  115. # new tables. While creating the tables from scratch had
  116. # the constraint enabled.
  117. #
  118. # So we have four situations that should end up at the same
  119. # db layout:
  120. #
  121. # 1. Fresh install.
  122. # Well, easy. Just uses the tables in models.py
  123. # 2. Fresh install using a git version just before this migration
  124. # The tables are all there, the unique constraint is also there.
  125. # This migration should do nothing.
  126. # But as we can't detect the uniqueconstraint easily,
  127. # this migration just adds the constraint again.
  128. # And possibly fails very loud. But ignores the failure.
  129. # 3. old install, not using git, just releases.
  130. # This one will get the new tables in #4 (now with constraint!)
  131. # And this migration is just skipped silently.
  132. # 4. old install, always on latest git.
  133. # This one has the tables, but lacks the constraint.
  134. # So this migration adds the constraint.
  135. @RegisterMigration(7, MIGRATIONS)
  136. def fix_CollectionItem_v0_constraint(db_conn):
  137. """Add the forgotten Constraint on CollectionItem"""
  138. global collectionitem_unique_constraint_done
  139. if collectionitem_unique_constraint_done:
  140. # Reset it. Maybe the whole thing gets run again
  141. # For a different db?
  142. collectionitem_unique_constraint_done = False
  143. return
  144. metadata = MetaData(bind=db_conn.bind)
  145. CollectionItem_table = inspect_table(metadata, 'core__collection_items')
  146. constraint = UniqueConstraint('collection', 'media_entry',
  147. name='core__collection_items_collection_media_entry_key',
  148. table=CollectionItem_table)
  149. try:
  150. constraint.create()
  151. except ProgrammingError:
  152. # User probably has an install that was run since the
  153. # collection tables were added, so we don't need to run this migration.
  154. pass
  155. db_conn.commit()
  156. @RegisterMigration(8, MIGRATIONS)
  157. def add_license_preference(db):
  158. metadata = MetaData(bind=db.bind)
  159. user_table = inspect_table(metadata, 'core__users')
  160. col = Column('license_preference', Unicode)
  161. col.create(user_table)
  162. db.commit()
  163. @RegisterMigration(9, MIGRATIONS)
  164. def mediaentry_new_slug_era(db):
  165. """
  166. Update for the new era for media type slugs.
  167. Entries without slugs now display differently in the url like:
  168. /u/cwebber/m/id=251/
  169. ... because of this, we should back-convert:
  170. - entries without slugs should be converted to use the id, if possible, to
  171. make old urls still work
  172. - slugs with = (or also : which is now also not allowed) to have those
  173. stripped out (small possibility of breakage here sadly)
  174. """
  175. def slug_and_user_combo_exists(slug, uploader):
  176. return db.execute(
  177. media_table.select(
  178. and_(media_table.c.uploader==uploader,
  179. media_table.c.slug==slug))).first() is not None
  180. def append_garbage_till_unique(row, new_slug):
  181. """
  182. Attach junk to this row until it's unique, then save it
  183. """
  184. if slug_and_user_combo_exists(new_slug, row.uploader):
  185. # okay, still no success;
  186. # let's whack junk on there till it's unique.
  187. new_slug += '-' + uuid.uuid4().hex[:4]
  188. # keep going if necessary!
  189. while slug_and_user_combo_exists(new_slug, row.uploader):
  190. new_slug += uuid.uuid4().hex[:4]
  191. db.execute(
  192. media_table.update(). \
  193. where(media_table.c.id==row.id). \
  194. values(slug=new_slug))
  195. metadata = MetaData(bind=db.bind)
  196. media_table = inspect_table(metadata, 'core__media_entries')
  197. for row in db.execute(media_table.select()):
  198. # no slug, try setting to an id
  199. if not row.slug:
  200. append_garbage_till_unique(row, six.text_type(row.id))
  201. # has "=" or ":" in it... we're getting rid of those
  202. elif u"=" in row.slug or u":" in row.slug:
  203. append_garbage_till_unique(
  204. row, row.slug.replace(u"=", u"-").replace(u":", u"-"))
  205. db.commit()
  206. @RegisterMigration(10, MIGRATIONS)
  207. def unique_collections_slug(db):
  208. """Add unique constraint to collection slug"""
  209. metadata = MetaData(bind=db.bind)
  210. collection_table = inspect_table(metadata, "core__collections")
  211. existing_slugs = {}
  212. slugs_to_change = []
  213. for row in db.execute(collection_table.select()):
  214. # if duplicate slug, generate a unique slug
  215. if row.creator in existing_slugs and row.slug in \
  216. existing_slugs[row.creator]:
  217. slugs_to_change.append(row.id)
  218. else:
  219. if not row.creator in existing_slugs:
  220. existing_slugs[row.creator] = [row.slug]
  221. else:
  222. existing_slugs[row.creator].append(row.slug)
  223. for row_id in slugs_to_change:
  224. new_slug = six.text_type(uuid.uuid4())
  225. db.execute(collection_table.update().
  226. where(collection_table.c.id == row_id).
  227. values(slug=new_slug))
  228. # sqlite does not like to change the schema when a transaction(update) is
  229. # not yet completed
  230. db.commit()
  231. constraint = UniqueConstraint('creator', 'slug',
  232. name='core__collection_creator_slug_key',
  233. table=collection_table)
  234. constraint.create()
  235. db.commit()
  236. @RegisterMigration(11, MIGRATIONS)
  237. def drop_token_related_User_columns(db):
  238. """
  239. Drop unneeded columns from the User table after switching to using
  240. itsdangerous tokens for email and forgot password verification.
  241. """
  242. metadata = MetaData(bind=db.bind)
  243. user_table = inspect_table(metadata, 'core__users')
  244. verification_key = user_table.columns['verification_key']
  245. fp_verification_key = user_table.columns['fp_verification_key']
  246. fp_token_expire = user_table.columns['fp_token_expire']
  247. verification_key.drop()
  248. fp_verification_key.drop()
  249. fp_token_expire.drop()
  250. db.commit()
  251. class CommentSubscription_v0(declarative_base()):
  252. __tablename__ = 'core__comment_subscriptions'
  253. id = Column(Integer, primary_key=True)
  254. created = Column(DateTime, nullable=False, default=datetime.datetime.now)
  255. media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
  256. user_id = Column(Integer, ForeignKey(User.id), nullable=False)
  257. notify = Column(Boolean, nullable=False, default=True)
  258. send_email = Column(Boolean, nullable=False, default=True)
  259. class Notification_v0(declarative_base()):
  260. __tablename__ = 'core__notifications'
  261. id = Column(Integer, primary_key=True)
  262. type = Column(Unicode)
  263. created = Column(DateTime, nullable=False, default=datetime.datetime.now)
  264. user_id = Column(Integer, ForeignKey(User.id), nullable=False,
  265. index=True)
  266. seen = Column(Boolean, default=lambda: False, index=True)
  267. class CommentNotification_v0(Notification_v0):
  268. __tablename__ = 'core__comment_notifications'
  269. id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
  270. subject_id = Column(Integer, ForeignKey(MediaComment.id))
  271. class ProcessingNotification_v0(Notification_v0):
  272. __tablename__ = 'core__processing_notifications'
  273. id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
  274. subject_id = Column(Integer, ForeignKey(MediaEntry.id))
  275. @RegisterMigration(12, MIGRATIONS)
  276. def add_new_notification_tables(db):
  277. metadata = MetaData(bind=db.bind)
  278. user_table = inspect_table(metadata, 'core__users')
  279. mediaentry_table = inspect_table(metadata, 'core__media_entries')
  280. mediacomment_table = inspect_table(metadata, 'core__media_comments')
  281. CommentSubscription_v0.__table__.create(db.bind)
  282. Notification_v0.__table__.create(db.bind)
  283. CommentNotification_v0.__table__.create(db.bind)
  284. ProcessingNotification_v0.__table__.create(db.bind)
  285. db.commit()
  286. @RegisterMigration(13, MIGRATIONS)
  287. def pw_hash_nullable(db):
  288. """Make pw_hash column nullable"""
  289. metadata = MetaData(bind=db.bind)
  290. user_table = inspect_table(metadata, "core__users")
  291. user_table.c.pw_hash.alter(nullable=True)
  292. # sqlite+sqlalchemy seems to drop this constraint during the
  293. # migration, so we add it back here for now a bit manually.
  294. if db.bind.url.drivername == 'sqlite':
  295. constraint = UniqueConstraint('username', table=user_table)
  296. constraint.create()
  297. db.commit()
  298. # oauth1 migrations
  299. class Client_v0(declarative_base()):
  300. """
  301. Model representing a client - Used for API Auth
  302. """
  303. __tablename__ = "core__clients"
  304. id = Column(Unicode, nullable=True, primary_key=True)
  305. secret = Column(Unicode, nullable=False)
  306. expirey = Column(DateTime, nullable=True)
  307. application_type = Column(Unicode, nullable=False)
  308. created = Column(DateTime, nullable=False, default=datetime.datetime.now)
  309. updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
  310. # optional stuff
  311. redirect_uri = Column(JSONEncoded, nullable=True)
  312. logo_url = Column(Unicode, nullable=True)
  313. application_name = Column(Unicode, nullable=True)
  314. contacts = Column(JSONEncoded, nullable=True)
  315. def __repr__(self):
  316. if self.application_name:
  317. return "<Client {0} - {1}>".format(self.application_name, self.id)
  318. else:
  319. return "<Client {0}>".format(self.id)
  320. class RequestToken_v0(declarative_base()):
  321. """
  322. Model for representing the request tokens
  323. """
  324. __tablename__ = "core__request_tokens"
  325. token = Column(Unicode, primary_key=True)
  326. secret = Column(Unicode, nullable=False)
  327. client = Column(Unicode, ForeignKey(Client_v0.id))
  328. user = Column(Integer, ForeignKey(User.id), nullable=True)
  329. used = Column(Boolean, default=False)
  330. authenticated = Column(Boolean, default=False)
  331. verifier = Column(Unicode, nullable=True)
  332. callback = Column(Unicode, nullable=False, default=u"oob")
  333. created = Column(DateTime, nullable=False, default=datetime.datetime.now)
  334. updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
  335. class AccessToken_v0(declarative_base()):
  336. """
  337. Model for representing the access tokens
  338. """
  339. __tablename__ = "core__access_tokens"
  340. token = Column(Unicode, nullable=False, primary_key=True)
  341. secret = Column(Unicode, nullable=False)
  342. user = Column(Integer, ForeignKey(User.id))
  343. request_token = Column(Unicode, ForeignKey(RequestToken_v0.token))
  344. created = Column(DateTime, nullable=False, default=datetime.datetime.now)
  345. updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
  346. class NonceTimestamp_v0(declarative_base()):
  347. """
  348. A place the timestamp and nonce can be stored - this is for OAuth1
  349. """
  350. __tablename__ = "core__nonce_timestamps"
  351. nonce = Column(Unicode, nullable=False, primary_key=True)
  352. timestamp = Column(DateTime, nullable=False, primary_key=True)
  353. @RegisterMigration(14, MIGRATIONS)
  354. def create_oauth1_tables(db):
  355. """ Creates the OAuth1 tables """
  356. Client_v0.__table__.create(db.bind)
  357. RequestToken_v0.__table__.create(db.bind)
  358. AccessToken_v0.__table__.create(db.bind)
  359. NonceTimestamp_v0.__table__.create(db.bind)
  360. db.commit()
  361. @RegisterMigration(15, MIGRATIONS)
  362. def wants_notifications(db):
  363. """Add a wants_notifications field to User model"""
  364. metadata = MetaData(bind=db.bind)
  365. user_table = inspect_table(metadata, "core__users")
  366. col = Column('wants_notifications', Boolean, default=True)
  367. col.create(user_table)
  368. db.commit()
  369. @RegisterMigration(16, MIGRATIONS)
  370. def upload_limits(db):
  371. """Add user upload limit columns"""
  372. metadata = MetaData(bind=db.bind)
  373. user_table = inspect_table(metadata, 'core__users')
  374. media_entry_table = inspect_table(metadata, 'core__media_entries')
  375. col = Column('uploaded', Integer, default=0)
  376. col.create(user_table)
  377. col = Column('upload_limit', Integer)
  378. col.create(user_table)
  379. col = Column('file_size', Integer, default=0)
  380. col.create(media_entry_table)
  381. db.commit()
  382. @RegisterMigration(17, MIGRATIONS)
  383. def add_file_metadata(db):
  384. """Add file_metadata to MediaFile"""
  385. metadata = MetaData(bind=db.bind)
  386. media_file_table = inspect_table(metadata, "core__mediafiles")
  387. col = Column('file_metadata', MutationDict.as_mutable(JSONEncoded))
  388. col.create(media_file_table)
  389. db.commit()
  390. ###################
  391. # Moderation tables
  392. ###################
  393. class ReportBase_v0(declarative_base()):
  394. __tablename__ = 'core__reports'
  395. id = Column(Integer, primary_key=True)
  396. reporter_id = Column(Integer, ForeignKey(User.id), nullable=False)
  397. report_content = Column(UnicodeText)
  398. reported_user_id = Column(Integer, ForeignKey(User.id), nullable=False)
  399. created = Column(DateTime, nullable=False, default=datetime.datetime.now)
  400. discriminator = Column('type', Unicode(50))
  401. resolver_id = Column(Integer, ForeignKey(User.id))
  402. resolved = Column(DateTime)
  403. result = Column(UnicodeText)
  404. __mapper_args__ = {'polymorphic_on': discriminator}
  405. class CommentReport_v0(ReportBase_v0):
  406. __tablename__ = 'core__reports_on_comments'
  407. __mapper_args__ = {'polymorphic_identity': 'comment_report'}
  408. id = Column('id',Integer, ForeignKey('core__reports.id'),
  409. primary_key=True)
  410. comment_id = Column(Integer, ForeignKey(MediaComment.id), nullable=True)
  411. class MediaReport_v0(ReportBase_v0):
  412. __tablename__ = 'core__reports_on_media'
  413. __mapper_args__ = {'polymorphic_identity': 'media_report'}
  414. id = Column('id',Integer, ForeignKey('core__reports.id'), primary_key=True)
  415. media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=True)
  416. class UserBan_v0(declarative_base()):
  417. __tablename__ = 'core__user_bans'
  418. user_id = Column(Integer, ForeignKey(User.id), nullable=False,
  419. primary_key=True)
  420. expiration_date = Column(Date)
  421. reason = Column(UnicodeText, nullable=False)
  422. class Privilege_v0(declarative_base()):
  423. __tablename__ = 'core__privileges'
  424. id = Column(Integer, nullable=False, primary_key=True, unique=True)
  425. privilege_name = Column(Unicode, nullable=False, unique=True)
  426. class PrivilegeUserAssociation_v0(declarative_base()):
  427. __tablename__ = 'core__privileges_users'
  428. privilege_id = Column(
  429. 'core__privilege_id',
  430. Integer,
  431. ForeignKey(User.id),
  432. primary_key=True)
  433. user_id = Column(
  434. 'core__user_id',
  435. Integer,
  436. ForeignKey(Privilege.id),
  437. primary_key=True)
  438. PRIVILEGE_FOUNDATIONS_v0 = [{'privilege_name':u'admin'},
  439. {'privilege_name':u'moderator'},
  440. {'privilege_name':u'uploader'},
  441. {'privilege_name':u'reporter'},
  442. {'privilege_name':u'commenter'},
  443. {'privilege_name':u'active'}]
  444. # vR1 stands for "version Rename 1". This only exists because we need
  445. # to deal with dropping some booleans and it's otherwise impossible
  446. # with sqlite.
  447. class User_vR1(declarative_base()):
  448. __tablename__ = 'rename__users'
  449. id = Column(Integer, primary_key=True)
  450. username = Column(Unicode, nullable=False, unique=True)
  451. email = Column(Unicode, nullable=False)
  452. pw_hash = Column(Unicode)
  453. created = Column(DateTime, nullable=False, default=datetime.datetime.now)
  454. wants_comment_notification = Column(Boolean, default=True)
  455. wants_notifications = Column(Boolean, default=True)
  456. license_preference = Column(Unicode)
  457. url = Column(Unicode)
  458. bio = Column(UnicodeText) # ??
  459. uploaded = Column(Integer, default=0)
  460. upload_limit = Column(Integer)
  461. @RegisterMigration(18, MIGRATIONS)
  462. def create_moderation_tables(db):
  463. # First, we will create the new tables in the database.
  464. #--------------------------------------------------------------------------
  465. ReportBase_v0.__table__.create(db.bind)
  466. CommentReport_v0.__table__.create(db.bind)
  467. MediaReport_v0.__table__.create(db.bind)
  468. UserBan_v0.__table__.create(db.bind)
  469. Privilege_v0.__table__.create(db.bind)
  470. PrivilegeUserAssociation_v0.__table__.create(db.bind)
  471. db.commit()
  472. # Then initialize the tables that we will later use
  473. #--------------------------------------------------------------------------
  474. metadata = MetaData(bind=db.bind)
  475. privileges_table= inspect_table(metadata, "core__privileges")
  476. user_table = inspect_table(metadata, 'core__users')
  477. user_privilege_assoc = inspect_table(
  478. metadata, 'core__privileges_users')
  479. # This section initializes the default Privilege foundations, that
  480. # would be created through the FOUNDATIONS system in a new instance
  481. #--------------------------------------------------------------------------
  482. for parameters in PRIVILEGE_FOUNDATIONS_v0:
  483. db.execute(privileges_table.insert().values(**parameters))
  484. db.commit()
  485. # This next section takes the information from the old is_admin and status
  486. # columns and converts those to the new privilege system
  487. #--------------------------------------------------------------------------
  488. admin_users_ids, active_users_ids, inactive_users_ids = (
  489. db.execute(
  490. user_table.select().where(
  491. user_table.c.is_admin==True)).fetchall(),
  492. db.execute(
  493. user_table.select().where(
  494. user_table.c.is_admin==False).where(
  495. user_table.c.status==u"active")).fetchall(),
  496. db.execute(
  497. user_table.select().where(
  498. user_table.c.is_admin==False).where(
  499. user_table.c.status!=u"active")).fetchall())
  500. # Get the ids for each of the privileges so we can reference them ~~~~~~~~~
  501. (admin_privilege_id, uploader_privilege_id,
  502. reporter_privilege_id, commenter_privilege_id,
  503. active_privilege_id) = [
  504. db.execute(privileges_table.select().where(
  505. privileges_table.c.privilege_name==privilege_name)).first()['id']
  506. for privilege_name in
  507. [u"admin",u"uploader",u"reporter",u"commenter",u"active"]
  508. ]
  509. # Give each user the appopriate privileges depending whether they are an
  510. # admin, an active user or an inactive user ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  511. for admin_user in admin_users_ids:
  512. admin_user_id = admin_user['id']
  513. for privilege_id in [admin_privilege_id, uploader_privilege_id,
  514. reporter_privilege_id, commenter_privilege_id,
  515. active_privilege_id]:
  516. db.execute(user_privilege_assoc.insert().values(
  517. core__privilege_id=admin_user_id,
  518. core__user_id=privilege_id))
  519. for active_user in active_users_ids:
  520. active_user_id = active_user['id']
  521. for privilege_id in [uploader_privilege_id, reporter_privilege_id,
  522. commenter_privilege_id, active_privilege_id]:
  523. db.execute(user_privilege_assoc.insert().values(
  524. core__privilege_id=active_user_id,
  525. core__user_id=privilege_id))
  526. for inactive_user in inactive_users_ids:
  527. inactive_user_id = inactive_user['id']
  528. for privilege_id in [uploader_privilege_id, reporter_privilege_id,
  529. commenter_privilege_id]:
  530. db.execute(user_privilege_assoc.insert().values(
  531. core__privilege_id=inactive_user_id,
  532. core__user_id=privilege_id))
  533. db.commit()
  534. # And then, once the information is taken from is_admin & status columns
  535. # we drop all of the vestigial columns from the User table.
  536. #--------------------------------------------------------------------------
  537. if db.bind.url.drivername == 'sqlite':
  538. # SQLite has some issues that make it *impossible* to drop boolean
  539. # columns. So, the following code is a very hacky workaround which
  540. # makes it possible. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  541. User_vR1.__table__.create(db.bind)
  542. db.commit()
  543. new_user_table = inspect_table(metadata, 'rename__users')
  544. replace_table_hack(db, user_table, new_user_table)
  545. else:
  546. # If the db is not run using SQLite, this process is much simpler ~~~~~
  547. status = user_table.columns['status']
  548. email_verified = user_table.columns['email_verified']
  549. is_admin = user_table.columns['is_admin']
  550. status.drop()
  551. email_verified.drop()
  552. is_admin.drop()
  553. db.commit()
  554. @RegisterMigration(19, MIGRATIONS)
  555. def drop_MediaEntry_collected(db):
  556. """
  557. Drop unused MediaEntry.collected column
  558. """
  559. metadata = MetaData(bind=db.bind)
  560. media_collected= inspect_table(metadata, 'core__media_entries')
  561. media_collected = media_collected.columns['collected']
  562. media_collected.drop()
  563. db.commit()
  564. @RegisterMigration(20, MIGRATIONS)
  565. def add_metadata_column(db):
  566. metadata = MetaData(bind=db.bind)
  567. media_entry = inspect_table(metadata, 'core__media_entries')
  568. col = Column('media_metadata', MutationDict.as_mutable(JSONEncoded),
  569. default=MutationDict())
  570. col.create(media_entry)
  571. db.commit()
  572. class PrivilegeUserAssociation_R1(declarative_base()):
  573. __tablename__ = 'rename__privileges_users'
  574. user = Column(
  575. "user",
  576. Integer,
  577. ForeignKey(User.id),
  578. primary_key=True)
  579. privilege = Column(
  580. "privilege",
  581. Integer,
  582. ForeignKey(Privilege.id),
  583. primary_key=True)
  584. @RegisterMigration(21, MIGRATIONS)
  585. def fix_privilege_user_association_table(db):
  586. """
  587. There was an error in the PrivilegeUserAssociation table that allowed for a
  588. dangerous sql error. We need to the change the name of the columns to be
  589. unique, and properly referenced.
  590. """
  591. metadata = MetaData(bind=db.bind)
  592. privilege_user_assoc = inspect_table(
  593. metadata, 'core__privileges_users')
  594. # This whole process is more complex if we're dealing with sqlite
  595. if db.bind.url.drivername == 'sqlite':
  596. PrivilegeUserAssociation_R1.__table__.create(db.bind)
  597. db.commit()
  598. new_privilege_user_assoc = inspect_table(
  599. metadata, 'rename__privileges_users')
  600. result = db.execute(privilege_user_assoc.select())
  601. for row in result:
  602. # The columns were improperly named before, so we switch the columns
  603. user_id, priv_id = row['core__privilege_id'], row['core__user_id']
  604. db.execute(new_privilege_user_assoc.insert().values(
  605. user=user_id,
  606. privilege=priv_id))
  607. db.commit()
  608. privilege_user_assoc.drop()
  609. new_privilege_user_assoc.rename('core__privileges_users')
  610. # much simpler if postgres though!
  611. else:
  612. privilege_user_assoc.c.core__user_id.alter(name="privilege")
  613. privilege_user_assoc.c.core__privilege_id.alter(name="user")
  614. db.commit()
  615. @RegisterMigration(22, MIGRATIONS)
  616. def add_index_username_field(db):
  617. """
  618. This migration has been found to be doing the wrong thing. See
  619. the documentation in migration 23 (revert_username_index) below
  620. which undoes this for those databases that did run this migration.
  621. Old description:
  622. This indexes the User.username field which is frequently queried
  623. for example a user logging in. This solves the issue #894
  624. """
  625. ## This code is left commented out *on purpose!*
  626. ##
  627. ## We do not normally allow commented out code like this in
  628. ## MediaGoblin but this is a special case: since this migration has
  629. ## been nullified but with great work to set things back below,
  630. ## this is commented out for historical clarity.
  631. #
  632. # metadata = MetaData(bind=db.bind)
  633. # user_table = inspect_table(metadata, "core__users")
  634. #
  635. # new_index = Index("ix_core__users_uploader", user_table.c.username)
  636. # new_index.create()
  637. #
  638. # db.commit()
  639. pass
  640. @RegisterMigration(23, MIGRATIONS)
  641. def revert_username_index(db):
  642. """
  643. Revert the stuff we did in migration 22 above.
  644. There were a couple of problems with what we did:
  645. - There was never a need for this migration! The unique
  646. constraint had an implicit b-tree index, so it wasn't really
  647. needed. (This is my (Chris Webber's) fault for suggesting it
  648. needed to happen without knowing what's going on... my bad!)
  649. - On top of that, databases created after the models.py was
  650. changed weren't the same as those that had been run through
  651. migration 22 above.
  652. As such, we're setting things back to the way they were before,
  653. but as it turns out, that's tricky to do!
  654. """
  655. metadata = MetaData(bind=db.bind)
  656. user_table = inspect_table(metadata, "core__users")
  657. indexes = dict(
  658. [(index.name, index) for index in user_table.indexes])
  659. # index from unnecessary migration
  660. users_uploader_index = indexes.get(u'ix_core__users_uploader')
  661. # index created from models.py after (unique=True, index=True)
  662. # was set in models.py
  663. users_username_index = indexes.get(u'ix_core__users_username')
  664. if users_uploader_index is None and users_username_index is None:
  665. # We don't need to do anything.
  666. # The database isn't in a state where it needs fixing
  667. #
  668. # (ie, either went through the previous borked migration or
  669. # was initialized with a models.py where core__users was both
  670. # unique=True and index=True)
  671. return
  672. if db.bind.url.drivername == 'sqlite':
  673. # Again, sqlite has problems. So this is tricky.
  674. # Yes, this is correct to use User_vR1! Nothing has changed
  675. # between the *correct* version of this table and migration 18.
  676. User_vR1.__table__.create(db.bind)
  677. db.commit()
  678. new_user_table = inspect_table(metadata, 'rename__users')
  679. replace_table_hack(db, user_table, new_user_table)
  680. else:
  681. # If the db is not run using SQLite, we don't need to do crazy
  682. # table copying.
  683. # Remove whichever of the not-used indexes are in place
  684. if users_uploader_index is not None:
  685. users_uploader_index.drop()
  686. if users_username_index is not None:
  687. users_username_index.drop()
  688. # Given we're removing indexes then adding a unique constraint
  689. # which *we know might fail*, thus probably rolling back the
  690. # session, let's commit here.
  691. db.commit()
  692. try:
  693. # Add the unique constraint
  694. constraint = UniqueConstraint(
  695. 'username', table=user_table)
  696. constraint.create()
  697. except ProgrammingError:
  698. # constraint already exists, no need to add
  699. db.rollback()
  700. db.commit()
  701. class Generator_R0(declarative_base()):
  702. __tablename__ = "core__generators"
  703. id = Column(Integer, primary_key=True)
  704. name = Column(Unicode, nullable=False)
  705. published = Column(DateTime, nullable=False, default=datetime.datetime.now)
  706. updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
  707. object_type = Column(Unicode, nullable=False)
  708. class ActivityIntermediator_R0(declarative_base()):
  709. __tablename__ = "core__activity_intermediators"
  710. id = Column(Integer, primary_key=True)
  711. type = Column(Unicode, nullable=False)
  712. # These are needed for migration 29
  713. TYPES = {
  714. "user": User,
  715. "media": MediaEntry,
  716. "comment": MediaComment,
  717. "collection": Collection,
  718. }
  719. class Activity_R0(declarative_base()):
  720. __tablename__ = "core__activities"
  721. id = Column(Integer, primary_key=True)
  722. actor = Column(Integer, ForeignKey(User.id), nullable=False)
  723. published = Column(DateTime, nullable=False, default=datetime.datetime.now)
  724. updated = Column(DateTime, nullable=False, default=datetime.datetime.now)
  725. verb = Column(Unicode, nullable=False)
  726. content = Column(Unicode, nullable=True)
  727. title = Column(Unicode, nullable=True)
  728. generator = Column(Integer, ForeignKey(Generator_R0.id), nullable=True)
  729. object = Column(Integer,
  730. ForeignKey(ActivityIntermediator_R0.id),
  731. nullable=False)
  732. target = Column(Integer,
  733. ForeignKey(ActivityIntermediator_R0.id),
  734. nullable=True)
  735. @RegisterMigration(24, MIGRATIONS)
  736. def activity_migration(db):
  737. """
  738. Creates everything to create activities in GMG
  739. - Adds Activity, ActivityIntermediator and Generator table
  740. - Creates GMG service generator for activities produced by the server
  741. - Adds the activity_as_object and activity_as_target to objects/targets
  742. - Retroactively adds activities for what we can acurately work out
  743. """
  744. # Set constants we'll use later
  745. FOREIGN_KEY = "core__activity_intermediators.id"
  746. ACTIVITY_COLUMN = "activity"
  747. # Create the new tables.
  748. ActivityIntermediator_R0.__table__.create(db.bind)
  749. Generator_R0.__table__.create(db.bind)
  750. Activity_R0.__table__.create(db.bind)
  751. db.commit()
  752. # Initiate the tables we want to use later
  753. metadata = MetaData(bind=db.bind)
  754. user_table = inspect_table(metadata, "core__users")
  755. activity_table = inspect_table(metadata, "core__activities")
  756. generator_table = inspect_table(metadata, "core__generators")
  757. collection_table = inspect_table(metadata, "core__collections")
  758. media_entry_table = inspect_table(metadata, "core__media_entries")
  759. media_comments_table = inspect_table(metadata, "core__media_comments")
  760. ai_table = inspect_table(metadata, "core__activity_intermediators")
  761. # Create the foundations for Generator
  762. db.execute(generator_table.insert().values(
  763. name="GNU Mediagoblin",
  764. object_type="service",
  765. published=datetime.datetime.now(),
  766. updated=datetime.datetime.now()
  767. ))
  768. db.commit()
  769. # Get the ID of that generator
  770. gmg_generator = db.execute(generator_table.select(
  771. generator_table.c.name==u"GNU Mediagoblin")).first()
  772. # Now we want to modify the tables which MAY have an activity at some point
  773. media_col = Column(ACTIVITY_COLUMN, Integer, ForeignKey(FOREIGN_KEY))
  774. media_col.create(media_entry_table)
  775. user_col = Column(ACTIVITY_COLUMN, Integer, ForeignKey(FOREIGN_KEY))
  776. user_col.create(user_table)
  777. comments_col = Column(ACTIVITY_COLUMN, Integer, ForeignKey(FOREIGN_KEY))
  778. comments_col.create(media_comments_table)
  779. collection_col = Column(ACTIVITY_COLUMN, Integer, ForeignKey(FOREIGN_KEY))
  780. collection_col.create(collection_table)
  781. db.commit()
  782. # Now we want to retroactively add what activities we can
  783. # first we'll add activities when people uploaded media.
  784. # these can't have content as it's not fesible to get the
  785. # correct content strings.
  786. for media in db.execute(media_entry_table.select()):
  787. # Now we want to create the intermedaitory
  788. db_ai = db.execute(ai_table.insert().values(
  789. type="media",
  790. ))
  791. db_ai = db.execute(ai_table.select(
  792. ai_table.c.id==db_ai.inserted_primary_key[0]
  793. )).first()
  794. # Add the activity
  795. activity = {
  796. "verb": "create",
  797. "actor": media.uploader,
  798. "published": media.created,
  799. "updated": media.created,
  800. "generator": gmg_generator.id,
  801. "object": db_ai.id
  802. }
  803. db.execute(activity_table.insert().values(**activity))
  804. # Add the AI to the media.
  805. db.execute(media_entry_table.update().values(
  806. activity=db_ai.id
  807. ).where(media_entry_table.c.id==media.id))
  808. # Now we want to add all the comments people made
  809. for comment in db.execute(media_comments_table.select()):
  810. # Get the MediaEntry for the comment
  811. media_entry = db.execute(
  812. media_entry_table.select(
  813. media_entry_table.c.id==comment.media_entry
  814. )).first()
  815. # Create an AI for target
  816. db_ai_media = db.execute(ai_table.select(
  817. ai_table.c.id==media_entry.activity
  818. )).first().id
  819. db.execute(
  820. media_comments_table.update().values(
  821. activity=db_ai_media
  822. ).where(media_comments_table.c.id==media_entry.id))
  823. # Now create the AI for the comment
  824. db_ai_comment = db.execute(ai_table.insert().values(
  825. type="comment"
  826. )).inserted_primary_key[0]
  827. activity = {
  828. "verb": "comment",
  829. "actor": comment.author,
  830. "published": comment.created,
  831. "updated": comment.created,
  832. "generator": gmg_generator.id,
  833. "object": db_ai_comment,
  834. "target": db_ai_media,
  835. }
  836. # Now add the comment object
  837. db.execute(activity_table.insert().values(**activity))
  838. # Now add activity to comment
  839. db.execute(media_comments_table.update().values(
  840. activity=db_ai_comment
  841. ).where(media_comments_table.c.id==comment.id))
  842. # Create 'create' activities for all collections
  843. for collection in db.execute(collection_table.select()):
  844. # create AI
  845. db_ai = db.execute(ai_table.insert().values(
  846. type="collection"
  847. ))
  848. db_ai = db.execute(ai_table.select(
  849. ai_table.c.id==db_ai.inserted_primary_key[0]
  850. )).first()
  851. # Now add link the collection to the AI
  852. db.execute(collection_table.update().values(
  853. activity=db_ai.id
  854. ).where(collection_table.c.id==collection.id))
  855. activity = {
  856. "verb": "create",
  857. "actor": collection.creator,
  858. "published": collection.created,
  859. "updated": collection.created,
  860. "generator": gmg_generator.id,
  861. "object": db_ai.id,
  862. }
  863. db.execute(activity_table.insert().values(**activity))
  864. # Now add the activity to the collection
  865. db.execute(collection_table.update().values(
  866. activity=db_ai.id
  867. ).where(collection_table.c.id==collection.id))
  868. db.commit()
  869. class Location_V0(declarative_base()):
  870. __tablename__ = "core__locations"
  871. id = Column(Integer, primary_key=True)
  872. name = Column(Unicode)
  873. position = Column(MutationDict.as_mutable(JSONEncoded))
  874. address = Column(MutationDict.as_mutable(JSONEncoded))
  875. @RegisterMigration(25, MIGRATIONS)
  876. def add_location_model(db):
  877. """ Add location model """
  878. metadata = MetaData(bind=db.bind)
  879. # Create location table
  880. Location_V0.__table__.create(db.bind)
  881. db.commit()
  882. # Inspect the tables we need
  883. user = inspect_table(metadata, "core__users")
  884. collections = inspect_table(metadata, "core__collections")
  885. media_entry = inspect_table(metadata, "core__media_entries")
  886. media_comments = inspect_table(metadata, "core__media_comments")
  887. # Now add location support to the various models
  888. col = Column("location", Integer, ForeignKey(Location_V0.id))
  889. col.create(user)
  890. col = Column("location", Integer, ForeignKey(Location_V0.id))
  891. col.create(collections)
  892. col = Column("location", Integer, ForeignKey(Location_V0.id))
  893. col.create(media_entry)
  894. col = Column("location", Integer, ForeignKey(Location_V0.id))
  895. col.create(media_comments)
  896. db.commit()
  897. @RegisterMigration(26, MIGRATIONS)
  898. def datetime_to_utc(db):
  899. """ Convert datetime stamps to UTC """
  900. # Get the server's timezone, this is what the database has stored
  901. server_timezone = dateutil.tz.tzlocal()
  902. ##
  903. # Look up all the timestamps and convert them to UTC
  904. ##
  905. metadata = MetaData(bind=db.bind)
  906. def dt_to_utc(dt):
  907. # Add the current timezone
  908. dt = dt.replace(tzinfo=server_timezone)
  909. # Convert to UTC
  910. return dt.astimezone(pytz.UTC)
  911. # Convert the User model
  912. user_table = inspect_table(metadata, "core__users")
  913. for user in db.execute(user_table.select()):
  914. db.execute(user_table.update().values(
  915. created=dt_to_utc(user.created)
  916. ).where(user_table.c.id==user.id))
  917. # Convert Client
  918. client_table = inspect_table(metadata, "core__clients")
  919. for client in db.execute(client_table.select()):
  920. db.execute(client_table.update().values(
  921. created=dt_to_utc(client.created),
  922. updated=dt_to_utc(client.updated)
  923. ).where(client_table.c.id==client.id))
  924. # Convert RequestToken
  925. rt_table = inspect_table(metadata, "core__request_tokens")
  926. for request_token in db.execute(rt_table.select()):
  927. db.execute(rt_table.update().values(
  928. created=dt_to_utc(request_token.created),
  929. updated=dt_to_utc(request_token.updated)
  930. ).where(rt_table.c.token==request_token.token))
  931. # Convert AccessToken
  932. at_table = inspect_table(metadata, "core__access_tokens")
  933. for access_token in db.execute(at_table.select()):
  934. db.execute(at_table.update().values(
  935. created=dt_to_utc(access_token.created),
  936. updated=dt_to_utc(access_token.updated)
  937. ).where(at_table.c.token==access_token.token))
  938. # Convert MediaEntry
  939. media_table = inspect_table(metadata, "core__media_entries")
  940. for media in db.execute(media_table.select()):
  941. db.execute(media_table.update().values(
  942. created=dt_to_utc(media.created)
  943. ).where(media_table.c.id==media.id))
  944. # Convert Media Attachment File
  945. media_attachment_table = inspect_table(metadata, "core__attachment_files")
  946. for ma in db.execute(media_attachment_table.select()):
  947. db.execute(media_attachment_table.update().values(
  948. created=dt_to_utc(ma.created)
  949. ).where(media_attachment_table.c.id==ma.id))
  950. # Convert MediaComment
  951. comment_table = inspect_table(metadata, "core__media_comments")
  952. for comment in db.execute(comment_table.select()):
  953. db.execute(comment_table.update().values(
  954. created=dt_to_utc(comment.created)
  955. ).where(comment_table.c.id==comment.id))
  956. # Convert Collection
  957. collection_table = inspect_table(metadata, "core__collections")
  958. for collection in db.execute(collection_table.select()):
  959. db.execute(collection_table.update().values(
  960. created=dt_to_utc(collection.created)
  961. ).where(collection_table.c.id==collection.id))
  962. # Convert Collection Item
  963. collection_item_table = inspect_table(metadata, "core__collection_items")
  964. for ci in db.execute(collection_item_table.select()):
  965. db.execute(collection_item_table.update().values(
  966. added=dt_to_utc(ci.added)
  967. ).where(collection_item_table.c.id==ci.id))
  968. # Convert Comment subscription
  969. comment_sub = inspect_table(metadata, "core__comment_subscriptions")
  970. for sub in db.execute(comment_sub.select()):
  971. db.execute(comment_sub.update().values(
  972. created=dt_to_utc(sub.created)
  973. ).where(comment_sub.c.id==sub.id))
  974. # Convert Notification
  975. notification_table = inspect_table(metadata, "core__notifications")
  976. for notification in db.execute(notification_table.select()):
  977. db.execute(notification_table.update().values(
  978. created=dt_to_utc(notification.created)
  979. ).where(notification_table.c.id==notification.id))
  980. # Convert ReportBase
  981. reportbase_table = inspect_table(metadata, "core__reports")
  982. for report in db.execute(reportbase_table.select()):
  983. db.execute(reportbase_table.update().values(
  984. created=dt_to_utc(report.created)
  985. ).where(reportbase_table.c.id==report.id))
  986. # Convert Generator
  987. generator_table = inspect_table(metadata, "core__generators")
  988. for generator in db.execute(generator_table.select()):
  989. db.execute(generator_table.update().values(
  990. published=dt_to_utc(generator.published),
  991. updated=dt_to_utc(generator.updated)
  992. ).where(generator_table.c.id==generator.id))
  993. # Convert Activity
  994. activity_table = inspect_table(metadata, "core__activities")
  995. for activity in db.execute(activity_table.select()):
  996. db.execute(activity_table.update().values(
  997. published=dt_to_utc(activity.published),
  998. updated=dt_to_utc(activity.updated)
  999. ).where(activity_table.c.id==activity.id))
  1000. # Commit this to the database
  1001. db.commit()
  1002. ##
  1003. # Migrations to handle migrating from activity specific foreign key to the
  1004. # new GenericForeignKey implementations. They have been split up to improve
  1005. # readability and minimise errors
  1006. ##
  1007. class GenericModelReference_V0(declarative_base()):
  1008. __tablename__ = "core__generic_model_reference"
  1009. id = Column(Integer, primary_key=True)
  1010. obj_pk = Column(Integer, nullable=False)
  1011. model_type = Column(Unicode, nullable=False)
  1012. @RegisterMigration(27, MIGRATIONS)
  1013. def create_generic_model_reference(db):
  1014. """ Creates the Generic Model Reference table """
  1015. GenericModelReference_V0.__table__.create(db.bind)
  1016. db.commit()
  1017. @RegisterMigration(28, MIGRATIONS)
  1018. def add_foreign_key_fields(db):
  1019. """
  1020. Add the fields for GenericForeignKey to the model under temporary name,
  1021. this is so that later a data migration can occur. They will be renamed to
  1022. the origional names.
  1023. """
  1024. metadata = MetaData(bind=db.bind)
  1025. activity_table = inspect_table(metadata, "core__activities")
  1026. # Create column and add to model.
  1027. object_column = Column("temp_object", Integer, ForeignKey(GenericModelReference_V0.id))
  1028. object_column.create(activity_table)
  1029. target_column = Column("temp_target", Integer, ForeignKey(GenericModelReference_V0.id))
  1030. target_column.create(activity_table)
  1031. # Commit this to the database
  1032. db.commit()
  1033. @RegisterMigration(29, MIGRATIONS)
  1034. def migrate_data_foreign_keys(db):
  1035. """
  1036. This will migrate the data from the old object and target attributes which
  1037. use the old ActivityIntermediator to the new temparay fields which use the
  1038. new GenericForeignKey.
  1039. """
  1040. metadata = MetaData(bind=db.bind)
  1041. activity_table = inspect_table(metadata, "core__activities")
  1042. ai_table = inspect_table(metadata, "core__activity_intermediators")
  1043. gmr_table = inspect_table(metadata, "core__generic_model_reference")
  1044. # Iterate through all activities doing the migration per activity.
  1045. for activity in db.execute(activity_table.select()):
  1046. # First do the "Activity.object" migration to "Activity.temp_object"
  1047. # I need to get the object from the Activity, I can't use the old
  1048. # Activity.get_object as we're in a migration.
  1049. object_ai = db.execute(ai_table.select(
  1050. ai_table.c.id==activity.object
  1051. )).first()
  1052. object_ai_type = ActivityIntermediator_R0.TYPES[object_ai.type]
  1053. object_ai_table = inspect_table(metadata, object_ai_type.__tablename__)
  1054. activity_object = db.execute(object_ai_table.select(
  1055. object_ai_table.c.activity==object_ai.id
  1056. )).first()
  1057. # now we need to create the GenericModelReference
  1058. object_gmr = db.execute(gmr_table.insert().values(
  1059. obj_pk=activity_object.id,
  1060. model_type=object_ai_type.__tablename__
  1061. ))
  1062. # Now set the ID of the GenericModelReference in the GenericForignKey
  1063. db.execute(activity_table.update().values(
  1064. temp_object=object_gmr.inserted_primary_key[0]
  1065. ))
  1066. # Now do same process for "Activity.target" to "Activity.temp_target"
  1067. # not all Activities have a target so if it doesn't just skip the rest
  1068. # of this.
  1069. if activity.target is None:
  1070. continue
  1071. # Now get the target for the activity.
  1072. target_ai = db.execute(ai_table.select(
  1073. ai_table.c.id==activity.target
  1074. )).first()
  1075. target_ai_type = ActivityIntermediator_R0.TYPES[target_ai.type]
  1076. target_ai_table = inspect_table(metadata, target_ai_type.__tablename__)
  1077. activity_target = db.execute(target_ai_table.select(
  1078. target_ai_table.c.activity==target_ai.id
  1079. )).first()
  1080. # We now want to create the new target GenericModelReference
  1081. target_gmr = db.execute(gmr_table.insert().values(
  1082. obj_pk=activity_target.id,
  1083. model_type=target_ai_type.__tablename__
  1084. ))
  1085. # Now set the ID of the GenericModelReference in the GenericForignKey
  1086. db.execute(activity_table.update().values(
  1087. temp_object=target_gmr.inserted_primary_key[0]
  1088. ))
  1089. # Commit to the database.
  1090. db.commit()
  1091. @RegisterMigration(30, MIGRATIONS)
  1092. def rename_and_remove_object_and_target(db):
  1093. """
  1094. Renames the new Activity.object and Activity.target fields and removes the
  1095. old ones.
  1096. """
  1097. metadata = MetaData(bind=db.bind)
  1098. activity_table = inspect_table(metadata, "core__activities")
  1099. # Firstly lets remove the old fields.
  1100. old_object_column = activity_table.columns["object"]
  1101. old_target_column = activity_table.columns["target"]
  1102. # Drop the tables.
  1103. old_object_column.drop()
  1104. old_target_column.drop()
  1105. # Now get the new columns.
  1106. new_object_column = activity_table.columns["temp_object"]
  1107. new_target_column = activity_table.columns["temp_target"]
  1108. # rename them to the old names.
  1109. new_object_column.alter(name="object_id")
  1110. new_target_column.alter(name="target_id")
  1111. # Commit the changes to the database.
  1112. db.commit()
  1113. @RegisterMigration(31, MIGRATIONS)
  1114. def remove_activityintermediator(db):
  1115. """
  1116. This removes the old specific ActivityIntermediator model which has been
  1117. superseeded by the GenericForeignKey field.
  1118. """
  1119. metadata = MetaData(bind=db.bind)
  1120. # Remove the columns which reference the AI
  1121. collection_table = inspect_table(metadata, "core__collections")
  1122. collection_ai_column = collection_table.columns["activity"]
  1123. collection_ai_column.drop()
  1124. media_entry_table = inspect_table(metadata, "core__media_entries")
  1125. media_entry_ai_column = media_entry_table.columns["activity"]
  1126. media_entry_ai_column.drop()
  1127. comments_table = inspect_table(metadata, "core__media_comments")
  1128. comments_ai_column = comments_table.columns["activity"]
  1129. comments_ai_column.drop()
  1130. user_table = inspect_table(metadata, "core__users")
  1131. user_ai_column = user_table.columns["activity"]
  1132. user_ai_column.drop()
  1133. # Drop the table
  1134. ai_table = inspect_table(metadata, "core__activity_intermediators")
  1135. ai_table.drop()
  1136. # Commit the changes
  1137. db.commit()