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

1import random 

2import string 

3from datetime import datetime 

4 

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 

11 

12from .base import ListPlugin, ListItemFormMixin 

13 

14 

15def generate_random_string(length=20): 

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

17 

18 

19class HTMLLinkWidget(forms.Widget): 

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

21 return mark_safe(value) if value else "" 

22 

23 

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

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 

68class TokenForm(forms.Form): 

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

70 

71 

72class TokenViaGetForm(forms.Form): 

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

74 

75 

76class 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 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.") 

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