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
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-23 13:16 +0200
1from typing import Type, Any
3from django import forms
5from .plugins import ListPlugin, ListItemFormMixin, ListTemplates, ListInline
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)
20 def __init__(self, *args, **kwargs):
21 super().__init__(*args, **kwargs)
22 self.set_initial_badges()
23 self.set_initial_position()
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 }
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
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"])
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
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
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
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
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
98class TimelineFlatForm(forms.Form):
99 title = forms.CharField(widget=forms.TextInput(), required=False, max_length=50)
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
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
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 )
139 @staticmethod
140 def get_form_classes() -> dict[str, Type[forms.Form]]:
141 return {"item": TimelineItemForm, "flat": TimelineFlatForm}
143 @staticmethod
144 def items_ordered_by_position(items, reverse=False):
145 return sorted(items, key=lambda item: item.get("position", 0), reverse=reverse)
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
173class FreelanceTimelinePlugin(TimelineMixin, ListPlugin):
174 name = "freelance_timeline"
175 verbose_name = "Freelance Timeline"
178class EmployedTimelinePlugin(TimelineMixin, ListPlugin):
179 name = "employed_timeline"
180 verbose_name = "Employed Timeline"