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