From 4ea6368547a8a233e9b250b63e562a3bed12899f Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 9 Jul 2024 13:26:17 -0700 Subject: [PATCH 1/3] Add the ability to rename a tag --- blog/admin.py | 9 ++++++++- blog/migrations/0021_previous_tag_name.py | 18 ++++++++++++++++++ blog/models.py | 13 +++++++++++++ blog/tests.py | 12 ++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 blog/migrations/0021_previous_tag_name.py diff --git a/blog/admin.py b/blog/admin.py index a0da9ee..499ff7c 100644 --- a/blog/admin.py +++ b/blog/admin.py @@ -4,7 +4,7 @@ from django.db.models import F from django import forms from xml.etree import ElementTree -from .models import Entry, Tag, Quotation, Blogmark, Comment, Series +from .models import Entry, Tag, Quotation, Blogmark, Comment, Series, PreviousTagName class BaseAdmin(admin.ModelAdmin): @@ -78,6 +78,13 @@ def get_search_results(self, request, queryset, search_term): else: return queryset.all(), False + def save_model(self, request, obj, form, change): + if change: + old_obj = Tag.objects.get(pk=obj.pk) + if old_obj.tag != obj.tag: + PreviousTagName.objects.create(tag=obj, previous_name=old_obj.tag) + super().save_model(request, obj, form, change) + admin.site.register( Comment, diff --git a/blog/migrations/0021_previous_tag_name.py b/blog/migrations/0021_previous_tag_name.py new file mode 100644 index 0000000..505788c --- /dev/null +++ b/blog/migrations/0021_previous_tag_name.py @@ -0,0 +1,18 @@ +from django.db import migrations, models + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0020_tag_description'), + ] + + operations = [ + migrations.CreateModel( + name='PreviousTagName', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('previous_name', models.SlugField()), + ('tag', models.ForeignKey(on_delete=models.CASCADE, to='blog.Tag')), + ], + ), + ] diff --git a/blog/models.py b/blog/models.py index 595e0a6..79a7c56 100644 --- a/blog/models.py +++ b/blog/models.py @@ -91,6 +91,19 @@ def get_related_tags(self, limit=10): self._related_tags = [tags_by_name[name] for name in tag_names] return self._related_tags + def rename_tag(self, new_name): + PreviousTagName.objects.create(tag=self, previous_name=self.tag) + self.tag = new_name + self.save() + + +class PreviousTagName(models.Model): + tag = models.ForeignKey(Tag, on_delete=models.CASCADE) + previous_name = models.SlugField() + + def __str__(self): + return self.previous_name + class Series(models.Model): created = models.DateTimeField(default=datetime.datetime.utcnow) diff --git a/blog/tests.py b/blog/tests.py index eccb120..8aff2ed 100644 --- a/blog/tests.py +++ b/blog/tests.py @@ -5,6 +5,7 @@ BlogmarkFactory, QuotationFactory, ) +from blog.models import Tag, PreviousTagName class BlogTests(TransactionTestCase): @@ -106,3 +107,14 @@ def test_do_typography_string(self): ), ): self.assertEqual(do_typography_string(input), expected) + + def test_rename_tag_creates_previous_tag_name(self): + tag = Tag.objects.create(tag="old-name") + tag.rename_tag("new-name") + self.assertEqual(tag.tag, "new-name") + previous_tag_name = PreviousTagName.objects.get(tag=tag) + self.assertEqual(previous_tag_name.previous_name, "old-name") + + def test_tag_with_hyphen(self): + tag = Tag.objects.create(tag="tag-with-hyphen") + self.assertEqual(tag.tag, "tag-with-hyphen") From 93c43a251bb578d5462029017e9b0e330c1a8365 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 9 Jul 2024 15:15:31 -0700 Subject: [PATCH 2/3] Admin tweaks and ran Black --- blog/admin.py | 5 +++++ blog/migrations/0021_previous_tag_name.py | 19 ++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/blog/admin.py b/blog/admin.py index 499ff7c..6c2e307 100644 --- a/blog/admin.py +++ b/blog/admin.py @@ -110,3 +110,8 @@ def save_model(self, request, obj, form, change): "slug", ), ) + + +admin.site.register( + PreviousTagName, raw_id_fields=("tag",), list_display=("previous_name", "tag") +) diff --git a/blog/migrations/0021_previous_tag_name.py b/blog/migrations/0021_previous_tag_name.py index 505788c..ec118c6 100644 --- a/blog/migrations/0021_previous_tag_name.py +++ b/blog/migrations/0021_previous_tag_name.py @@ -1,18 +1,27 @@ from django.db import migrations, models + class Migration(migrations.Migration): dependencies = [ - ('blog', '0020_tag_description'), + ("blog", "0020_tag_description"), ] operations = [ migrations.CreateModel( - name='PreviousTagName', + name="PreviousTagName", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('previous_name', models.SlugField()), - ('tag', models.ForeignKey(on_delete=models.CASCADE, to='blog.Tag')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("previous_name", models.SlugField()), + ("tag", models.ForeignKey(on_delete=models.CASCADE, to="blog.Tag")), ], ), ] From 580cd3455ee13333250df76d09be110489f3bba8 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 9 Jul 2024 15:21:17 -0700 Subject: [PATCH 3/3] Implement redirects for previous tags --- blog/tests.py | 9 +++++++++ blog/views.py | 23 +++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/blog/tests.py b/blog/tests.py index 8aff2ed..9124180 100644 --- a/blog/tests.py +++ b/blog/tests.py @@ -110,10 +110,19 @@ def test_do_typography_string(self): def test_rename_tag_creates_previous_tag_name(self): tag = Tag.objects.create(tag="old-name") + tag.entry_set.create( + title="Test entry", + body="Test entry body", + created="2020-01-01", + ) + assert self.client.get("/tags/old-name/").status_code == 200 + assert self.client.get("/tags/new-name/").status_code == 404 tag.rename_tag("new-name") self.assertEqual(tag.tag, "new-name") previous_tag_name = PreviousTagName.objects.get(tag=tag) self.assertEqual(previous_tag_name.previous_name, "old-name") + assert self.client.get("/tags/old-name/").status_code == 301 + assert self.client.get("/tags/new-name/").status_code == 200 def test_tag_with_hyphen(self): tag = Tag.objects.create(tag="tag-with-hyphen") diff --git a/blog/views.py b/blog/views.py index c64ff20..d388319 100644 --- a/blog/views.py +++ b/blog/views.py @@ -1,5 +1,4 @@ # coding=utf8 -from django.http.response import HttpResponseRedirect from django.shortcuts import render, get_object_or_404 from django.http import HttpResponse, JsonResponse from django.contrib.admin.views.decorators import staff_member_required @@ -23,6 +22,7 @@ Photoset, Series, Tag, + PreviousTagName, load_mixed_objects, ) import requests @@ -354,9 +354,20 @@ def tag_index(request): def archive_tag(request, tags, atom=False): from .feeds import EverythingTagged - tags = Tag.objects.filter(tag__in=tags.split("+")).values_list("tag", flat=True)[:3] - if not tags: + tags_ = Tag.objects.filter(tag__in=tags.split("+")).values_list("tag", flat=True)[ + :3 + ] + if not tags_: + # Try for a previous tag name + if "+" not in tags: + try: + previous = PreviousTagName.objects.get(previous_name=tags) + except PreviousTagName.DoesNotExist: + raise Http404 + return Redirect("/tag/%s/" % previous.tag.tag) + raise Http404 + tags = tags_ items = [] from django.db import connection @@ -770,15 +781,15 @@ def user_from_cookies(request): def redirect_entry(request, pk): - return HttpResponseRedirect(get_object_or_404(Entry, pk=pk).get_absolute_url()) + return Redirect(get_object_or_404(Entry, pk=pk).get_absolute_url()) def redirect_blogmark(request, pk): - return HttpResponseRedirect(get_object_or_404(Blogmark, pk=pk).get_absolute_url()) + return Redirect(get_object_or_404(Blogmark, pk=pk).get_absolute_url()) def redirect_quotation(request, pk): - return HttpResponseRedirect(get_object_or_404(Quotation, pk=pk).get_absolute_url()) + return Redirect(get_object_or_404(Quotation, pk=pk).get_absolute_url()) def about(request):