Coverage for src/django_resume/plugins/tokens.py: 50%
80 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-13 13:17 +0200
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-13 13:17 +0200
1import random
2import string
3from datetime import datetime
5from django import forms
6from django.core.exceptions import PermissionDenied
7from django.http import HttpRequest
8from django.urls import reverse
9from django.utils import timezone
10from django.utils.safestring import mark_safe
12from .base import ListPlugin, ListItemFormMixin
15def generate_random_string(length=20):
16 return "".join(random.choices(string.ascii_letters + string.digits, k=length))
19class HTMLLinkWidget(forms.Widget):
20 def render(self, name, value, attrs=None, renderer=None):
21 return mark_safe(value) if value else ""
24class TokenItemForm(ListItemFormMixin, forms.Form):
25 token = forms.CharField(max_length=255, required=True, label="Token")
26 receiver = forms.CharField(max_length=255)
27 created = forms.DateTimeField(widget=forms.HiddenInput(), required=False)
28 cv_link = forms.CharField(required=False, label="CV Link", widget=HTMLLinkWidget())
30 def __init__(self, *args, **kwargs):
31 super().__init__(*args, **kwargs)
33 if not self.initial.get("token"):
34 self.fields["token"].initial = generate_random_string()
35 self.token = self.initial.get("token") or self.fields["token"].initial
37 if "created" in self.initial and isinstance(self.initial["created"], str):
38 self.initial["created"] = datetime.fromisoformat(self.initial["created"])
39 else:
40 # Set the 'created' field to the current time if it's not already set
41 self.fields["created"].initial = timezone.now()
43 self.generate_cv_link(self.resume)
45 def generate_cv_link(self, resume):
46 base_url = reverse("django_resume:cv", kwargs={"slug": resume.slug})
47 link = f"{base_url}?token={self.token}"
48 self.fields["cv_link"].initial = mark_safe(
49 f'<a href="{link}" target="_blank">{link}</a>'
50 )
52 def clean_token(self):
53 token = self.cleaned_data["token"]
54 if not token:
55 raise forms.ValidationError("Token required.")
56 return token
58 def clean_created(self):
59 created = self.cleaned_data["created"]
60 return created.isoformat()
62 def clean(self):
63 cleaned_data = super().clean()
64 cleaned_data.pop("cv_link", None) # Remove 'cv_link' from cleaned_data
65 return cleaned_data
68class TokenForm(forms.Form):
69 token_required = forms.BooleanField(required=False, label="Token Required")
72class TokenViaGetForm(forms.Form):
73 token = forms.CharField(max_length=255, required=True, label="Token")
76class TokenPlugin(ListPlugin):
77 """
78 Generate tokens for a resume.
80 If you want to restrict access to a resume's resume, you can generate a token.
81 The token can be shared with the resume, and they can access their resume using the token.
82 """
84 name = "token"
85 verbose_name = "CV Token"
86 flat_template = "django_resume/plain/token_flat.html"
87 flat_form_template = "django_resume/plain/token_flat_form.html"
89 @staticmethod
90 def get_admin_item_form():
91 return TokenItemForm
93 @staticmethod
94 def get_admin_form():
95 return TokenForm
97 def get_form_classes(self):
98 return {"item": TokenItemForm, "flat": TokenForm}
100 @staticmethod
101 def check_permissions(request: HttpRequest, plugin_data: dict) -> None:
102 token_required = plugin_data.get("flat", {"token_required": True}).get(
103 "token_required", True
104 )
105 if not token_required or request.user.is_authenticated: 105 ↛ 107line 105 didn't jump to line 107 because the condition on line 105 was always true
106 return None
107 form = TokenViaGetForm(request.GET)
108 if not form.is_valid():
109 raise PermissionDenied("Token required to access this page.")
110 token = form.cleaned_data["token"]
111 if token is None:
112 raise PermissionDenied("Token required to access this page.")
113 tokens = set(item["token"] for item in plugin_data.get("items", []))
114 if token in tokens:
115 return None
116 raise PermissionDenied("Invalid token.")
118 def get_context(
119 self,
120 request: HttpRequest,
121 plugin_data: dict,
122 resume_pk: int,
123 *,
124 context: dict,
125 edit: bool = False,
126 ) -> dict:
127 self.check_permissions(request, plugin_data)
128 return {}