diff --git a/blog/admin.py b/blog/admin.py index 22e1093d16..4af8550427 100644 --- a/blog/admin.py +++ b/blog/admin.py @@ -11,8 +11,15 @@ @admin.register(Entry) class EntryAdmin(admin.ModelAdmin): - list_display = ("headline", "pub_date", "is_active", "is_published", "author") - list_filter = ("is_active",) + list_display = ( + "headline", + "pub_date", + "is_active", + "is_published", + "is_searchable", + "author", + ) + list_filter = ("is_active", "is_searchable") exclude = ("summary_html", "body_html") prepopulated_fields = {"slug": ("headline",)} raw_id_fields = ["social_media_card"] diff --git a/blog/migrations/0006_entry_is_searchable.py b/blog/migrations/0006_entry_is_searchable.py new file mode 100644 index 0000000000..b871d4f475 --- /dev/null +++ b/blog/migrations/0006_entry_is_searchable.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2025-07-24 07:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0005_entry_social_media_card'), + ] + + operations = [ + migrations.AddField( + model_name='entry', + name='is_searchable', + field=models.BooleanField(help_text='Tick to make this entry allow this entry to appear in the Django documentation search.', null=True), + ), + ] diff --git a/blog/migrations/0007_set_is_searchable.py b/blog/migrations/0007_set_is_searchable.py new file mode 100644 index 0000000000..5dca5ff15b --- /dev/null +++ b/blog/migrations/0007_set_is_searchable.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2 on 2025-07-24 07:22 + +from django.db import migrations + + +def set_is_searchable(apps, schema_editor): + Entry = apps.get_model("blog", "Entry") + # If this is large, this should be split into batched updates + Entry.objects.filter(is_searchable__isnull=True).update(is_searchable=False) + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0006_entry_is_searchable'), + ] + + operations = [ + migrations.RunPython(set_is_searchable, reverse_code=migrations.RunPython.noop, elidable=True), + ] diff --git a/blog/migrations/0008_alter_entry_is_searchable.py b/blog/migrations/0008_alter_entry_is_searchable.py new file mode 100644 index 0000000000..05ec003eb8 --- /dev/null +++ b/blog/migrations/0008_alter_entry_is_searchable.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2025-07-24 07:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0007_set_is_searchable'), + ] + + operations = [ + migrations.AlterField( + model_name='entry', + name='is_searchable', + field=models.BooleanField(default=False, help_text='Tick to make this entry allow this entry to appear in the Django documentation search.'), + ), + ] diff --git a/blog/models.py b/blog/models.py index 988c607eb4..2abb0be330 100644 --- a/blog/models.py +++ b/blog/models.py @@ -168,7 +168,7 @@ def get_absolute_url(self): "day": self.pub_date.strftime("%d").lower(), "slug": self.slug, } - return reverse("weblog:entry", kwargs=kwargs) + return reverse("weblog:entry", kwargs=kwargs, host="www") def is_published(self): """ diff --git a/docs/migrations/0007_documentrelease_support_end.py b/docs/migrations/0007_documentrelease_support_end.py new file mode 100644 index 0000000000..2115f5ecc8 --- /dev/null +++ b/docs/migrations/0007_documentrelease_support_end.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2025-07-23 16:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('docs', '0006_alter_document_metadata_noop'), + ] + + operations = [ + migrations.AddField( + model_name='documentrelease', + name='support_end', + field=models.DateField(blank=True, help_text='The end of support for this release of Django.', null=True), + ), + ] diff --git a/docs/models.py b/docs/models.py index fb6714b9bb..2487d7ee61 100644 --- a/docs/models.py +++ b/docs/models.py @@ -22,6 +22,7 @@ from django.utils.html import strip_tags from django_hosts.resolvers import reverse +from blog.models import Entry from releases.models import Release from . import utils @@ -95,6 +96,11 @@ class DocumentRelease(models.Model): on_delete=models.CASCADE, ) is_default = models.BooleanField(default=False) + support_end = models.DateField( + null=True, + blank=True, + help_text="The end of support for this release of Django.", + ) objects = DocumentReleaseQuerySet.as_manager() @@ -212,6 +218,32 @@ def sync_to_db(self, decoded_documents): ) document.save(update_fields=("metadata",)) + def _sync_blog_to_db(self): + """ + Sync the blog entries into search based on the release documents + support end date. + """ + if self.lang == "en" and self.support_end: + for entry in Entry.objects.published(self.support_end): + Document.objects.create( + release=self, + path=entry.get_absolute_url(), + title=entry.headline, + metadata={ + "body": entry.body_html, + "breadcrumbs": [ + {"path": "weblog", "title": "News"}, + ], + "parents": "weblog", + "slug": entry.slug, + "title": entry.headline, + "toc": "", + }, + config=TSEARCH_CONFIG_LANGUAGES.get( + self.lang[:2], DEFAULT_TEXT_SEARCH_CONFIG + ), + ) + def _clean_document_path(path): # We have to be a bit careful to reverse-engineer the correct @@ -382,3 +414,16 @@ def body(self): with open(str(self.full_path)) as fp: doc = json.load(fp) return doc["body"] + + def document_url(self): + if self.metadata["parents"] == "weblog": + return self.path + return reverse( + "document-detail", + kwargs={ + "lang": self.release.lang, + "version": self.release.version, + "url": self.path, + }, + host="docs", + ) diff --git a/docs/search.py b/docs/search.py index 1a2ebcaa70..ed47c8a3a2 100644 --- a/docs/search.py +++ b/docs/search.py @@ -65,6 +65,7 @@ class DocumentationCategory(TextChoices): TOPICS = "topics", _("Using Django") HOWTO = "howto", _("How-to guides") RELEASE_NOTES = "releases", _("Release notes") + WEBSITE = "weblog", _("Django Website") @classmethod def parse(cls, value, default=None): diff --git a/docs/templates/docs/search_results.html b/docs/templates/docs/search_results.html index 4f400a8384..de3fb10bf1 100644 --- a/docs/templates/docs/search_results.html +++ b/docs/templates/docs/search_results.html @@ -43,11 +43,11 @@

{% translate "No search query given" %}

{% for result in page.object_list %}

- {{ result.headline|safe }} + {{ result.headline|safe }}

{% for breadcrumb in result.breadcrumbs %} - {{ breadcrumb.title }}{% if not forloop.last %} »{% endif %} + {{ breadcrumb.title }}{% if not forloop.last %} »{% endif %} {% endfor %}
@@ -60,7 +60,7 @@