Coverage for src/django_resume/plugins/tokens.py: 46%

1 import random

2 import string

3 from datetime import datetime

4

5 from django import forms

6 from django.core.exceptions import PermissionDenied

7 from django.http import HttpRequest

8 from django.urls import reverse

9 from django.utils import timezone

10 from django.utils.safestring import mark_safe

11

12 from .base import ListPlugin, ListItemFormMixin

13

14

15 def generate_random_string(length=20):

16 return "".join(random.choices(string.ascii_letters + string.digits, k=length))

17

18

19 class HTMLLinkWidget(forms.Widget):

20 def render(self, name, value, attrs=None, renderer=None):

21 return mark_safe(value) if value else ""

22

23

24 class 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())

29

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

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

32

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

36

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()

42

43 self.generate_cv_link(self.resume)

44

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 )

51

52 def clean_token(self):

53 token = self.cleaned_data["token"]

54 if not token:

55 raise forms.ValidationError("Token required.")

56 return token

57

58 def clean_created(self):

59 created = self.cleaned_data["created"]

60 return created.isoformat()

61

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

66

67

68 class TokenForm(forms.Form):

69 token_required = forms.BooleanField(required=False, label="Token Required")

70

71

72 class TokenViaGetForm(forms.Form):

73 token = forms.CharField(max_length=255, required=True, label="Token")

74

75

76 class TokenPlugin(ListPlugin):

77 """

78 Generate tokens for a resume.

79

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 """

83

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"

88

89 @staticmethod

90 def get_admin_item_form():

91 return TokenItemForm

92

93 @staticmethod

94 def get_admin_form():

95 return TokenForm

96

97 def get_form_classes(self):

98 return {"item": TokenItemForm, "flat": TokenForm}

99

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:

106 return

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

116 raise PermissionDenied("Invalid token.")

117

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 {}