Skip to content

Commit cac7fdf

Browse files
anlu85olofk
authored andcommitted
Adds robots.txt and sitemap views with indexing control
Implements `robots.txt` and `sitemap.xml` views to improve SEO. Introduces a setting (`INDEXABLE`) to control whether the sitemap is available, returning a 404 if indexing is disabled. Adds tests for `robots.txt` and `sitemap.xml` to verify correct behavior based on `INDEXABLE` setting.
1 parent b7d65f1 commit cac7fdf

File tree

8 files changed

+107
-12
lines changed

8 files changed

+107
-12
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1+
"""
2+
Context processor for SEO-related settings.
3+
4+
Provides the INDEXABLE setting to Django templates for use in meta tags and robots.txt.
5+
"""
6+
17
from django.conf import settings
28

39
def seo_settings(request):
10+
"""
11+
Add the INDEXABLE setting to the template context.
12+
13+
Args:
14+
request: The current HttpRequest object.
15+
16+
Returns:
17+
dict: A dictionary with the INDEXABLE setting.
18+
"""
419
return {'INDEXABLE': settings.INDEXABLE}

core_directory/sitemaps.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
Classes:
44
ProjectSitemap: Generates sitemap entries for all Project objects.
55
CorePackageSitemap: Generates sitemap entries for all CorePackage objects.
6-
StaticViewSitemap: Generates sitemap entries for static views such as 'landing', 'core-package-list', and 'vendor-list'.
7-
Each sitemap class specifies the change frequency and priority for its entries, and provides methods to retrieve the items to be included in the sitemap.
6+
StaticViewSitemap: Generates sitemap entries for static views such as 'landing',
7+
'core-package-list', and 'vendor-list'.
8+
Each sitemap class specifies the change frequency and priority for its entries,
9+
and provides methods to retrieve the items to be included in the sitemap.
810
"""
911

1012
from django.contrib.sitemaps import Sitemap
11-
from .models import CorePackage, Vendor
1213
from django.urls import reverse
14+
from .models import CorePackage, Vendor
1315

1416
class VendorSitemap(Sitemap):
1517
"""
@@ -24,7 +26,7 @@ class VendorSitemap(Sitemap):
2426
priority = 0.6
2527

2628
def items(self):
27-
return Vendor.objects.all()
29+
return Vendor.objects.all().order_by('sanitized_name', 'pk')
2830

2931
class CorePackageSitemap(Sitemap):
3032
"""
@@ -44,7 +46,7 @@ class CorePackageSitemap(Sitemap):
4446
changefreq = "daily"
4547
priority = 0.8
4648
def items(self):
47-
return CorePackage.objects.all()
49+
return CorePackage.objects.all().order_by('vlnv_name')
4850

4951
class StaticViewSitemap(Sitemap):
5052
"""

core_directory/tests/system/__init__.py

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import pytest
2+
from django.urls import reverse
3+
from django.conf import settings
4+
5+
@pytest.mark.django_db
6+
def test_robots_txt_indexable_true(settings, client):
7+
settings.INDEXABLE = True
8+
url = reverse("robots_txt")
9+
response = client.get(url)
10+
assert response.status_code == 200
11+
assert response["Content-Type"].startswith("text/plain")
12+
assert "Disallow:" in response.content.decode()
13+
assert "Disallow: /" not in response.content.decode()
14+
15+
@pytest.mark.django_db
16+
def test_robots_txt_indexable_false(settings, client):
17+
settings.INDEXABLE = False
18+
url = reverse("robots_txt")
19+
response = client.get(url)
20+
assert response.status_code == 200
21+
assert response["Content-Type"].startswith("text/plain")
22+
assert "Disallow: /" in response.content.decode()
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# tests/test_sitemaps.py
2+
3+
import pytest
4+
from django.urls import reverse, NoReverseMatch
5+
from django.conf import settings
6+
from django.test import override_settings
7+
8+
from core_directory.models import Vendor, Library, Project, CorePackage
9+
10+
@pytest.mark.django_db
11+
@override_settings(INDEXABLE=True)
12+
def test_sitemap_with_data(client):
13+
# Create test data
14+
vendor = Vendor.objects.create(name="Acme")
15+
library = Library.objects.create(vendor=vendor, name="Lib1")
16+
project = Project.objects.create(vendor=vendor, library=library, name="Core1", description="desc")
17+
core_package = CorePackage.objects.create(
18+
project=project,
19+
vlnv_name="acme:lib1:core1:1.0.0",
20+
version="1.0.0",
21+
core_url="https://example.com/core",
22+
description="desc"
23+
)
24+
25+
# The sitemap should be available
26+
url = reverse("sitemap")
27+
response = client.get(url)
28+
assert response.status_code == 200
29+
content = response.content.decode()
30+
31+
# Check for expected URLs in the sitemap
32+
assert "<urlset" in content
33+
# Vendor page
34+
assert vendor.get_absolute_url() in content
35+
# CorePackage page
36+
assert core_package.get_absolute_url() in content
37+
# Static views
38+
assert reverse("landing") in content
39+
assert reverse("core-package-list") in content
40+
assert reverse("vendor-list") in content
41+
42+
@pytest.mark.django_db
43+
@override_settings(INDEXABLE=False)
44+
def test_sitemap_not_available_when_indexing_disabled(client):
45+
url = reverse("sitemap")
46+
response = client.get(url)
47+
assert response.status_code == 404

core_directory/tests/test_urls.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
("api_docs_landing", {}, 200, False, "get"),
1313
("schema", {}, 200, False, "get"),
1414
("swagger_ui", {}, 200, False, "get"),
15-
("redoc_ui", {}, 200, False, "get"),
1615
])
1716
def test_url_resolves_and_returns(client, mocker, url_name, kwargs, expected_status, needs_object, method):
1817
"""
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import pytest
22
from django.urls import reverse, resolve
3+
from django.test import override_settings
34

5+
@pytest.mark.django_db
6+
@override_settings(INDEXABLE=False)
47
@pytest.mark.parametrize("url_name, kwargs, expected_status", [
58
("landing", {}, 200),
69
("core-detail", {"pk": 1}, 200),
@@ -13,6 +16,8 @@
1316
}, 200),
1417
("vendor-list", {}, 200),
1518
("vendor-detail", {"sanitized_name": "acme"}, 200),
19+
("robots_txt", {}, 200),
20+
("sitemap", {}, 200),
1621
])
1722
def test_project_urls(client, url_name, kwargs, expected_status):
1823
"""

project/urls.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
2020
"""
2121
from django.conf import settings
22+
from django.http import HttpResponseNotFound
2223
from django.contrib import admin
2324
from django.contrib.sitemaps.views import sitemap
2425
from django.urls import include, path
@@ -41,6 +42,14 @@
4142
'vendors': VendorSitemap,
4243
}
4344

45+
def guarded_sitemap_view(request, *args, **kwargs):
46+
"""
47+
Return the sitemap if INDEXABLE is True, else return 404.
48+
"""
49+
if not settings.INDEXABLE:
50+
return HttpResponseNotFound("Sitemap is disabled.")
51+
return sitemap(request, *args, **kwargs)
52+
4453
urlpatterns = [
4554

4655
path('', landing, name='landing'),
@@ -64,10 +73,6 @@
6473
path('admin/', admin.site.urls),
6574
path('api/', include('core_directory.urls')),
6675

67-
path('robots.txt', robots_txt, name='robots_txt')
76+
path('robots.txt', robots_txt, name='robots_txt'),
77+
path('sitemap.xml', guarded_sitemap_view, {'sitemaps': sitemaps}, name='sitemap'),
6878
]
69-
70-
if settings.INDEXABLE:
71-
urlpatterns += [
72-
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
73-
]

0 commit comments

Comments
 (0)