Coverage for posts/views.py: 59.62%

92 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-02 19:56 -0700

1from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin 

2from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector 

3from django.core.mail import send_mail 

4from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator 

5from django.db.models import Count 

6from django.shortcuts import get_object_or_404, render 

7from django.urls import reverse_lazy 

8from django.views.decorators.http import require_POST 

9from django.views.generic.edit import CreateView, DeleteView, UpdateView 

10from taggit.models import Tag 

11 

12from .forms import CommentForm, EmailPostForm, SearchForm 

13from .models import Post 

14 

15 

16def post_list(request, tag_slug=None): 

17 post_list = Post.published.all() 1ab

18 tag = None 1ab

19 if tag_slug: 19 ↛ 20line 19 didn't jump to line 20 because the condition on line 19 was never true1ab

20 tag = get_object_or_404(Tag, slug=tag_slug) 

21 post_list = post_list.filter(tags__in=[tag]) 

22 paginator = Paginator(post_list, 3) 1ab

23 page_number = request.GET.get("page", 1) 1ab

24 try: 1ab

25 posts = paginator = paginator.page(page_number) 1ab

26 except PageNotAnInteger: 

27 posts = paginator.page(1) 

28 except EmptyPage: 

29 posts = paginator.page(paginator.num_pages) 

30 return render(request, "posts/post_list.html", {"posts": posts, "tag": tag}) 1ab

31 

32 

33def post_detail(request, year, month, day, post): 

34 post = get_object_or_404( 1e

35 Post, 

36 status=Post.Status.PUBLISHED, 

37 slug=post, 

38 publish__year=year, 

39 publish__month=month, 

40 publish__day=day, 

41 ) 

42 comments = post.comments.filter(active=True) 1e

43 form = CommentForm() 1e

44 

45 post_tags_ids = post.tags.values_list("id", flat=True) 1e

46 similar_posts = Post.published.filter(tags__in=post_tags_ids).exclude(id=post.id) 1e

47 similar_posts = similar_posts.annotate(same_tags=Count("tags")).order_by( 1e

48 "-same_tags", "-publish" 

49 )[:4] 

50 

51 return render( 1e

52 request, 

53 "posts/post_detail.html", 

54 { 

55 "post": post, 

56 "comments": comments, 

57 "form": form, 

58 "similar_posts": similar_posts, 

59 }, 

60 ) 

61 

62 

63class PostCreateView(LoginRequiredMixin, CreateView): 

64 model = Post 

65 template_name = "posts/post_new.html" 

66 fields = ["title", "tags", "status", "body"] 

67 

68 def form_valid(self, form): 

69 form.instance.author = self.request.user 

70 return super().form_valid(form) 

71 

72 

73class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): 

74 model = Post 

75 template_name = "posts/post_edit.html" 

76 fields = ["title", "tags", "status", "body"] 

77 

78 def test_func(self): 

79 obj = self.get_object() 1f

80 return obj.author == self.request.user 1f

81 

82 

83class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView): 

84 model = Post 

85 template_name = "posts/post_delete.html" 

86 success_url = reverse_lazy("post_list") 

87 

88 def test_func(self): 

89 obj = self.get_object() 1g

90 return obj.author == self.request.user 1g

91 

92 

93def post_share(request, post_id): 

94 post = get_object_or_404(Post, id=post_id, status=Post.Status.PUBLISHED) 

95 sent = False 

96 

97 if request.method == "POST": 

98 form = EmailPostForm(request.POST) 

99 if form.is_valid(): 

100 # fmt: off 

101 cd = form.cleaned_data 

102 post_url = request.build_absolute_uri(post.get_absolute_url()) 

103 subject = f"{cd['name']} recommends you read " f"{post.title}" 

104 message = ( 

105 f"Read {post.title} at {post_url}\n\n" 

106 f"{cd['name']}'s comments: {cd['comments']}" 

107 ) 

108 send_mail(subject, message, "kevin.bowen@gmail.com", [cd["to"]]) 

109 sent = True 

110 # fmt: on 

111 else: 

112 form = EmailPostForm() 

113 return render( 

114 request, "posts/post_share.html", {"post": post, "form": form, "sent": sent} 

115 ) 

116 

117 

118def post_search(request): 

119 form = SearchForm() 

120 query = None 

121 results = [] 

122 

123 if "query" in request.GET: 

124 form = SearchForm(request.GET) 

125 if form.is_valid(): 

126 query = form.cleaned_data["query"] 

127 search_vector = SearchVector("title", weight="A") + SearchVector( 

128 "body", weight="B" 

129 ) 

130 search_query = SearchQuery(query) 

131 results = ( 

132 Post.published.annotate( 

133 search=search_vector, rank=SearchRank(search_vector, search_query) 

134 ) 

135 .filter(rank__gte=0.3) 

136 .order_by("-rank") 

137 ) 

138 

139 return render( 

140 request, "posts/search.html", {"form": form, "query": query, "results": results} 

141 ) 

142 

143 

144@require_POST 

145def comment_add(request, post_id): 

146 post = get_object_or_404(Post, id=post_id, status=Post.Status.PUBLISHED) 1cd

147 comment = None 1cd

148 form = CommentForm(data=request.POST) 1cd

149 if form.is_valid(): 149 ↛ 153line 149 didn't jump to line 153 because the condition on line 149 was always true1cd

150 comment = form.save(commit=False) 1cd

151 comment.post = post 1cd

152 comment.save() 1cd

153 return render( 1cd

154 request, 

155 "posts/comment_add.html", 

156 {"post": post, "form": form, "comment": comment}, 

157 )