def test_admin_licence_list_view(self): """ The admin list view of licences should display the name of the licence. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") # Create a licence with English fields and French translations licence = LicenceFactory(name="some licence name") with switch_language(licence, "fr"): licence.name = "quelque nom de licence" licence.save() # Get the admin list view in English url = reverse("admin:courses_licence_changelist") response = self.client.get(url, follow=True) # Check that the page includes the name in English with switch_language(licence, "en"): self.assertContains(response, licence.name, status_code=200) # Get the admin change view in French with translation.override("fr"): url = reverse("admin:courses_licence_changelist") response = self.client.get(url) # Check that the page includes the name in French with switch_language(licence, "fr"): self.assertContains(response, licence.name)
def test_section_header_level(self): """ Header level can be changed from context variable 'header_level'. """ # We deliberately use level '10' since it can be substituted from any # reasonable default level. header_format = """<h10 class="licence-plugin__title">{}</h10>""" # Dummy slot where to include plugin placeholder = Placeholder.objects.create(slot="test") # Create random values for parameters with a factory, empty url to # simplify render of tested HTML licence = LicenceFactory(url="") # Template context with additional variable to define a custom header # level for header markup context = self.get_practical_plugin_context({"header_level": 10}) # Init base Section plugin with required title add_plugin(placeholder, LicencePlugin, "en", licence=licence) # Render placeholder so plugin is fully rendered in real situation html = context["cms_content_renderer"].render_placeholder( placeholder, context=context, language="en") expected_header = header_format.format(licence.name) # Expected header markup should match given 'header_level' context # variable self.assertInHTML(expected_header, html)
def test_section_context_and_html(self): """ Instanciating this plugin with an instance should populate the context and render in the template. """ placeholder = Placeholder.objects.create(slot="test") # Create random values for parameters with a factory licence = LicenceFactory() model_instance = add_plugin(placeholder, LicencePlugin, "en", licence=licence) plugin_instance = model_instance.get_plugin_class_instance() plugin_context = plugin_instance.render({}, model_instance, None) # Check if "instance" is in plugin context self.assertIn("instance", plugin_context) # Check if parameters, generated by the factory, are correctly set in # "instance" of plugin context self.assertEqual(plugin_context["instance"].licence.name, licence.name) # Template context context = self.get_practical_plugin_context() # Get generated html for licence name html = context["cms_content_renderer"].render_plugin( model_instance, {}) # Check rendered name self.assertIn(licence.name, html)
def test_models_license_fields_content_required(self): """ A "content" text is required when instantiating a licence. """ with self.assertRaises(IntegrityError) as cm: LicenceFactory(content=None) self.assertIn( 'null value in column "content" violates not-null', str(cm.exception) )
def test_models_licence_fields_content_internationalized(self): """ The "content" field on Licence is internationalized using django-parler. """ licence = LicenceFactory(content="licence text content") with switch_language(licence, "en"): self.assertEqual(licence.content, "licence text content") with switch_language(licence, "fr"): self.assertEqual(licence.content, "licence text content") licence.content = "contenu textuel de la licence" licence.save() self.assertEqual(licence.content, "contenu textuel de la licence") with switch_language(licence, "en"): self.assertEqual(licence.content, "licence text content")
def test_models_licence_fields_name_internationalized(self): """ The "name" field on Licence is internationalized using django-parler. """ licence = LicenceFactory(name="licence name") with switch_language(licence, "en"): self.assertEqual(licence.name, "licence name") with switch_language(licence, "fr"): self.assertEqual(licence.name, "licence name") licence.name = "nom de la licence" licence.save() self.assertEqual(licence.name, "nom de la licence") with switch_language(licence, "en"): self.assertEqual(licence.name, "licence name")
def test_models_license_fields_name_required(self): """ A "name" is required when instantiating a licence. """ with self.assertRaises(IntegrityError) as cm: LicenceFactory(name=None) self.assertTrue( # Postgresql 'null value in column "name" violates not-null' in str(cm.exception) # Mysql or "Column 'name' cannot be null" in str(cm.exception) )
def test_admin_licence_change_view_get(self): """ The admin change view should include the name and content fields for the given language. """ user = UserFactory(is_staff=True, is_superuser=True) self.client.login(username=user.username, password="******") # Create a licence with English fields and French translations licence = LicenceFactory(name="some licence name", content="some licence text content") with switch_language(licence, "fr"): licence.name = "quelque nom de licence" licence.content = "quelque contenu textuel de licence" licence.save() # Get the admin change view in English url = reverse("admin:courses_licence_change", args=[licence.id]) response = self.client.get(url) # Check that the page includes the name and content in English with switch_language(licence, "en"): self.assertContains(response, licence.name) self.assertContains(response, licence.content) # Get the admin change view in French url = reverse("admin:courses_licence_change", args=[licence.id]) response = self.client.get("{}?language=fr".format(url)) # Check that the page includes the name and content in French with switch_language(licence, "fr"): self.assertContains(response, licence.name) self.assertContains(response, licence.content)
def test_licence_plugin_rdfa_property_deactivated(self): """ The RDFa licence property can be deactivated from context variable 'is_license_property'. """ # Dummy slot where to include plugin placeholder = Placeholder.objects.create(slot="test") # Create random values for parameters with a factory licence = LicenceFactory() # Template context with additional variable to deactivate license property context = self.get_practical_plugin_context({"is_license_property": False}) add_plugin(placeholder, LicencePlugin, "en", licence=licence) # Render placeholder so plugin is fully rendered in real situation html = context["cms_content_renderer"].render_placeholder( placeholder, context=context, language="en" ) # RDFa markup should not be present self.assertNotIn('property="license"', html)
def test_licence_plugin_rdfa_property_default_with_url(self): """ The RDFa licence property should be present by default on the url if any. """ # Dummy slot where to include plugin placeholder = Placeholder.objects.create(slot="test") licence = LicenceFactory(url="https://example.com") context = self.get_practical_plugin_context({}) add_plugin(placeholder, LicencePlugin, "en", licence=licence) # Render placeholder so plugin is fully rendered in real situation html = context["cms_content_renderer"].render_placeholder( placeholder, context=context, language="en" ) # RDFa markup should be present by default self.assertEqual(html.count('property="license"'), 1) self.assertEqual( html.count('<a href="https://example.com" property="license">'), 1 )
def test_licence_plugin_rdfa_property_activated_no_url(self): """ The RDFa licence property is tagged on the content if there is no url. """ # Dummy slot where to include plugin placeholder = Placeholder.objects.create(slot="test") # Create a license with no url licence = LicenceFactory(url="") # Template context with additional variable to activate license property context = self.get_practical_plugin_context({"is_license_property": True}) add_plugin(placeholder, LicencePlugin, "en", licence=licence) # Render placeholder so plugin is fully rendered in real situation html = context["cms_content_renderer"].render_placeholder( placeholder, context=context, language="en" ) # RDFa markup should be present but on the content self.assertEqual(html.count('property="license"'), 1) self.assertEqual( html.count('<div class="licence-plugin__content" property="license">'), 1 )
def test_indexers_licences_get_es_documents(self): """ Happy path: licence data is fetched from the models properly formatted """ licence1 = LicenceFactory(name="my first licence") with switch_language(licence1, "fr"): licence1.name = "ma première licence" licence1.content = "première licence contenu" licence1.save() licence2 = LicenceFactory(name="my second licence") with switch_language(licence2, "fr"): licence2.name = "ma deuxième licence" licence2.content = "deuxième licence contenu" licence2.save() # The results were properly formatted and passed to the consumer self.assertEqual( list( LicencesIndexer.get_es_documents(index="some_index", action="some_action")), [ { "_id": licence1.id, "_index": "some_index", "_op_type": "some_action", "complete": { "en": [ "my first licence", "first licence", "licence", ], "fr": [ "ma première licence", "première licence", "licence", ], }, "content": { "en": licence1.content, "fr": "première licence contenu", }, "title": { "en": "my first licence", "fr": "ma première licence", }, "title_raw": { "en": "my first licence", "fr": "ma première licence", }, }, { "_id": licence2.id, "_index": "some_index", "_op_type": "some_action", "complete": { "en": [ "my second licence", "second licence", "licence", ], "fr": [ "ma deuxième licence", "deuxième licence", "licence", ], }, "content": { "en": licence2.content, "fr": "deuxième licence contenu", }, "title": { "en": "my second licence", "fr": "ma deuxième licence", }, "title_raw": { "en": "my second licence", "fr": "ma deuxième licence", }, }, ], )
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) # Create pages as described in PAGES_INFOS pages_created = recursive_page_creation(site, PAGE_INFOS) # Create some licences licences = LicenceFactory.create_batch(NB_LICENCES) # Create organizations under the `Organizations` page organizations = OrganizationFactory.create_batch( NB_ORGANIZATIONS, page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["organizations"], fill_banner=True, fill_description=True, fill_logo=True, should_publish=True, ) # Generate each category tree and return a list of the leaf categories levels = list(create_categories(LEVELS_INFO, pages_created["categories"])) subjects = list( create_categories(SUBJECTS_INFO, pages_created["categories"])) title = PersonTitleFactory(translation=None) PersonTitleTranslationFactory(master=title, language_code="en", title="Doctor", abbreviation="Dr.") PersonTitleTranslationFactory(master=title, language_code="fr", title="Docteur", abbreviation="Dr.") # Create persons under the `persons` page persons = PersonFactory.create_batch( NB_PERSONS, page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["persons"], person_title=random.choice([title, None]), fill_portrait=True, fill_resume=True, should_publish=True, ) # Create courses under the `Course` page with categories and organizations # relations courses = [] for _ in range(NB_COURSES): video_sample = random.choice(VIDEO_SAMPLE_LINKS) course = CourseFactory( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["courses"], fill_licences=[ ("course_license_content", random.choice(licences)), ("course_license_participation", random.choice(licences)), ], fill_team=random.sample(persons, NB_COURSES_PERSONS_PLUGINS), fill_teaser=video_sample, fill_cover=video_sample.image, fill_categories=[ *random.sample( subjects, random.randint(1, NB_COURSES_SUBJECT_RELATIONS)), random.choice(levels), ], fill_organizations=random.sample( organizations, NB_COURSES_ORGANIZATION_RELATIONS), fill_texts=[ "course_syllabus", "course_format", "course_prerequisites", "course_plan", # "course_license_content", # "course_license_participation", ], should_publish=True, ) courses.append(course) # Add a random number of course runs to the course nb_course_runs = get_number_of_course_runs() # 1) Make sure we have one course run open for enrollment now = timezone.now() CourseRunFactory( __sequence=nb_course_runs, page_in_navigation=False, page_parent=course.extended_object, start=now + timedelta(days=1), enrollment_start=now - timedelta(days=5), enrollment_end=now + timedelta(days=5), should_publish=True, ) # 2) Add more random course runs for i in range(nb_course_runs - 1, 0, -1): CourseRunFactory( __sequence=i, page_in_navigation=False, page_languages=["en", "fr"], page_parent=course.extended_object, should_publish=True, ) # 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_file = file_getter("banner")() wrapped_banner = File(banner_file, banner_file.name) banner = Image.objects.create(file=wrapped_banner) # - Get a logo image logo_file = file_getter("logo")() wrapped_logo = File(logo_file, logo_file.name) logo = Image.objects.create(file=wrapped_logo) # - Create the home page in each language for language, content in HOMEPAGE_CONTENT.items(): # Add a banner add_plugin( language=language, placeholder=placeholder, plugin_type="LargeBannerPlugin", title=content["banner_title"], background_image=banner, logo=logo, logo_alt_text="logo", content=content["banner_content"], template=content["banner_template"], ) # Add highlighted courses 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, NB_HOME_HIGHLIGHTED_COURSES): add_plugin( language=language, placeholder=placeholder, plugin_type="CoursePlugin", target=courses_section, page=course.extended_object, ) # 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, NB_HOME_HIGHLIGHTED_ORGANIZATIONS): add_plugin( language=language, placeholder=placeholder, plugin_type="OrganizationPlugin", target=organizations_section, page=organization.extended_object, ) # 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, NB_HOME_HIGHLIGHTED_SUBJECTS): add_plugin( language=language, placeholder=placeholder, plugin_type="CategoryPlugin", target=subjects_section, page=subject.extended_object, ) # 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, NB_HOME_HIGHLIGHTED_PERSONS): add_plugin( language=language, placeholder=placeholder, plugin_type="PersonPlugin", target=persons_section, page=person.extended_object, ) # Once content has been added we must publish again homepage in every # edited Languages pages_created["home"].publish("en") pages_created["home"].publish("fr")
def test_templates_course_detail_rdfa(self): """ Extract RDFa tags from the HTML markup and check that it is complete as expected. """ # Create organizations main_organization = OrganizationFactory(page_title="Main org", fill_logo=True, should_publish=True) other_organization = OrganizationFactory(page_title="Other org", fill_logo=True, should_publish=True) # Create persons author1 = PersonFactory(page_title="François", fill_portrait=True) placeholder = author1.extended_object.placeholders.get(slot="bio") add_plugin( language="en", placeholder=placeholder, plugin_type="PlainTextPlugin", body="La bio de François", ) author2 = PersonFactory(page_title="Jeanne", fill_portrait=True, should_publish=True) # Create a course with cover image, team and organizations licence_content, licence_participation = LicenceFactory.create_batch(2) course = CourseFactory( code="abcde", effort=[3, "hour"], page_title="Very interesting course", fill_cover=True, fill_organizations=[main_organization, other_organization], fill_team=[author1, author2], fill_licences=[ ("course_license_content", licence_content), ("course_license_participation", licence_participation), ], ) # Add an introduction to the course placeholder = course.extended_object.placeholders.get( slot="course_introduction") add_plugin( language="en", placeholder=placeholder, plugin_type="PlainTextPlugin", body="Introduction to interesting course", ) # Create an ongoing open course run that will be published (created before # publishing the page) now = datetime(2030, 6, 15, tzinfo=timezone.utc) CourseRunFactory( direct_course=course, start=datetime(2030, 6, 30, tzinfo=timezone.utc), end=datetime(2030, 8, 1, tzinfo=timezone.utc), enrollment_start=datetime(2030, 6, 14, tzinfo=timezone.utc), enrollment_end=datetime(2030, 6, 16, tzinfo=timezone.utc), languages=["en", "fr"], ) CourseRunFactory( direct_course=course, start=datetime(2030, 6, 1, tzinfo=timezone.utc), end=datetime(2030, 7, 10, tzinfo=timezone.utc), enrollment_start=datetime(2030, 6, 13, tzinfo=timezone.utc), enrollment_end=datetime(2030, 6, 20, tzinfo=timezone.utc), languages=["de"], ) author1.extended_object.publish("en") course.extended_object.publish("en") url = course.extended_object.get_absolute_url() with mock.patch.object(timezone, "now", return_value=now): response = self.client.get(url) self.assertEqual(response.status_code, 200) processor = pyRdfa() content = str(response.content) parser = html5lib.HTMLParser( tree=html5lib.treebuilders.getTreeBuilder("dom")) dom = parser.parse(io.StringIO(content)) graph = processor.graph_from_DOM(dom) # Retrieve the course top node (body) (subject, ) = graph.subjects( URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("https://schema.org/Course"), ) self.assertEqual(len(list(graph.triples((subject, None, None)))), 38) # Opengraph self.assertTrue(( subject, URIRef("http://ogp.me/ns#url"), Literal("http://example.com/en/very-interesting-course/"), ) in graph) self.assertTrue((subject, URIRef("http://ogp.me/ns#site_name"), Literal("example.com")) in graph) self.assertTrue((subject, URIRef("http://ogp.me/ns#type"), Literal("website")) in graph) self.assertTrue((subject, URIRef("http://ogp.me/ns#locale"), Literal("en")) in graph) self.assertTrue((subject, URIRef("http://ogp.me/ns#determiner"), Literal("")) in graph) self.assertTrue(( subject, URIRef("http://ogp.me/ns#title"), Literal("Very interesting course"), ) in graph) self.assertTrue(( subject, URIRef("http://ogp.me/ns#description"), Literal("Introduction to interesting course"), ) in graph) (image_value, ) = graph.objects(subject, URIRef("http://ogp.me/ns#image")) pattern = ( r"/media/filer_public_thumbnails/filer_public/.*cover\.jpg__" r"1200x630_q85_crop_replace_alpha-%23FFFFFF_subject_location") self.assertIsNotNone(re.search(pattern, str(image_value))) # Schema.org # - Course self.assertTrue(( subject, URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("https://schema.org/Course"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/name"), Literal("Very interesting course"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/description"), Literal("Introduction to interesting course"), ) in graph) self.assertTrue((subject, URIRef("https://schema.org/courseCode"), Literal("ABCDE")) in graph) self.assertTrue((subject, URIRef("https://schema.org/isAccessibleForFree"), Literal("true")) in graph) self.assertTrue((subject, URIRef("https://schema.org/timeRequired"), Literal("PT3H")) in graph) self.assertTrue(( subject, URIRef("https://schema.org/stylesheet"), URIRef("/static/richie/css/main.css"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/shortcut"), URIRef("/static/richie/favicon/favicon.ico"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/icon"), URIRef("/static/richie/favicon/favicon.ico"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/icon"), URIRef("/static/richie/favicon/favicon-16x16.png"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/icon"), URIRef("/static/richie/favicon/favicon-32x32.png"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/apple-touch-icon"), URIRef("/static/richie/favicon/apple-touch-icon.png"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/mask-icon"), URIRef("/static/richie/favicon/safari-pinned-tab.svg"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/manifest"), URIRef("/static/richie/favicon/site.webmanifest"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/noreferrer"), URIRef("https://www.facebook.com/example"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/noopener"), URIRef("https://www.facebook.com/example"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/alternate"), URIRef("http://example.com/en/very-interesting-course/"), ) in graph) self.assertTrue(( subject, URIRef("https://schema.org/alternate"), URIRef("http://example.com/fr/very-interesting-course/"), ) in graph) (image_value, ) = graph.objects(subject, URIRef("https://schema.org/image")) pattern = ( r"/media/filer_public_thumbnails/filer_public/.*cover\.jpg__" r"300x170_q85_crop_replace_alpha-%23FFFFFF_subject_location") self.assertIsNotNone(re.search(pattern, str(image_value))) self.assertTrue((subject, URIRef("https://schema.org/license"), URIRef(licence_content.url)) in graph) self.assertTrue(( None, URIRef("https://schema.org/license"), URIRef(licence_participation.url), ) not in graph) # - Main organization (Provider) self.assertTrue((subject, URIRef("https://schema.org/provider"), URIRef("/en/main-org/")) in graph) self.assertTrue(( URIRef("/en/main-org/"), URIRef("https://schema.org/name"), Literal("Main org"), ) in graph) self.assertTrue(( URIRef("/en/main-org/"), URIRef("https://schema.org/url"), Literal("http://example.com/en/main-org/"), ) in graph) (logo_value, ) = graph.objects(URIRef("/en/main-org/"), URIRef("https://schema.org/logo")) pattern = (r"/media/filer_public_thumbnails/filer_public/.*logo.jpg__" r"200x113_q85_replace_alpha-%23FFFFFF_subject_location") self.assertIsNotNone(re.search(pattern, str(logo_value))) # - Organizations (Contributor) contributor_subjects = list( graph.objects(subject, URIRef("https://schema.org/contributor"))) self.assertEqual(len(contributor_subjects), 2) self.assertTrue(( contributor_subjects[0], URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("https://schema.org/CollegeOrUniversity"), ) in graph) self.assertTrue(( contributor_subjects[1], URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("https://schema.org/CollegeOrUniversity"), ) in graph) self.assertTrue(( URIRef("/en/main-org/"), URIRef("https://schema.org/name"), Literal("Main org"), ) in graph) self.assertTrue(( URIRef("/en/other-org/"), URIRef("https://schema.org/name"), Literal("Other org"), ) in graph) self.assertTrue(( URIRef("/en/main-org/"), URIRef("https://schema.org/url"), Literal("http://example.com/en/main-org/"), ) in graph) self.assertTrue(( URIRef("/en/other-org/"), URIRef("https://schema.org/url"), Literal("http://example.com/en/other-org/"), ) in graph) pattern = (r"/media/filer_public_thumbnails/filer_public/.*logo.jpg__" r"200x113_q85_replace_alpha-%23FFFFFF_subject_location") (logo_value, ) = graph.objects(URIRef("/en/main-org/"), URIRef("https://schema.org/logo")) self.assertIsNotNone(re.search(pattern, str(logo_value))) (logo_value, ) = graph.objects(URIRef("/en/other-org/"), URIRef("https://schema.org/logo")) self.assertIsNotNone(re.search(pattern, str(logo_value))) # - Team (Person) author_subjects = list( graph.objects(subject, URIRef("https://schema.org/author"))) self.assertEqual(len(author_subjects), 2) self.assertTrue(( author_subjects[0], URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("https://schema.org/Person"), ) in graph) self.assertTrue(( author_subjects[1], URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("https://schema.org/Person"), ) in graph) for name in ["Fran\\xc3\\xa7ois", "Jeanne"]: (author_subject, ) = graph.subjects( URIRef("https://schema.org/name"), Literal(name)) self.assertTrue(author_subject in author_subjects) (author_subject, ) = graph.subjects( URIRef("https://schema.org/description"), Literal("La bio de Fran\\xc3\\xa7ois"), ) self.assertTrue(author_subject in author_subjects) for url in [ "http://example.com/en/francois/", "http://example.com/en/jeanne/" ]: (author_subject, ) = graph.subjects( URIRef("https://schema.org/url"), Literal(url)) self.assertTrue(author_subject in author_subjects) pattern = ( r"/media/filer_public_thumbnails/filer_public/.*portrait.jpg__" r"200x200_q85_crop_replace_alpha-%23FFFFFF_subject_location") for author_subject in author_subjects: (portrait_value, ) = graph.objects( author_subject, URIRef("https://schema.org/image")) self.assertIsNotNone(re.search(pattern, str(portrait_value))) # - Course runs (CourseInstance) course_run_subjects = list( graph.objects(subject, URIRef("https://schema.org/hasCourseInstance"))) self.assertEqual(len(course_run_subjects), 2) self.assertTrue(( course_run_subjects[0], URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("https://schema.org/CourseInstance"), ) in graph) self.assertTrue(( course_run_subjects[0], URIRef("https://schema.org/courseMode"), Literal("online"), ) in graph) self.assertTrue(( course_run_subjects[1], URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("https://schema.org/CourseInstance"), ) in graph) self.assertTrue(( course_run_subjects[1], URIRef("https://schema.org/courseMode"), Literal("online"), ) in graph) for start_date in ["2030-06-01", "2030-06-30"]: (subject, ) = graph.subjects( URIRef("https://schema.org/startDate"), Literal(start_date)) self.assertTrue(subject in course_run_subjects) for end_date in ["2030-07-10", "2030-08-01"]: (subject, ) = graph.subjects(URIRef("https://schema.org/endDate"), Literal(end_date)) self.assertTrue(subject in course_run_subjects)
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) # Create pages as described in PAGES_INFOS pages_created = recursive_page_creation(site, PAGE_INFOS) # Create some licences licences = LicenceFactory.create_batch(NB_LICENCES) # Create organizations under the `Organizations` page organizations = OrganizationFactory.create_batch( NB_ORGANIZATIONS, languages=[l[0] for l in settings.LANGUAGES], parent=pages_created["organizations"], fill_banner=True, fill_description=True, fill_logo=True, should_publish=True, in_navigation=True, ) # Create subjects under the `Subjects` page subjects = SubjectFactory.create_batch( NB_SUBJECTS, languages=[l[0] for l in settings.LANGUAGES], parent=pages_created["subjects"], fill_banner=True, fill_description=True, fill_logo=True, should_publish=True, in_navigation=True, ) # Django parler require a language to be manually set when working out of # request/response flow and PersonTitle use 'parler' translation.activate(settings.LANGUAGE_CODE) # Create persons under the `persons` page persons = PersonFactory.create_batch( NB_PERSONS, languages=[l[0] for l in settings.LANGUAGES], parent=pages_created["persons"], fill_portrait=True, fill_resume=True, should_publish=True, in_navigation=True, ) # Create courses under the `Course` page with subjects and organizations # relations for _ in range(NB_COURSES): course_organizations = random.sample( organizations, NB_COURSES_ORGANIZATION_RELATIONS) video_sample = random.choice(VIDEO_SAMPLE_LINKS) course = CourseFactory( languages=[l[0] for l in settings.LANGUAGES], parent=pages_created["courses"], organization_main=random.choice(course_organizations), fill_licences=[ ("course_license_content", random.choice(licences)), ("course_license_participation", random.choice(licences)), ], fill_team=random.sample(persons, NB_COURSES_PERSONS_PLUGINS), fill_teaser=video_sample, fill_cover=video_sample.image, fill_texts=[ "course_syllabus", "course_format", "course_prerequisites", "course_plan", # "course_license_content", # "course_license_participation", ], with_organizations=course_organizations, with_subjects=random.sample(subjects, NB_COURSES_SUBJECT_RELATIONS), should_publish=True, in_navigation=True, ) # Add a random number of course runs to the course nb_course_runs = get_number_of_course_runs() if nb_course_runs > 0: CourseRunFactory.create_batch(nb_course_runs, course=course)
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) # Create pages as described in PAGES_INFOS pages_created = recursive_page_creation(site, PAGES_INFO) # Create the footer links 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 FOOTER_CONTENT.items(): # Create the <ul> section to carry the list of links section_plugin = add_plugin( footer_placeholder, plugin_type="SectionPlugin", language=language, template="richie/section/section_list.html", ) # One column per content object for footer_info in content: column_plugin = add_plugin( footer_placeholder, plugin_type="SectionPlugin", language=language, target=section_plugin, template="richie/section/section_list.html", title=footer_info.get("title"), ) for item_info in footer_info.get("items", []): if "internal_link" in item_info: item_info = item_info.copy() item_info["internal_link"] = pages_created[ item_info["internal_link"] ] add_plugin( footer_placeholder, plugin_type="LinkPlugin", language=language, target=column_plugin, **item_info, ) # Create some licences licences = LicenceFactory.create_batch( NB_OBJECTS["licences"], logo__file__from_path=pick_image("licence")() ) # Generate each category tree and return a list of the leaf categories icons = list( create_categories( **ICONS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("logo"), page_parent=pages_created["categories"], ) ) levels = list( create_categories( **LEVELS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("logo"), page_parent=pages_created["categories"], ) ) subjects = list( create_categories( **SUBJECTS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("logo"), page_parent=pages_created["categories"], ) ) partnerships = list( create_categories( **PARTNERSHIPS_INFO, fill_banner=pick_image("banner"), fill_logo=pick_image("logo"), page_parent=pages_created["categories"], ) ) # Create organizations under the `Organizations` page organizations = [] for i in range(NB_OBJECTS["organizations"]): # Randomly assign each organization to a partnership level category organizations.append( 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 _ in range(NB_OBJECTS["persons"]): # Randomly assign each person to a set of organizations person_organizations = random.sample( organizations, random.randint(1, NB_OBJECTS["person_organizations"]), # nosec ) person = PersonFactory( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["persons"], fill_categories=random.sample( subjects, random.randint(1, NB_OBJECTS["person_subjects"]) # nosec ), fill_organizations=person_organizations, fill_portrait=pick_image("portrait"), fill_bio=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 _ in range(NB_OBJECTS["courses"]): video_sample = random.choice(VIDEO_SAMPLE_LINKS) # nosec # Randomly assign each course to a set of organizations course_organizations = random.sample( organizations, 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 = CourseFactory( page_in_navigation=True, page_languages=["en", "fr"], page_parent=pages_created["courses"], fill_licences=[ ("course_license_content", random.choice(licences)), # nosec ("course_license_participation", random.choice(licences)), # nosec ], fill_team=random.sample( eligible_persons, min( random.randint(1, 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, NB_OBJECTS["course_subjects"]) # nosec ), random.choice(levels), # nosec ], fill_icons=random.sample(icons, get_number_of_icons()), fill_organizations=course_organizations, fill_texts=[ "course_description", "course_format", "course_prerequisites", "course_plan", # "course_license_content", # "course_license_participation", ], should_publish=True, ) course.create_permissions_for_organization(course_organizations[0]) courses.append(course) # 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): CourseRunFactory( __sequence=i, languages=random.sample( languages_subset, random.randint(1, len(languages_subset)) # nosec ), page_in_navigation=False, page_languages=["en", "fr"], page_parent=course.extended_object, should_publish=True, ) # Create blog posts under the `News` page blogposts = [] for _ in range(NB_OBJECTS["blogposts"]): post = 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(subjects, NB_OBJECTS["blogpost_categories"]), random.choice(levels), # nosec ], fill_author=random.sample(persons, 1), should_publish=True, ) blogposts.append(post) # Create programs under the `Programs` page programs = [] for _ in range(NB_OBJECTS["programs"]): program = 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, NB_OBJECTS["programs_courses"])], should_publish=True, ) programs.append(program) # 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 HOMEPAGE_CONTENT.items(): # Add a banner add_plugin( language=language, placeholder=placeholder, plugin_type="LargeBannerPlugin", title=content["banner_title"], background_image=banner, logo=logo, logo_alt_text="logo", 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, 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, 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, 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, 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, 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, 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"], ) # 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(VIDEO_SAMPLE_LINKS) # nosec # - Create sample page in each language for language, content in 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 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), locale=language # nosec ).generate({}) 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 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_indexers_courses_get_es_documents_from_models( self, _mock_picture): """ Happy path: the data is retrieved from the models properly formatted """ # Create a course with a page in both english and french published_categories = [ CategoryFactory( fill_icon=True, page_title={ "en": "Title cat 1", "fr": "Titre cat 1" }, should_publish=True, ), CategoryFactory( fill_icon=True, page_title={ "en": "Title cat 2", "fr": "Titre cat 2" }, should_publish=True, ), ] draft_category = CategoryFactory(fill_icon=True) main_organization = OrganizationFactory( page_title={ "en": "english main organization title", "fr": "titre organisation principale français", }, should_publish=True, ) other_draft_organization = OrganizationFactory( page_title={ "en": "english other organization title", "fr": "titre autre organisation français", }) other_published_organization = OrganizationFactory( page_title={ "en": "english other organization title", "fr": "titre autre organisation français", }, should_publish=True, ) person1 = PersonFactory( page_title={ "en": "Eugène Delacroix", "fr": "Eugène Delacroix" }, should_publish=True, ) person2 = PersonFactory( page_title={ "en": "Comte de Saint-Germain", "fr": "Earl of Saint-Germain" }, should_publish=True, ) person_draft = PersonFactory(page_title={ "en": "Jules de Polignac", "fr": "Jules de Polignac" }) licence1, licence2, licence3, _licence4 = LicenceFactory.create_batch( 4) # Keep a licence unused to check that it is not returned. Link also licences to the # "student content licence" placeholder to check they are ignored licences_by_placeholders = [ ("course_license_content", licence1), ("course_license_content", licence2), ("course_license_participation", licence2), ("course_license_participation", licence3), ] course = CourseFactory( duration=[3, WEEK], effort=[2, HOUR], fill_categories=published_categories + [draft_category], fill_cover=True, fill_icons=published_categories + [draft_category], fill_licences=licences_by_placeholders, fill_organizations=[ main_organization, other_draft_organization, other_published_organization, ], fill_team=[person1, person_draft, person2], page_title={ "en": "an english course title", "fr": "un titre cours français", }, ) CourseRunFactory.create_batch(2, direct_course=course) course.extended_object.publish("en") course.extended_object.publish("fr") course.refresh_from_db() # Add a description in several languages placeholder = course.public_extension.extended_object.placeholders.get( slot="course_description") plugin_params = { "placeholder": placeholder, "plugin_type": "CKEditorPlugin" } add_plugin(body="english description line 1.", language="en", **plugin_params) add_plugin(body="english description line 2.", language="en", **plugin_params) add_plugin(body="a propos français ligne 1.", language="fr", **plugin_params) add_plugin(body="a propos français ligne 2.", language="fr", **plugin_params) # Add an introduction in several languages placeholder = course.public_extension.extended_object.placeholders.get( slot="course_introduction") plugin_params = { "placeholder": placeholder, "plugin_type": "PlainTextPlugin" } add_plugin(body="english introduction.", language="en", **plugin_params) add_plugin(body="introduction française.", language="fr", **plugin_params) # The results were properly formatted and passed to the consumer expected_course = { "_id": course.get_es_id(), "_index": "some_index", "_op_type": "some_action", "absolute_url": { "en": "/en/an-english-course-title/", "fr": "/fr/un-titre-cours-francais/", }, "categories": [ published_categories[0].get_es_id(), published_categories[1].get_es_id(), ], "categories_names": { "en": ["Title cat 1", "Title cat 2"], "fr": ["Titre cat 1", "Titre cat 2"], }, "code": course.code, "complete": { "en": [ "an english course title", "english course title", "course title", "title", ], "fr": [ "un titre cours français", "titre cours français", "cours français", "français", ], }, "course_runs": [{ "start": course_run.public_course_run.start, "end": course_run.public_course_run.end, "enrollment_start": course_run.public_course_run.enrollment_start, "enrollment_end": course_run.public_course_run.enrollment_end, "languages": course_run.public_course_run.languages, } for course_run in course.course_runs.order_by("-end")], "cover_image": { "en": { "info": "picture info" }, "fr": { "info": "picture info" }, }, "description": { "en": "english description line 1. english description line 2.", "fr": "a propos français ligne 1. a propos français ligne 2.", }, "duration": { "en": "3 weeks", "fr": "3 semaines" }, "effort": { "en": "2 hours", "fr": "2 heures" }, "icon": { "en": { "color": published_categories[0].color, "info": "picture info", "title": "Title cat 1", }, "fr": { "color": published_categories[0].color, "info": "picture info", "title": "Titre cat 1", }, }, "introduction": { "en": "english introduction.", "fr": "introduction française.", }, "is_new": False, "is_listed": True, "licences": [licence1.id, licence2.id], "organization_highlighted": { "en": "english main organization title", "fr": "titre organisation principale français", }, "organization_highlighted_cover_image": {}, "organizations": [ main_organization.get_es_id(), other_published_organization.get_es_id(), ], "organizations_names": { "en": [ "english main organization title", "english other organization title", ], "fr": [ "titre organisation principale français", "titre autre organisation français", ], }, "persons": [ person1.get_es_id(), person2.get_es_id(), ], "persons_names": { "en": ["Eugène Delacroix", "Comte de Saint-Germain"], "fr": ["Eugène Delacroix", "Earl of Saint-Germain"], }, "pace": 40, "title": { "fr": "un titre cours français", "en": "an english course title" }, } indexed_courses = list( CoursesIndexer.get_es_documents(index="some_index", action="some_action")) self.assertEqual(len(indexed_courses), 1) self.assertEqual(indexed_courses[0], expected_course)