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
« 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
12from .forms import CommentForm, EmailPostForm, SearchForm 1a
13from .models import Post 1a
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
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
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]
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 )
63class PostCreateView(LoginRequiredMixin, CreateView): 1a
64 model = Post 1a
65 template_name = "posts/post_new.html" 1a
66 fields = ["title", "tags", "status", "body"] 1a
68 def form_valid(self, form): 1a
69 form.instance.author = self.request.user
70 return super().form_valid(form)
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
78 def test_func(self): 1a
79 obj = self.get_object() 1a
80 return obj.author == self.request.user 1a
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
88 def test_func(self): 1a
89 obj = self.get_object() 1a
90 return obj.author == self.request.user 1a
93def post_share(request, post_id): 1a
94 post = get_object_or_404(Post, id=post_id, status=Post.Status.PUBLISHED)
95 sent = False
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 )
118def post_search(request): 1a
119 form = SearchForm()
120 query = None
121 results = []
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 )
139 return render(
140 request, "posts/search.html", {"form": form, "query": query, "results": results}
141 )
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 )