4 Commits

6 changed files with 179 additions and 7 deletions

View File

@@ -0,0 +1,56 @@
from django.db import migrations
LANGUAGES = ("en", "de", "fr", "es", "it", "pt", "ru")
def _add_column_if_missing(schema_editor, table_name, column_name, column_sql):
connection = schema_editor.connection
with connection.cursor() as cursor:
existing = {
row.name
for row in connection.introspection.get_table_description(cursor, table_name)
}
if column_name in existing:
return
quoted_table = schema_editor.quote_name(table_name)
quoted_column = schema_editor.quote_name(column_name)
cursor.execute(
f"ALTER TABLE {quoted_table} ADD COLUMN {quoted_column} {column_sql}"
)
def add_checkout_i18n_columns(apps, schema_editor):
for lang in LANGUAGES:
_add_column_if_missing(
schema_editor,
"checkout_fixedsurcharge",
f"name_{lang}",
"varchar(32) NULL",
)
_add_column_if_missing(
schema_editor,
"checkout_percentagesurcharge",
f"name_{lang}",
"varchar(32) NULL",
)
_add_column_if_missing(
schema_editor,
"checkout_paymentmethod",
f"label_{lang}",
"varchar(32) NULL",
)
class Migration(migrations.Migration):
dependencies = [
("mandelstudio", "0004_contact_messages"),
("checkout", "0017_remove_unused_price_fields"),
]
operations = [
migrations.RunPython(
add_checkout_i18n_columns,
migrations.RunPython.noop,
)
]

View File

@@ -0,0 +1,43 @@
from django.db import migrations
LANGUAGES = ("en", "de", "fr", "es", "it", "pt", "ru")
def _add_column_if_missing(schema_editor, table_name, column_name, column_sql):
connection = schema_editor.connection
with connection.cursor() as cursor:
existing = {
row.name
for row in connection.introspection.get_table_description(cursor, table_name)
}
if column_name in existing:
return
quoted_table = schema_editor.quote_name(table_name)
quoted_column = schema_editor.quote_name(column_name)
cursor.execute(
f"ALTER TABLE {quoted_table} ADD COLUMN {quoted_column} {column_sql}"
)
def add_address_i18n_columns(apps, schema_editor):
for lang in LANGUAGES:
_add_column_if_missing(
schema_editor,
"address_country",
f"printable_name_{lang}",
"varchar(128) NULL",
)
class Migration(migrations.Migration):
dependencies = [
("mandelstudio", "0005_checkout_i18n_columns"),
]
operations = [
migrations.RunPython(
add_address_i18n_columns,
migrations.RunPython.noop,
)
]

View File

@@ -18,6 +18,7 @@ ALLOWED_HOSTS = [
"www.mandelblog.com", "www.mandelblog.com",
"mandelblog.com", "mandelblog.com",
] ]
CANONICAL_BASE_URL = "https://www.mandelblog.com"
if "salt_target" in globals(): if "salt_target" in globals():
ALLOWED_HOSTS.append("mandelstudio.%s" % salt_target) # pylint: disable=E0602 ALLOWED_HOSTS.append("mandelstudio.%s" % salt_target) # pylint: disable=E0602
# pylint: disable=E0602 # pylint: disable=E0602

View File

@@ -1,5 +1,6 @@
from django.contrib.sitemaps.views import index as sitemap_index_view from django.contrib.sitemaps.views import index as sitemap_index_view
from django.contrib.sitemaps.views import sitemap as sitemap_section_view from django.contrib.sitemaps.views import sitemap as sitemap_section_view
from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
from wagtail.models import Locale, Page from wagtail.models import Locale, Page
@@ -15,6 +16,13 @@ from ocyan.plugin.wagtail_oscar_integration.sitemap import (
) )
def _absolute_sitemap_url(request, path: str) -> str:
canonical_base = getattr(settings, "CANONICAL_BASE_URL", "").rstrip("/")
if canonical_base:
return f"{canonical_base}{path}"
return request.build_absolute_uri(path)
class WagtailSitemap(BaseWagtailSitemap): class WagtailSitemap(BaseWagtailSitemap):
def items(self): def items(self):
page_ids = [] page_ids = []
@@ -74,7 +82,7 @@ def sitemap_section(request, section=None):
def robots_txt(request): def robots_txt(request):
sitemap_url = request.build_absolute_uri("/sitemap.xml") sitemap_url = _absolute_sitemap_url(request, "/sitemap.xml")
content = "\n".join( content = "\n".join(
[ [
"User-agent: *", "User-agent: *",

View File

@@ -2,12 +2,41 @@ from __future__ import annotations
from django import template from django import template
from django.conf import settings from django.conf import settings
from urllib.parse import urlsplit, urlunsplit
from wagtail.models import Page from wagtail.models import Page
register = template.Library() register = template.Library()
def _canonical_base_url() -> str:
return getattr(settings, "CANONICAL_BASE_URL", "").rstrip("/")
def _with_canonical_host(url_or_path: str | None) -> str:
value = url_or_path or ""
base = _canonical_base_url()
if not base:
return value
if not value:
return f"{base}/"
if value.startswith("/"):
return f"{base}{value}"
parsed = urlsplit(value)
if parsed.scheme and parsed.netloc:
canonical = urlsplit(base)
return urlunsplit(
(
canonical.scheme,
canonical.netloc,
parsed.path,
parsed.query,
parsed.fragment,
)
)
return f"{base}/{value.lstrip('/')}"
def _normalize_language_code(language_code: str | None) -> str: def _normalize_language_code(language_code: str | None) -> str:
return (language_code or settings.LANGUAGE_CODE).split("-")[0] return (language_code or settings.LANGUAGE_CODE).split("-")[0]
@@ -41,10 +70,10 @@ def _translated_pages(page):
def _build_absolute_url(request, path: str | None, page=None) -> str: def _build_absolute_url(request, path: str | None, page=None) -> str:
if path and request is not None: if path and request is not None:
return request.build_absolute_uri(path) return _with_canonical_host(request.build_absolute_uri(path))
if page is not None: if page is not None:
return getattr(page, "full_url", "") or path or "" return _with_canonical_host(getattr(page, "full_url", "") or path or "")
return path or "" return _with_canonical_host(path or "")
@register.simple_tag @register.simple_tag
@@ -89,7 +118,7 @@ def page_canonical_url(context):
if page is not None and getattr(page, "url", None): if page is not None and getattr(page, "url", None):
return _build_absolute_url(request, page.url, page) return _build_absolute_url(request, page.url, page)
if request is not None: if request is not None:
return request.build_absolute_uri() return _with_canonical_host(request.build_absolute_uri())
return "" return ""

View File

@@ -1,5 +1,5 @@
from django.conf.urls.i18n import i18n_patterns from django.conf.urls.i18n import i18n_patterns
from django.urls import path from django.urls import include, path
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
from ocyan.core.fender import config from ocyan.core.fender import config
@@ -7,6 +7,9 @@ from ocyan.main.urls import urlpatterns as ocyan_urlpatterns
from ocyan.plugin.contact_form.entrypoint import SHOP_BASE_URL from ocyan.plugin.contact_form.entrypoint import SHOP_BASE_URL
from ocyan.plugin.wagtail_oscar_integration.constants import CACHE_DURATION from ocyan.plugin.wagtail_oscar_integration.constants import CACHE_DURATION
from ocyan.plugin.wordspinner.views.ai_search import ai_search_view from ocyan.plugin.wordspinner.views.ai_search import ai_search_view
from ocyan.plugin.wordspinner.views.bulk import BulkAIOperationsView
from ocyan.plugin.wordspinner.views.bulk import ProcessNextBulkAIJobView
from ocyan.plugin.wordspinner.views.results import GeneratedResultsListView
from ocyan.plugin.contact_form.views import post_contact_form from ocyan.plugin.contact_form.views import post_contact_form
@@ -36,6 +39,13 @@ contact_form_urlpatterns = [
), ),
] ]
wordspinner_urlpatterns = [
path(
"wordspinner/",
include(("ocyan.plugin.wordspinner.urls", "wordspinner"), namespace="wordspinner"),
),
]
# Ensure public AI search routes are resolved before Wagtail catch-all URLs. # Ensure public AI search routes are resolved before Wagtail catch-all URLs.
ai_search_urlpatterns = [ ai_search_urlpatterns = [
path("ai-search/", ai_search_view, name="wordspinner_ai_search_public"), path("ai-search/", ai_search_view, name="wordspinner_ai_search_public"),
@@ -56,13 +66,38 @@ ai_search_urlpatterns = [
), ),
] ]
wordspinner_i18n_aliases = [
path(
"<str:lang_code>/wordspinner/ai/bulk/",
BulkAIOperationsView.as_view(),
name="wordspinner_bulk_ai_operations_i18n",
),
path(
"<str:lang_code>/wordspinner/ai/bulk/process-next/",
ProcessNextBulkAIJobView.as_view(),
name="wordspinner_bulk_ai_process_next_i18n",
),
path(
"<str:lang_code>/wordspinner/results/",
GeneratedResultsListView.as_view(),
name="wordspinner_generated_results_i18n",
),
]
if config.i18n_enabled: if config.i18n_enabled:
urlpatterns += i18n_patterns( urlpatterns += i18n_patterns(
*wordspinner_urlpatterns,
*contact_form_urlpatterns, *contact_form_urlpatterns,
*ai_search_urlpatterns, *ai_search_urlpatterns,
*wordspinner_i18n_aliases,
prefix_default_language=False, prefix_default_language=False,
) )
else: else:
urlpatterns += contact_form_urlpatterns + ai_search_urlpatterns urlpatterns += (
wordspinner_urlpatterns
+ contact_form_urlpatterns
+ ai_search_urlpatterns
+ wordspinner_i18n_aliases
)
urlpatterns += ocyan_urlpatterns urlpatterns += ocyan_urlpatterns