def test_models_course_copy_relations_cloning(self): """When cloning a page, the course runs should not be copied.""" course = factories.CourseFactory(page_title="my course title") page = course.extended_object TitleFactory(page=course.extended_object, language="fr", title="mon titre de cours") course_run = factories.CourseRunFactory(direct_course=course, title="my run") CourseRunTranslation.objects.create(master=course_run, language_code="fr", title="ma session") # Copy the course page as its own child copy_page = page.copy(page.node.site, parent_node=page.node, translations=True, extensions=True) self.assertEqual(Course.objects.count(), 2) self.assertEqual(CourseRun.objects.count(), 1) self.assertEqual(CourseRunTranslation.objects.count(), 2) self.assertEqual(Title.objects.count(), 4) self.assertIsNone(copy_page.course.runs.first())
def test_models_course_copy_relations_publish(self): """ When publishing a draft course, the draft course run should be copied to a newly created course run with its parler translations. In a second part of the test, we check that when publishing a course that was already published, the draft course run should be copied to the existing public course run with its parler translations. """ # 1- Publishing a draft course course = factories.CourseFactory(page_title="my course title") TitleFactory(page=course.extended_object, language="fr", title="mon titre de cours") course_run = factories.CourseRunFactory(direct_course=course, title="my run") CourseRunTranslation.objects.create(master=course_run, language_code="fr", title="ma session") self.assertEqual(Course.objects.count(), 1) self.assertEqual(CourseRun.objects.count(), 1) self.assertEqual(CourseRunTranslation.objects.count(), 2) self.assertEqual(Title.objects.count(), 2) self.assertTrue(course.extended_object.publish("fr")) self.assertEqual(Course.objects.count(), 2) self.assertEqual(CourseRun.objects.count(), 2) self.assertEqual(CourseRunTranslation.objects.count(), 3) self.assertEqual(Title.objects.count(), 3) # 2- Publishing a course that was already published self.assertTrue(course.extended_object.publish("en")) self.assertEqual(CourseRunTranslation.objects.count(), 4) self.assertEqual(Title.objects.count(), 4) course_run.refresh_from_db() public_course_run = course_run.public_course_run self.assertEqual(public_course_run.title, "my run") with switch_language(public_course_run, "fr"): self.assertEqual(public_course_run.title, "ma session")
def test_models_course_copy_relations_publish_recursive_loop(self): """ In a previous version of the the CourseRun method "copy_translations" in which we used instances instead of update queries, this test was generating an infinite recursive loop. """ course = factories.CourseFactory(page_title="my course title") TitleFactory(page=course.extended_object, language="fr", title="mon titre de cours") course_run = factories.CourseRunFactory(direct_course=course, title="my run") course_run_translation_fr = CourseRunTranslation.objects.create( master=course_run, language_code="fr", title="ma session") self.assertTrue(course.extended_object.publish("fr")) course_run_translation_fr.title = "ma session modifiée" course_run_translation_fr.save() self.assertTrue(course.extended_object.publish("fr"))
def create_demo_site(): """ Create a simple site tree structure for developpers to work in realistic environment. We create multilingual pages, add organizations under the related page and add plugins to each page. """ site = Site.objects.get(id=1) site.domain = getattr(settings, "RICHIE_DEMO_SITE_DOMAIN", defaults.DEFAULT_DEMO_SITE_DOMAIN) site.name = "Richie demonstration" site.save() lms_endpoint = (getattr(settings, "LMS_BACKENDS", None) or [{ "BASE_URL": defaults.DEFAULT_LMS_ENDPOINT }])[0]["BASE_URL"] # Create pages as described in PAGES_INFOS pages_created = recursive_page_creation(site, defaults.PAGES_INFO) # Create the footer links def create_footer_link(**link_info): """ Use LinkPlugin to create a link in footer menu with link_info Links can be nested into a NestedItemPlugin, in this case link_info contains a target key. """ if "internal_link" in link_info: link_info = link_info.copy() link_info["internal_link"] = pages_created[ link_info["internal_link"]] add_plugin(plugin_type="LinkPlugin", **link_info) footer_static_ph = StaticPlaceholder.objects.get_or_create( code="footer")[0] for footer_placeholder in [ footer_static_ph.draft, footer_static_ph.public ]: for language, content in defaults.FOOTER_CONTENT.items(): for footer_info in content: if "items" in footer_info: # Create the first level items for main columns nest_column_plugin = add_plugin( footer_placeholder, plugin_type="NestedItemPlugin", language=language, content=footer_info.get("title", ""), ) # Create the second level items for links for item_info in footer_info.get("items", []): create_footer_link( language=language, placeholder=footer_placeholder, target=nest_column_plugin, **item_info, ) else: # Create link at first level create_footer_link(language=language, placeholder=footer_placeholder, **footer_info) # Create some licences licences = (factories.LicenceFactory.create_batch( defaults.NB_OBJECTS["licences"], logo__file__from_path=pick_image("licence")(), ) if defaults.NB_OBJECTS.get("licences") else []) # Generate each category tree and return a list of the leaf categories icons = list( create_categories( **defaults.ICONS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("category_logo"), page_parent=pages_created["categories"], )) levels = list( create_categories( **defaults.LEVELS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("category_logo"), page_parent=pages_created["categories"], )) subjects = list( create_categories( **defaults.SUBJECTS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("category_logo"), page_parent=pages_created["categories"], )) partnerships = list( create_categories( **defaults.PARTNERSHIPS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("category_logo"), page_parent=pages_created["categories"], )) tags = list( create_categories( **defaults.TAGS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("category_logo"), page_parent=pages_created["categories"], )) # Create organizations under the `Organizations` page organizations = [] for i in range(defaults.NB_OBJECTS["organizations"]): # Randomly assign each organization to a partnership level category organizations.append( factories.OrganizationFactory( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["organizations"], fill_banner=pick_image("banner"), fill_categories=[random.choice(partnerships)] # nosec if (i % 2 == 0) else [], fill_description=True, fill_logo=pick_image("logo"), should_publish=True, with_permissions=True, )) # Create persons under the `persons` page persons = [] persons_for_organization = defaultdict(list) for _i in range(defaults.NB_OBJECTS["persons"]): # Randomly assign each person to a set of organizations person_organizations = random.sample( organizations, random.randint( 1, defaults.NB_OBJECTS["person_organizations"]), # nosec ) person = factories.PersonFactory( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["persons"], fill_categories=random.sample( subjects, random.randint( 1, defaults.NB_OBJECTS["person_subjects"]), # nosec ), fill_organizations=person_organizations, fill_portrait=pick_image("portrait"), fill_bio=True, fill_maincontent=True, should_publish=True, ) persons.append(person) for organization in person_organizations: persons_for_organization[organization.id].append(person) # Assign each person randomly to an organization so that our course are tagged realistically # If organizations and persons are tagged randomly on courses, each organizations will # in the end be related to most persons... not what we want. # Create courses under the `Course` page with categories and organizations # relations courses = [] for _i in range(defaults.NB_OBJECTS["courses"]): video_sample = random.choice(factories.VIDEO_SAMPLE_LINKS) # nosec # Randomly assign each course to a set of organizations course_organizations = random.sample( organizations, defaults.NB_OBJECTS["course_organizations"]) # Only the persons members of these organizations are eligible to be part # of the course team eligible_persons = set(person for o in course_organizations for person in persons_for_organization[o.id]) course_licences = ([ ("course_license_content", random.choice(licences)), # nosec ("course_license_participation", random.choice(licences)), # nosec ] if licences else []) course = factories.CourseFactory( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["courses"], fill_licences=course_licences, fill_team=random.sample( eligible_persons, min( random.randint( 1, defaults.NB_OBJECTS["course_persons"]), # nosec len(eligible_persons), ), ), fill_teaser=video_sample, fill_cover=pick_image("cover")(video_sample.image), fill_categories=[ *random.sample( subjects, random.randint( 1, defaults.NB_OBJECTS["course_subjects"]), # nosec ), random.choice(levels), # nosec ], fill_icons=random.sample(icons, get_number_of_icons()), fill_organizations=course_organizations, fill_plan=True, fill_texts=[ "course_assessment", "course_description", "course_introduction", "course_format", "course_prerequisites", "course_skills", ], ) course.create_permissions_for_organization(course_organizations[0]) courses.append(course) # Add extra information for language in course.extended_object.get_languages(): placeholder = course.extended_object.placeholders.get( slot="course_information") nb_half_rows = 2 * random.randint(0, 1) # nosec nb_full_rows = random.randint(0, 2) # nosec nb_cards = 4 * random.randint(0, 1) # nosec # Partners if nb_half_rows or nb_full_rows: partner_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=defaults.COURSE_CONTENT[language]["partners_title"], ) for _i in range(nb_half_rows): glimpse_data = factory.build( dict, FACTORY_CLASS=GlimpseFactory, variant=glimpse_defaults.ROW_HALF, image=image_getter(pick_image("logo")()), ) glimpse_data["image"].save() add_plugin( language=language, placeholder=placeholder, plugin_type="GlimpsePlugin", target=partner_section, **glimpse_data, ) for _i in range(nb_full_rows): glimpse_data = factory.build( dict, FACTORY_CLASS=GlimpseFactory, variant=glimpse_defaults.ROW_FULL, image=image_getter(pick_image("logo")()), ) glimpse_data["image"].save() add_plugin( language=language, placeholder=placeholder, plugin_type="GlimpsePlugin", target=partner_section, **glimpse_data, ) # Sponsors if nb_cards: sponsor_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=defaults.COURSE_CONTENT[language]["sponsors_title"], ) for _i in range(nb_cards): glimpse_data = factory.build( dict, FACTORY_CLASS=GlimpseFactory, variant=glimpse_defaults.CARD_SQUARE, image=image_getter(pick_image("logo")()), ) glimpse_data["image"].save() add_plugin( language=language, placeholder=placeholder, plugin_type="GlimpsePlugin", target=sponsor_section, **glimpse_data, ) # Add a random number of course runs to the course nb_course_runs = get_number_of_course_runs() # pick a subset of languages for this course (otherwise all courses will have more or # less all the languages across their course runs!) languages_subset = random.sample( ["de", "en", "es", "fr", "it", "nl"], random.randint(1, 4) # nosec ) for i in range(nb_course_runs): course_run = factories.CourseRunFactory( __sequence=i, languages=random.sample( languages_subset, random.randint(1, len(languages_subset)) # nosec ), direct_course=course, resource_link= f"{lms_endpoint}/courses/course-v1:edX+DemoX+Demo_Course/info", ) for language in course.extended_object.get_languages(): with translation.override(language): models.CourseRunTranslation.objects.update_or_create( master=course_run, language_code=language, defaults={"title": _(f"Run {i:d}")}, ) # Publish the course in all languages for language in course.extended_object.get_languages(): course.extended_object.publish(language) # Create blog posts under the `News` page blogposts = [] for _i in range(defaults.NB_OBJECTS["blogposts"]): post = factories.BlogPostFactory.create( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["blogposts"], fill_cover=pick_image("cover"), fill_excerpt=True, fill_body=True, fill_categories=[ *random.sample(levels, defaults.NB_OBJECTS["blogpost_levels"]), *random.sample(tags, defaults.NB_OBJECTS["blogpost_tags"]), ], fill_author=random.sample(persons, 1), should_publish=True, ) blogposts.append(post) # Create programs under the `Programs` page programs = [] for _i in range(defaults.NB_OBJECTS["programs"]): program = factories.ProgramFactory.create( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["programs"], fill_cover=pick_image("cover"), fill_excerpt=True, fill_body=True, fill_courses=[ *random.sample(courses, defaults.NB_OBJECTS["programs_courses"]) ], should_publish=True, ) programs.append(program) # Create some content on the programs list page placeholder = pages_created["programs"].placeholders.get( slot="maincontent") for language in pages_created["programs"].get_languages(): create_text_plugin( pages_created["programs"], placeholder, nb_paragraphs=random.randint(3, 4), # nosec languages=[language], plugin_type="TextPlugin", ) # Once content has been added we must publish again homepage pages_created["programs"].publish(language) # Once everything has been created, use some content to create a homepage placeholder = pages_created["home"].placeholders.get(slot="maincontent") # - Get a banner image banner = image_getter(pick_image("banner")()) # - Get a logo image logo = image_getter(pick_image("logo")()) # - Create the home page in each language for language, content in defaults.HOMEPAGE_CONTENT.items(): # Add a banner add_plugin( language=language, placeholder=placeholder, plugin_type="LargeBannerPlugin", title=content["banner_title"], background_image=banner, content=content["banner_content"], template=content["banner_template"], ) # Add highlighted courses with a button courses_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["courses_title"], template=content["section_template"], ) for course in random.sample(courses, defaults.NB_OBJECTS["home_courses"]): add_plugin( language=language, placeholder=placeholder, plugin_type="CoursePlugin", target=courses_section, page=course.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=courses_section, name=content["courses_button_title"], template=content["button_template_name"], internal_link=pages_created["courses"], ) # Add highlighted blogposts blogposts_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["blogposts_title"], template=content["section_template"], ) for blogpost in random.sample(blogposts, defaults.NB_OBJECTS["home_blogposts"]): add_plugin( language=language, placeholder=placeholder, plugin_type="BlogPostPlugin", target=blogposts_section, page=blogpost.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=blogposts_section, name=content["blogposts_button_title"], template=content["button_template_name"], internal_link=pages_created["blogposts"], ) # Add highlighted programs programs_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["programs_title"], template=content["section_template"], ) for program in random.sample(programs, defaults.NB_OBJECTS["home_programs"]): add_plugin( language=language, placeholder=placeholder, plugin_type="ProgramPlugin", target=programs_section, page=program.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=programs_section, name=content["programs_button_title"], template=content["button_template_name"], internal_link=pages_created["programs"], ) # Add highlighted organizations organizations_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["organizations_title"], template=content["section_template"], ) for organization in random.sample( organizations, defaults.NB_OBJECTS["home_organizations"]): add_plugin( language=language, placeholder=placeholder, plugin_type="OrganizationPlugin", target=organizations_section, page=organization.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=organizations_section, name=content["organizations_button_title"], template=content["button_template_name"], internal_link=pages_created["organizations"], ) # Add highlighted subjects subjects_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["subjects_title"], template=content["section_template"], ) for subject in random.sample(subjects, defaults.NB_OBJECTS["home_subjects"]): add_plugin( language=language, placeholder=placeholder, plugin_type="CategoryPlugin", target=subjects_section, page=subject.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=subjects_section, name=content["subjects_button_title"], template=content["button_template_name"], internal_link=pages_created["categories"], ) # Add highlighted persons persons_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["persons_title"], template=content["section_template"], ) for person in random.sample(persons, defaults.NB_OBJECTS["home_persons"]): add_plugin( language=language, placeholder=placeholder, plugin_type="PersonPlugin", target=persons_section, page=person.extended_object, ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=persons_section, name=content["persons_button_title"], template=content["button_template_name"], internal_link=pages_created["persons"], ) # Add Glimpse quotes with empty title quotes_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title="", ) for _i in range(3): glimpse_data = factory.build( dict, FACTORY_CLASS=GlimpseFactory, variant=glimpse_defaults.QUOTE, title=None, image=image_getter(pick_image("portrait")()), ) glimpse_data["image"].save() add_plugin( language=language, placeholder=placeholder, plugin_type="GlimpsePlugin", target=quotes_section, **glimpse_data, ) # Add Glimpse cards with empty content cards_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title="", ) for _i in range(4): glimpse_data = factory.build( dict, FACTORY_CLASS=GlimpseFactory, variant=glimpse_defaults.CARD_SQUARE, content=None, image=image_getter(pick_image("cover")()), ) glimpse_data["image"].save() add_plugin( language=language, placeholder=placeholder, plugin_type="GlimpsePlugin", target=cards_section, **glimpse_data, ) # Once content has been added we must publish again homepage pages_created["home"].publish(language) # Fill the single column sample page placeholder = pages_created["annex__about"].placeholders.get( slot="maincontent") # - Get a banner image banner = image_getter(pick_image("banner")()) # - Get a logo image logo = image_getter(pick_image("logo")()) # - Get a video video_sample = random.choice(factories.VIDEO_SAMPLE_LINKS) # nosec # - Create sample page in each language for language, content in defaults.SINGLECOLUMN_CONTENT.items(): # Add a banner add_plugin( language=language, placeholder=placeholder, plugin_type="LargeBannerPlugin", title=content["banner_title"], background_image=banner, content=content["banner_content"], template=content["banner_template"], ) # HTML paragraphs create_text_plugin( pages_created["annex__about"], placeholder, nb_paragraphs=random.randint(3, 4), # nosec languages=[language], plugin_type="TextPlugin", ) # A large video sample add_plugin( language=language, placeholder=placeholder, plugin_type="VideoPlayerPlugin", label=video_sample.label, embed_link=video_sample.url, template="full-width", ) # Section with some various plugins sample_section = add_plugin( language=language, placeholder=placeholder, plugin_type="SectionPlugin", title=content["section_sample_title"], template=content["section_sample_template"], ) add_plugin( language=language, placeholder=placeholder, plugin_type="OrganizationPlugin", target=sample_section, page=random.choice(organizations).extended_object, # nosec ) add_plugin( language=language, placeholder=placeholder, plugin_type="CoursePlugin", target=sample_section, page=random.choice(courses).extended_object, # nosec ) add_plugin( language=language, placeholder=placeholder, plugin_type="OrganizationPlugin", target=sample_section, page=random.choice(organizations).extended_object, # nosec ) add_plugin( language=language, placeholder=placeholder, plugin_type="BlogPostPlugin", target=sample_section, page=random.choice(blogposts).extended_object, # nosec ) add_plugin( language=language, placeholder=placeholder, plugin_type="LinkPlugin", target=sample_section, name=content["section_sample_button_title"], template=content["button_template_name"], internal_link=pages_created["home"], ) # Add a licence if licences: add_plugin( language=language, placeholder=placeholder, plugin_type="LicencePlugin", licence=random.choice(licences), # nosec ) # Add a simple picture entry add_plugin( language=language, placeholder=placeholder, plugin_type="SimplePicturePlugin", picture=logo, ) # Add a plain text text = factory.Faker( "text", max_nb_chars=random.randint(150, 250) # nosec ).generate({"locale": language}) add_plugin( language=language, placeholder=placeholder, plugin_type="PlainTextPlugin", body=text, ) # Once content has been added we must publish again the about page pages_created["annex__about"].publish(language) # Create a sitemap page placeholder = pages_created["annex__sitemap"].placeholders.get( slot="maincontent") for language in pages_created["annex__sitemap"].get_languages(): parent_instance = add_plugin(language=language, placeholder=placeholder, plugin_type="HTMLSitemapPlugin") for name, params in defaults.SITEMAP_PAGE_PARAMS.items(): add_plugin( language=language, placeholder=placeholder, plugin_type="HTMLSitemapPagePlugin", target=parent_instance, root_page=pages_created[name], **params, ) # Once content has been added we must publish again the sitemap pages_created["annex__sitemap"].publish(language)
def test_models_course_get_course_runs(self): """ The `get_course_runs` method should return all descendants ranked by start date, not only direct children. """ course = factories.CourseFactory(page_languages=["en", "fr"]) # Create draft and published course runs for this course course_run = factories.CourseRunFactory(direct_course=course) self.assertTrue(course.extended_object.publish("en")) self.assertTrue(course.extended_object.publish("fr")) course_run_draft = factories.CourseRunFactory(direct_course=course) # Create a child course with draft and published course runs (what results from # snapshotting a course) child_course = factories.CourseFactory( page_languages=["en", "fr"], page_parent=course.extended_object) child_course_run = factories.CourseRunFactory( direct_course=child_course) self.assertTrue(child_course.extended_object.publish("en")) self.assertTrue(child_course.extended_object.publish("fr")) child_course_run_draft = factories.CourseRunFactory( direct_course=child_course) # Create another course, not related to the first one, with draft and published course runs other_course = factories.CourseFactory(page_languages=["en", "fr"]) factories.CourseRunFactory(direct_course=other_course) self.assertTrue(other_course.extended_object.publish("en")) self.assertTrue(other_course.extended_object.publish("fr")) factories.CourseRunFactory(direct_course=other_course) # Check that the draft course retrieves all its descendant course runs # 3 draft course runs and 2 published course runs per course self.assertEqual(CourseRun.objects.count(), 3 * 3) sorted_runs = sorted( [ course_run, course_run_draft, child_course_run, child_course_run_draft ], key=lambda o: o.start, reverse=True, ) for run in sorted_runs: run.refresh_from_db() with self.assertNumQueries(2): self.assertEqual(list(course.get_course_runs()), sorted_runs) # Check that the published course retrieves only the published descendant course runs course.refresh_from_db() public_course = course.public_extension with self.assertNumQueries(3): result = list(public_course.get_course_runs()) expected_public_course_runs = sorted( [course_run.public_course_run, child_course_run.public_course_run], key=lambda o: o.start, reverse=True, ) self.assertEqual(result, expected_public_course_runs)