Coverage for src/django_resume/timelines.py: 79%

93 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-23 13:16 +0200

1from typing import Type, Any 

2 

3from django import forms 

4 

5from .plugins import ListPlugin, ListItemFormMixin, ListTemplates, ListInline 

6 

7 

8class TimelineItemForm(ListItemFormMixin, forms.Form): 

9 role = forms.CharField(widget=forms.TextInput()) 

10 company_url = forms.URLField( 

11 widget=forms.URLInput(), required=False, assume_scheme="https" 

12 ) 

13 company_name = forms.CharField(widget=forms.TextInput(), max_length=50) 

14 description = forms.CharField(widget=forms.Textarea()) 

15 start = forms.CharField(widget=forms.TextInput(), required=False) 

16 end = forms.CharField(widget=forms.TextInput(), required=False) 

17 badges = forms.CharField(widget=forms.TextInput(), required=False) 

18 position = forms.IntegerField(widget=forms.NumberInput(), required=False) 

19 

20 def __init__(self, *args, **kwargs): 

21 super().__init__(*args, **kwargs) 

22 self.set_initial_badges() 

23 self.set_initial_position() 

24 

25 @staticmethod 

26 def get_initial() -> dict[str, Any]: 

27 """Just some default values.""" 

28 return { 

29 "company_name": "company_name", 

30 "company_url": "https://example.com", 

31 "role": "role", 

32 "start": "start", 

33 "end": "end", 

34 "description": "description", 

35 "badges": "badges", 

36 } 

37 

38 def set_context(self, item: dict, context: dict[str, Any]) -> dict[str, Any]: 

39 context["entry"] = { 

40 "id": item["id"], 

41 "company_url": item["company_url"], 

42 "company_name": item["company_name"], 

43 "role": item["role"], 

44 "start": item["start"], 

45 "end": item["end"], 

46 "description": item["description"], 

47 "badges": item["badges"], 

48 "edit_url": context["edit_url"], 

49 "delete_url": context["delete_url"], 

50 } 

51 return context 

52 

53 def set_initial_badges(self): 

54 """Transform the list of badges into a comma-separated string.""" 

55 if "badges" in self.initial and isinstance(self.initial["badges"], list): 

56 self.initial["badges"] = ",".join(self.initial["badges"]) 

57 

58 @staticmethod 

59 def get_max_position(items): 

60 """Return the maximum position value from the existing items.""" 

61 positions = [item.get("position", 0) for item in items] 

62 return max(positions) if positions else -1 

63 

64 def set_initial_position(self): 

65 """Set the position to the next available position.""" 

66 if "position" not in self.initial: 

67 self.initial["position"] = self.get_max_position(self.existing_items) + 1 

68 

69 def clean_title(self): 

70 title = self.cleaned_data["title"] 

71 if title == "Senor Developer": 

72 print("No Senor! Validation Error!") 

73 raise forms.ValidationError("No Senor!") 

74 return title 

75 

76 def clean_badges(self): 

77 badges = self.cleaned_data.get("badges", "") 

78 # Split the comma-separated values and strip any extra spaces 

79 badge_list = [badge.strip() for badge in badges.split(",")] 

80 return badge_list 

81 

82 def clean_position(self): 

83 position = self.cleaned_data.get("position", 0) 

84 if position < 0: 84 ↛ 85line 84 didn't jump to line 85 because the condition on line 84 was never true

85 raise forms.ValidationError("Position must be a positive integer.") 

86 for item in self.existing_items: 

87 if item["id"] == self.cleaned_data["id"]: 

88 # updating the existing item, so we can skip checking its position 

89 continue 

90 if item.get("position") == position: 

91 max_position = self.get_max_position(self.existing_items) 

92 raise forms.ValidationError( 

93 f"Position must be unique - take {max_position + 1} instead." 

94 ) 

95 return position 

96 

97 

98class TimelineFlatForm(forms.Form): 

99 title = forms.CharField(widget=forms.TextInput(), required=False, max_length=50) 

100 

101 @staticmethod 

102 def set_context(item: dict, context: dict[str, Any]) -> dict[str, Any]: 

103 context["timeline"] = {"title": item.get("title", "")} 

104 context["timeline"]["edit_flat_url"] = context["edit_flat_url"] 

105 return context 

106 

107 

108class TimelineForContext: 

109 def __init__( 

110 self, 

111 *, 

112 title: str, 

113 ordered_entries: list[dict], 

114 templates: ListTemplates, 

115 add_item_url: str, 

116 edit_flat_url: str, 

117 edit_flat_post_url: str, 

118 ): 

119 self.title = title 

120 self.ordered_entries = ordered_entries 

121 self.templates = templates 

122 self.add_item_url = add_item_url 

123 self.edit_flat_url = edit_flat_url 

124 self.edit_flat_post_url = edit_flat_post_url 

125 

126 

127class TimelineMixin: 

128 name: str 

129 verbose_name: str 

130 inline: ListInline 

131 templates = ListTemplates( 

132 main="django_resume/plain/timeline.html", 

133 flat="django_resume/plain/timeline_flat.html", 

134 flat_form="django_resume/plain/timeline_flat_form.html", 

135 item="django_resume/plain/timeline_item.html", 

136 item_form="django_resume/plain/timeline_item_form.html", 

137 ) 

138 

139 @staticmethod 

140 def get_form_classes() -> dict[str, Type[forms.Form]]: 

141 return {"item": TimelineItemForm, "flat": TimelineFlatForm} 

142 

143 @staticmethod 

144 def items_ordered_by_position(items, reverse=False): 

145 return sorted(items, key=lambda item: item.get("position", 0), reverse=reverse) 

146 

147 def get_context( 

148 self, plugin_data, person_pk, *, context: dict[str, Any] 

149 ) -> TimelineForContext: 

150 ordered_entries = self.items_ordered_by_position( 

151 plugin_data.get("items", []), reverse=True 

152 ) 

153 if context.get("show_edit_button", False): 

154 # if there should be edit buttons, add the edit URLs to each entry 

155 for entry in ordered_entries: 

156 entry["edit_url"] = self.inline.get_edit_item_url( 

157 person_pk, item_id=entry["id"] 

158 ) 

159 entry["delete_url"] = self.inline.get_delete_item_url( 

160 person_pk, item_id=entry["id"] 

161 ) 

162 timeline = TimelineForContext( 

163 title=plugin_data.get("flat", {}).get("title", self.verbose_name), 

164 ordered_entries=ordered_entries, 

165 templates=self.templates, 

166 add_item_url=self.inline.get_edit_item_url(person_pk), 

167 edit_flat_url=self.inline.get_edit_flat_url(person_pk), 

168 edit_flat_post_url=self.inline.get_edit_flat_post_url(person_pk), 

169 ) 

170 return timeline 

171 

172 

173class FreelanceTimelinePlugin(TimelineMixin, ListPlugin): 

174 name = "freelance_timeline" 

175 verbose_name = "Freelance Timeline" 

176 

177 

178class EmployedTimelinePlugin(TimelineMixin, ListPlugin): 

179 name = "employed_timeline" 

180 verbose_name = "Employed Timeline"