Coverage for posts/views.py: 59.62%

92 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-22 12:02 -0700

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

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

3from django.core.mail import send_mail 1a

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

5from django.db.models import Count 1a

6from django.shortcuts import get_object_or_404, render 1a

7from django.urls import reverse_lazy 1a

8from django.views.decorators.http import require_POST 1a

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

10from taggit.models import Tag 1a

11 

12from .forms import CommentForm, EmailPostForm, SearchForm 1a

13from .models import Post 1a

14 

15 

16def post_list(request, tag_slug=None): 1a

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

18 tag = None 1a

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

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) 1a

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

24 try: 1a

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

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}) 1a

31 

32 

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

34 post = get_object_or_404( 1a

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) 1a

43 form = CommentForm() 1a

44 

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

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

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

48 "-same_tags", "-publish" 

49 )[:4] 

50 

51 return render( 1a

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): 1a

64 model = Post 1a

65 template_name = "posts/post_new.html" 1a

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

67 

68 def form_valid(self, form): 1a

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

70 return super().form_valid(form) 

71 

72 

73class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): 1a

74 model = Post 1a

75 template_name = "posts/post_edit.html" 1a

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

77 

78 def test_func(self): 1a

79 obj = self.get_object() 1a

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

81 

82 

83class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView): 1a

84 model = Post 1a

85 template_name = "posts/post_delete.html" 1a

86 success_url = reverse_lazy("post_list") 1a

87 

88 def test_func(self): 1a

89 obj = self.get_object() 1a

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

91 

92 

93def post_share(request, post_id): 1a

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): 1a

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 1a

145def comment_add(request, post_id): 1a

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

147 comment = None 1a

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

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

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

151 comment.post = post 1a

152 comment.save() 1a

153 return render( 1a

154 request, 

155 "posts/comment_add.html", 

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

157 )