async def test_filter(self, m_aioresponses) -> None: entry_url = 'https://en.wikipedia.org/wiki/Amsterdam' links = [ Link(entry_url), # Add a link to Wikipedia, but using a locale that's not used by the app, to test it's ignored. Link('https://nl.wikipedia.org/wiki/Amsterdam'), # Add a link that doesn't point to Wikipedia at all to test it's ignored. Link('https://example.com'), ] api_url = 'https://en.wikipedia.org/w/api.php?action=query&titles=Amsterdam&prop=extracts&exintro&format=json&formatversion=2' title = 'Amstelredam' extract = 'De hoofdstad van Nederland.' api_response_body = { 'query': { 'pages': [ { 'title': title, 'extract': extract, }, ], } } m_aioresponses.get(api_url, payload=api_response_body) with TemporaryDirectory() as output_directory_path: with TemporaryDirectory() as cache_directory_path: configuration = Configuration( output_directory_path, 'https://ancestry.example.com') configuration.cache_directory_path = Path(cache_directory_path) configuration.extensions.add(ExtensionConfiguration(Wikipedia)) async with App(configuration) as app: actual = app.jinja2_environment.from_string( '{% for entry in (links | wikipedia) %}{{ entry.content }}{% endfor %}').render(links=links) self.assertEquals(extract, actual)
def _load_urls(owner: HasLinks, element: ElementTree.Element): url_elements = _xpath(element, './ns:url') for url_element in url_elements: link = Link(str(url_element.get('href'))) link.relationship = 'external' link.label = url_element.get('description') owner.links.add(link)
async def test_populate_link_should_set_locale(self, expected: str, entry_language: str, locale: Optional[str], m_retriever) -> None: link = Link('http://%s.wikipedia.org/wiki/Amsterdam' % entry_language) link.locale = locale with TemporaryDirectory() as output_directory_path: with TemporaryDirectory() as cache_directory_path: configuration = Configuration( output_directory_path, 'https://example.com') configuration.cache_directory_path = cache_directory_path async with App(configuration) as app: sut = _Populator(app, m_retriever) await sut.populate_link(link, entry_language) self.assertEqual(expected, link.locale)
async def test_populate_link_should_set_relationship(self, expected: str, relationship: Optional[str], m_retriever) -> None: link = Link('http://en.wikipedia.org/wiki/Amsterdam') link.relationship = relationship with TemporaryDirectory() as output_directory_path: with TemporaryDirectory() as cache_directory_path: configuration = Configuration( output_directory_path, 'https://example.com') configuration.cache_directory_path = cache_directory_path async with App(configuration) as app: sut = _Populator(app, m_retriever) await sut.populate_link(link, 'en') self.assertEqual(expected, link.relationship)
async def test_populate_link_should_set_label(self, expected: str, label: Optional[str], m_retriever) -> None: link = Link('http://en.wikipedia.org/wiki/Amsterdam') link.label = label entry = Entry('en', 'The_city_of_Amsterdam', 'The city of Amsterdam', 'Amsterdam, such a lovely place!') with TemporaryDirectory() as output_directory_path: with TemporaryDirectory() as cache_directory_path: configuration = Configuration( output_directory_path, 'https://example.com') configuration.cache_directory_path = cache_directory_path async with App(configuration) as app: sut = _Populator(app, m_retriever) await sut.populate_link(link, 'en', entry) self.assertEqual(expected, link.label)
async def test_populate_should_populate_existing_link(self, m_retriever) -> None: entry_language = 'en' entry_name = 'Amsterdam' entry_title = 'Amsterdam' entry_content = 'Capitol of the Netherlands' entry = Entry(entry_language, entry_name, entry_title, entry_content) m_retriever.get_entry.return_value = entry resource = Source('the_source', 'The Source') link = Link('https://en.wikipedia.org/wiki/Amsterdam') resource.links.add(link) with TemporaryDirectory() as output_directory_path: with TemporaryDirectory() as cache_directory_path: configuration = Configuration( output_directory_path, 'https://example.com') configuration.cache_directory_path = cache_directory_path async with App(configuration) as app: app.ancestry.entities.append(resource) sut = _Populator(app, m_retriever) await sut.populate() m_retriever.get_entry.assert_called_once_with(entry_language, entry_name) self.assertEqual(1, len(resource.links)) self.assertEqual('Amsterdam', link.label) self.assertEqual('en', link.locale) self.assertEqual(MediaType('text/html'), link.media_type) self.assertIsNotNone(link.description) self.assertEqual('external', link.relationship)
async def test_populate_link_should_convert_http_to_https(self, m_retriever) -> None: link = Link('http://en.wikipedia.org/wiki/Amsterdam') entry_language = 'nl' with TemporaryDirectory() as output_directory_path: with TemporaryDirectory() as cache_directory_path: configuration = Configuration( output_directory_path, 'https://example.com') configuration.cache_directory_path = cache_directory_path async with App(configuration) as app: sut = _Populator(app, m_retriever) await sut.populate_link(link, entry_language) self.assertEqual('https://en.wikipedia.org/wiki/Amsterdam', link.url)
async def test_populate_should_add_translation_links(self, m_retriever) -> None: entry_language = 'en' entry_name = 'Amsterdam' entry_title = 'Amsterdam' entry_content = 'Capitol of the Netherlands' entry = Entry(entry_language, entry_name, entry_title, entry_content) added_entry_language = 'nl' added_entry_name = 'Amsterdam' added_entry_title = 'Amsterdam' added_entry_content = 'Hoofdstad van Nederland' added_entry = Entry(added_entry_language, added_entry_name, added_entry_title, added_entry_content) m_retriever.get_entry.side_effect = [ entry, added_entry ] m_retriever.get_translations.return_value = { entry_language: entry_name, added_entry_language: added_entry_name, } resource = Source('the_source', 'The Source') link_en = Link('https://en.wikipedia.org/wiki/Amsterdam') resource.links.add(link_en) with TemporaryDirectory() as output_directory_path: with TemporaryDirectory() as cache_directory_path: configuration = Configuration( output_directory_path, 'https://example.com') configuration.cache_directory_path = cache_directory_path configuration.locales.replace([ LocaleConfiguration('en-US', 'en'), LocaleConfiguration('nl-NL', 'nl'), ]) async with App(configuration) as app: app.ancestry.entities.append(resource) sut = _Populator(app, m_retriever) await sut.populate() m_retriever.get_entry.assert_has_calls([ call(entry_language, entry_name), call(added_entry_language, added_entry_name), ]) m_retriever.get_translations.assert_called_once_with(entry_language, entry_name) self.assertEqual(2, len(resource.links)) link_nl = resource.links.difference({link_en}).pop() self.assertEqual('Amsterdam', link_nl.label) self.assertEqual('nl', link_nl.locale) self.assertEqual(MediaType('text/html'), link_nl.media_type) self.assertIsNotNone(link_nl.description) self.assertEqual('external', link_nl.relationship)
async def test_populate_should_ignore_non_wikipedia_links(self, m_retriever) -> None: link = Link('https://example.com') resource = Source('the_source', 'The Source') resource.links.add(link) with TemporaryDirectory() as output_directory_path: with TemporaryDirectory() as cache_directory_path: configuration = Configuration( output_directory_path, 'https://example.com') configuration.cache_directory_path = cache_directory_path async with App(configuration) as app: app.ancestry.entities.append(resource) sut = _Populator(app, m_retriever) await sut.populate() self.assertSetEqual({link}, resource.links)
async def test_post_load(self, m_aioresponses) -> None: resource = Source('the_source', 'The Source') link = Link('https://en.wikipedia.org/wiki/Amsterdam') resource.links.add(link) entry_title = 'Amstelredam' entry_extract = 'Capitol of the Netherlands' entry_api_response_body = { 'query': { 'pages': [ { 'title': entry_title, 'extract': entry_extract, }, ], } } entry_api_url = 'https://en.wikipedia.org/w/api.php?action=query&titles=Amsterdam&prop=extracts&exintro&format=json&formatversion=2' m_aioresponses.get(entry_api_url, payload=entry_api_response_body) translations_api_response_body = { 'query': { 'pages': [ { 'langlinks': [], }, ], }, } translations_api_url = 'https://en.wikipedia.org/w/api.php?action=query&titles=Amsterdam&prop=langlinks&lllimit=500&format=json&formatversion=2' m_aioresponses.get(translations_api_url, payload=translations_api_response_body) with TemporaryDirectory() as output_directory_path: with TemporaryDirectory() as cache_directory_path: configuration = Configuration( output_directory_path, 'https://example.com') configuration.cache_directory_path = Path(cache_directory_path) configuration.extensions.add(ExtensionConfiguration(Wikipedia)) async with App(configuration) as app: app.ancestry.entities.append(resource) await load(app) self.assertEqual(1, len(resource.links)) self.assertEqual(entry_title, link.label) self.assertEqual('en', link.locale) self.assertEqual(MediaType('text/html'), link.media_type) self.assertIsNotNone(link.description) self.assertEqual('external', link.relationship)
async def _populate_entity(self, entity: Entity, locales: Set[str]) -> None: if not isinstance(entity, HasLinks): return entry_links = set() for link in entity.links: try: entry_language, entry_name = _parse_url(link.url) entry_links.add((entry_language, entry_name)) except NotAnEntryError: continue entry = None if link.label is None: with suppress(RetrievalError): entry = await self._retriever.get_entry( entry_language, entry_name) await self.populate_link(link, entry_language, entry) for entry_language, entry_name in list(entry_links): entry_translations = await self._retriever.get_translations( entry_language, entry_name) if len(entry_translations) == 0: continue entry_languages = list(entry_translations.keys()) for locale in locales.difference({entry_language}): added_entry_language = negotiate_locale( locale, entry_languages) if added_entry_language is None: continue added_entry_name = entry_translations[added_entry_language] if (added_entry_language, added_entry_name) in entry_links: continue try: added_entry = await self._retriever.get_entry( added_entry_language, added_entry_name) except RetrievalError: continue added_link = Link(added_entry.url) await self.populate_link(added_link, added_entry_language, added_entry) entity.links.add(added_link) entry_links.add((added_entry_language, added_entry_name))
async def test_link_should_encode_full(self) -> None: link = Link('https://example.com') link.label = 'The Link' link.relationship = 'external' link.locale = 'nl-NL' link.media_type = MediaType('text/html') expected = { 'url': 'https://example.com', 'relationship': 'external', 'label': 'The Link', 'locale': 'nl-NL', 'mediaType': 'text/html', } await self.assert_encodes(expected, link, 'link')
async def populate_link(self, link: Link, entry_language: str, entry: Optional[Entry] = None) -> None: if link.url.startswith('http:'): link.url = 'https:' + link.url[5:] if link.media_type is None: link.media_type = MediaType('text/html') if link.relationship is None: link.relationship = 'external' if link.locale is None: link.locale = entry_language if link.description is None: # There are valid reasons for links in locales that aren't supported. with suppress(ValueError): async with self._app.with_locale(link.locale): link.description = _('Read more on Wikipedia.') if entry is not None and link.label is None: link.label = entry.title
async def test_person_should_encode_full(self): parent_id = 'the_parent' parent = Person(parent_id) child_id = 'the_child' child = Person(child_id) sibling_id = 'the_sibling' sibling = Person(sibling_id) sibling.parents.append(parent) person_id = 'the_person' person_affiliation_name = 'Person' person_individual_name = 'The' person = Person(person_id) PersonName(person, person_individual_name, person_affiliation_name) person.parents.append(parent) person.children.append(child) person.private = False link = Link('https://example.com/the-person') link.label = 'The Person Online' person.links.add(link) person.citations.append(Citation('the_citation', Source('The Source'))) Presence(person, Subject(), Event('the_event', Birth())) expected = { '$schema': '/schema.json#/definitions/person', '@context': { 'parents': 'https://schema.org/parent', 'children': 'https://schema.org/child', 'siblings': 'https://schema.org/sibling', }, '@type': 'https://schema.org/Person', 'id': person_id, 'names': [ { '@context': { 'individual': 'https://schema.org/givenName', 'affiliation': 'https://schema.org/familyName', }, 'individual': person_individual_name, 'affiliation': person_affiliation_name, }, ], 'parents': [ '/en/person/the_parent/index.json', ], 'children': [ '/en/person/the_child/index.json', ], 'siblings': [ '/en/person/the_sibling/index.json', ], 'private': False, 'presences': [ { '@context': { 'event': 'https://schema.org/performerIn', }, 'role': 'subject', 'event': '/en/event/the_event/index.json', }, ], 'citations': [ '/en/citation/the_citation/index.json', ], 'links': [ { 'url': '/en/person/the_person/index.json', 'relationship': 'canonical', 'mediaType': 'application/json', }, { 'url': '/nl/person/the_person/index.json', 'relationship': 'alternate', 'locale': 'nl-NL', }, { 'url': '/en/person/the_person/index.html', 'relationship': 'alternate', 'mediaType': 'text/html', }, { 'url': 'https://example.com/the-person', 'label': 'The Person Online', }, ], } await self.assert_encodes(expected, person, 'person')
def _encode_entity(self, encoded: Dict, entity: Entity) -> None: self._encode_schema( encoded, upper_camel_case_to_lower_camel_case( get_entity_type_name(entity.entity_type()))) if 'links' not in encoded: encoded['links'] = [] if not isinstance(entity.id, GeneratedEntityId): encoded['id'] = entity.id canonical = Link(self._generate_url(entity)) canonical.relationship = 'canonical' canonical.media_type = 'application/json' encoded['links'].append(canonical) for locale_configuration in self._app.configuration.locales: if locale_configuration.locale == self._locale: continue translation = Link( self._generate_url(entity, locale=locale_configuration.locale)) translation.relationship = 'alternate' translation.locale = locale_configuration.locale encoded['links'].append(translation) html = Link(self._generate_url(entity, media_type='text/html')) html.relationship = 'alternate' html.media_type = 'text/html' encoded['links'].append(html)
def test_relationship(self) -> None: url = 'https://example.com' sut = Link(url) self.assertIsNone(sut.relationship)
def test_label(self) -> None: url = 'https://example.com' sut = Link(url) self.assertIsNone(sut.label)
def test_media_type(self) -> None: url = 'https://example.com' sut = Link(url) self.assertIsNone(sut.media_type)
def test_description(self) -> None: url = 'https://example.com' sut = Link(url) self.assertIsNone(sut.description)
def test_url(self) -> None: url = 'https://example.com' sut = Link(url) self.assertEquals(url, sut.url)
async def test_source_should_encode_full(self): source = Source('the_source', 'The Source') source.author = 'The Author' source.publisher = 'The Publisher' source.date = Date(2000, 1, 1) source.contained_by = Source('the_containing_source', 'The Containing Source') link = Link('https://example.com/the-source') link.label = 'The Source Online' source.links.add(link) source.contains.append( Source('the_contained_source', 'The Contained Source')) Citation('the_citation', source) expected = { '$schema': '/schema.json#/definitions/source', '@context': { 'name': 'https://schema.org/name', }, '@type': 'https://schema.org/Thing', 'id': 'the_source', 'name': 'The Source', 'author': 'The Author', 'publisher': 'The Publisher', 'contains': [ '/en/source/the_contained_source/index.json', ], 'citations': [ '/en/citation/the_citation/index.json', ], 'containedBy': '/en/source/the_containing_source/index.json', 'date': { 'year': 2000, 'month': 1, 'day': 1, }, 'links': [ { 'url': '/en/source/the_source/index.json', 'relationship': 'canonical', 'mediaType': 'application/json', }, { 'url': '/nl/source/the_source/index.json', 'relationship': 'alternate', 'locale': 'nl-NL', }, { 'url': '/en/source/the_source/index.html', 'relationship': 'alternate', 'mediaType': 'text/html', }, { 'url': 'https://example.com/the-source', 'label': 'The Source Online', }, ], } await self.assert_encodes(expected, source, 'source')
async def test_link_should_encode_minimal(self) -> None: link = Link('https://example.com') expected = { 'url': 'https://example.com', } await self.assert_encodes(expected, link, 'link')
async def test_place_should_encode_full(self): place_id = 'the_place' name = 'The Place' locale = 'nl-NL' latitude = 12.345 longitude = -54.321 coordinates = Point(latitude, longitude) place = Place(place_id, [PlaceName(name, locale)]) place.coordinates = coordinates Enclosure(place, Place('the_enclosing_place', [])) Enclosure(Place('the_enclosed_place', []), place) link = Link('https://example.com/the-place') link.label = 'The Place Online' place.links.add(link) place.events.append(Event('E1', Birth())) expected = { '$schema': '/schema.json#/definitions/place', '@context': { 'enclosedBy': 'https://schema.org/containedInPlace', 'encloses': 'https://schema.org/containsPlace', 'events': 'https://schema.org/event', 'coordinates': 'https://schema.org/geo', }, '@type': 'https://schema.org/Place', 'id': place_id, 'names': [ { 'name': name, 'locale': 'nl-NL', }, ], 'events': [ '/en/event/E1/index.json', ], 'links': [ { 'url': '/en/place/the_place/index.json', 'relationship': 'canonical', 'mediaType': 'application/json', }, { 'url': '/nl/place/the_place/index.json', 'relationship': 'alternate', 'locale': 'nl-NL', }, { 'url': '/en/place/the_place/index.html', 'relationship': 'alternate', 'mediaType': 'text/html', }, { 'url': 'https://example.com/the-place', 'label': 'The Place Online', }, ], 'coordinates': { '@context': { 'latitude': 'https://schema.org/latitude', 'longitude': 'https://schema.org/longitude', }, '@type': 'https://schema.org/GeoCoordinates', 'latitude': latitude, 'longitude': longitude, }, 'encloses': [ '/en/place/the_enclosed_place/index.json', ], 'enclosedBy': [ '/en/place/the_enclosing_place/index.json', ], } await self.assert_encodes(expected, place, 'place')
async def load(self) -> None: amsterdam = Place('betty-demo-amsterdam', [PlaceName('Amsterdam')]) amsterdam.coordinates = Point(52.366667, 4.9) amsterdam.links.add(Link('https://nl.wikipedia.org/wiki/Amsterdam')) self._app.ancestry.entities.append(amsterdam) ilpendam = Place('betty-demo-ilpendam', [PlaceName('Ilpendam')]) ilpendam.coordinates = Point(52.465556, 4.951111) ilpendam.links.add(Link('https://nl.wikipedia.org/wiki/Ilpendam')) self._app.ancestry.entities.append(ilpendam) personal_accounts = Source('betty-demo-personal-accounts', 'Personal accounts') self._app.ancestry.entities.append(personal_accounts) cite_first_person_account = Citation('betty-demo-first-person-account', personal_accounts) self._app.ancestry.entities.append(cite_first_person_account) bevolkingsregister_amsterdam = Source( 'betty-demo-bevolkingsregister-amsterdam', 'Bevolkingsregister Amsterdam') bevolkingsregister_amsterdam.author = 'Gemeente Amsterdam' bevolkingsregister_amsterdam.publisher = 'Gemeente Amsterdam' self._app.ancestry.entities.append(bevolkingsregister_amsterdam) david_marinus_lankester = Person('betty-demo-david-marinus-lankester') PersonName(david_marinus_lankester, 'David Marinus', 'Lankester') self._app.ancestry.entities.append(david_marinus_lankester) geertruida_van_ling = Person('betty-demo-geertruida-van-ling') PersonName(geertruida_van_ling, 'Geertruida', 'Van Ling') self._app.ancestry.entities.append(geertruida_van_ling) marriage_of_dirk_jacobus_lankester_and_jannigje_palsen = Event( 'betty-demo-marriage-of-dirk-jacobus-lankester-and-jannigje-palsen', Marriage(), Date(1922, 7, 4)) marriage_of_dirk_jacobus_lankester_and_jannigje_palsen.place = ilpendam self._app.ancestry.entities.append( marriage_of_dirk_jacobus_lankester_and_jannigje_palsen) birth_of_dirk_jacobus_lankester = Event( 'betty-demo-birth-of-dirk-jacobus-lankester', Birth(), Date(1897, 8, 25)) birth_of_dirk_jacobus_lankester.place = amsterdam self._app.ancestry.entities.append(birth_of_dirk_jacobus_lankester) death_of_dirk_jacobus_lankester = Event( 'betty-demo-death-of-dirk-jacobus-lankester', Death(), Date(1986, 8, 18)) death_of_dirk_jacobus_lankester.place = amsterdam self._app.ancestry.entities.append(death_of_dirk_jacobus_lankester) dirk_jacobus_lankester = Person('betty-demo-dirk-jacobus-lankester') PersonName(dirk_jacobus_lankester, 'Dirk Jacobus', 'Lankester') Presence(dirk_jacobus_lankester, Subject(), birth_of_dirk_jacobus_lankester) Presence(dirk_jacobus_lankester, Subject(), death_of_dirk_jacobus_lankester) Presence(dirk_jacobus_lankester, Subject(), marriage_of_dirk_jacobus_lankester_and_jannigje_palsen) dirk_jacobus_lankester.parents.append(david_marinus_lankester, geertruida_van_ling) self._app.ancestry.entities.append(dirk_jacobus_lankester) birth_of_marinus_david_lankester = Event( 'betty-demo-birth-of-marinus-david', Birth(), DateRange(Date(1874, 1, 15), Date(1874, 3, 21), start_is_boundary=True, end_is_boundary=True)) birth_of_marinus_david_lankester.place = amsterdam self._app.ancestry.entities.append(birth_of_marinus_david_lankester) death_of_marinus_david_lankester = Event( 'betty-demo-death-of-marinus-david', Death(), Date(1971)) death_of_marinus_david_lankester.place = amsterdam self._app.ancestry.entities.append(death_of_marinus_david_lankester) marinus_david_lankester = Person('betty-demo-marinus-david-lankester') PersonName(marinus_david_lankester, 'Marinus David', 'Lankester') Presence(marinus_david_lankester, Subject(), birth_of_marinus_david_lankester) Presence(marinus_david_lankester, Subject(), death_of_marinus_david_lankester) marinus_david_lankester.parents.append(david_marinus_lankester, geertruida_van_ling) self._app.ancestry.entities.append(marinus_david_lankester) birth_of_jacoba_gesina_lankester = Event( 'betty-demo-birth-of-jacoba-gesina', Birth(), Date(1900, 3, 14)) birth_of_jacoba_gesina_lankester.place = amsterdam self._app.ancestry.entities.append(birth_of_jacoba_gesina_lankester) jacoba_gesina_lankester = Person('betty-demo-jacoba-gesina-lankester') PersonName(jacoba_gesina_lankester, 'Jacoba Gesina', 'Lankester') Presence(jacoba_gesina_lankester, Subject(), birth_of_jacoba_gesina_lankester) jacoba_gesina_lankester.parents.append(david_marinus_lankester, geertruida_van_ling) self._app.ancestry.entities.append(jacoba_gesina_lankester) jannigje_palsen = Person('betty-demo-jannigje-palsen') PersonName(jannigje_palsen, 'Jannigje', 'Palsen') Presence(jannigje_palsen, Subject(), marriage_of_dirk_jacobus_lankester_and_jannigje_palsen) self._app.ancestry.entities.append(jannigje_palsen) marriage_of_johan_de_boer_and_liberta_lankester = Event( 'betty-demo-marriage-of-johan-de-boer-and-liberta-lankester', Marriage(), Date(1953, 6, 19)) marriage_of_johan_de_boer_and_liberta_lankester.place = amsterdam self._app.ancestry.entities.append( marriage_of_johan_de_boer_and_liberta_lankester) cite_birth_of_liberta_lankester_from_bevolkingsregister_amsterdam = Citation( 'betty-demo-birth-of-liberta-lankester-from-bevolkingsregister-amsterdam', bevolkingsregister_amsterdam) cite_birth_of_liberta_lankester_from_bevolkingsregister_amsterdam.location = 'Amsterdam' self._app.ancestry.entities.append( cite_birth_of_liberta_lankester_from_bevolkingsregister_amsterdam) birth_of_liberta_lankester = Event( 'betty-demo-birth-of-liberta-lankester', Birth(), Date(1929, 12, 22)) birth_of_liberta_lankester.place = amsterdam birth_of_liberta_lankester.citations.append( cite_birth_of_liberta_lankester_from_bevolkingsregister_amsterdam) self._app.ancestry.entities.append(birth_of_liberta_lankester) death_of_liberta_lankester = Event( 'betty-demo-death-of-liberta-lankester', Death(), Date(2015, 1, 17)) death_of_liberta_lankester.place = amsterdam death_of_liberta_lankester.citations.append(cite_first_person_account) self._app.ancestry.entities.append(death_of_liberta_lankester) liberta_lankester = Person('betty-demo-liberta-lankester') PersonName(liberta_lankester, 'Liberta', 'Lankester') PersonName(liberta_lankester, 'Betty') Presence(liberta_lankester, Subject(), birth_of_liberta_lankester) Presence(liberta_lankester, Subject(), death_of_liberta_lankester) Presence(liberta_lankester, Subject(), marriage_of_johan_de_boer_and_liberta_lankester) liberta_lankester.parents.append(dirk_jacobus_lankester, jannigje_palsen) self._app.ancestry.entities.append(liberta_lankester) birth_of_johan_de_boer = Event('betty-demo-birth-of-johan-de-boer', Birth(), Date(1930, 6, 20)) birth_of_johan_de_boer.place = amsterdam self._app.ancestry.entities.append(birth_of_johan_de_boer) death_of_johan_de_boer = Event('betty-demo-death-of-johan-de-boer', Death(), Date(1999, 3, 10)) death_of_johan_de_boer.place = amsterdam death_of_johan_de_boer.citations.append(cite_first_person_account) self._app.ancestry.entities.append(death_of_johan_de_boer) johan_de_boer = Person('betty-demo-johan-de-boer') PersonName(johan_de_boer, 'Johan', 'De Boer') PersonName(johan_de_boer, 'Hans') Presence(johan_de_boer, Subject(), birth_of_johan_de_boer) Presence(johan_de_boer, Subject(), death_of_johan_de_boer) Presence(johan_de_boer, Subject(), marriage_of_johan_de_boer_and_liberta_lankester) self._app.ancestry.entities.append(johan_de_boer) parent_of_bart_feenstra_child_of_liberta_lankester = Person( 'betty-demo-parent-of-bart-feenstra-child-of-liberta-lankester') PersonName(parent_of_bart_feenstra_child_of_liberta_lankester, 'Bart\'s parent') parent_of_bart_feenstra_child_of_liberta_lankester.parents.append( johan_de_boer, liberta_lankester) self._app.ancestry.entities.append( parent_of_bart_feenstra_child_of_liberta_lankester) bart_feenstra = Person('betty-demo-bart-feenstra') PersonName(bart_feenstra, 'Bart', 'Feenstra') bart_feenstra.parents.append( parent_of_bart_feenstra_child_of_liberta_lankester) self._app.ancestry.entities.append(bart_feenstra)