diff --git a/rendercv/data_model.py b/rendercv/data_model.py index 5cc3482..c91c7a5 100644 --- a/rendercv/data_model.py +++ b/rendercv/data_model.py @@ -835,14 +835,12 @@ class Connection(BaseModel): @field_validator("value") @classmethod - def check_type_of_value( - cls, value: str - ) -> str: + def check_type_of_value(cls, value: str) -> str: if not re.search(r"[^\d\-+]", str(value)): # If there is nothing other than digits, hyphens, and plus signs, then it is # a phone number value = "tel:" + value - + return value @computed_field @@ -903,6 +901,11 @@ class Section(BaseModel): description="The entries of the section. The format depends on the entry type.", ) + @field_validator("title") + @classmethod + def make_first_letters_uppercase(cls, title: str) -> str: + return title.title() + class CurriculumVitae(BaseModel): """This class bindes all the information of a CV together.""" @@ -1001,6 +1004,36 @@ class CurriculumVitae(BaseModel): ), ) + @model_validator(mode="after") + @classmethod + def check_if_the_section_names_are_unique(self, model): + pre_defined_section_names = [ + "Education", + "Work Experience", + "Academic Projects", + "Personal Projects", + "Certificates", + "Extracurricular Activities", + "Test Scores", + "Skills", + "Publications", + ] + if model.custom_sections is not None: + custom_section_names = [] + for custom_section in model.custom_sections: + custom_section_names.append(custom_section.title) + + section_names = pre_defined_section_names + custom_section_names + seen = set() + duplicates = {val for val in section_names if (val in seen or seen.add(val))} + if len(duplicates) > 0: + raise ValueError( + "The section names should be unique. The following section names are" + f" duplicated: {duplicates}" + ) + + return model + @computed_field @cached_property def connections(self) -> list[str]: @@ -1063,11 +1096,11 @@ class CurriculumVitae(BaseModel): link_text = None for section_name in self.section_order: - # capitalize the first letter of each word in the section name: - section_name = section_name.title() - # Create a section for each section name in the section order: if section_name in pre_defined_sections: + if pre_defined_sections[section_name] is None: + continue + entry_type = pre_defined_sections[section_name][0].__class__.__name__ entries = pre_defined_sections[section_name] if section_name == "Test Scores": diff --git a/tests/test_rendercv.py b/tests/test_rendercv.py index 07e056c..c376b22 100644 --- a/tests/test_rendercv.py +++ b/tests/test_rendercv.py @@ -508,6 +508,130 @@ class TestRendercv(unittest.TestCase): result = connection.url self.assertEqual(result, expected) + def test_data_curriculum_vitae_connections(self): + input = { + "name": "John Doe", + "location": "My Location", + "phone": "+905559876543", + "email": "john@doe.com", + "website": "https://www.example.com/", + } + exptected_length = 4 + cv = data_model.CurriculumVitae(**input) + result = len(cv.connections) + with self.subTest(msg="without social networks"): + self.assertEqual(result, exptected_length) + + input = { + "name": "John Doe", + "location": "My Location", + "phone": "+905559876543", + "email": "john@doe.com", + "website": "https://www.example.com/", + "social_networks": [ + {"network": "LinkedIn", "username": "username"}, + {"network": "GitHub", "username": "sinaatalay"}, + {"network": "Instagram", "username": "username"}, + ], + } + exptected_length = 7 + cv = data_model.CurriculumVitae(**input) + result = len(cv.connections) + with self.subTest(msg="with social networks"): + self.assertEqual(result, exptected_length) + + def test_data_curriculum_vitae_custom_sections(self): + # Valid custom sections: + input = { + "name": "John Doe", + "custom_sections": [ + { + "title": "My Custom Section 1", + "entry_type": "OneLineEntry", + "entries": [ + { + "name": "My Custom Entry Name", + "details": "My Custom Entry Value", + }, + { + "name": "My Custom Entry Name", + "details": "My Custom Entry Value", + }, + ], + }, + { + "title": "My Custom Section 2", + "entry_type": "NormalEntry", + "entries": [ + {"name": "My Custom Entry Name"}, + {"name": "My Custom Entry Name"}, + ], + }, + { + "title": "My Custom Section 3", + "entry_type": "ExperienceEntry", + "entries": [ + { + "company": "My Custom Entry Name", + "position": "My Custom Entry Value", + }, + { + "company": "My Custom Entry Name", + "position": "My Custom Entry Value", + }, + ], + }, + { + "title": "My Custom Section 4", + "entry_type": "EducationEntry", + "entries": [ + { + "institution": "My Custom Entry Name", + "area": "My Custom Entry Value", + }, + { + "institution": "My Custom Entry Name", + "area": "My Custom Entry Value", + }, + ], + }, + { + "title": "My Custom Section 5", + "entry_type": "PublicationEntry", + "entries": [ + { + "title": "My Publication", + "authors": [ + "Author 1", + "Author 2", + ], + "doi": "10.1103/PhysRevB.76.054309", + "date": "2020-01-01", + }, + { + "title": "My Publication", + "authors": [ + "Author 1", + "Author 2", + ], + "doi": "10.1103/PhysRevB.76.054309", + "date": "2020-01-01", + }, + ], + }, + ], + } + + with self.subTest(msg="valid custom sections"): + cv = data_model.CurriculumVitae(**input) + self.assertEqual(len(cv.sections), 5) + + # Custom sections with duplicate titles: + input["custom_sections"][1]["title"] = "My Custom Section 1" + with self.subTest(msg="custom sections with duplicate titles"): + with self.assertRaises(ValidationError): + data_model.CurriculumVitae(**input) + if __name__ == "__main__": unittest.main()