Add tags on articles and list view
This commit is contained in:
parent
8d5edfd8df
commit
4ddf916364
6 changed files with 60 additions and 7 deletions
40
articles/migrations/0030_tag_slug.py
Normal file
40
articles/migrations/0030_tag_slug.py
Normal file
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -25,6 +25,7 @@ class User(AbstractUser):
|
||||||
|
|
||||||
class Tag(models.Model):
|
class Tag(models.Model):
|
||||||
name = models.CharField(max_length=255, unique=True)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
|
slug = models.CharField(max_length=255, unique=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
|
|
@ -20,7 +20,7 @@ footer > :first-child {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav a:not(:first-child):before {
|
nav a:not(:first-child):before, a.tag:not(:first-of-type):before {
|
||||||
content: '\00B7';
|
content: '\00B7';
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
color: var(--nc-tx-1);
|
color: var(--nc-tx-1);
|
||||||
|
|
|
@ -2,4 +2,7 @@
|
||||||
Published on {% include "articles/snippets/datetime.html" %}
|
Published on {% include "articles/snippets/datetime.html" %}
|
||||||
· {{ article.get_read_time }} min read
|
· {{ article.get_read_time }} min read
|
||||||
{% include "articles/snippets/admin_link.html" %}
|
{% include "articles/snippets/admin_link.html" %}
|
||||||
|
{% if article.tags.all %}
|
||||||
|
<br>{% for tag in article.tags.all %}<a href="{% url "tag" slug=tag.slug %}" class="tag">{{ tag.name }}</a>{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -6,6 +6,7 @@ urlpatterns = [
|
||||||
path("", html.ArticlesListView.as_view(), name="articles-list"),
|
path("", html.ArticlesListView.as_view(), name="articles-list"),
|
||||||
path("drafts/", html.DraftsListView.as_view(), name="drafts-list"),
|
path("drafts/", html.DraftsListView.as_view(), name="drafts-list"),
|
||||||
path("search/", html.SearchArticlesListView.as_view(), name="search"),
|
path("search/", html.SearchArticlesListView.as_view(), name="search"),
|
||||||
|
path("tag/<slug:slug>/", html.TagArticlesListView.as_view(), name="tag"),
|
||||||
path("feed/", feeds.CompleteFeed(), name="complete-feed"),
|
path("feed/", feeds.CompleteFeed(), name="complete-feed"),
|
||||||
path("api/render/<int:article_pk>/", api.render_article, name="api-render-article"),
|
path("api/render/<int:article_pk>/", api.render_article, name="api-render-article"),
|
||||||
path("<slug:slug>/", html.ArticleDetailView.as_view(), name="article-detail"),
|
path("<slug:slug>/", html.ArticleDetailView.as_view(), name="article-detail"),
|
||||||
|
|
|
@ -5,9 +5,10 @@ from typing import Dict
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
from articles.models import Article
|
from articles.models import Article, Tag
|
||||||
|
|
||||||
|
|
||||||
class BaseArticleListView(generic.ListView):
|
class BaseArticleListView(generic.ListView):
|
||||||
|
@ -41,9 +42,11 @@ class BaseArticleListView(generic.ListView):
|
||||||
return "&".join(map(lambda item: f"{item[0]}={item[1]}", querystring.items()))
|
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)
|
queryset = Article.objects.filter(status=Article.PUBLISHED)
|
||||||
|
|
||||||
|
|
||||||
|
class ArticlesListView(PublicArticleListView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
home_article = Article.objects.filter(
|
home_article = Article.objects.filter(
|
||||||
|
@ -53,8 +56,7 @@ class ArticlesListView(BaseArticleListView):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class SearchArticlesListView(BaseArticleListView):
|
class SearchArticlesListView(PublicArticleListView):
|
||||||
queryset = Article.objects.filter(status=Article.PUBLISHED)
|
|
||||||
template_name = "articles/article_search.html"
|
template_name = "articles/article_search.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -85,6 +87,12 @@ class SearchArticlesListView(BaseArticleListView):
|
||||||
return {}
|
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):
|
class DraftsListView(LoginRequiredMixin, BaseArticleListView):
|
||||||
queryset = Article.objects.filter(status=Article.DRAFT)
|
queryset = Article.objects.filter(status=Article.DRAFT)
|
||||||
|
|
||||||
|
@ -103,9 +111,9 @@ class ArticleDetailView(generic.DetailView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
key = self.request.GET.get("draft_key")
|
key = self.request.GET.get("draft_key")
|
||||||
if 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:
|
if not self.request.user.is_authenticated:
|
||||||
queryset = queryset.filter(status=Article.PUBLISHED)
|
queryset = queryset.filter(status=Article.PUBLISHED)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
Reference in a new issue