From 2adbd7be54579dde870d489cd90a2d13e57838f6 Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Fri, 3 Jul 2015 15:40:55 +0200 Subject: [PATCH] Add legacy mapping for old material/resource ids Also add legacy redirects for the old material download paths --- indico/modules/attachments/blueprint.py | 24 +++++ indico/modules/attachments/controllers/compat.py | 49 ++++++++++ .../modules/attachments/models/legacy_mapping.py | 102 +++++++++++++++++++++ indico/web/flask/blueprints/files.py | 33 +++---- indico_zodbimport/modules/attachments.py | 9 +- ...c365e54_add_legacy_attachment_mapping_tables.py | 77 ++++++++++++++++ 6 files changed, 274 insertions(+), 20 deletions(-) create mode 100644 indico/modules/attachments/controllers/compat.py create mode 100644 indico/modules/attachments/models/legacy_mapping.py create mode 100644 migrations/versions/201507031455_3778dc365e54_add_legacy_attachment_mapping_tables.py diff --git a/indico/modules/attachments/blueprint.py b/indico/modules/attachments/blueprint.py index 1c44032a7..175c92794 100644 --- a/indico/modules/attachments/blueprint.py +++ b/indico/modules/attachments/blueprint.py @@ -18,6 +18,7 @@ from __future__ import unicode_literals import itertools +from indico.modules.attachments.controllers.compat import compat_folder, compat_attachment from indico.modules.attachments.controllers.display.category import RHDownloadCategoryAttachment from indico.modules.attachments.controllers.display.event import RHDownloadEventAttachment from indico.modules.attachments.controllers.management.category import (RHManageCategoryAttachments, @@ -98,3 +99,26 @@ for object_type, prefixes in items: _bp.add_url_rule(prefix + '/attachments///', 'download', _dispatch(RHDownloadEventAttachment, RHDownloadCategoryAttachment), defaults={'object_type': object_type}) + + +# Setup legacy redirects for the old URLs +_compat_bp = IndicoBlueprint('compat_attachments', __name__, url_prefix='/event/') +compat_folder_rules = [ + '/material//', + '/session//contribution//material//', + '/contribution//material//' +] +compat_attachment_rules = [ + '/material//', + '/material//.', + '/session//material//', + '/session//material//.', + '/session//contribution//material//.', + '/session//contribution///material//.', + '/contribution//material//.', + '/contribution///material//.', +] +for rule in compat_folder_rules: + _compat_bp.add_url_rule(rule, 'folder', compat_folder) +for rule in compat_attachment_rules: + _compat_bp.add_url_rule(rule, 'attachment', compat_attachment) diff --git a/indico/modules/attachments/controllers/compat.py b/indico/modules/attachments/controllers/compat.py new file mode 100644 index 000000000..f8950feb6 --- /dev/null +++ b/indico/modules/attachments/controllers/compat.py @@ -0,0 +1,49 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from flask import current_app, redirect + +from indico.modules.attachments.models.legacy_mapping import LegacyAttachmentFolderMapping, LegacyAttachmentMapping +from indico.web.flask.util import url_for +from MaKaC.webinterface.rh.base import RHSimple + + +def _clean_args(kwargs): + if 'contrib_id' in kwargs: + kwargs['contribution_id'] = kwargs.pop('contrib_id') + if 'subcontrib_id' in kwargs: + kwargs['subcontribution_id'] = kwargs.pop('subcontrib_id') + # extension is just to make the links prettier + kwargs.pop('ext', None) + # session id is only used for actual sessions, not for stuff inside them + if 'contribution_id' in kwargs: + kwargs.pop('session_id', None) + + +@RHSimple.wrap_function +def compat_folder(**kwargs): + _clean_args(kwargs) + mapping = LegacyAttachmentFolderMapping.find(**kwargs).first_or_404() + return redirect(url_for('attachments.list_folder', mapping.folder), 302 if current_app.debug else 301) + + +@RHSimple.wrap_function +def compat_attachment(**kwargs): + _clean_args(kwargs) + mapping = LegacyAttachmentMapping.find(**kwargs).first_or_404() + return redirect(mapping.attachment.download_url, 302 if current_app.debug else 301) diff --git a/indico/modules/attachments/models/legacy_mapping.py b/indico/modules/attachments/models/legacy_mapping.py new file mode 100644 index 000000000..7894f4bee --- /dev/null +++ b/indico/modules/attachments/models/legacy_mapping.py @@ -0,0 +1,102 @@ +# This file is part of Indico. +# Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN). +# +# Indico is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# Indico is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Indico; if not, see . + +from __future__ import unicode_literals + +from sqlalchemy.ext.declarative import declared_attr + +from indico.core.db import db +from indico.core.db.sqlalchemy.links import LinkMixin, LinkType +from indico.core.db.sqlalchemy.util.models import auto_table_args +from indico.util.string import return_ascii + + +class LegacyAttachmentFolderMapping(LinkMixin, db.Model): + """Legacy attachmentfolder id mapping + + Legacy folders ("materials") had ids unique only within their + linked object. This table maps those ids for a specific object + to the new globally unique folder id. + """ + + __tablename__ = 'legacy_folder_id_map' + allowed_link_types = LinkMixin.allowed_link_types - {LinkType.category} + + @declared_attr + def __table_args__(cls): + return auto_table_args(cls, schema='attachments') + + material_id = db.Column( + db.String, + nullable=False + ) + folder_id = db.Column( + db.Integer, + db.ForeignKey('attachments.folders.id'), + primary_key=True, + autoincrement=False + ) + folder = db.relationship( + 'AttachmentFolder', + lazy=False + ) + + @return_ascii + def __repr__(self): + return ''.format( + self.folder, self.material_id, self.link_repr + ) + + +class LegacyAttachmentMapping(LinkMixin, db.Model): + """Legacy attachment id mapping + + Legacy attachments ("resources") had ids unique only within their + folder and its linked object. This table maps those ids for a + specific object to the new globally unique attachment id. + """ + + __tablename__ = 'legacy_attachment_id_map' + allowed_link_types = LinkMixin.allowed_link_types - {LinkType.category} + + @declared_attr + def __table_args__(cls): + return auto_table_args(cls, schema='attachments') + + material_id = db.Column( + db.String, + nullable=False + ) + resource_id = db.Column( + db.String, + nullable=False + ) + attachment_id = db.Column( + db.Integer, + db.ForeignKey('attachments.attachments.id'), + primary_key=True, + autoincrement=False + ) + attachment = db.relationship( + 'Attachment', + lazy=False + ) + + @return_ascii + def __repr__(self): + return ''.format( + self.attachment, self.material_id, self.resource_id, self.link_repr + ) diff --git a/indico/web/flask/blueprints/files.py b/indico/web/flask/blueprints/files.py index b04e9c434..fdf57ef49 100644 --- a/indico/web/flask/blueprints/files.py +++ b/indico/web/flask/blueprints/files.py @@ -21,45 +21,46 @@ from indico.web.flask.wrappers import IndicoBlueprint files = IndicoBlueprint('files', __name__) # Material (event) -files.add_url_rule('/event//material//', 'materialDisplay', materialDisplay.RHMaterialDisplay) -files.add_url_rule('/event//contribution//material//', 'materialDisplay', +files.add_url_rule('/event//material-old//', 'materialDisplay', materialDisplay.RHMaterialDisplay) +files.add_url_rule('/event//contribution//material-old//', 'materialDisplay', materialDisplay.RHMaterialDisplay) -files.add_url_rule('/event//session//contribution//material//', +files.add_url_rule('/event//session//contribution//material-old//', 'materialDisplay', materialDisplay.RHMaterialDisplay) -files.add_url_rule('/event//material//accesskey', 'materialDisplay-accessKey', +files.add_url_rule('/event//material-old//accesskey', 'materialDisplay-accessKey', materialDisplay.RHMaterialDisplayStoreAccessKey, methods=('POST',)) # Material (category) -files.add_url_rule('/category//material//', 'materialDisplay', materialDisplay.RHMaterialDisplay) -files.add_url_rule('/category//material//accesskey', 'materialDisplay-accessKey', +files.add_url_rule('/category//material-old//', 'materialDisplay', materialDisplay.RHMaterialDisplay) +files.add_url_rule('/category//material-old//accesskey', 'materialDisplay-accessKey', materialDisplay.RHMaterialDisplayStoreAccessKey, methods=('POST',)) # File access (event) files.add_url_rule( - '/event//session//material//.', 'getFile-access', + '/event//session//material-old//.', 'getFile-access', fileAccess.RHFileAccess) files.add_url_rule( - '/event//session//material//', 'getFile-access', fileAccess.RHFileAccess) + '/event//session//material-old//', 'getFile-access', fileAccess.RHFileAccess) files.add_url_rule( - '/event//session//contribution//material//.', + '/event//session//contribution//material-old//.', 'getFile-access', fileAccess.RHFileAccess) files.add_url_rule( - '/event//session//contribution///material//.', + '/event//session//contribution///material-old//.', 'getFile-access', fileAccess.RHFileAccess) -files.add_url_rule('/event//contribution//material//.', 'getFile-access', +files.add_url_rule('/event//contribution//material-old//.', 'getFile-access', fileAccess.RHFileAccess) -files.add_url_rule('/event//contribution///material//.', +files.add_url_rule('/event//contribution///material-old//.', 'getFile-access', fileAccess.RHFileAccess) -files.add_url_rule('/event//material//.', 'getFile-access', fileAccess.RHFileAccess) +files.add_url_rule('/event//material-old//.', 'getFile-access', fileAccess.RHFileAccess) +# XXX: keep this one when removing the old material stuff files.add_url_rule('/event//registration/attachments/-.', 'getFile-access', fileAccess.RHFileAccess) -files.add_url_rule('/event//material//', 'getFile-access', fileAccess.RHFileAccess) +files.add_url_rule('/event//material-old//', 'getFile-access', fileAccess.RHFileAccess) # File access (category) -files.add_url_rule('/category//material//.', 'getFile-access', +files.add_url_rule('/category//material-old//.', 'getFile-access', fileAccess.RHFileAccess) -files.add_url_rule('/category//material//', 'getFile-access', fileAccess.RHFileAccess) +files.add_url_rule('/category//material-old//', 'getFile-access', fileAccess.RHFileAccess) # File access (generic) files.add_url_rule('/file/video.swf', 'getFile-flash', fileAccess.RHVideoFlashAccess) diff --git a/indico_zodbimport/modules/attachments.py b/indico_zodbimport/modules/attachments.py index 60d09f6e3..ff98ce5ca 100644 --- a/indico_zodbimport/modules/attachments.py +++ b/indico_zodbimport/modules/attachments.py @@ -30,6 +30,7 @@ from indico.core.db import db from indico.core.config import Config from indico.modules.attachments.models.attachments import Attachment, AttachmentType, AttachmentFile from indico.modules.attachments.models.folders import AttachmentFolder +from indico.modules.attachments.models.legacy_mapping import LegacyAttachmentFolderMapping, LegacyAttachmentMapping from indico.modules.attachments.models.principals import AttachmentPrincipal, AttachmentFolderPrincipal from indico.modules.users import User from indico.util.console import cformat, verbose_iterator @@ -107,7 +108,6 @@ class AttachmentImporter(Importer): for category, material, resources in committing_iterator(self._iter_category_materials(), n=1000): folder = self._folder_from_material(material, category) - db.session.add(folder) if not self.quiet: self.print_success(cformat('%{cyan}[{}]').format(folder.title), event_id=category.id) @@ -127,8 +127,7 @@ class AttachmentImporter(Importer): for event, obj, material, resources in committing_iterator(self._iter_event_materials(), n=1000): folder = self._folder_from_material(material, obj) - db.session.add(folder) - # TODO: mapping table entry for old/new ids + db.session.add(LegacyAttachmentFolderMapping(linked_object=obj, material_id=material.id, folder=folder)) if not self.quiet: self.print_success(cformat('%{cyan}[{}]%{reset} %{blue!}({})').format(folder.title, folder.link_repr), event_id=event.id) @@ -136,7 +135,8 @@ class AttachmentImporter(Importer): attachment = self._attachment_from_resource(folder, material, resource, event) if attachment is None: continue - # TODO: mapping table entry for old/new ids + db.session.add(LegacyAttachmentMapping(linked_object=obj, material_id=material.id, + resource_id=resource.id, attachment=attachment)) if not self.quiet: if attachment.type == AttachmentType.link: self.print_success(cformat('- %{cyan}{}').format(attachment.title), event_id=event.id) @@ -149,6 +149,7 @@ class AttachmentImporter(Importer): linked_object=linked_object, is_always_visible=not material._Material__ac._hideFromUnauthorizedUsers) protection_from_ac(folder, material._Material__ac) + db.session.add(folder) return folder def _get_file_info(self, resource): diff --git a/migrations/versions/201507031455_3778dc365e54_add_legacy_attachment_mapping_tables.py b/migrations/versions/201507031455_3778dc365e54_add_legacy_attachment_mapping_tables.py new file mode 100644 index 000000000..84cf778d2 --- /dev/null +++ b/migrations/versions/201507031455_3778dc365e54_add_legacy_attachment_mapping_tables.py @@ -0,0 +1,77 @@ +"""Add legacy attachment mapping tables + +Revision ID: 3778dc365e54 +Revises: 20392a888368 +Create Date: 2015-07-03 14:55:32.796877 +""" + +import sqlalchemy as sa +from alembic import op +from indico.core.db.sqlalchemy import PyIntEnum +from indico.core.db.sqlalchemy.links import LinkType + + +# revision identifiers, used by Alembic. +revision = '3778dc365e54' +down_revision = '20392a888368' + + +def upgrade(): + op.create_table( + 'legacy_folder_id_map', + sa.Column('category_id', sa.Integer(), nullable=True, index=True), + sa.Column('event_id', sa.Integer(), nullable=True, index=True), + sa.Column('session_id', sa.String(), nullable=True), + sa.Column('contribution_id', sa.String(), nullable=True), + sa.Column('subcontribution_id', sa.String(), nullable=True), + sa.Column('material_id', sa.String(), nullable=False), + sa.Column('folder_id', sa.Integer(), autoincrement=False, nullable=False), + sa.Column('link_type', PyIntEnum(LinkType, exclude_values={LinkType.category}), nullable=False), + sa.CheckConstraint('link_type != 2 OR (contribution_id IS NULL AND subcontribution_id IS NULL AND ' + 'category_id IS NULL AND session_id IS NULL AND event_id IS NOT NULL)', + name='valid_event_link'), + sa.CheckConstraint('link_type != 3 OR (subcontribution_id IS NULL AND category_id IS NULL AND ' + 'session_id IS NULL AND event_id IS NOT NULL AND contribution_id IS NOT NULL)', + name='valid_contribution_link'), + sa.CheckConstraint('link_type != 4 OR (category_id IS NULL AND session_id IS NULL AND event_id IS NOT NULL AND ' + 'contribution_id IS NOT NULL AND subcontribution_id IS NOT NULL)', + name='valid_subcontribution_link'), + sa.CheckConstraint('link_type != 5 OR (contribution_id IS NULL AND subcontribution_id IS NULL AND ' + 'category_id IS NULL AND event_id IS NOT NULL AND session_id IS NOT NULL)', + name='valid_session_link'), + sa.ForeignKeyConstraint(['folder_id'], ['attachments.folders.id']), + sa.PrimaryKeyConstraint('folder_id'), + schema='attachments' + ) + op.create_table( + 'legacy_attachment_id_map', + sa.Column('category_id', sa.Integer(), nullable=True, index=True), + sa.Column('event_id', sa.Integer(), nullable=True, index=True), + sa.Column('session_id', sa.String(), nullable=True), + sa.Column('contribution_id', sa.String(), nullable=True), + sa.Column('subcontribution_id', sa.String(), nullable=True), + sa.Column('material_id', sa.String(), nullable=False), + sa.Column('resource_id', sa.String(), nullable=False), + sa.Column('attachment_id', sa.Integer(), autoincrement=False, nullable=False), + sa.Column('link_type', PyIntEnum(LinkType, exclude_values={LinkType.category}), nullable=False), + sa.CheckConstraint('link_type != 2 OR (contribution_id IS NULL AND subcontribution_id IS NULL AND ' + 'category_id IS NULL AND session_id IS NULL AND event_id IS NOT NULL)', + name='valid_event_link'), + sa.CheckConstraint('link_type != 3 OR (subcontribution_id IS NULL AND category_id IS NULL AND ' + 'session_id IS NULL AND event_id IS NOT NULL AND contribution_id IS NOT NULL)', + name='valid_contribution_link'), + sa.CheckConstraint('link_type != 4 OR (category_id IS NULL AND session_id IS NULL AND event_id IS NOT NULL AND ' + 'contribution_id IS NOT NULL AND subcontribution_id IS NOT NULL)', + name='valid_subcontribution_link'), + sa.CheckConstraint('link_type != 5 OR (contribution_id IS NULL AND subcontribution_id IS NULL AND ' + 'category_id IS NULL AND event_id IS NOT NULL AND session_id IS NOT NULL)', + name='valid_session_link'), + sa.ForeignKeyConstraint(['attachment_id'], ['attachments.attachments.id']), + sa.PrimaryKeyConstraint('attachment_id'), + schema='attachments' + ) + + +def downgrade(): + op.drop_table('legacy_attachment_id_map', schema='attachments') + op.drop_table('legacy_folder_id_map', schema='attachments') -- 2.11.4.GIT