Replace keywords by a Tag model
This commit is contained in:
parent
5722903301
commit
26cc694008
6 changed files with 123 additions and 20 deletions
|
@ -1,11 +1,9 @@
|
||||||
import copy
|
|
||||||
|
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.contrib.admin import register
|
from django.contrib.admin import register
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
from .models import Article, User
|
from .models import Article, Tag, User
|
||||||
|
|
||||||
admin.site.register(User, UserAdmin)
|
admin.site.register(User, UserAdmin)
|
||||||
|
|
||||||
|
@ -40,7 +38,7 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||||
{
|
{
|
||||||
"fields": [
|
"fields": [
|
||||||
("title", "slug"),
|
("title", "slug"),
|
||||||
("author", "keywords"),
|
("author", "tags"),
|
||||||
("status", "published_at"),
|
("status", "published_at"),
|
||||||
("created_at", "updated_at"),
|
("created_at", "updated_at"),
|
||||||
("views_count", "read_time"),
|
("views_count", "read_time"),
|
||||||
|
@ -71,7 +69,8 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||||
]
|
]
|
||||||
prepopulated_fields = {"slug": ("title",)}
|
prepopulated_fields = {"slug": ("title",)}
|
||||||
change_form_template = "articles/article_change_form.html"
|
change_form_template = "articles/article_change_form.html"
|
||||||
search_fields = ["title", "content"]
|
search_fields = ["title", "content", "tags__name"]
|
||||||
|
autocomplete_fields = ["tags"]
|
||||||
|
|
||||||
def publish(self, request, queryset):
|
def publish(self, request, queryset):
|
||||||
if not request.user.has_perm("articles.change_article"):
|
if not request.user.has_perm("articles.change_article"):
|
||||||
|
@ -133,3 +132,9 @@ class ArticleAdmin(admin.ModelAdmin):
|
||||||
return bool(instance.custom_css)
|
return bool(instance.custom_css)
|
||||||
|
|
||||||
has_custom_css.boolean = True
|
has_custom_css.boolean = True
|
||||||
|
|
||||||
|
|
||||||
|
@register(Tag)
|
||||||
|
class TagAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["name"]
|
||||||
|
search_fields = ["name"]
|
||||||
|
|
62
articles/migrations/0027_auto_20210303_1633.py
Normal file
62
articles/migrations/0027_auto_20210303_1633.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# Generated by Django 3.1.5 on 2021-03-03 15:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def forwards(apps, schema_editor):
|
||||||
|
Tag = apps.get_model("articles", "Tag")
|
||||||
|
Article = apps.get_model("articles", "Article")
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
articles = Article.objects.using(db_alias).all()
|
||||||
|
for article in articles:
|
||||||
|
tags = []
|
||||||
|
for keyword in list(
|
||||||
|
filter(None, map(lambda k: k.strip(), article.keywords.split(",")))
|
||||||
|
):
|
||||||
|
tag = Tag.objects.using(db_alias).filter(name__iexact=keyword).first()
|
||||||
|
if tag is None:
|
||||||
|
tag = Tag.objects.create(name=keyword)
|
||||||
|
tags.append(tag)
|
||||||
|
article.tags.set(tags)
|
||||||
|
article.keywords = ""
|
||||||
|
Article.objects.bulk_update(articles, ["keywords"])
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(apps, schema_editor):
|
||||||
|
Article = apps.get_model("articles", "Article")
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
articles = Article.objects.using(db_alias).all()
|
||||||
|
for article in articles:
|
||||||
|
article.keywords = ",".join(map(lambda tag: tag.name, article.tags.all()))
|
||||||
|
Article.objects.bulk_update(articles, ["keywords"])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0026_article_draft_key"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Tag",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=255, unique=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="article",
|
||||||
|
name="tags",
|
||||||
|
field=models.ManyToManyField(related_name="articles", to="articles.Tag"),
|
||||||
|
),
|
||||||
|
migrations.RunPython(forwards, backwards),
|
||||||
|
]
|
17
articles/migrations/0028_remove_article_keywords.py
Normal file
17
articles/migrations/0028_remove_article_keywords.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.1.5 on 2021-03-03 15:52
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0027_auto_20210303_1633"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="article",
|
||||||
|
name="keywords",
|
||||||
|
),
|
||||||
|
]
|
17
articles/migrations/0029_auto_20210303_1711.py
Normal file
17
articles/migrations/0029_auto_20210303_1711.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.1.5 on 2021-03-03 16:11
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0028_remove_article_keywords"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="tag",
|
||||||
|
options={"ordering": ["name"]},
|
||||||
|
),
|
||||||
|
]
|
|
@ -23,6 +23,16 @@ class User(AbstractUser):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Tag(models.Model):
|
||||||
|
name = models.CharField(max_length=255, unique=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["name"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Article(models.Model):
|
class Article(models.Model):
|
||||||
DRAFT = "draft"
|
DRAFT = "draft"
|
||||||
PUBLISHED = "published"
|
PUBLISHED = "published"
|
||||||
|
@ -39,11 +49,11 @@ class Article(models.Model):
|
||||||
author = models.ForeignKey(User, on_delete=models.PROTECT, default=1)
|
author = models.ForeignKey(User, on_delete=models.PROTECT, default=1)
|
||||||
views_count = models.IntegerField(default=0)
|
views_count = models.IntegerField(default=0)
|
||||||
slug = models.SlugField(unique=True, max_length=255)
|
slug = models.SlugField(unique=True, max_length=255)
|
||||||
keywords = models.CharField(max_length=255, blank=True)
|
|
||||||
has_code = models.BooleanField(default=False, blank=True)
|
has_code = models.BooleanField(default=False, blank=True)
|
||||||
is_home = models.BooleanField(default=False, blank=True)
|
is_home = models.BooleanField(default=False, blank=True)
|
||||||
custom_css = models.TextField(blank=True)
|
custom_css = models.TextField(blank=True)
|
||||||
draft_key = models.UUIDField(default=uuid.uuid4)
|
draft_key = models.UUIDField(default=uuid.uuid4)
|
||||||
|
tags = models.ManyToManyField(to=Tag, related_name="articles")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["-published_at"]
|
ordering = ["-published_at"]
|
||||||
|
@ -105,22 +115,14 @@ class Article(models.Model):
|
||||||
@cached_property
|
@cached_property
|
||||||
def get_related_articles(self):
|
def get_related_articles(self):
|
||||||
related_articles = set()
|
related_articles = set()
|
||||||
for keyword in self.get_formatted_keywords:
|
for tag in self.tags.all():
|
||||||
potential_articles = Article.objects.filter(
|
related_articles.update(tag.articles.all())
|
||||||
keywords__icontains=keyword,
|
|
||||||
status=Article.PUBLISHED,
|
|
||||||
).exclude(pk=self.pk)
|
|
||||||
for article in potential_articles:
|
|
||||||
if keyword in article.get_formatted_keywords:
|
|
||||||
related_articles.add(article)
|
|
||||||
sample_size = min([len(related_articles), 3])
|
sample_size = min([len(related_articles), 3])
|
||||||
return random.sample(related_articles, sample_size)
|
return random.sample(related_articles, sample_size)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def get_formatted_keywords(self):
|
def keywords(self):
|
||||||
return list(
|
return ", ".join(map(lambda tag: tag.name, self.tags.all()))
|
||||||
filter(None, map(lambda k: k.strip().lower(), self.keywords.split(",")))
|
|
||||||
)
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def get_minified_custom_css(self):
|
def get_minified_custom_css(self):
|
||||||
|
|
|
@ -74,9 +74,9 @@ class SearchArticlesListView(BaseArticleListView):
|
||||||
operator.and_, (Q(content__icontains=term) for term in search_terms)
|
operator.and_, (Q(content__icontains=term) for term in search_terms)
|
||||||
)
|
)
|
||||||
| reduce(
|
| reduce(
|
||||||
operator.and_, (Q(keywords__icontains=term) for term in search_terms)
|
operator.and_, (Q(tags__name__icontains=term) for term in search_terms)
|
||||||
)
|
)
|
||||||
)
|
).distinct()
|
||||||
|
|
||||||
def get_additional_querystring_params(self) -> Dict[str, str]:
|
def get_additional_querystring_params(self) -> Dict[str, str]:
|
||||||
search_expression = self.request.GET.get("s")
|
search_expression = self.request.GET.get("s")
|
||||||
|
|
Reference in a new issue