diff --git a/kolibri/core/content/contentschema/versions/__init__.py b/kolibri/core/content/contentschema/versions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/kolibri/core/content/contentschema/versions/content_schema_1.py b/kolibri/core/content/contentschema/versions/content_schema_1.py new file mode 100644 index 00000000000..34f10952230 --- /dev/null +++ b/kolibri/core/content/contentschema/versions/content_schema_1.py @@ -0,0 +1,203 @@ +# coding: utf-8 +from sqlalchemy import Boolean +from sqlalchemy import CHAR +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Text +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship + +Base = declarative_base() +metadata = Base.metadata + + +class ContentContenttag(Base): + __tablename__ = "content_contenttag" + + id = Column(CHAR(32), primary_key=True) + tag_name = Column(String(30), nullable=False) + + +class ContentLanguage(Base): + __tablename__ = "content_language" + + id = Column(String(14), primary_key=True) + lang_code = Column(String(3), nullable=False, index=True) + lang_subcode = Column(String(10), index=True) + lang_name = Column(String(100)) + lang_direction = Column(String(3), nullable=False) + + +class ContentLocalfile(Base): + __tablename__ = "content_localfile" + + id = Column(String(32), primary_key=True) + extension = Column(String(40), nullable=False) + available = Column(Boolean, nullable=False) + file_size = Column(Integer) + + +class ContentContentnode(Base): + __tablename__ = "content_contentnode" + + id = Column(CHAR(32), primary_key=True) + title = Column(String(200), nullable=False) + content_id = Column(CHAR(32), nullable=False, index=True) + channel_id = Column(CHAR(32), nullable=False, index=True) + description = Column(String(400)) + sort_order = Column(Float) + license_owner = Column(String(200), nullable=False) + author = Column(String(200), nullable=False) + kind = Column(String(200), nullable=False) + available = Column(Boolean, nullable=False) + stemmed_metaphone = Column(String(1800), nullable=False) + lft = Column(Integer, nullable=False, index=True) + rght = Column(Integer, nullable=False, index=True) + tree_id = Column(Integer, nullable=False, index=True) + level = Column(Integer, nullable=False, index=True) + lang_id = Column(ForeignKey("content_language.id"), index=True) + license_description = Column(String(400)) + license_name = Column(String(50)) + parent_id = Column(ForeignKey("content_contentnode.id"), index=True) + + lang = relationship("ContentLanguage") + parent = relationship("ContentContentnode", remote_side=[id]) + + +class ContentAssessmentmetadata(Base): + __tablename__ = "content_assessmentmetadata" + + id = Column(CHAR(32), primary_key=True) + assessment_item_ids = Column(Text, nullable=False) + number_of_assessments = Column(Integer, nullable=False) + mastery_model = Column(Text, nullable=False) + randomize = Column(Boolean, nullable=False) + is_manipulable = Column(Boolean, nullable=False) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + + +class ContentChannelmetadata(Base): + __tablename__ = "content_channelmetadata" + + id = Column(CHAR(32), primary_key=True) + name = Column(String(200), nullable=False) + description = Column(String(400), nullable=False) + author = Column(String(400), nullable=False) + version = Column(Integer, nullable=False) + thumbnail = Column(Text, nullable=False) + last_updated = Column(String) + min_schema_version = Column(String(50), nullable=False) + root_id = Column(ForeignKey("content_contentnode.id"), nullable=False, index=True) + + root = relationship("ContentContentnode") + + +class ContentContentnodeHasPrerequisite(Base): + __tablename__ = "content_contentnode_has_prerequisite" + __table_args__ = ( + Index( + "content_contentnode_has_prerequisite_from_contentnode_id_c9e1d527_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeRelated(Base): + __tablename__ = "content_contentnode_related" + __table_args__ = ( + Index( + "content_contentnode_related_from_contentnode_id_fc2ed20c_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeTags(Base): + __tablename__ = "content_contentnode_tags" + __table_args__ = ( + Index( + "content_contentnode_tags_contentnode_id_64a4ac15_uniq", + "contentnode_id", + "contenttag_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + contenttag_id = Column( + ForeignKey("content_contenttag.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + contenttag = relationship("ContentContenttag") + + +class ContentFile(Base): + __tablename__ = "content_file" + + id = Column(CHAR(32), primary_key=True) + available = Column(Boolean, nullable=False) + preset = Column(String(150), nullable=False) + supplementary = Column(Boolean, nullable=False) + thumbnail = Column(Boolean, nullable=False) + priority = Column(Integer, index=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + lang_id = Column(ForeignKey("content_language.id"), index=True) + local_file_id = Column( + ForeignKey("content_localfile.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + lang = relationship("ContentLanguage") + local_file = relationship("ContentLocalfile") diff --git a/kolibri/core/content/contentschema/versions/content_schema_2.py b/kolibri/core/content/contentschema/versions/content_schema_2.py new file mode 100644 index 00000000000..1ea8a3f1809 --- /dev/null +++ b/kolibri/core/content/contentschema/versions/content_schema_2.py @@ -0,0 +1,218 @@ +# coding: utf-8 +from sqlalchemy import Boolean +from sqlalchemy import CHAR +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Text +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship + +Base = declarative_base() +metadata = Base.metadata + + +class ContentContenttag(Base): + __tablename__ = "content_contenttag" + + id = Column(CHAR(32), primary_key=True) + tag_name = Column(String(30), nullable=False) + + +class ContentLanguage(Base): + __tablename__ = "content_language" + + id = Column(String(14), primary_key=True) + lang_code = Column(String(3), nullable=False, index=True) + lang_subcode = Column(String(10), index=True) + lang_name = Column(String(100)) + lang_direction = Column(String(3), nullable=False) + + +class ContentLocalfile(Base): + __tablename__ = "content_localfile" + + id = Column(String(32), primary_key=True) + extension = Column(String(40), nullable=False) + available = Column(Boolean, nullable=False) + file_size = Column(Integer) + + +class ContentContentnode(Base): + __tablename__ = "content_contentnode" + __table_args__ = ( + Index( + "content_contentnode_level_channel_id_kind_fd732cc4_idx", + "level", + "channel_id", + "kind", + ), + Index( + "content_contentnode_level_channel_id_available_29f0bb18_idx", + "level", + "channel_id", + "available", + ), + ) + + id = Column(CHAR(32), primary_key=True) + title = Column(String(200), nullable=False) + content_id = Column(CHAR(32), nullable=False, index=True) + channel_id = Column(CHAR(32), nullable=False, index=True) + description = Column(String(400)) + sort_order = Column(Float) + license_owner = Column(String(200), nullable=False) + author = Column(String(200), nullable=False) + kind = Column(String(200), nullable=False) + available = Column(Boolean, nullable=False) + stemmed_metaphone = Column(String(1800), nullable=False) + lft = Column(Integer, nullable=False, index=True) + rght = Column(Integer, nullable=False, index=True) + tree_id = Column(Integer, nullable=False, index=True) + level = Column(Integer, nullable=False, index=True) + lang_id = Column(ForeignKey("content_language.id"), index=True) + license_description = Column(String(400)) + license_name = Column(String(50)) + coach_content = Column(Boolean, nullable=False) + parent_id = Column(ForeignKey("content_contentnode.id"), index=True) + + lang = relationship("ContentLanguage") + parent = relationship("ContentContentnode", remote_side=[id]) + + +class ContentAssessmentmetadata(Base): + __tablename__ = "content_assessmentmetadata" + + id = Column(CHAR(32), primary_key=True) + assessment_item_ids = Column(Text, nullable=False) + number_of_assessments = Column(Integer, nullable=False) + mastery_model = Column(Text, nullable=False) + randomize = Column(Boolean, nullable=False) + is_manipulable = Column(Boolean, nullable=False) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + + +class ContentChannelmetadata(Base): + __tablename__ = "content_channelmetadata" + + id = Column(CHAR(32), primary_key=True) + name = Column(String(200), nullable=False) + description = Column(String(400), nullable=False) + author = Column(String(400), nullable=False) + version = Column(Integer, nullable=False) + thumbnail = Column(Text, nullable=False) + last_updated = Column(String) + min_schema_version = Column(String(50), nullable=False) + root_id = Column(ForeignKey("content_contentnode.id"), nullable=False, index=True) + + root = relationship("ContentContentnode") + + +class ContentContentnodeHasPrerequisite(Base): + __tablename__ = "content_contentnode_has_prerequisite" + __table_args__ = ( + Index( + "content_contentnode_has_prerequisite_from_contentnode_id_to_contentnode_id_c9e1d527_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeRelated(Base): + __tablename__ = "content_contentnode_related" + __table_args__ = ( + Index( + "content_contentnode_related_from_contentnode_id_to_contentnode_id_fc2ed20c_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeTags(Base): + __tablename__ = "content_contentnode_tags" + __table_args__ = ( + Index( + "content_contentnode_tags_contentnode_id_contenttag_id_64a4ac15_uniq", + "contentnode_id", + "contenttag_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + contenttag_id = Column( + ForeignKey("content_contenttag.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + contenttag = relationship("ContentContenttag") + + +class ContentFile(Base): + __tablename__ = "content_file" + + id = Column(CHAR(32), primary_key=True) + available = Column(Boolean, nullable=False) + preset = Column(String(150), nullable=False) + supplementary = Column(Boolean, nullable=False) + thumbnail = Column(Boolean, nullable=False) + priority = Column(Integer, index=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + lang_id = Column(ForeignKey("content_language.id"), index=True) + local_file_id = Column( + ForeignKey("content_localfile.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + lang = relationship("ContentLanguage") + local_file = relationship("ContentLocalfile") diff --git a/kolibri/core/content/contentschema/versions/content_schema_3.py b/kolibri/core/content/contentschema/versions/content_schema_3.py new file mode 100644 index 00000000000..6a54e1ec685 --- /dev/null +++ b/kolibri/core/content/contentschema/versions/content_schema_3.py @@ -0,0 +1,202 @@ +# coding: utf-8 +from sqlalchemy import Boolean +from sqlalchemy import CHAR +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Text +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship + +Base = declarative_base() +metadata = Base.metadata + + +class ContentContenttag(Base): + __tablename__ = "content_contenttag" + + id = Column(CHAR(32), primary_key=True) + tag_name = Column(String(30), nullable=False) + + +class ContentLanguage(Base): + __tablename__ = "content_language" + + id = Column(String(14), primary_key=True) + lang_code = Column(String(3), nullable=False, index=True) + lang_subcode = Column(String(10), index=True) + lang_name = Column(String(100)) + lang_direction = Column(String(3), nullable=False) + + +class ContentLocalfile(Base): + __tablename__ = "content_localfile" + + id = Column(String(32), primary_key=True) + extension = Column(String(40), nullable=False) + available = Column(Boolean, nullable=False) + file_size = Column(Integer) + + +class ContentContentnode(Base): + __tablename__ = "content_contentnode" + + id = Column(CHAR(32), primary_key=True) + license_name = Column(String(50)) + license_description = Column(Text) + title = Column(String(200), nullable=False) + coach_content = Column(Boolean, nullable=False) + content_id = Column(CHAR(32), nullable=False, index=True) + channel_id = Column(CHAR(32), nullable=False, index=True) + description = Column(Text) + sort_order = Column(Float) + license_owner = Column(String(200), nullable=False) + author = Column(String(200), nullable=False) + kind = Column(String(200), nullable=False) + available = Column(Boolean, nullable=False) + lft = Column(Integer, nullable=False, index=True) + rght = Column(Integer, nullable=False, index=True) + tree_id = Column(Integer, nullable=False, index=True) + level = Column(Integer, nullable=False, index=True) + lang_id = Column(ForeignKey("content_language.id"), index=True) + parent_id = Column(ForeignKey("content_contentnode.id"), index=True) + + lang = relationship("ContentLanguage") + parent = relationship("ContentContentnode", remote_side=[id]) + + +class ContentAssessmentmetadata(Base): + __tablename__ = "content_assessmentmetadata" + + id = Column(CHAR(32), primary_key=True) + assessment_item_ids = Column(Text, nullable=False) + number_of_assessments = Column(Integer, nullable=False) + mastery_model = Column(Text, nullable=False) + randomize = Column(Boolean, nullable=False) + is_manipulable = Column(Boolean, nullable=False) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + + +class ContentChannelmetadata(Base): + __tablename__ = "content_channelmetadata" + + id = Column(CHAR(32), primary_key=True) + name = Column(String(200), nullable=False) + description = Column(String(400), nullable=False) + author = Column(String(400), nullable=False) + version = Column(Integer, nullable=False) + thumbnail = Column(Text, nullable=False) + last_updated = Column(String) + min_schema_version = Column(String(50), nullable=False) + root_id = Column(ForeignKey("content_contentnode.id"), nullable=False, index=True) + + root = relationship("ContentContentnode") + + +class ContentContentnodeHasPrerequisite(Base): + __tablename__ = "content_contentnode_has_prerequisite" + __table_args__ = ( + Index( + "content_contentnode_has_prerequisite_from_contentnode_id_to_contentnode_id_c9e1d527_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeRelated(Base): + __tablename__ = "content_contentnode_related" + __table_args__ = ( + Index( + "content_contentnode_related_from_contentnode_id_to_contentnode_id_fc2ed20c_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeTags(Base): + __tablename__ = "content_contentnode_tags" + __table_args__ = ( + Index( + "content_contentnode_tags_contentnode_id_contenttag_id_64a4ac15_uniq", + "contentnode_id", + "contenttag_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + contenttag_id = Column( + ForeignKey("content_contenttag.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + contenttag = relationship("ContentContenttag") + + +class ContentFile(Base): + __tablename__ = "content_file" + + id = Column(CHAR(32), primary_key=True) + preset = Column(String(150), nullable=False) + supplementary = Column(Boolean, nullable=False) + thumbnail = Column(Boolean, nullable=False) + priority = Column(Integer, index=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + lang_id = Column(ForeignKey("content_language.id"), index=True) + local_file_id = Column( + ForeignKey("content_localfile.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + lang = relationship("ContentLanguage") + local_file = relationship("ContentLocalfile") diff --git a/kolibri/core/content/contentschema/versions/content_schema_4.py b/kolibri/core/content/contentschema/versions/content_schema_4.py new file mode 100644 index 00000000000..61f087f75f3 --- /dev/null +++ b/kolibri/core/content/contentschema/versions/content_schema_4.py @@ -0,0 +1,204 @@ +# coding: utf-8 +from sqlalchemy import Boolean +from sqlalchemy import CHAR +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Text +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship + +Base = declarative_base() +metadata = Base.metadata + + +class ContentContenttag(Base): + __tablename__ = "content_contenttag" + + id = Column(CHAR(32), primary_key=True) + tag_name = Column(String(30), nullable=False) + + +class ContentLanguage(Base): + __tablename__ = "content_language" + + id = Column(String(14), primary_key=True) + lang_code = Column(String(3), nullable=False, index=True) + lang_subcode = Column(String(10), index=True) + lang_name = Column(String(100)) + lang_direction = Column(String(3), nullable=False) + + +class ContentLocalfile(Base): + __tablename__ = "content_localfile" + + id = Column(String(32), primary_key=True) + extension = Column(String(40), nullable=False) + available = Column(Boolean, nullable=False) + file_size = Column(Integer) + + +class ContentContentnode(Base): + __tablename__ = "content_contentnode" + + id = Column(CHAR(32), primary_key=True) + license_name = Column(String(50)) + license_description = Column(Text) + title = Column(String(200), nullable=False) + coach_content = Column(Boolean, nullable=False) + content_id = Column(CHAR(32), nullable=False, index=True) + channel_id = Column(CHAR(32), nullable=False, index=True) + description = Column(Text) + sort_order = Column(Float) + license_owner = Column(String(200), nullable=False) + author = Column(String(200), nullable=False) + kind = Column(String(200), nullable=False) + available = Column(Boolean, nullable=False) + options = Column(Text) + lft = Column(Integer, nullable=False, index=True) + rght = Column(Integer, nullable=False, index=True) + tree_id = Column(Integer, nullable=False, index=True) + level = Column(Integer, nullable=False, index=True) + lang_id = Column(ForeignKey("content_language.id"), index=True) + parent_id = Column(ForeignKey("content_contentnode.id"), index=True) + + lang = relationship("ContentLanguage") + parent = relationship("ContentContentnode", remote_side=[id]) + + +class ContentAssessmentmetadata(Base): + __tablename__ = "content_assessmentmetadata" + + id = Column(CHAR(32), primary_key=True) + assessment_item_ids = Column(Text, nullable=False) + number_of_assessments = Column(Integer, nullable=False) + mastery_model = Column(Text, nullable=False) + randomize = Column(Boolean, nullable=False) + is_manipulable = Column(Boolean, nullable=False) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + + +class ContentChannelmetadata(Base): + __tablename__ = "content_channelmetadata" + + id = Column(CHAR(32), primary_key=True) + name = Column(String(200), nullable=False) + description = Column(String(400), nullable=False) + tagline = Column(String(150)) + author = Column(String(400), nullable=False) + version = Column(Integer, nullable=False) + thumbnail = Column(Text, nullable=False) + last_updated = Column(String) + min_schema_version = Column(String(50), nullable=False) + root_id = Column(ForeignKey("content_contentnode.id"), nullable=False, index=True) + + root = relationship("ContentContentnode") + + +class ContentContentnodeHasPrerequisite(Base): + __tablename__ = "content_contentnode_has_prerequisite" + __table_args__ = ( + Index( + "content_contentnode_has_prerequisite_from_contentnode_id_to_contentnode_id_c9e1d527_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeRelated(Base): + __tablename__ = "content_contentnode_related" + __table_args__ = ( + Index( + "content_contentnode_related_from_contentnode_id_to_contentnode_id_fc2ed20c_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeTags(Base): + __tablename__ = "content_contentnode_tags" + __table_args__ = ( + Index( + "content_contentnode_tags_contentnode_id_contenttag_id_64a4ac15_uniq", + "contentnode_id", + "contenttag_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + contenttag_id = Column( + ForeignKey("content_contenttag.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + contenttag = relationship("ContentContenttag") + + +class ContentFile(Base): + __tablename__ = "content_file" + + id = Column(CHAR(32), primary_key=True) + preset = Column(String(150), nullable=False) + supplementary = Column(Boolean, nullable=False) + thumbnail = Column(Boolean, nullable=False) + priority = Column(Integer, index=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + lang_id = Column(ForeignKey("content_language.id"), index=True) + local_file_id = Column( + ForeignKey("content_localfile.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + lang = relationship("ContentLanguage") + local_file = relationship("ContentLocalfile") diff --git a/kolibri/core/content/contentschema/versions/content_schema_current.py b/kolibri/core/content/contentschema/versions/content_schema_current.py new file mode 100644 index 00000000000..6da096186f6 --- /dev/null +++ b/kolibri/core/content/contentschema/versions/content_schema_current.py @@ -0,0 +1,238 @@ +# coding: utf-8 +from sqlalchemy import BigInteger +from sqlalchemy import Boolean +from sqlalchemy import CHAR +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Text +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship + +Base = declarative_base() +metadata = Base.metadata + + +class ContentContenttag(Base): + __tablename__ = "content_contenttag" + + id = Column(CHAR(32), primary_key=True) + tag_name = Column(String(30), nullable=False) + + +class ContentLanguage(Base): + __tablename__ = "content_language" + + id = Column(String(14), primary_key=True) + lang_code = Column(String(3), nullable=False, index=True) + lang_subcode = Column(String(10), index=True) + lang_name = Column(String(100)) + lang_direction = Column(String(3), nullable=False) + + +class ContentLocalfile(Base): + __tablename__ = "content_localfile" + + id = Column(String(32), primary_key=True) + available = Column(Boolean, nullable=False) + file_size = Column(Integer) + extension = Column(String(40), nullable=False) + + +class ContentContentnode(Base): + __tablename__ = "content_contentnode" + __table_args__ = ( + Index( + "content_contentnode_level_channel_id_kind_fd732cc4_idx", + "level", + "channel_id", + "kind", + ), + Index( + "content_contentnode_level_channel_id_available_29f0bb18_idx", + "level", + "channel_id", + "available", + ), + ) + + id = Column(CHAR(32), primary_key=True) + title = Column(String(200), nullable=False) + content_id = Column(CHAR(32), nullable=False, index=True) + channel_id = Column(CHAR(32), nullable=False, index=True) + description = Column(Text) + sort_order = Column(Float) + license_owner = Column(String(200), nullable=False) + author = Column(String(200), nullable=False) + kind = Column(String(200), nullable=False) + available = Column(Boolean, nullable=False) + lft = Column(Integer, nullable=False, index=True) + rght = Column(Integer, nullable=False, index=True) + tree_id = Column(Integer, nullable=False, index=True) + level = Column(Integer, nullable=False, index=True) + lang_id = Column(ForeignKey("content_language.id"), index=True) + license_description = Column(Text) + license_name = Column(String(50)) + coach_content = Column(Boolean, nullable=False) + num_coach_contents = Column(Integer) + on_device_resources = Column(Integer) + options = Column(Text) + parent_id = Column(ForeignKey("content_contentnode.id"), index=True) + + lang = relationship("ContentLanguage") + parent = relationship("ContentContentnode", remote_side=[id]) + + +class ContentAssessmentmetadata(Base): + __tablename__ = "content_assessmentmetadata" + + id = Column(CHAR(32), primary_key=True) + assessment_item_ids = Column(Text, nullable=False) + number_of_assessments = Column(Integer, nullable=False) + mastery_model = Column(Text, nullable=False) + randomize = Column(Boolean, nullable=False) + is_manipulable = Column(Boolean, nullable=False) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + + +class ContentChannelmetadata(Base): + __tablename__ = "content_channelmetadata" + + id = Column(CHAR(32), primary_key=True) + name = Column(String(200), nullable=False) + description = Column(String(400), nullable=False) + author = Column(String(400), nullable=False) + version = Column(Integer, nullable=False) + thumbnail = Column(Text, nullable=False) + last_updated = Column(String) + min_schema_version = Column(String(50), nullable=False) + root_id = Column(ForeignKey("content_contentnode.id"), nullable=False, index=True) + published_size = Column(BigInteger) + total_resource_count = Column(Integer) + order = Column(Integer) + public = Column(Boolean) + tagline = Column(String(150)) + + root = relationship("ContentContentnode") + + +class ContentContentnodeHasPrerequisite(Base): + __tablename__ = "content_contentnode_has_prerequisite" + __table_args__ = ( + Index( + "content_contentnode_has_prerequisite_from_contentnode_id_to_contentnode_id_c9e1d527_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeRelated(Base): + __tablename__ = "content_contentnode_related" + __table_args__ = ( + Index( + "content_contentnode_related_from_contentnode_id_to_contentnode_id_fc2ed20c_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeTags(Base): + __tablename__ = "content_contentnode_tags" + __table_args__ = ( + Index( + "content_contentnode_tags_contentnode_id_contenttag_id_64a4ac15_uniq", + "contentnode_id", + "contenttag_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + contenttag_id = Column( + ForeignKey("content_contenttag.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + contenttag = relationship("ContentContenttag") + + +class ContentFile(Base): + __tablename__ = "content_file" + + id = Column(CHAR(32), primary_key=True) + preset = Column(String(150), nullable=False) + supplementary = Column(Boolean, nullable=False) + thumbnail = Column(Boolean, nullable=False) + priority = Column(Integer, index=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + lang_id = Column(ForeignKey("content_language.id"), index=True) + local_file_id = Column( + ForeignKey("content_localfile.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + lang = relationship("ContentLanguage") + local_file = relationship("ContentLocalfile") + + +class ContentChannelmetadataIncludedLanguages(Base): + __tablename__ = "content_channelmetadata_included_languages" + + id = Column(Integer, primary_key=True) + channelmetadata_id = Column( + ForeignKey("content_channelmetadata.id"), nullable=False + ) + language_id = Column(ForeignKey("content_language.id"), nullable=False) + + channelmetadata = relationship("ContentChannelmetadata") + language = relationship("ContentLanguage") diff --git a/kolibri/core/content/contentschema/versions/content_schema_unversioned.py b/kolibri/core/content/contentschema/versions/content_schema_unversioned.py new file mode 100644 index 00000000000..7aa3fb200a9 --- /dev/null +++ b/kolibri/core/content/contentschema/versions/content_schema_unversioned.py @@ -0,0 +1,192 @@ +# coding: utf-8 +from sqlalchemy import Boolean +from sqlalchemy import CHAR +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Text +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship + +Base = declarative_base() +metadata = Base.metadata + + +class ContentChannelmetadata(Base): + __tablename__ = "content_channelmetadata" + + id = Column(CHAR(32), primary_key=True) + name = Column(String(200), nullable=False) + description = Column(String(400), nullable=False) + author = Column(String(400), nullable=False) + version = Column(Integer, nullable=False) + thumbnail = Column(Text, nullable=False) + root_pk = Column(CHAR(32), nullable=False) + + +class ContentContenttag(Base): + __tablename__ = "content_contenttag" + + id = Column(CHAR(32), primary_key=True) + tag_name = Column(String(30), nullable=False) + + +class ContentLanguage(Base): + __tablename__ = "content_language" + + id = Column(String(14), primary_key=True) + lang_code = Column(String(3), nullable=False, index=True) + lang_subcode = Column(String(10), index=True) + lang_name = Column(String(100)) + lang_direction = Column(String(3), nullable=False) + + +class ContentLicense(Base): + __tablename__ = "content_license" + + id = Column(Integer, primary_key=True) + license_name = Column(String(50), nullable=False) + license_description = Column(String(400)) + + +class ContentContentnode(Base): + __tablename__ = "content_contentnode" + + id = Column(CHAR(32), primary_key=True) + title = Column(String(200), nullable=False) + content_id = Column(CHAR(32), nullable=False, index=True) + description = Column(String(400)) + sort_order = Column(Float) + license_owner = Column(String(200), nullable=False) + author = Column(String(200), nullable=False) + kind = Column(String(200), nullable=False) + available = Column(Boolean, nullable=False) + stemmed_metaphone = Column(String(1800), nullable=False) + lft = Column(Integer, nullable=False, index=True) + rght = Column(Integer, nullable=False, index=True) + tree_id = Column(Integer, nullable=False, index=True) + level = Column(Integer, nullable=False, index=True) + lang_id = Column(ForeignKey("content_language.id"), index=True) + license_id = Column(ForeignKey("content_license.id"), index=True) + parent_id = Column(ForeignKey("content_contentnode.id"), index=True) + + lang = relationship("ContentLanguage") + license = relationship("ContentLicense") + parent = relationship("ContentContentnode", remote_side=[id]) + + +class ContentAssessmentmetadata(Base): + __tablename__ = "content_assessmentmetadata" + + id = Column(CHAR(32), primary_key=True) + assessment_item_ids = Column(Text, nullable=False) + number_of_assessments = Column(Integer, nullable=False) + mastery_model = Column(Text, nullable=False) + randomize = Column(Boolean, nullable=False) + is_manipulable = Column(Boolean, nullable=False) + contentnode_id = Column(ForeignKey("content_contentnode.id"), index=True) + + contentnode = relationship("ContentContentnode") + + +class ContentContentnodeHasPrerequisite(Base): + __tablename__ = "content_contentnode_has_prerequisite" + __table_args__ = ( + Index( + "content_contentnode_has_prerequisite_from_contentnode_id_c9e1d527_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeRelated(Base): + __tablename__ = "content_contentnode_related" + __table_args__ = ( + Index( + "content_contentnode_related_from_contentnode_id_fc2ed20c_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeTags(Base): + __tablename__ = "content_contentnode_tags" + __table_args__ = ( + Index( + "content_contentnode_tags_contentnode_id_64a4ac15_uniq", + "contentnode_id", + "contenttag_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + contenttag_id = Column( + ForeignKey("content_contenttag.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + contenttag = relationship("ContentContenttag") + + +class ContentFile(Base): + __tablename__ = "content_file" + + id = Column(CHAR(32), primary_key=True) + checksum = Column(String(400), nullable=False) + extension = Column(String(40), nullable=False) + available = Column(Boolean, nullable=False) + file_size = Column(Integer) + preset = Column(String(150), nullable=False) + supplementary = Column(Boolean, nullable=False) + thumbnail = Column(Boolean, nullable=False) + priority = Column(Integer) + contentnode_id = Column(ForeignKey("content_contentnode.id"), index=True) + lang_id = Column(ForeignKey("content_language.id"), index=True) + + contentnode = relationship("ContentContentnode") + lang = relationship("ContentLanguage") diff --git a/kolibri/core/content/contentschema/versions/content_schema_v020beta1.py b/kolibri/core/content/contentschema/versions/content_schema_v020beta1.py new file mode 100644 index 00000000000..d26c3aebb11 --- /dev/null +++ b/kolibri/core/content/contentschema/versions/content_schema_v020beta1.py @@ -0,0 +1,173 @@ +# coding: utf-8 +from sqlalchemy import Boolean +from sqlalchemy import CHAR +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Text +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship + +Base = declarative_base() +metadata = Base.metadata + + +class ContentChannelmetadata(Base): + __tablename__ = "content_channelmetadata" + + id = Column(CHAR(32), primary_key=True) + name = Column(String(200), nullable=False) + description = Column(String(400), nullable=False) + author = Column(String(400), nullable=False) + version = Column(Integer, nullable=False) + thumbnail = Column(Text, nullable=False) + root_pk = Column(CHAR(32), nullable=False) + + +class ContentContenttag(Base): + __tablename__ = "content_contenttag" + + id = Column(CHAR(32), primary_key=True) + tag_name = Column(String(30), nullable=False) + + +class ContentLanguage(Base): + __tablename__ = "content_language" + + id = Column(String(7), primary_key=True) + lang_code = Column(String(3), nullable=False, index=True) + lang_subcode = Column(String(3), index=True) + + +class ContentLicense(Base): + __tablename__ = "content_license" + + id = Column(Integer, primary_key=True) + license_name = Column(String(50), nullable=False) + + +class ContentContentnode(Base): + __tablename__ = "content_contentnode" + + id = Column(CHAR(32), primary_key=True) + title = Column(String(200), nullable=False) + content_id = Column(CHAR(32), nullable=False) + description = Column(String(400)) + sort_order = Column(Float) + license_owner = Column(String(200), nullable=False) + author = Column(String(200), nullable=False) + kind = Column(String(200), nullable=False) + available = Column(Boolean, nullable=False) + lft = Column(Integer, nullable=False, index=True) + rght = Column(Integer, nullable=False, index=True) + tree_id = Column(Integer, nullable=False, index=True) + level = Column(Integer, nullable=False, index=True) + license_id = Column(ForeignKey("content_license.id"), index=True) + stemmed_metaphone = Column(String(1800), nullable=False) + parent_id = Column(ForeignKey("content_contentnode.id"), index=True) + + license = relationship("ContentLicense") + parent = relationship("ContentContentnode", remote_side=[id]) + + +class ContentContentnodeHasPrerequisite(Base): + __tablename__ = "content_contentnode_has_prerequisite" + __table_args__ = ( + Index( + "content_contentnode_has_prerequisite_from_contentnode_id_c9e1d527_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeRelated(Base): + __tablename__ = "content_contentnode_related" + __table_args__ = ( + Index( + "content_contentnode_related_from_contentnode_id_fc2ed20c_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeTags(Base): + __tablename__ = "content_contentnode_tags" + __table_args__ = ( + Index( + "content_contentnode_tags_contentnode_id_64a4ac15_uniq", + "contentnode_id", + "contenttag_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + contenttag_id = Column( + ForeignKey("content_contenttag.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + contenttag = relationship("ContentContenttag") + + +class ContentFile(Base): + __tablename__ = "content_file" + + id = Column(CHAR(32), primary_key=True) + checksum = Column(String(400), nullable=False) + extension = Column(String(40), nullable=False) + available = Column(Boolean, nullable=False) + file_size = Column(Integer) + supplementary = Column(Boolean, nullable=False) + thumbnail = Column(Boolean, nullable=False) + priority = Column(Integer) + contentnode_id = Column(ForeignKey("content_contentnode.id"), index=True) + lang_id = Column(ForeignKey("content_language.id"), index=True) + preset = Column(String(150), nullable=False) + + contentnode = relationship("ContentContentnode") + lang = relationship("ContentLanguage") diff --git a/kolibri/core/content/contentschema/versions/content_schema_v040beta3.py b/kolibri/core/content/contentschema/versions/content_schema_v040beta3.py new file mode 100644 index 00000000000..012547799a4 --- /dev/null +++ b/kolibri/core/content/contentschema/versions/content_schema_v040beta3.py @@ -0,0 +1,188 @@ +# coding: utf-8 +from sqlalchemy import Boolean +from sqlalchemy import CHAR +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Text +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship + +Base = declarative_base() +metadata = Base.metadata + + +class ContentChannelmetadata(Base): + __tablename__ = "content_channelmetadata" + + id = Column(CHAR(32), primary_key=True) + name = Column(String(200), nullable=False) + description = Column(String(400), nullable=False) + author = Column(String(400), nullable=False) + version = Column(Integer, nullable=False) + thumbnail = Column(Text, nullable=False) + root_pk = Column(CHAR(32), nullable=False) + + +class ContentContenttag(Base): + __tablename__ = "content_contenttag" + + id = Column(CHAR(32), primary_key=True) + tag_name = Column(String(30), nullable=False) + + +class ContentLanguage(Base): + __tablename__ = "content_language" + + id = Column(String(7), primary_key=True) + lang_code = Column(String(3), nullable=False, index=True) + lang_subcode = Column(String(3), index=True) + + +class ContentLicense(Base): + __tablename__ = "content_license" + + id = Column(Integer, primary_key=True) + license_name = Column(String(50), nullable=False) + license_description = Column(String(400)) + + +class ContentContentnode(Base): + __tablename__ = "content_contentnode" + + id = Column(CHAR(32), primary_key=True) + title = Column(String(200), nullable=False) + content_id = Column(CHAR(32), nullable=False, index=True) + description = Column(String(400)) + sort_order = Column(Float) + license_owner = Column(String(200), nullable=False) + author = Column(String(200), nullable=False) + kind = Column(String(200), nullable=False) + available = Column(Boolean, nullable=False) + stemmed_metaphone = Column(String(1800), nullable=False) + lft = Column(Integer, nullable=False, index=True) + rght = Column(Integer, nullable=False, index=True) + tree_id = Column(Integer, nullable=False, index=True) + level = Column(Integer, nullable=False, index=True) + license_id = Column(ForeignKey("content_license.id"), index=True) + parent_id = Column(ForeignKey("content_contentnode.id"), index=True) + + license = relationship("ContentLicense") + parent = relationship("ContentContentnode", remote_side=[id]) + + +class ContentAssessmentmetadata(Base): + __tablename__ = "content_assessmentmetadata" + + id = Column(CHAR(32), primary_key=True) + assessment_item_ids = Column(Text, nullable=False) + number_of_assessments = Column(Integer, nullable=False) + mastery_model = Column(Text, nullable=False) + randomize = Column(Boolean, nullable=False) + is_manipulable = Column(Boolean, nullable=False) + contentnode_id = Column(ForeignKey("content_contentnode.id"), index=True) + + contentnode = relationship("ContentContentnode") + + +class ContentContentnodeHasPrerequisite(Base): + __tablename__ = "content_contentnode_has_prerequisite" + __table_args__ = ( + Index( + "content_contentnode_has_prerequisite_from_contentnode_id_c9e1d527_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeHasPrerequisite.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeRelated(Base): + __tablename__ = "content_contentnode_related" + __table_args__ = ( + Index( + "content_contentnode_related_from_contentnode_id_fc2ed20c_uniq", + "from_contentnode_id", + "to_contentnode_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + from_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + to_contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + + from_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.from_contentnode_id == ContentContentnode.id", + ) + to_contentnode = relationship( + "ContentContentnode", + primaryjoin="ContentContentnodeRelated.to_contentnode_id == ContentContentnode.id", + ) + + +class ContentContentnodeTags(Base): + __tablename__ = "content_contentnode_tags" + __table_args__ = ( + Index( + "content_contentnode_tags_contentnode_id_64a4ac15_uniq", + "contentnode_id", + "contenttag_id", + unique=True, + ), + ) + + id = Column(Integer, primary_key=True) + contentnode_id = Column( + ForeignKey("content_contentnode.id"), nullable=False, index=True + ) + contenttag_id = Column( + ForeignKey("content_contenttag.id"), nullable=False, index=True + ) + + contentnode = relationship("ContentContentnode") + contenttag = relationship("ContentContenttag") + + +class ContentFile(Base): + __tablename__ = "content_file" + + id = Column(CHAR(32), primary_key=True) + checksum = Column(String(400), nullable=False) + extension = Column(String(40), nullable=False) + available = Column(Boolean, nullable=False) + file_size = Column(Integer) + preset = Column(String(150), nullable=False) + supplementary = Column(Boolean, nullable=False) + thumbnail = Column(Boolean, nullable=False) + priority = Column(Integer) + contentnode_id = Column(ForeignKey("content_contentnode.id"), index=True) + lang_id = Column(ForeignKey("content_language.id"), index=True) + + contentnode = relationship("ContentContentnode") + lang = relationship("ContentLanguage") diff --git a/kolibri/core/content/fixtures/1_content_schema b/kolibri/core/content/fixtures/1_content_schema deleted file mode 100644 index 94ff91848f9..00000000000 Binary files a/kolibri/core/content/fixtures/1_content_schema and /dev/null differ diff --git a/kolibri/core/content/fixtures/2_content_schema b/kolibri/core/content/fixtures/2_content_schema deleted file mode 100644 index 7d1f09c953d..00000000000 Binary files a/kolibri/core/content/fixtures/2_content_schema and /dev/null differ diff --git a/kolibri/core/content/fixtures/3_content_schema b/kolibri/core/content/fixtures/3_content_schema deleted file mode 100644 index 47d2a9baf1e..00000000000 Binary files a/kolibri/core/content/fixtures/3_content_schema and /dev/null differ diff --git a/kolibri/core/content/fixtures/4_content_data.json b/kolibri/core/content/fixtures/4_content_data.json index b1b7b495521..841e2105cd3 100644 --- a/kolibri/core/content/fixtures/4_content_data.json +++ b/kolibri/core/content/fixtures/4_content_data.json @@ -1 +1 @@ -{"content_contenttag": [{"tag_name": "tag_1", "id": "1d21d6cf40554b68977366f8218248ae"}, {"tag_name": "tag_2", "id": "77b57a14a1f0466bb27ea7de8ff468be"}, {"tag_name": "tag_3", "id": "222455c2cc484298b1501a13e1c7eb5c"}], "content_contentnode_has_prerequisite": [{"from_contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c", "to_contentnode_id": "da7ecc42e62553eebc8121242746e88a", "id": 1}], "content_contentnode": [{"options": "{}", "content_id": "ffdfadc415214ec0b1438f002f23d7bf", "lft": 1, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 12, "description": "balbla1", "id": "da7ecc42e62553eebc8121242746e88a", "tree_id": 1, "sort_order": null, "license_name": "WTFPL", "level": 0, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "root", "parent_id": null, "kind": "topic", "coach_content": false, "available": false}, {"options": "{}", "content_id": "c6f49ea527824f398f4d5d26faf19396", "lft": 2, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 3, "description": "balbla2", "id": "32a941fb77c2576e8f6b294cde4c3b0c", "tree_id": 1, "sort_order": null, "license_name": "WTFPL", "level": 1, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "c1", "parent_id": "da7ecc42e62553eebc8121242746e88a", "kind": "video", "coach_content": false, "available": false}, {"options": "{}", "content_id": "1fe38e5af42f42678f8db66a18f8267a", "lft": 4, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 11, "description": "balbla3", "id": "2e8bac07947855369fe2d77642dfc870", "tree_id": 1, "sort_order": null, "license_name": "GNU", "level": 1, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "c2", "parent_id": "da7ecc42e62553eebc8121242746e88a", "kind": "topic", "coach_content": false, "available": false}, {"options": "{}", "content_id": "ce603df7c46b424b934348995e1b05fb", "lft": 5, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 6, "description": "balbla4", "id": "2b6926ed22025518a8b9da91745b51d3", "tree_id": 1, "sort_order": null, "license_name": "CC", "level": 2, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "c2c1", "parent_id": "2e8bac07947855369fe2d77642dfc870", "kind": "exercise", "coach_content": false, "available": false}, {"options": "{}", "content_id": "481e1bda1faa445d801ceb2afbd2f42f", "lft": 7, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 8, "description": "balbla5", "id": "4d0c890de9b65d6880ccfa527800e0f4", "tree_id": 1, "sort_order": null, "license_name": "GNU", "level": 2, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "c2c2", "parent_id": "2e8bac07947855369fe2d77642dfc870", "kind": "document", "coach_content": false, "available": false}, {"options": "{}", "content_id": "f2332710c2fd483386cdeb5ecbdda81f", "lft": 9, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 10, "description": "balbla5", "id": "b391bfeec8a458f89f013cf1ca9cf33a", "tree_id": 1, "sort_order": null, "license_name": "GNU", "level": 2, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "c2c3", "parent_id": "2e8bac07947855369fe2d77642dfc870", "kind": "audio", "coach_content": false, "available": false}, {"options": "{}", "content_id": "f2332710c2fd483386cdeb5ecbdda81e", "lft": 9, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 10, "description": "balbla5", "id": "b391bfeec8a458f89f013cf1ca9cf331", "tree_id": 1, "sort_order": null, "license_name": null, "level": 2, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "no_license", "parent_id": "da7ecc42e62553eebc8121242746e88a", "kind": "audio", "coach_content": false, "available": false}], "content_language": [{"lang_direction": "ltr", "lang_subcode": "01", "id": "1", "lang_name": "English-Test", "lang_code": "en"}, {"lang_direction": "ltr", "lang_subcode": "02", "id": "2", "lang_name": "Chinese-Test", "lang_code": "zh"}, {"lang_direction": "ltr", "lang_subcode": "03", "id": "3", "lang_name": "Spanish-Test", "lang_code": "es"}], "content_contentnode_related": [{"to_contentnode_id": "2e8bac07947855369fe2d77642dfc870", "id": 1, "from_contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c"}, {"to_contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c", "id": 2, "from_contentnode_id": "2e8bac07947855369fe2d77642dfc870"}], "content_contentnode_tags": [{"contenttag_id": "77b57a14a1f0466bb27ea7de8ff468be", "id": 1, "contentnode_id": "da7ecc42e62553eebc8121242746e88a"}, {"contenttag_id": "1d21d6cf40554b68977366f8218248ae", "id": 2, "contentnode_id": "da7ecc42e62553eebc8121242746e88a"}, {"contenttag_id": "222455c2cc484298b1501a13e1c7eb5c", "id": 3, "contentnode_id": "da7ecc42e62553eebc8121242746e88a"}, {"contenttag_id": "1d21d6cf40554b68977366f8218248ae", "id": 4, "contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c"}, {"contenttag_id": "77b57a14a1f0466bb27ea7de8ff468be", "id": 5, "contentnode_id": "2e8bac07947855369fe2d77642dfc870"}], "content_file": [{"contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c", "thumbnail": false, "preset": "high_res_video", "lang_id": "1", "priority": null, "supplementary": false, "id": "9f9438fe6b0d42dd8e913d7d04cfb2b1", "local_file_id": "9f9438fe6b0d42dd8e913d7d04cfb2b2"}, {"contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c", "thumbnail": false, "preset": "low_res_video", "lang_id": "1", "priority": null, "supplementary": false, "id": "725257a0570044acbd59f8cf6a68b2bf", "local_file_id": "725257a0570044acbd59f8cf6a68b2be"}, {"contentnode_id": "2b6926ed22025518a8b9da91745b51d3", "thumbnail": false, "preset": "vector_video", "lang_id": "3", "priority": null, "supplementary": false, "id": "e00699f859624e0f875ac6fe1e13d649", "local_file_id": "e00699f859624e0f875ac6fe1e13d648"}, {"contentnode_id": "2b6926ed22025518a8b9da91745b51d3", "thumbnail": false, "preset": "low_res_video", "lang_id": null, "priority": null, "supplementary": false, "id": "4c30dc7619f74f97ae2ccd4fffd09bf3", "local_file_id": "4c30dc7619f74f97ae2ccd4fffd09bf2"}, {"contentnode_id": "2b6926ed22025518a8b9da91745b51d3", "thumbnail": false, "preset": "caption", "lang_id": null, "priority": null, "supplementary": true, "id": "8ad3fffedf144cba9492e16daec1e39b", "local_file_id": "8ad3fffedf144cba9492e16daec1e39a"}], "content_localfile": [{"file_size": null, "id": "9f9438fe6b0d42dd8e913d7d04cfb2b2", "available": false, "extension": "mp4"}, {"file_size": null, "id": "725257a0570044acbd59f8cf6a68b2be", "available": false, "extension": "mp4"}, {"file_size": null, "id": "e00699f859624e0f875ac6fe1e13d648", "available": false, "extension": "mp4"}, {"file_size": null, "id": "4c30dc7619f74f97ae2ccd4fffd09bf2", "available": false, "extension": "mp3"}, {"file_size": null, "id": "8ad3fffedf144cba9492e16daec1e39a", "available": false, "extension": "vtt"}], "content_assessmentmetadata": [{"randomize": false, "number_of_assessments": 1, "id": "6199dde695db4ee4ab392222d5af1e5c", "is_manipulable": false, "mastery_model": "{}", "assessment_item_ids": "[\"test\"]", "contentnode_id": "2b6926ed22025518a8b9da91745b51d3"}], "content_channelmetadata": [{"version": 0, "tagline": null, "name": "testing", "min_schema_version": "1", "thumbnail": "", "author": "eli", "description": "test data", "id": "6199dde695db4ee4ab392222d5af1e5c", "root_id": "da7ecc42e62553eebc8121242746e88a", "last_updated": null}]} \ No newline at end of file +{"content_contenttag": [{"tag_name": "tag_1", "id": "1d21d6cf40554b68977366f8218248ae"}, {"tag_name": "tag_2", "id": "77b57a14a1f0466bb27ea7de8ff468be"}, {"tag_name": "tag_3", "id": "222455c2cc484298b1501a13e1c7eb5c"}], "content_contentnode_has_prerequisite": [{"from_contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c", "id": 1, "to_contentnode_id": "da7ecc42e62553eebc8121242746e88a"}], "content_contentnode": [{"options": "{}", "content_id": "ffdfadc415214ec0b1438f002f23d7bf", "lft": 1, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 12, "description": "balbla1", "id": "da7ecc42e62553eebc8121242746e88a", "tree_id": 1, "sort_order": null, "license_name": "WTFPL", "level": 0, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "root", "parent_id": null, "kind": "topic", "coach_content": false, "available": false}, {"options": "{}", "content_id": "c6f49ea527824f398f4d5d26faf19396", "lft": 2, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 3, "description": "balbla2", "id": "32a941fb77c2576e8f6b294cde4c3b0c", "tree_id": 1, "sort_order": null, "license_name": "WTFPL", "level": 1, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "c1", "parent_id": "da7ecc42e62553eebc8121242746e88a", "kind": "video", "coach_content": false, "available": false}, {"options": "{}", "content_id": "1fe38e5af42f42678f8db66a18f8267a", "lft": 4, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 11, "description": "balbla3", "id": "2e8bac07947855369fe2d77642dfc870", "tree_id": 1, "sort_order": null, "license_name": "GNU", "level": 1, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "c2", "parent_id": "da7ecc42e62553eebc8121242746e88a", "kind": "topic", "coach_content": false, "available": false}, {"options": "{}", "content_id": "ce603df7c46b424b934348995e1b05fb", "lft": 5, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 6, "description": "balbla4", "id": "2b6926ed22025518a8b9da91745b51d3", "tree_id": 1, "sort_order": null, "license_name": "CC", "level": 2, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "c2c1", "parent_id": "2e8bac07947855369fe2d77642dfc870", "kind": "exercise", "coach_content": false, "available": false}, {"options": "{}", "content_id": "481e1bda1faa445d801ceb2afbd2f42f", "lft": 7, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 8, "description": "balbla5", "id": "4d0c890de9b65d6880ccfa527800e0f4", "tree_id": 1, "sort_order": null, "license_name": "GNU", "level": 2, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "c2c2", "parent_id": "2e8bac07947855369fe2d77642dfc870", "kind": "document", "coach_content": false, "available": false}, {"options": "{}", "content_id": "f2332710c2fd483386cdeb5ecbdda81f", "lft": 9, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 10, "description": "balbla5", "id": "b391bfeec8a458f89f013cf1ca9cf33a", "tree_id": 1, "sort_order": null, "license_name": "GNU", "level": 2, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "c2c3", "parent_id": "2e8bac07947855369fe2d77642dfc870", "kind": "audio", "coach_content": false, "available": false}, {"options": "{}", "content_id": "f2332710c2fd483386cdeb5ecbdda81e", "lft": 9, "channel_id": "6199dde695db4ee4ab392222d5af1e5c", "rght": 10, "description": "balbla5", "id": "b391bfeec8a458f89f013cf1ca9cf331", "tree_id": 1, "sort_order": null, "license_name": null, "level": 2, "license_owner": "", "license_description": null, "lang_id": null, "author": "", "title": "no_license", "parent_id": "da7ecc42e62553eebc8121242746e88a", "kind": "audio", "coach_content": false, "available": false}], "content_language": [{"lang_direction": "ltr", "lang_subcode": "01", "id": "1", "lang_name": "English-Test", "lang_code": "en"}, {"lang_direction": "ltr", "lang_subcode": "02", "id": "2", "lang_name": "Chinese-Test", "lang_code": "zh"}, {"lang_direction": "ltr", "lang_subcode": "03", "id": "3", "lang_name": "Spanish-Test", "lang_code": "es"}], "content_contentnode_related": [{"id": 1, "from_contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c", "to_contentnode_id": "2e8bac07947855369fe2d77642dfc870"}, {"id": 2, "from_contentnode_id": "2e8bac07947855369fe2d77642dfc870", "to_contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c"}], "content_contentnode_tags": [{"contentnode_id": "da7ecc42e62553eebc8121242746e88a", "contenttag_id": "77b57a14a1f0466bb27ea7de8ff468be", "id": 1}, {"contentnode_id": "da7ecc42e62553eebc8121242746e88a", "contenttag_id": "1d21d6cf40554b68977366f8218248ae", "id": 2}, {"contentnode_id": "da7ecc42e62553eebc8121242746e88a", "contenttag_id": "222455c2cc484298b1501a13e1c7eb5c", "id": 3}, {"contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c", "contenttag_id": "1d21d6cf40554b68977366f8218248ae", "id": 4}, {"contentnode_id": "2e8bac07947855369fe2d77642dfc870", "contenttag_id": "77b57a14a1f0466bb27ea7de8ff468be", "id": 5}], "content_file": [{"priority": null, "supplementary": false, "id": "9f9438fe6b0d42dd8e913d7d04cfb2b1", "local_file_id": "9f9438fe6b0d42dd8e913d7d04cfb2b2", "contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c", "thumbnail": false, "preset": "high_res_video", "lang_id": "1"}, {"priority": null, "supplementary": false, "id": "725257a0570044acbd59f8cf6a68b2bf", "local_file_id": "725257a0570044acbd59f8cf6a68b2be", "contentnode_id": "32a941fb77c2576e8f6b294cde4c3b0c", "thumbnail": false, "preset": "low_res_video", "lang_id": "1"}, {"priority": null, "supplementary": false, "id": "e00699f859624e0f875ac6fe1e13d649", "local_file_id": "e00699f859624e0f875ac6fe1e13d648", "contentnode_id": "2b6926ed22025518a8b9da91745b51d3", "thumbnail": false, "preset": "vector_video", "lang_id": "3"}, {"priority": null, "supplementary": false, "id": "4c30dc7619f74f97ae2ccd4fffd09bf3", "local_file_id": "4c30dc7619f74f97ae2ccd4fffd09bf2", "contentnode_id": "2b6926ed22025518a8b9da91745b51d3", "thumbnail": false, "preset": "low_res_video", "lang_id": null}, {"priority": null, "supplementary": true, "id": "8ad3fffedf144cba9492e16daec1e39b", "local_file_id": "8ad3fffedf144cba9492e16daec1e39a", "contentnode_id": "2b6926ed22025518a8b9da91745b51d3", "thumbnail": false, "preset": "caption", "lang_id": null}], "content_localfile": [{"file_size": null, "extension": "mp4", "available": false, "id": "9f9438fe6b0d42dd8e913d7d04cfb2b2"}, {"file_size": null, "extension": "mp4", "available": false, "id": "725257a0570044acbd59f8cf6a68b2be"}, {"file_size": null, "extension": "mp4", "available": false, "id": "e00699f859624e0f875ac6fe1e13d648"}, {"file_size": null, "extension": "mp3", "available": false, "id": "4c30dc7619f74f97ae2ccd4fffd09bf2"}, {"file_size": null, "extension": "vtt", "available": false, "id": "8ad3fffedf144cba9492e16daec1e39a"}], "content_assessmentmetadata": [{"mastery_model": "{}", "assessment_item_ids": "[\"test\"]", "contentnode_id": "2b6926ed22025518a8b9da91745b51d3", "randomize": false, "number_of_assessments": 1, "id": "6199dde695db4ee4ab392222d5af1e5c", "is_manipulable": false}], "content_channelmetadata": [{"root_id": "da7ecc42e62553eebc8121242746e88a", "last_updated": null, "version": 0, "author": "eli", "description": "test data", "tagline": null, "min_schema_version": "1", "thumbnail": "", "name": "testing", "id": "6199dde695db4ee4ab392222d5af1e5c"}]} \ No newline at end of file diff --git a/kolibri/core/content/fixtures/4_content_schema b/kolibri/core/content/fixtures/4_content_schema deleted file mode 100644 index c651a294033..00000000000 Binary files a/kolibri/core/content/fixtures/4_content_schema and /dev/null differ diff --git a/kolibri/core/content/fixtures/current_content_schema b/kolibri/core/content/fixtures/current_content_schema deleted file mode 100644 index a620124dbfd..00000000000 Binary files a/kolibri/core/content/fixtures/current_content_schema and /dev/null differ diff --git a/kolibri/core/content/fixtures/unversioned_content_schema b/kolibri/core/content/fixtures/unversioned_content_schema deleted file mode 100644 index d233238b993..00000000000 Binary files a/kolibri/core/content/fixtures/unversioned_content_schema and /dev/null differ diff --git a/kolibri/core/content/fixtures/v0.2.0-beta1_content_schema b/kolibri/core/content/fixtures/v0.2.0-beta1_content_schema deleted file mode 100644 index c7141d3f5f6..00000000000 Binary files a/kolibri/core/content/fixtures/v0.2.0-beta1_content_schema and /dev/null differ diff --git a/kolibri/core/content/fixtures/v0.4.0-beta3_content_schema b/kolibri/core/content/fixtures/v0.4.0-beta3_content_schema deleted file mode 100644 index 2449e8f27ed..00000000000 Binary files a/kolibri/core/content/fixtures/v0.4.0-beta3_content_schema and /dev/null differ diff --git a/kolibri/core/content/management/commands/generate_schema.py b/kolibri/core/content/management/commands/generate_schema.py index 35faf8972a2..197100a6be7 100644 --- a/kolibri/core/content/management/commands/generate_schema.py +++ b/kolibri/core/content/management/commands/generate_schema.py @@ -1,7 +1,6 @@ import io import json import os -import pickle import shutil import sys from collections import defaultdict @@ -12,17 +11,21 @@ from django.core.management import call_command from django.core.management.base import BaseCommand from django.db import connections +from sqlacodegen.codegen import CodeGenerator from sqlalchemy import create_engine from sqlalchemy import MetaData -from sqlalchemy.ext.automap import automap_base from sqlalchemy.orm import sessionmaker from kolibri.core.content.apps import KolibriContentConfig from kolibri.core.content.constants.schema_versions import CONTENT_SCHEMA_VERSION from kolibri.core.content.constants.schema_versions import CURRENT_SCHEMA_VERSION +from kolibri.core.content.utils.sqlalchemybridge import ( + coerce_version_name_to_valid_module_path, +) from kolibri.core.content.utils.sqlalchemybridge import get_default_db_string -from kolibri.core.content.utils.sqlalchemybridge import SCHEMA_PATH_TEMPLATE +from kolibri.core.content.utils.sqlalchemybridge import prepare_base from kolibri.core.content.utils.sqlalchemybridge import SharingPool +from kolibri.core.content.utils.sqlalchemybridge import SQLALCHEMY_CLASSES_PATH_TEMPLATE DATA_PATH_TEMPLATE = os.path.join( os.path.dirname(__file__), "../../fixtures/{name}_content_data.json" @@ -93,13 +96,23 @@ def handle(self, *args, **options): if name != "channelmetadatacache" ] metadata.reflect(bind=engine, only=table_names) - Base = automap_base(metadata=metadata) + Base = prepare_base(metadata, name=version) # TODO map relationship backreferences using the django names - Base.prepare() session = sessionmaker(bind=engine, autoflush=False)() - with open(SCHEMA_PATH_TEMPLATE.format(name=version), "wb") as f: - pickle.dump(metadata, f, protocol=2) + metadata.bind = engine + + generator = CodeGenerator( + metadata, False, False, True, True, False, nocomments=False + ) + + with io.open( + SQLALCHEMY_CLASSES_PATH_TEMPLATE.format( + name=coerce_version_name_to_valid_module_path(version) + ), + "w", + ) as f: + generator.render(f) # Only do this if we are generating a new export schema version if not no_export_schema: diff --git a/kolibri/core/content/test/test_channel_import.py b/kolibri/core/content/test/test_channel_import.py index 1004245db12..65e09889d8a 100644 --- a/kolibri/core/content/test/test_channel_import.py +++ b/kolibri/core/content/test/test_channel_import.py @@ -1,7 +1,6 @@ import io import json import os -import pickle import tempfile import uuid @@ -37,6 +36,7 @@ from kolibri.core.content.utils.channel_import import ChannelImport from kolibri.core.content.utils.channel_import import import_channel_from_local_db from kolibri.core.content.utils.sqlalchemybridge import get_default_db_string +from kolibri.core.content.utils.sqlalchemybridge import load_metadata @patch("kolibri.core.content.utils.channel_import.Bridge") @@ -405,8 +405,7 @@ def set_content_fixture(self, db_path_mock): "sqlite:///" + self.content_db_path, convert_unicode=True ) - with open(SCHEMA_PATH_TEMPLATE.format(name=self.schema_name), "rb") as f: - metadata = pickle.load(f) + metadata = load_metadata(self.schema_name) data_path = DATA_PATH_TEMPLATE.format(name=self.data_name) with io.open(data_path, mode="r", encoding="utf-8") as f: diff --git a/kolibri/core/content/utils/sqlalchemybridge.py b/kolibri/core/content/utils/sqlalchemybridge.py index ee453568aa7..75261f911d6 100644 --- a/kolibri/core/content/utils/sqlalchemybridge.py +++ b/kolibri/core/content/utils/sqlalchemybridge.py @@ -1,6 +1,6 @@ +import importlib import logging import os -import pickle from django.apps import apps from django.conf import settings @@ -176,21 +176,53 @@ def set_all_class_defaults(Base): pass -SCHEMA_PATH_TEMPLATE = os.path.join( - os.path.dirname(__file__), "../fixtures/{name}_content_schema" +__SQLALCHEMY_CLASSES_PATH = ( + "contentschema", + "versions", ) +__SQLALCHEMY_CLASSES_MODULE_NAME = "content_schema_{name}" -def prepare_bases(): +SQLALCHEMY_CLASSES_PATH_TEMPLATE = os.path.join( + os.path.dirname(__file__), + "..", + *(__SQLALCHEMY_CLASSES_PATH + (__SQLALCHEMY_CLASSES_MODULE_NAME + ".py",)) +) - for name in CONTENT_DB_SCHEMA_VERSIONS + [CURRENT_SCHEMA_VERSION]: +SQLALCHEMY_CLASSES_MODULE_PATH_TEMPLATE = ".".join( + tuple(__name__.split(".")[:-2]) + + __SQLALCHEMY_CLASSES_PATH + + (__SQLALCHEMY_CLASSES_MODULE_NAME,) +) - with open(SCHEMA_PATH_TEMPLATE.format(name=name), "rb") as f: - metadata = pickle.load(f) - cascade_relationships = name == CURRENT_SCHEMA_VERSION - BASES[name] = prepare_base( - metadata, cascade_relationships=cascade_relationships + +def coerce_version_name_to_valid_module_path(name): + # Only required to support the legacy schema versions that + # use Kolibri versions explicitly in their name. + return name.replace(".", "").replace("-", "") + + +def load_metadata(name): + module = importlib.import_module( + SQLALCHEMY_CLASSES_MODULE_PATH_TEMPLATE.format( + name=coerce_version_name_to_valid_module_path(name) ) + ) + return module.Base.metadata + + +def prepare_bases(): + + for name in CONTENT_DB_SCHEMA_VERSIONS + [CURRENT_SCHEMA_VERSION]: + try: + metadata = load_metadata(name) + BASES[name] = prepare_base(metadata, name=name) + except ImportError: + logger.error( + "Tried to load content schema version {} but valid schema import was not found".format( + name + ) + ) def get_model_from_cls(cls): @@ -208,13 +240,13 @@ def get_field_from_model_by_column(model, column): return next((f for f in model._meta.fields if f.column == column), None) -def prepare_base(metadata, cascade_relationships=False): +def prepare_base(metadata, name=None): """ Create a Base mapping for models for a particular schema version of the content app A Base mapping defines the mapping from database tables to the SQLAlchemy ORM and is our main entrypoint for interacting with content databases and the content app tables of the default database. - If cascade_relationships is True, then also attempt to use Django model information + If name is CURRENT_SCHEMA_VERSION, then also attempt to use Django model information to setup proper relationship cascade behaviour to allow deletion in SQLAlchemy. """ # Set up the base mapping using the automap_base method, using the metadata passed in @@ -232,7 +264,7 @@ def _gen_relationship( base, direction, return_fn, attrname, local_cls, referred_cls, **kw ) - if cascade_relationships: + if name == CURRENT_SCHEMA_VERSION: Base.prepare(generate_relationship=_gen_relationship) else: Base.prepare() diff --git a/requirements/base.txt b/requirements/base.txt index a52dc32d297..53b00404c84 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -24,7 +24,7 @@ morango==0.5.0 tzlocal==1.5.1 pytz==2018.5 python-dateutil==2.7.5 -sqlalchemy==1.2.10 +sqlalchemy==1.3.17 semver==2.8.1 django-redis-cache==2.0.0 redis==3.2.1 diff --git a/requirements/dev.txt b/requirements/dev.txt index c68c76cd7d4..4d72ff2915b 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -12,3 +12,4 @@ tabulate==0.8.2 fonttools==3.29.0 nodeenv==1.3.3 click==7.0 +sqlacodegen==2.1.0