From 58e0d3671102ccc7f6d5e24410e41f234f0a132a Mon Sep 17 00:00:00 2001
From: Marcin Kwiatkowski
Date: Mon, 6 Oct 2025 07:29:45 +0200
Subject: [PATCH 01/16] feat: added canonical url for post
---
.../Testtrack/M1 - Django Learning/mysite/blog/models.py | 7 +++++++
.../mysite/blog/templates/blog/post/list.html | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
index 9d8e590..023a3e7 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
@@ -1,4 +1,5 @@
from django.db import models
+from django.urls import reverse
from django.utils import timezone
from django.conf import settings
@@ -39,3 +40,9 @@ class Meta:
def __str__(self):
return self.title
+
+ def get_absolute_url(self):
+ return reverse(
+ 'blog:post_detail',
+ args=[self.id]
+ )
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
index 8f182df..16e5803 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
@@ -6,7 +6,7 @@
Blog Posts
{% for post in posts %}
From 403e10100b7ae0a2d4b197654ce7add25f23d2ca Mon Sep 17 00:00:00 2001
From: Marcin Kwiatkowski
Date: Mon, 6 Oct 2025 07:40:32 +0200
Subject: [PATCH 02/16] feat: add friendly URLs to Post
---
.../blog/migrations/0002_alter_post_slug.py | 19 ++++++++++++++++++
.../mysite/blog/models.py | 12 +++++++++--
.../M1 - Django Learning/mysite/blog/urls.py | 6 +++++-
.../M1 - Django Learning/mysite/blog/views.py | 9 ++++++---
.../M1 - Django Learning/mysite/db.sqlite3 | Bin 147456 -> 151552 bytes
5 files changed, 40 insertions(+), 6 deletions(-)
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0002_alter_post_slug.py
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0002_alter_post_slug.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0002_alter_post_slug.py
new file mode 100644
index 0000000..2f3c474
--- /dev/null
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0002_alter_post_slug.py
@@ -0,0 +1,19 @@
+# Generated by Django 5.2.7 on 2025-10-06 05:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('blog', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='post',
+ name='slug',
+ field=models.SlugField(
+ max_length=250, unique_for_date='published_at'),
+ ),
+ ]
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
index 023a3e7..728b42d 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
@@ -16,7 +16,10 @@ class Status(models.TextChoices):
PUBLISHED = 'PB', 'Published'
title = models.CharField(max_length=250)
- slug = models.SlugField(max_length=250)
+ slug = models.SlugField(
+ max_length=250,
+ unique_for_date='published_at'
+ )
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
@@ -44,5 +47,10 @@ def __str__(self):
def get_absolute_url(self):
return reverse(
'blog:post_detail',
- args=[self.id]
+ args=[
+ self.published_at.year,
+ self.published_at.month,
+ self.published_at.day,
+ self.slug
+ ]
)
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
index d560190..853f1e6 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
@@ -5,5 +5,9 @@
urlpatterns = [
path('', views.post_list, name='post_list'),
- path('/', views.post_detail, name='post_detail')
+ path(
+ '////',
+ views.post_detail,
+ name='post_detail'
+ )
]
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
index 45eb8dc..3d1694a 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
@@ -11,11 +11,14 @@ def post_list(request):
)
-def post_detail(request, id):
+def post_detail(request, year, month, day, post):
post = get_object_or_404(
Post,
- id=id,
- status=Post.Status.PUBLISHED
+ status=Post.Status.PUBLISHED,
+ slug=post,
+ published_at__year=year,
+ published_at__month=month,
+ published_at__day=day
)
return render(
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/db.sqlite3 b/Projects/Testtrack/M1 - Django Learning/mysite/db.sqlite3
index 221ef97b4960e3126712b1a68c20b9ed723f5938..cbcd301ef1619ed7430ccbc8d58e8844d6dc2ec6 100644
GIT binary patch
delta 703
zcmY+BO=uHA7>4(oNh!OnnMBebsWh9mOIUE5*_GUPS6O74@V_
z!c#Rhbg%I$15Xen8y9Eccl+dulx**jWw_l|$Gy)=82{!HoQ4x*@Fb}x7A&hxm@Wa7m
znp_$qIylq~Bye&S)BElLeLel+`3T;Wo6d0mq6eKrIvMYGH2VK
Tc!{kObh%@`St!e1$)5WIw%Nt;
delta 367
zcmZozz}e8iIYC-bhk=1X1&Cq5ZlaDcqt3>JCH&07-13vz1=KeyDtzSHEHC|ng`a`{
zC4U0{ZvHaAwl9``ZEZk(U&45wefb$n`9nVh&2Ij!Yh5}vNcO@{+U}7}d9-Pehk9~ST
zCZptbFDJ%+M&?E))9HV57!4U!rpxCsN=$dlWwc(e_+Z>pB__p`(F{Lq1
zKkLuLC1hz7;KT(C1ZMto4E*Q#Kl0z-ELd=WUr>Nql`%0dza%5INVg!rxP*E7Ie8|<
uElllvn;itQ8D&_QIT@Kj1QRneCnv;&O$_{-_%HIm1e&mffBGhSCO-hl=4jRc
From 1f02607989e10ac28fbbc0de7ff205019ac0b9af Mon Sep 17 00:00:00 2001
From: Marcin Kwiatkowski
Date: Mon, 6 Oct 2025 08:01:51 +0200
Subject: [PATCH 03/16] feat: added pagination
---
.../mysite/blog/templates/blog/post/list.html | 1 +
.../mysite/blog/templates/pagination.html | 13 +++++++++++++
.../M1 - Django Learning/mysite/blog/views.py | 7 ++++++-
.../M1 - Django Learning/mysite/db.sqlite3 | Bin 151552 -> 151552 bytes
4 files changed, 20 insertions(+), 1 deletion(-)
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/pagination.html
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
index 16e5803..4082642 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
@@ -17,4 +17,5 @@
{% empty %}
No posts available.
{% endfor %}
+ {% include "pagination.html" with page=posts %}
{% endblock %}
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/pagination.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/pagination.html
new file mode 100644
index 0000000..08de57e
--- /dev/null
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/pagination.html
@@ -0,0 +1,13 @@
+
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
index 3d1694a..b7897fa 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
@@ -1,9 +1,14 @@
+from django.core.paginator import Paginator
from django.shortcuts import get_object_or_404, render
from .models import Post
def post_list(request):
- posts = Post.published.all()
+ post_list = Post.published.all()
+ paginator = Paginator(post_list, 3)
+ page_number = request.GET.get('page', 1)
+ posts = paginator.get_page(page_number)
+
return render(
request,
'blog/post/list.html',
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/db.sqlite3 b/Projects/Testtrack/M1 - Django Learning/mysite/db.sqlite3
index cbcd301ef1619ed7430ccbc8d58e8844d6dc2ec6..bb8f934ee9c0ae7f7f1ddd4396a27c16cd259ce2 100644
GIT binary patch
delta 685
zcmZ{hziSg=7{}lDe$@-fJqbllNVM0K(m}5G-p|B9kZWBO2?2)=B3MmoB9a8}5G9mW
zI*1Nc>2T1g{so$LxUskhUFy)e{vb;_l!71*EyVXyI@H?bJG}4nd|p1^cX3TzTod1=
zvGzVQjkV7Giy9VIo@qA(PG(sXuY8UVFYRdIj)wJCQh;3qhwvK?U=O;myOC;bL7ZnX
z9ufx02YVlJn$C3*9Kj#>1^dv0E}i=dF^(0n5CtFZZ(=!_6eJhHCbVH%>Pc@UcVkph
zrG?vqh@dE5#iDDKU--3VL#cbpgR1XWpDKQBwxQg*bGMzHtyHR&Y(Z(y&)*v|h+(Tb
zQHi4vyI?y7%bv{TEyFfvcuE_`Gn8h!jiz6tkXF<48H#vFVULa>*|~c4NyVeQApIT7
zsS^${CJD(IIVbFW!0S^oR
zO9uYm{9pOs^S|6ISa6en`pfl<(py=Wn)x<62&6G?f4iSiT!4p#?*#+@e*S8HXTBGk
z6%}sqZGUl(QAUxAng0_5{~!ME{GWiLm3-UV`Iyoe6-^Zk4Xjw0IT@Kj1QRneCnpyJ
z0|PVvIR>CT5BSdkZ92d|{hU0LF)vtr69fN4{*(NhfZ|K|r*G0{(wCECc4Ra%Ff!FO
zG|)8w8fa)`WMO4ure|ScX>4S){l7jFs{j`>UpoVTCqD;YJJ@>mdS)&LRdvVdQ~j6>
Qr=R9yvfcj2kBNm50IrZsIRF3v
From 4d578c5766809a9b61dfd0945466b3d8c542f342 Mon Sep 17 00:00:00 2001
From: Marcin Kwiatkowski
Date: Mon, 6 Oct 2025 08:14:00 +0200
Subject: [PATCH 04/16] refactor: refactor views to use classes
---
.../mysite/blog/templates/blog/post/list.html | 2 +-
.../M1 - Django Learning/mysite/blog/urls.py | 4 +-
.../M1 - Django Learning/mysite/blog/views.py | 47 ++++++++-----------
3 files changed, 23 insertions(+), 30 deletions(-)
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
index 4082642..6c90493 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
@@ -17,5 +17,5 @@
{% empty %}
No posts available.
{% endfor %}
- {% include "pagination.html" with page=posts %}
+ {% include "pagination.html" with page=page_obj %}
{% endblock %}
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
index 853f1e6..49ea4d5 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
@@ -4,10 +4,10 @@
app_name = 'blog'
urlpatterns = [
- path('', views.post_list, name='post_list'),
+ path('', views.PostListView.as_view(), name='post_list'),
path(
'////',
- views.post_detail,
+ views.PostDetailView.as_view(),
name='post_detail'
)
]
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
index b7897fa..a77fe62 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
@@ -1,33 +1,26 @@
-from django.core.paginator import Paginator
-from django.shortcuts import get_object_or_404, render
+from django.shortcuts import get_object_or_404
+from django.views.generic import ListView, DetailView
from .models import Post
-def post_list(request):
- post_list = Post.published.all()
- paginator = Paginator(post_list, 3)
- page_number = request.GET.get('page', 1)
- posts = paginator.get_page(page_number)
+class PostListView(ListView):
+ queryset = Post.published.all()
+ context_object_name = 'posts'
+ paginate_by = 3
+ template_name = 'blog/post/list.html'
- return render(
- request,
- 'blog/post/list.html',
- {'posts': posts}
- )
+class PostDetailView(DetailView):
+ model = Post
+ template_name = 'blog/post/detail.html'
+ context_object_name = 'post'
-def post_detail(request, year, month, day, post):
- post = get_object_or_404(
- Post,
- status=Post.Status.PUBLISHED,
- slug=post,
- published_at__year=year,
- published_at__month=month,
- published_at__day=day
- )
-
- return render(
- request,
- 'blog/post/detail.html',
- {'post': post}
- )
+ def get_object(self, queryset=None):
+ return get_object_or_404(
+ Post,
+ status=Post.Status.PUBLISHED,
+ slug=self.kwargs['post'],
+ published_at__year=self.kwargs['year'],
+ published_at__month=self.kwargs['month'],
+ published_at__day=self.kwargs['day']
+ )
From 404e71e94531a1254c2063dfab3abad2a382856d Mon Sep 17 00:00:00 2001
From: Marcin Kwiatkowski
Date: Tue, 7 Oct 2025 06:55:42 +0200
Subject: [PATCH 05/16] feat: added sharing via email form
---
.gitignore | 1 +
.../M1 - Django Learning/mysite/.env.example | 3 ++
.../M1 - Django Learning/mysite/.gitignore | 1 +
.../M1 - Django Learning/mysite/blog/forms.py | 11 ++++++
.../blog/templates/blog/post/detail.html | 3 ++
.../blog/templates/blog/post/share.html | 20 ++++++++++
.../M1 - Django Learning/mysite/blog/urls.py | 3 +-
.../M1 - Django Learning/mysite/blog/views.py | 39 ++++++++++++++++++-
.../mysite/mysite/settings.py | 9 +++++
.../mysite/requirements.txt | 1 +
10 files changed, 89 insertions(+), 2 deletions(-)
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/.env.example
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/.gitignore
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/blog/forms.py
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/share.html
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/requirements.txt
diff --git a/.gitignore b/.gitignore
index 97daa84..4f59cf1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,6 +57,7 @@ venv/
env/
ENV/
.env/
+.env
# Testing
.coverage
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/.env.example b/Projects/Testtrack/M1 - Django Learning/mysite/.env.example
new file mode 100644
index 0000000..42b9fcf
--- /dev/null
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/.env.example
@@ -0,0 +1,3 @@
+EMAIL_HOST_USER=
+EMAIL_HOST_PASSWORD=
+DEFAULT_FROM_EMAIL=
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/.gitignore b/Projects/Testtrack/M1 - Django Learning/mysite/.gitignore
new file mode 100644
index 0000000..4c49bd7
--- /dev/null
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/.gitignore
@@ -0,0 +1 @@
+.env
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/forms.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/forms.py
new file mode 100644
index 0000000..a1c23a9
--- /dev/null
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/forms.py
@@ -0,0 +1,11 @@
+from django import forms
+
+
+class EmailPostForm(forms.Form):
+ name = forms.CharField(max_length=25)
+ email = forms.EmailField()
+ to = forms.EmailField()
+ comments = forms.CharField(
+ required=False,
+ widget=forms.Textarea
+ )
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html
index 73f1030..112be94 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html
@@ -8,4 +8,7 @@ {{ post.title }}
Published on {{ post.published_at }} by {{post.author}}
{{ post.body|linebreaks }}
+
+ Share via email
+
{% endblock %}
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/share.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/share.html
new file mode 100644
index 0000000..d85103b
--- /dev/null
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/share.html
@@ -0,0 +1,20 @@
+{% extends "blog/base.html" %}
+
+{% block title %}Share Post{% endblock %}
+
+{% block content %}
+
+{% if sent %}
+ Message has been sent.
+
+ Sending post {{ post.title }} to {{ recipient_email }} was successful.
+
+{% else %}
+ Share Post: {{ post.title }} via email
+
+{% endif %}
+{% endblock %}
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
index 49ea4d5..c91dd74 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
@@ -9,5 +9,6 @@
'////',
views.PostDetailView.as_view(),
name='post_detail'
- )
+ ),
+ path('/share/', views.post_share, name='post_share')
]
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
index a77fe62..714e687 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
@@ -1,6 +1,8 @@
-from django.shortcuts import get_object_or_404
+from django.shortcuts import get_object_or_404, render
from django.views.generic import ListView, DetailView
+from django.core.mail import send_mail
from .models import Post
+from .forms import EmailPostForm
class PostListView(ListView):
@@ -24,3 +26,38 @@ def get_object(self, queryset=None):
published_at__month=self.kwargs['month'],
published_at__day=self.kwargs['day']
)
+
+
+def post_share(request, post_id):
+ post = get_object_or_404(
+ Post,
+ id=post_id,
+ status=Post.Status.PUBLISHED
+ )
+ sent = False
+
+ if request.method == 'POST':
+ form = EmailPostForm(request.POST)
+ if form.is_valid():
+ cd = form.cleaned_data
+ post_url = request.build_absolute_uri(post.get_absolute_url())
+ subject = f"{cd['name']} recommends you read " f"{post.title}"
+ message = (
+ f"Read {post.title} at {post_url}\n\n"
+ f"comments: {cd['name']}: {cd['comments']}"
+ )
+
+ send_mail(
+ subject,
+ message,
+ from_email=None,
+ recipient_list=[cd['to']]
+ )
+ sent = True
+ else:
+ form = EmailPostForm()
+
+ return render(
+ request,
+ 'blog/post/share.html',
+ {'post': post, 'form': form, 'sent': sent})
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/mysite/settings.py b/Projects/Testtrack/M1 - Django Learning/mysite/mysite/settings.py
index aec6ed2..65c129c 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/mysite/settings.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/mysite/settings.py
@@ -11,6 +11,7 @@
"""
from pathlib import Path
+from decouple import config
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -121,3 +122,11 @@
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+
+EMAIL_HOST = 'smtp.gmail.com'
+EMAIL_HOST_USER = config('EMAIL_HOST_USER')
+EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
+EMAIL_PORT = 587
+EMAIL_USE_TLS = True
+DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL')
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/requirements.txt b/Projects/Testtrack/M1 - Django Learning/mysite/requirements.txt
new file mode 100644
index 0000000..1d7168a
--- /dev/null
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/requirements.txt
@@ -0,0 +1 @@
+python-decouple>=3.8
From 4316650b693dbc28c14fd5cdeb64fa09acb76910 Mon Sep 17 00:00:00 2001
From: Marcin Kwiatkowski
Date: Tue, 7 Oct 2025 08:43:06 +0200
Subject: [PATCH 06/16] feat: added comments
---
.../M1 - Django Learning/mysite/blog/admin.py | 9 ++++-
.../M1 - Django Learning/mysite/blog/forms.py | 7 ++++
.../mysite/blog/migrations/0003_comment.py | 33 ++++++++++++++++++
.../mysite/blog/models.py | 23 ++++++++++++
.../blog/templates/blog/post/comment.html | 12 +++++++
.../blog/templates/blog/post/detail.html | 16 +++++++++
.../blog/post/includes/comment_form.html | 14 ++++++++
.../M1 - Django Learning/mysite/blog/urls.py | 7 +++-
.../M1 - Django Learning/mysite/blog/views.py | 33 +++++++++++++++++-
.../M1 - Django Learning/mysite/db.sqlite3 | Bin 151552 -> 159744 bytes
10 files changed, 151 insertions(+), 3 deletions(-)
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0003_comment.py
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/comment.html
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/includes/comment_form.html
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/admin.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/admin.py
index a6157cd..095f9e4 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/admin.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/admin.py
@@ -1,5 +1,5 @@
from django.contrib import admin
-from .models import Post
+from .models import Post, Comment
@admin.register(Post)
@@ -12,3 +12,10 @@ class PostAdmin(admin.ModelAdmin):
date_hierarchy = 'published_at'
ordering = ('status', 'published_at')
show_facets = admin.ShowFacets.ALWAYS
+
+
+@admin.register(Comment)
+class CommentAdmin(admin.ModelAdmin):
+ list_display = ('name', 'email', 'post', 'created_at', 'active')
+ list_filter = ('active', 'created_at', 'updated_at')
+ search_fields = ('name', 'email', 'body')
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/forms.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/forms.py
index a1c23a9..b98117e 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/forms.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/forms.py
@@ -1,4 +1,5 @@
from django import forms
+from .models import Comment
class EmailPostForm(forms.Form):
@@ -9,3 +10,9 @@ class EmailPostForm(forms.Form):
required=False,
widget=forms.Textarea
)
+
+
+class CommentForm(forms.ModelForm):
+ class Meta:
+ model = Comment
+ fields = ('name', 'email', 'body')
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0003_comment.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0003_comment.py
new file mode 100644
index 0000000..ab10940
--- /dev/null
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0003_comment.py
@@ -0,0 +1,33 @@
+# Generated by Django 5.2.7 on 2025-10-06 13:05
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('blog', '0002_alter_post_slug'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Comment',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True,
+ primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=80)),
+ ('email', models.EmailField(max_length=254)),
+ ('body', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('active', models.BooleanField(default=True)),
+ ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
+ related_name='comments', to='blog.post')),
+ ],
+ options={
+ 'ordering': ('created_at',),
+ 'indexes': [models.Index(fields=['created_at'], name='blog_commen_created_4e025c_idx')],
+ },
+ ),
+ ]
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
index 728b42d..82e8a07 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
@@ -54,3 +54,26 @@ def get_absolute_url(self):
self.slug
]
)
+
+
+class Comment(models.Model):
+ post = models.ForeignKey(
+ Post,
+ on_delete=models.CASCADE,
+ related_name='comments'
+ )
+ name = models.CharField(max_length=80)
+ email = models.EmailField()
+ body = models.TextField()
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+ active = models.BooleanField(default=True)
+
+ class Meta:
+ ordering = ('created_at',)
+ indexes = [
+ models.Index(fields=['created_at']),
+ ]
+
+ def __str__(self):
+ return f'Comment by {self.name} on {self.post}'
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/comment.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/comment.html
new file mode 100644
index 0000000..00c9b7f
--- /dev/null
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/comment.html
@@ -0,0 +1,12 @@
+{% extends "blog/base.html" %}
+
+{% block title %}Add a comment{% endblock %}
+
+{% block content %}
+ {% if comment %}
+ Your comment has been added.
+ Back to post
+ {% else %}
+ {% include "blog/post/includes/comment_form.html" %}
+ {% endif %}
+{% endblock %}
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html
index 112be94..3a99069 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html
@@ -11,4 +11,20 @@ {{ post.title }}
Share via email
+ {% with comments.count as total_comments %}
+
+ {{ total_comments }} Comment{{ total_comments|pluralize }}
+
+ {% endwith %}
+ {% for comment in comments %}
+
+ {% empty %}
+ No comments yet.
+ {% endfor %}
+ {% include "blog/post/includes/comment_form.html" %}
{% endblock %}
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/includes/comment_form.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/includes/comment_form.html
new file mode 100644
index 0000000..ed3e169
--- /dev/null
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/includes/comment_form.html
@@ -0,0 +1,14 @@
+Add new comment
+
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
index c91dd74..440616a 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
@@ -10,5 +10,10 @@
views.PostDetailView.as_view(),
name='post_detail'
),
- path('/share/', views.post_share, name='post_share')
+ path('/share/', views.post_share, name='post_share'),
+ path(
+ '/comment/',
+ views.post_comment,
+ name='post_comment'
+ )
]
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
index 714e687..c905598 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
@@ -1,8 +1,9 @@
from django.shortcuts import get_object_or_404, render
from django.views.generic import ListView, DetailView
+from django.views.decorators.http import require_POST
from django.core.mail import send_mail
from .models import Post
-from .forms import EmailPostForm
+from .forms import EmailPostForm, CommentForm
class PostListView(ListView):
@@ -27,6 +28,16 @@ def get_object(self, queryset=None):
published_at__day=self.kwargs['day']
)
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ post = self.get_object()
+
+ context['comments'] = post.comments.filter(active=True)
+
+ context['form'] = CommentForm()
+
+ return context
+
def post_share(request, post_id):
post = get_object_or_404(
@@ -61,3 +72,23 @@ def post_share(request, post_id):
request,
'blog/post/share.html',
{'post': post, 'form': form, 'sent': sent})
+
+
+@require_POST
+def post_comment(request, post_id):
+ post = get_object_or_404(
+ Post,
+ id=post_id,
+ status=Post.Status.PUBLISHED
+ )
+ comment = None
+ form = CommentForm(data=request.POST)
+ if form.is_valid():
+ comment = form.save(commit=False)
+ comment.post = post
+ comment.save()
+ return render(
+ request,
+ 'blog/post/comment.html',
+ {'post': post, 'form': form, 'comment': comment}
+ )
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/db.sqlite3 b/Projects/Testtrack/M1 - Django Learning/mysite/db.sqlite3
index bb8f934ee9c0ae7f7f1ddd4396a27c16cd259ce2..817fc69c091f5fecc40044a7bb6d4bbb1af577d6 100644
GIT binary patch
delta 1896
zcmaJ?Z%i9y7{9l7*gH!5w3EU%0=(QTtl{7F52dXpqt=EkW8fSH1Cny|4ygS{=m{Fe
z02edkvP{xsHGXj)h<-6KrXj?rnZ_?>`=I+|Fft4#W(KE=#y^Rg_+J0$wF8^nle>F=
z&+qqp-#*WCE7!S|Z@AYQEWH#(wUMjz>tE||q2Bi<)`7NGZ)EglyRLd8=P+Nac63{-
zD61_n8Ba|Mg5U~AQi+6|RGfmdr_&*H3Lflm`Gp?8%iZO2`vj*m_ubK^s#+EX8TcM1
z-~*V4%kT!g1B1C1?14jOBW*(}U5Sd$Omti8*oPUq9f{8?GvQfzE)h+qqp2j$T<$)3
zf+aRBoHiw`(9>2#cO&UJF*%(IC!*7HqCyhV)@lM~Ax^=Ma0|YIk072i3!`8$GuDQ@
zY;gl@X6eKw8XE>O`aU0P_}K8eVb(A}EHZ{=9#>Jw*2&IC<%N>VpqRw!GcM-0HWQ59
z!Af#mR%9)z@aA7!Qvny5Ax_J1g*PP}ZX~#4?W`zC^;$OaT!Q3wPxB0ojaM@ICw;8(
zrg6h~-FSsq4jG3IG029>4C7h`*-?eR=EBQzGffr-9Yd;eNw_ih_4x5Q8bc_f|H8+@
z2%Lq}5F%24n+{je2(dUHoAa?L9~*PyJ8!ShRw|a&PoUPSiw25{t*!Z%SF0Y=*PpVX
zReLlk$uDUB$8cm$78O|vyJb>Nk#JPft}1Q8r$T|@5I!~dMCcjJ|9>KnhX%_q3XhNT
zM1adHhzf7t{AB`#b;!SYE`qM8W
zv_f2ld%S||^T<;gOG@j3ZXe83&5l3lwn)$0UrPJ+^)?%N*`7D2mC@cmQj{DHJU$RA
zr^^Itg2SDBSsPI7Tkmn;Z1lI&O~aXH3v6r4dLhzmG^g_(sX
zU&Lp`xkxmL$!Iw#$KxqHlA4Q4y0JyR&=L~w$BX<_RE|q&ay!Nc3MV$cv^36^&pemU
z?-sgT9#@Yb4C_@fuKhnTP|DS^w|3Cch6)Oy*Y9+9`Fu{N!?PK;PVzB&t50V(sq>zO`)Z;62KPyM?LvMYF|=BWjE<%`u7FvmU>81xb%MDD
b&oS)rt@Zp5S*R8Z
delta 647
zcmZp8z}c{XbAq&>J_7@TDiEsxF(VMWOw=)E)Zdt}gr8Z2>)B*>0nN>d3fs6gKa-MU
z6=CO3VB~+vpTNJHzl^_|e-{55{wtdm4LtZKC+Lfcu?e#xn3tX(pPQLplvo1PP%Od)
zRLss_%fSDR|1|$C{=NLQ{0sQIHwzY|^G~j|Z)cX_el(fg!4l}MMckVoc`LCn%kbWr
z%pL#|nZvvJPOL@|vkZUiWcCXn5n2A}AL5x$<*_>lxjKfpDug&X`M4@<|D3=W&%|i9JvfV*xh)
zyA1rl`M>hN2L|_J{<}cWpM>~h`or~%%0T(Y4E%rizw>|Mf6M=Z|1nVhJU=H3GbbY(
zh?xB5zJw4ve+>gr_9p)+{yqHj`MdaQfU45?r`Mck>=0n%b7bJ(&tJ{&%=dz?pU)8(
zP`Z5E9q%#j599*|KMW`{Fl^^DV7kjUIY8jJpB%FzqmhA;sji`cu7Rn7p`n$Lg_Vh!
po`r>_v5^t8II}T|w1JhWv6ZPQlNhrxC$cu6xRI5q;W7s%0RSB=zEJ=G
From 784300d4b4145cd9d42213f882e3f017bdd6e255 Mon Sep 17 00:00:00 2001
From: Marcin Kwiatkowski
Date: Tue, 7 Oct 2025 19:20:25 +0200
Subject: [PATCH 07/16] feat: added tags
---
.../mysite/blog/migrations/0004_post_tags.py | 21 ++++++++++++++++++
.../mysite/blog/models.py | 2 ++
.../mysite/blog/templates/blog/post/list.html | 9 ++++++++
.../M1 - Django Learning/mysite/blog/urls.py | 5 +++++
.../M1 - Django Learning/mysite/blog/views.py | 20 ++++++++++++++++-
.../M1 - Django Learning/mysite/db.sqlite3 | Bin 159744 -> 212992 bytes
.../mysite/mysite/settings.py | 3 ++-
.../mysite/requirements.txt | 1 +
8 files changed, 59 insertions(+), 2 deletions(-)
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0004_post_tags.py
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0004_post_tags.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0004_post_tags.py
new file mode 100644
index 0000000..f212111
--- /dev/null
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/migrations/0004_post_tags.py
@@ -0,0 +1,21 @@
+# Generated by Django 5.2.7 on 2025-10-07 11:52
+
+import taggit.managers
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('blog', '0003_comment'),
+ ('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='post',
+ name='tags',
+ field=taggit.managers.TaggableManager(
+ help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
+ ),
+ ]
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
index 82e8a07..441ae16 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/models.py
@@ -2,6 +2,7 @@
from django.urls import reverse
from django.utils import timezone
from django.conf import settings
+from taggit.managers import TaggableManager
class PublishedManager(models.Manager):
@@ -34,6 +35,7 @@ class Status(models.TextChoices):
objects = models.Manager()
published = PublishedManager()
+ tags = TaggableManager()
class Meta:
ordering = ('-published_at',)
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
index 6c90493..3bbc51a 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/list.html
@@ -4,12 +4,21 @@
{% block content %}
Blog Posts
+ {% if tag %}
+ Posts tagged with "{{ tag.name }}"
+ {% endif %}
{% for post in posts %}
+
+ Tags:
+ {% for tag in post.tags.all %}
+ {{ tag.name }}{% if not forloop.last %}, {% endif %}
+ {% endfor %}
+
Published on {{ post.published_at }} by {{post.author}}
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
index 440616a..059c9c8 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/urls.py
@@ -5,6 +5,11 @@
urlpatterns = [
path('', views.PostListView.as_view(), name='post_list'),
+ path(
+ 'tag//',
+ views.PostListView.as_view(),
+ name='post_list_by_tag'
+ ),
path(
'////',
views.PostDetailView.as_view(),
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
index c905598..91a5ed7 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
@@ -2,16 +2,34 @@
from django.views.generic import ListView, DetailView
from django.views.decorators.http import require_POST
from django.core.mail import send_mail
+from taggit.models import Tag
from .models import Post
from .forms import EmailPostForm, CommentForm
class PostListView(ListView):
- queryset = Post.published.all()
context_object_name = 'posts'
paginate_by = 3
template_name = 'blog/post/list.html'
+ def get_queryset(self):
+ queryset = Post.published.all()
+ tag_slug = self.kwargs.get('tag_slug', None)
+
+ if tag_slug:
+ tag = get_object_or_404(Tag, slug=tag_slug)
+ queryset = queryset.filter(tags__in=[tag])
+ self.tag = tag
+ else:
+ self.tag = None
+
+ return queryset
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['tag'] = self.tag
+ return context
+
class PostDetailView(DetailView):
model = Post
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/db.sqlite3 b/Projects/Testtrack/M1 - Django Learning/mysite/db.sqlite3
index 817fc69c091f5fecc40044a7bb6d4bbb1af577d6..3eb53d46988c1bba6aeebc1998fcfb0963ac72a9 100644
GIT binary patch
delta 3602
zcmai1eQaCR6@S<7*)Q)o_OYR^>m+S$Cu!_Bsr~#B$0hx6LmE<=k3s`XqI$7o*SK};
zHgSM;11GIiiB?FhEcVbQjc)&JNJG;UO<}OAh<}E*Ngz!_W74)VCc0^`R_!R`V`JyN
z=a1(&DYD*u_nrGY=l;&U=iGC>bd6hDGF@!9j#3mA#-|q_8lOim^@VJs)Xwvp^!TNN
zJJ?cv@CDrYS@8^5@xv})W3!vE?Pz(e%vyYA{tSOV_h0TMF46LF%WErf%juSq(4nVn
z-gq)s$OHm`uz0etP!x-a%t9~_jPwhE{y@|z2)iP|UE$cyP-HL`4bHb4D93Vl98sBU
z5w!-y`E))pmzMQ1=~T9uo)c#a`C>YcroMSHEf$i;)3Ze}n^F*2WrBFIU{0Xu~-mMAs7e;!*VCGu68IIiH06`p!T=w+F>z~E2ihg
zVrc`$mErm*y0*S~Ary#>Y(ewmb@L(ZN+eP#aWS7gwU`#O`Bb{m$H%HGi^f9(L-s}|
zgHf)I%hfqHlH2B1$E&1z6B!v$rIW~P
zaUr0+ldG`MOuusI!B-jD(|WtiPQ@*zk|}RGYML;`OfD1GdK-7;d#xoKLwscIT6*jZ
z@ss9d>rv%ZDz)_5v56sGvSuB+%?#N}YCd;a7~&!tH#=oPJK^fq+$M&gpyG&Y<@uve
z&C?#RmDzXV7T60h2thyefD78ehV-wPrJJIT>Ek_mnh?H6X?G}Xx7>1FO53TlZl!gt
zjQr*53~i?0lGuG@J3~6C=cL|!L|lD2OZJe}U&YC;ZBaS!nMsP7glY4eraLBID{buO
z&vCc7D=ilGPi&Xrx?zeQBfle4)LR(z7pO<5H>gK0#kc7~d&{pII~;O2s0XDOtoM6E
zd*spfxSFG8vst`u7a^j^Vz5!9M9NUo~!#5=pXMI-elF
zkd~4pwED&@d4)*dm?aT@+3I8ME*DvzD+i1Y>D?qbwK|t3r}cziA=l`@kC@BExrK)V
z6nqTt!LQ+$@DiMdZ^H?A91h@u_Coh2o}nBrm6Y77^}xiT^xm3u^~Brch08y@=Dak;
zayncsFFu|X>(UJ4rp(?3nm
zwti+jYoz$6*^_L);iCS1{W$Y7ca}&S2}xYgtf#4252ZkRSi60qp9*OTiG~#q?M`?7u9B&;igUMuePEU*-
zo*wCQx)*Yb8R=4%*sC({?s0?tuutBnh*lrf#o!u27?SH9SOld4_KvViE
zO*WU>Mp^qb^4q-uzuE$e>0m0F5>m&Si21&bShI~9rOwJ(RmpaFmuqOZH-5l?4gHZNPpi?
zwo2bGkWJEy88Rg2sC2PF+Gn^Fb(NXY-Gnxp
zmg}faImqQZ9}OQ-@HyOuj}ZT7=rb^cBP^}Hbb9>aSou#34ewBJ6aI>?o%q?0j}J9o
zg*_G;Yl}m@QL1gLj-hO}cBY{|1dC_#e$1+F=
z*`)XX8gBIU+Xu;(n$4giz?IIzfh*GbN2QBsk)yFOSts@$sWZhnmGAayF1addb88P7
zR^KB6MmhY732fsTtmAEbfKkno*nw))KLoN`_a6ugde&kZ&lQ#U@nSwdQ7BE7Sj7&k
zA5xiQK`F-bX=O55N*4=Lc2PeTFh}qQzQK<;k2yS#O{{B{Q)8&+_TFM;b42BX`}8+B
zvlcn{gkxoCTICOSNLbRQkDZ-lWeltQdz}RSbkXHF!)YU*+6fln1?7X#%P}#yK{p2>
zPOpxDt+6c7cm(?B%zpUQilu;ITCYN|bKO>UnAk~{h(o6P!h&Emelyw(c}8Q8vd@_^
z)R++Y*`-L|r37498utW@j+low60n9E6zHW33iOK=
zixZ9|EAcW4RVLK;&A=~I@1>9nFz{fj09=Noz
z)qN5k0$Xu!P?y+H>um-G3{E}abohL5*=3.8
+django-taggit>=6.1.0
From 833a30160b3110564ebe807eba23367c52948820 Mon Sep 17 00:00:00 2001
From: Marcin Kwiatkowski
Date: Tue, 7 Oct 2025 19:31:26 +0200
Subject: [PATCH 08/16] feat: added similar posts
---
.../blog/templates/blog/post/detail.html | 10 ++++++++++
.../M1 - Django Learning/mysite/blog/views.py | 9 +++++++++
.../M1 - Django Learning/mysite/db.sqlite3 | Bin 212992 -> 212992 bytes
3 files changed, 19 insertions(+)
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html
index 3a99069..ffd940d 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/post/detail.html
@@ -11,6 +11,16 @@ {{ post.title }}
Share via email
+ Similar posts
+ {% for post in similar_posts %}
+
+
+ {{ post.title }}
+
+
+ {% empty %}
+ No similar posts found.
+ {% endfor %}
{% with comments.count as total_comments %}
{{ total_comments }} Comment{{ total_comments|pluralize }}
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
index 91a5ed7..c95647f 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/views.py
@@ -1,3 +1,4 @@
+from django.db.models import Count
from django.shortcuts import get_object_or_404, render
from django.views.generic import ListView, DetailView
from django.views.decorators.http import require_POST
@@ -54,6 +55,14 @@ def get_context_data(self, **kwargs):
context['form'] = CommentForm()
+ post_tags_ids = post.tags.values_list('id', flat=True)
+ similar_posts = Post.published.filter(
+ tags__in=post_tags_ids).exclude(id=post.id)
+ similar_posts = similar_posts.annotate(same_tags=Count(
+ 'tags')).order_by('-same_tags', '-published_at')[:4]
+
+ context['similar_posts'] = similar_posts
+
return context
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/db.sqlite3 b/Projects/Testtrack/M1 - Django Learning/mysite/db.sqlite3
index 3eb53d46988c1bba6aeebc1998fcfb0963ac72a9..ea15298592e12b64c29a0f4e49f445f3a0e6ad04 100644
GIT binary patch
delta 598
zcmaKp%WD%+6vpqpGs9$t%zSAOjQD623rbC9Cc`u&JMF3qAE4r@sH-k52whZK0=nwL
z2QDt;KX4Un7PBlUMO0`Fq)4HSNM#afq@m)gy6{YDi*DT9dpP{Q?;O5UsaTbY_3$ts
zdvs)!k3GHdBG0wQr=0WLygTKDQx3mBnK7}*@DpFKg^$?4I$mQDZ_%g^Og@3^T^DLX
zH#LQG-H?qM_erqK@C)CtjZf&1q=i*9u}p$GOjR*>|1rYV1wNe0n8_uE1>C`?-LW6o
zXRS?Z)*4DK)sJWAlZmUQ!7#)xOWESXXRh3wxP4-LW@cP3xP>6+xjDC(^@`;}saz=L
zT{rLp|C&LH)1>INc<$tX6kd>bJ>M^mgtP1X-Z}dvQ&DB8(#~%%PutExRvqEU_l`K2
zaLa*T4gwK&9Ki<%{u%4Jr9kdn5V!W*u{SkKBjNN#F?Y$<5bb5sMMU0Bite4zbS+L!
zsNr@^q*H`#{FOCfjopN0yT*_1nQS7$SM>TO{Uo(GQ#JNGK+GD$4nCuc4_G7S6;is&
z={kzG`_r)QmdM1znhf9RtPvfxN$4}AX&=V9q3F8WFN7{5D94ChVfcX_+H}noVxJ?O
R(1?tE{}g209#jrxe*;*~s?Y!c
delta 332
zcmXZWu}cC`00!{)-W_-Z@4mAfj3R9df}TlBV>vig{s%R+=HybyK@HJRNyCGi`VXWW
z9!=5|Z8?3LTbj<$?8Vm20yWOU5s3qlnH
zJx>_~iYjX|dSLwVHvv`r9
zid|&vu6<&!@K;`}%qF+FDmLiBs@)
Date: Tue, 7 Oct 2025 19:38:52 +0200
Subject: [PATCH 09/16] feat: added total_posts template tag
---
.../mysite/blog/templates/blog/base.html | 7 ++++---
.../mysite/blog/templatetags/__init__.py | 0
.../mysite/blog/templatetags/blog_tags.py | 9 +++++++++
3 files changed, 13 insertions(+), 3 deletions(-)
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/blog/templatetags/__init__.py
create mode 100644 Projects/Testtrack/M1 - Django Learning/mysite/blog/templatetags/blog_tags.py
diff --git a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/base.html b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/base.html
index 2e4bba1..a90ceea 100644
--- a/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/base.html
+++ b/Projects/Testtrack/M1 - Django Learning/mysite/blog/templates/blog/base.html
@@ -1,3 +1,4 @@
+{% load blog_tags %}
{% load static %}
@@ -9,11 +10,11 @@
- {% block content %}{% endblock %}
+ {% block content %}{% endblock %}
+ Comment {{ forloop.counter }} by {{ comment.name }} on {{ comment.created_at }} +
+ {{ comment.body|linebreaks }} +