From 4ddf91636499c92e585f423ae6674be66f2b2654 Mon Sep 17 00:00:00 2001
From: Gabriel Augendre
Date: Thu, 4 Mar 2021 18:24:22 +0100
Subject: [PATCH] Add tags on articles and list view
---
articles/migrations/0030_tag_slug.py | 40 +++++++++++++++++++
articles/models.py | 1 +
articles/static/public.css | 2 +-
.../templates/articles/snippets/metadata.html | 3 ++
articles/urls.py | 1 +
articles/views/html.py | 20 +++++++---
6 files changed, 60 insertions(+), 7 deletions(-)
create mode 100644 articles/migrations/0030_tag_slug.py
diff --git a/articles/migrations/0030_tag_slug.py b/articles/migrations/0030_tag_slug.py
new file mode 100644
index 0000000..d74517a
--- /dev/null
+++ b/articles/migrations/0030_tag_slug.py
@@ -0,0 +1,40 @@
+# Generated by Django 3.1.5 on 2021-03-04 17:17
+
+from django.db import migrations, models
+from django.utils.text import slugify
+
+
+def forwards(apps, schema_editor):
+ Tag = apps.get_model("articles", "Tag")
+ db_alias = schema_editor.connection.alias
+ tags = Tag.objects.using(db_alias).all()
+ for tag in tags:
+ tag.slug = slugify(tag.name)
+ Tag.objects.bulk_update(tags, ["slug"])
+
+
+def backwards(apps, schema_editor):
+ Tag = apps.get_model("articles", "Tag")
+ db_alias = schema_editor.connection.alias
+ Tag.objects.using(db_alias).update(slug="")
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("articles", "0029_auto_20210303_1711"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="tag",
+ name="slug",
+ field=models.CharField(blank=True, max_length=255),
+ ),
+ migrations.RunPython(forwards, backwards),
+ migrations.AlterField(
+ model_name="tag",
+ name="slug",
+ field=models.CharField(max_length=255, unique=True),
+ ),
+ ]
diff --git a/articles/models.py b/articles/models.py
index fa84a7c..99c314d 100644
--- a/articles/models.py
+++ b/articles/models.py
@@ -25,6 +25,7 @@ class User(AbstractUser):
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
+ slug = models.CharField(max_length=255, unique=True)
class Meta:
ordering = ["name"]
diff --git a/articles/static/public.css b/articles/static/public.css
index 2f504ae..ef41fe2 100644
--- a/articles/static/public.css
+++ b/articles/static/public.css
@@ -20,7 +20,7 @@ footer > :first-child {
margin-top: 1em;
}
-nav a:not(:first-child):before {
+nav a:not(:first-child):before, a.tag:not(:first-of-type):before {
content: '\00B7';
margin: 0 5px;
color: var(--nc-tx-1);
diff --git a/articles/templates/articles/snippets/metadata.html b/articles/templates/articles/snippets/metadata.html
index 83e0239..3a27653 100644
--- a/articles/templates/articles/snippets/metadata.html
+++ b/articles/templates/articles/snippets/metadata.html
@@ -2,4 +2,7 @@
Published on {% include "articles/snippets/datetime.html" %}
· {{ article.get_read_time }} min read
{% include "articles/snippets/admin_link.html" %}
+ {% if article.tags.all %}
+
{% for tag in article.tags.all %}{{ tag.name }}{% endfor %}
+ {% endif %}
diff --git a/articles/urls.py b/articles/urls.py
index a14d21a..7ca1683 100644
--- a/articles/urls.py
+++ b/articles/urls.py
@@ -6,6 +6,7 @@ urlpatterns = [
path("", html.ArticlesListView.as_view(), name="articles-list"),
path("drafts/", html.DraftsListView.as_view(), name="drafts-list"),
path("search/", html.SearchArticlesListView.as_view(), name="search"),
+ path("tag//", html.TagArticlesListView.as_view(), name="tag"),
path("feed/", feeds.CompleteFeed(), name="complete-feed"),
path("api/render//", api.render_article, name="api-render-article"),
path("/", html.ArticleDetailView.as_view(), name="article-detail"),
diff --git a/articles/views/html.py b/articles/views/html.py
index b9b3413..4174e3d 100644
--- a/articles/views/html.py
+++ b/articles/views/html.py
@@ -5,9 +5,10 @@ from typing import Dict
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import F, Q
+from django.shortcuts import get_object_or_404
from django.views import generic
-from articles.models import Article
+from articles.models import Article, Tag
class BaseArticleListView(generic.ListView):
@@ -41,9 +42,11 @@ class BaseArticleListView(generic.ListView):
return "&".join(map(lambda item: f"{item[0]}={item[1]}", querystring.items()))
-class ArticlesListView(BaseArticleListView):
+class PublicArticleListView(BaseArticleListView):
queryset = Article.objects.filter(status=Article.PUBLISHED)
+
+class ArticlesListView(PublicArticleListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
home_article = Article.objects.filter(
@@ -53,8 +56,7 @@ class ArticlesListView(BaseArticleListView):
return context
-class SearchArticlesListView(BaseArticleListView):
- queryset = Article.objects.filter(status=Article.PUBLISHED)
+class SearchArticlesListView(PublicArticleListView):
template_name = "articles/article_search.html"
def get_context_data(self, **kwargs):
@@ -85,6 +87,12 @@ class SearchArticlesListView(BaseArticleListView):
return {}
+class TagArticlesListView(PublicArticleListView):
+ def get_queryset(self):
+ tag = get_object_or_404(Tag, slug=self.kwargs.get("slug"))
+ return super().get_queryset().filter(tags=tag)
+
+
class DraftsListView(LoginRequiredMixin, BaseArticleListView):
queryset = Article.objects.filter(status=Article.DRAFT)
@@ -103,9 +111,9 @@ class ArticleDetailView(generic.DetailView):
def get_queryset(self):
key = self.request.GET.get("draft_key")
if key:
- return Article.objects.filter(draft_key=key)
+ return Article.objects.filter(draft_key=key).prefetch_related("tags")
- queryset = super().get_queryset()
+ queryset = super().get_queryset().prefetch_related("tags")
if not self.request.user.is_authenticated:
queryset = queryset.filter(status=Article.PUBLISHED)
return queryset