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

1 from typing import Type, Any

2

3 from django import forms

4

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

6

7

8 class 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

98 class 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

108 class 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

127 class 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

173 class FreelanceTimelinePlugin(TimelineMixin, ListPlugin):

174 name = "freelance_timeline"

175 verbose_name = "Freelance Timeline"

176

177

178 class EmployedTimelinePlugin(TimelineMixin, ListPlugin):

179 name = "employed_timeline"

180 verbose_name = "Employed Timeline"