Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Category snippet localization #7183

Merged
merged 9 commits into from
Aug 9, 2021
1 change: 1 addition & 0 deletions network-api/networkapi/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@
'blog_tags': 'networkapi.wagtailpages.templatetags.blog_tags',
'card_tags': 'networkapi.wagtailpages.templatetags.card_tags',
'class_tags': 'networkapi.wagtailpages.templatetags.class_tags',
'debug_tags': 'networkapi.wagtailpages.templatetags.debug_tags',
Copy link
Contributor Author

@Pomax Pomax Aug 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slipping this in with the rest: having this around just makes life easier

'homepage_tags': 'networkapi.wagtailpages.templatetags.homepage_tags',
'localization': 'networkapi.wagtailpages.templatetags.localization',
'mini_site_tags': 'networkapi.wagtailpages.templatetags.mini_site_tags',
Expand Down
17 changes: 17 additions & 0 deletions network-api/networkapi/wagtailpages/pagemodels/mixin/snippets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.conf import settings
from wagtail.core.models import Locale

DEFAULT_LOCALE = Locale.objects.get(language_code=settings.LANGUAGE_CODE)


class LocalizedSnippet():
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@property
def original(self):
try:
return self.get_translation(DEFAULT_LOCALE)
except AttributeError:
return self

89 changes: 56 additions & 33 deletions network-api/networkapi/wagtailpages/pagemodels/products.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from wagtail.snippets.models import register_snippet

from wagtail_localize.fields import SynchronizedField, TranslatableField

from wagtail_airtable.mixins import AirtableMixin

from networkapi.wagtailpages.fields import ExtendedBoolean, ExtendedYesNoField
Expand All @@ -36,7 +35,7 @@

# TODO: Move this util function
from networkapi.buyersguide.utils import get_category_og_image_upload_path

from .mixin.snippets import LocalizedSnippet

TRACK_RECORD_CHOICES = [
('Great', 'Great'),
Expand All @@ -45,6 +44,31 @@
('Bad', 'Bad')
]

DEFAULT_LOCALE_ID = Locale.objects.get(language_code=settings.LANGUAGE_CODE).id


def get_language_code_from_request(request):
"""
Accepts a request. Returns a language code (string) if there is one. Falls back to English.
"""
default_language_code = settings.LANGUAGE_CODE
if hasattr(request, 'LANGUAGE_CODE'):
default_language_code = request.LANGUAGE_CODE
return default_language_code

def get_categories_for_locale(language_code):
"""
Make sure that we check both the "whatever the current locale" category
for whether or not it's hidden, but also the original English version.
"""
return [
cat
for cat in BuyersGuideProductCategory.objects.filter(
hidden=False,
locale=Locale.objects.get(language_code=language_code)
)
if not cat.original.hidden
]

def get_product_subset(cutoff_date, authenticated, key, products, language_code='en'):
"""
Expand All @@ -53,10 +77,7 @@ def get_product_subset(cutoff_date, authenticated, key, products, language_code=
to the system or not (authenticated users get to
see all products, including draft products)
"""
products = products.filter(
review_date__gte=cutoff_date,
locale=Locale.objects.get(language_code=language_code)
)
products = products.filter(review_date__gte=cutoff_date)
if not authenticated:
products = products.live()
products = sort_average(products)
Expand All @@ -71,7 +92,7 @@ def sort_average(products):


@register_snippet
class BuyersGuideProductCategory(TranslatableMixin, models.Model):
class BuyersGuideProductCategory(TranslatableMixin, LocalizedSnippet, models.Model):
"""
A simple category class for use with Buyers Guide products,
registered as snippet so that we can moderate them if and
Expand Down Expand Up @@ -121,11 +142,6 @@ class BuyersGuideProductCategory(TranslatableMixin, models.Model):
def published_product_page_count(self):
return ProductPage.objects.filter(product_categories__category=self).live().count()

@property
def published_product_count(self):
# TODO: REMOVE: LEGACY FUNCTION
return ProductPage.objects.filter(product_category=self, draft=False).count()

def __str__(self):
return self.name

Expand Down Expand Up @@ -805,7 +821,8 @@ def get_or_create_votes(self):
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
context['product'] = self
context['categories'] = BuyersGuideProductCategory.objects.filter(hidden=False)
language_code = get_language_code_from_request(request)
context['categories'] = get_categories_for_locale(language_code)
context['mediaUrl'] = settings.MEDIA_URL
context['use_commento'] = settings.USE_COMMENTO
context['pageTitle'] = f'{self.title} | ' + gettext("Privacy & security guide") + ' | Mozilla Foundation'
Expand Down Expand Up @@ -1291,7 +1308,9 @@ class Meta:


class ExcludedCategories(TranslatableMixin, Orderable):
"""This allows us to select one or more blog authors from Snippets."""
"""
This allows us to filter categories from showing up on the PNI site
"""

page = ParentalKey("wagtailpages.BuyersGuidePage", related_name="excluded_categories")
category = models.ForeignKey(
Expand Down Expand Up @@ -1380,13 +1399,6 @@ def get_banner(self):
SynchronizedField('search_image'),
]

def get_language_code(self, request):
"""Accepts a request. Returns a language code (string) if there is one. Falls back to English."""
default_language_code = settings.LANGUAGE_CODE
if hasattr(request, 'LANGUAGE_CODE'):
default_language_code = request.LANGUAGE_CODE
return default_language_code

@route(r'^about/$', name='how-to-use-view')
def about_page(self, request):
context = self.get_context(request)
Expand Down Expand Up @@ -1460,14 +1472,28 @@ def product_view(self, request, slug):
@route(r'^categories/(?P<slug>[\w\W]+)/', name='category-view')
def categories_page(self, request, slug):
context = self.get_context(request, bypass_products=True)
language_code = self.get_language_code(request)
language_code = get_language_code_from_request(request)
locale_id = Locale.objects.get(language_code=language_code).id
slug = slugify(slug)

# If getting by slug fails, also try to get it by name.
# because we may be working with localized content, and the slug
# will always be our english slug, we need to find the english
# category first, and then find its corresponding localized version
try:
category = BuyersGuideProductCategory.objects.get(slug=slug)
original_category = BuyersGuideProductCategory.objects.get(slug=slug, locale_id=DEFAULT_LOCALE_ID)
except BuyersGuideProductCategory.DoesNotExist:
category = get_object_or_404(BuyersGuideProductCategory, name__iexact=slug)
original_category = get_object_or_404(BuyersGuideProductCategory, name__iexact=slug)

if locale_id != DEFAULT_LOCALE_ID:
try:
category = BuyersGuideProductCategory.objects.get(
translation_key=original_category.translation_key,
locale_id=DEFAULT_LOCALE_ID,
)
except BuyersGuideProductCategory.DoesNotExist:
category = original_category
else:
category = original_category

authenticated = request.user.is_authenticated
key = f'cat_product_dicts_{slug}_auth' if authenticated else f'cat_product_dicts_{slug}_live'
Expand All @@ -1480,12 +1506,13 @@ def categories_page(self, request, slug):
self.cutoff_date,
authenticated,
key,
ProductPage.objects.filter(product_categories__category__in=[category])
ProductPage.objects.filter(product_categories__category__in=[original_category])
.exclude(product_categories__category__id__in=exclude_cat_ids),
language_code=language_code
)

context['category'] = category.slug
context['category'] = slug
context['current_category'] = category
context['products'] = products
context['pageTitle'] = f'{category} | ' + gettext("Privacy & security guide") + ' | Mozilla Foundation'
context['template_cache_key_fragment'] = f'{category.slug}_{request.LANGUAGE_CODE}'
Expand Down Expand Up @@ -1525,7 +1552,7 @@ def get_sitemap_urls(self, request):

def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
language_code = self.get_language_code(request)
language_code = get_language_code_from_request(request)

authenticated = request.user.is_authenticated
key = 'home_product_dicts_authed' if authenticated else 'home_product_dicts_live'
Expand All @@ -1542,11 +1569,7 @@ def get_context(self, request, *args, **kwargs):
language_code=language_code
)

categories = BuyersGuideProductCategory.objects.filter(
hidden=False,
locale=Locale.objects.get(language_code=language_code)
)
context['categories'] = categories
context['categories'] = get_categories_for_locale(language_code)
context['products'] = products
context['web_monetization_pointer'] = settings.WEB_MONETIZATION_POINTER
pni_home_page = BuyersGuidePage.objects.first()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% extends "pages/base.html" %}

{% load bg_nav_tags localization i18n static wagtailroutablepage_tags wagtailmetadata_tags %}
{% load bg_nav_tags localization i18n static wagtailroutablepage_tags wagtailmetadata_tags debug_tags %}

{% get_current_language as lang_code %}

Expand Down Expand Up @@ -85,15 +85,18 @@
<div class="row">
<div class="col">
{% if pagetype == "product" or pagetype == "about" %}
<a class="multipage-link active" href="{{ page.url }}">{% trans "All" %}</a>
<a class="multipage-link active" href="{% relocalized_url page.localized.url lang_code %}">{% trans "All" %}</a>
{% else %}
<a class="multipage-link {% if not category %} active{% endif %}" href="{{ page.url }}">{% trans "All" %}</a>
<a class="multipage-link {% if not category %} active{% endif %}" href="{% relocalized_url page.localized.url lang_code %}">{% trans "All" %}</a>
{% endif %}

{% for cat in categories %}
{% if cat.published_product_page_count > 0 %}
{% routablepageurl home_page 'category-view' cat.slug as cat_url %}
<a class="multipage-link {% check_active_category current_category cat %}{% if cat.featured is True %} featured{% endif %}{% if category == cat.slug %} active{% endif %}" href="{{ cat_url }}" data-name="{{ cat.name }}">{{ cat.name }}</a>
{% with original=cat.original %}
{% if original.published_product_page_count > 0 %}
{% localizedroutablepageurl home_page 'category-view' lang_code original.slug as cat_url %}
<a class="multipage-link {% check_active_category current_category cat %}{% if original.featured is True %} featured{% endif %}" href="{{ cat_url }}" data-name="{{ cat.name }}">{{ cat.name }}</a>
{% endif %}
{% endwith %}
{% endfor %}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
{% endcomment %}

{% for cat in sorted_categories %}
{% with original=cat.original %}
{% if cat.hidden == False and cat.published_product_page_count > 0 %}
{% url 'category-view' cat.slug as cat_url %}
{% localizedroutablepageurl home_page 'category-view' lang_code original.slug as cat_url %}
<a
class="multipage-link {% check_active_category current_category cat %}{% if cat.featured is True %} featured{% endif %}"
class="multipage-link {% check_active_category current_category cat %}{% if original.featured is True %} featured{% endif %}"
href="{{ cat_url }}"
data-name="{{ cat.name }}">
{{ cat.name }}
</a>
{% endif %}
{% endwith %}
{% endfor %}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{% extends "buyersguide/bg_base.html" %}

{% load bg_selector_tags env l10n i18n static wagtailimages_tags %}
{% load bg_selector_tags env l10n i18n localization static wagtailimages_tags %}

{% get_current_language as lang_code %}

{% block head_extra %}
<meta property="og:title" content="{% blocktrans context "This can be localized. This is a reference to the “*batteries not included” mention on toys." %}privacy not included - {{ product.title }}{% endblocktrans %}" />
Expand Down Expand Up @@ -297,7 +299,7 @@ <h3 class="h2-heading mb-4">{% trans "Related products" %}</h3>
{% for related_product_page in product.related_product_pages.all %}
{% with related_product=related_product_page.related_product.localized %}
<div class="related-product col-6 col-md-3 mb-3 mb-md-0">
<a class="d-block{% if related_product.adult_content %} adult-content{% endif %}" href="{{ related_product.url }}">
<a class="d-block{% if related_product.adult_content %} adult-content{% endif %}" href="{% relocalized_url related_product.url lang_code %}">
<div class="img-container">
{% image related_product.image width-600 as img %}
<img
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% load static i18n wagtailimages_tags %}
{% load static i18n wagtailimages_tags localization %}

{% get_current_language as lang_code %}

<figure class="product-box d-flex flex-column justify-content-between{% if product.draft %} draft-product{% endif %}{% if product.adult_content %} adult-content{% endif %}{% if product.privacy_ding %} privacy-ding{% endif%}" data-creepiness="{{ product.creepiness }}">
<div class="top-left-badge-container">
Expand All @@ -15,7 +16,7 @@

{% include "fragments/adult_content_badge.html" with product=product %}

<a class="product-image text-center mt-4 h-100 d-flex flex-column justify-content-between" href="{{ product.url }}">
<a class="product-image text-center mt-4 h-100 d-flex flex-column justify-content-between" href="{% relocalized_url product.url lang_code %}">
<picture class="product-thumbnail">
<source
{% image product.image fill-360x360 as img_1x %}
Expand All @@ -32,7 +33,7 @@
</a>

<figcaption class="d-block mt-md-2 text-left">
<a class="product-links" href="{{ product.url }}">
<a class="product-links" href="{% relocalized_url product.url lang_code %}">
<div class="product-company body-small">{{product.company}}</div>
<div class="product-name body">{{product.title}}</div>
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
# Determine if a category nav link should be marked active
@register.simple_tag(name='check_active_category')
def check_active_category(current_category, target_category):
# because we're working with potentially localized data,
# make sure to compare the linguistic originals.
current_category = getattr(current_category, 'alias_of', current_category)
target_category = getattr(target_category, 'alias_of', target_category)
return 'active' if current_category == target_category else ''


Expand All @@ -15,7 +19,7 @@ def check_active_category(current_category, target_category):
def bg_active_nav(current, target):
return 'active' if urlparse(current).path == urlparse(target).path else ''


"""
# Instantiate a list of category page links based on the current page's relation to them
# NOTE: this points to the new, namespaced category_nav_links. If we need to revert to the old app, change this back.
@register.inclusion_tag('buyersguide/fragments/category_nav_links.html', takes_context=True)
Expand All @@ -25,3 +29,4 @@ def category_nav(context, current_url, current_category, all_categories):
'current_category': current_category,
'sorted_categories': all_categories.order_by('-featured', 'sort_order', 'name'), # featured categories first
}
"""
12 changes: 12 additions & 0 deletions network-api/networkapi/wagtailpages/templatetags/debug_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.template import Library
from django.conf import settings
register = Library()

if settings.DEBUG:
print('→ Running in DEBUG mode: enabling debug template tag "inspect_object" in tag collection "debug_tags".\n')

@register.simple_tag
def inspect_object(instance, prefix_label=""):
output = str(dir(instance)).replace(', ', ',\n')
prefix_label = str(prefix_label)
return f'<pre>{prefix_label} {output}</pre>'
23 changes: 21 additions & 2 deletions network-api/networkapi/wagtailpages/templatetags/localization.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
from django import template
from django.conf import settings
from django.utils.translation import get_language_info
from wagtail.contrib.routable_page.templatetags.wagtailroutablepage_tags import routablepageurl

register = template.Library()

DEFAULT_LOCALE = 'en_US'

mappings = {
'en': 'en_US',
'de': 'de_DE',
Expand All @@ -20,6 +19,9 @@
'pt-BR': 'pt_BR',
}

DEFAULT_LOCALE_CODE = settings.LANGUAGE_CODE
DEFAULT_LOCALE = mappings.get(DEFAULT_LOCALE_CODE)


# This filter turns Wagtail language codes into OpenGraph locale strings
@register.filter
Expand Down Expand Up @@ -49,3 +51,20 @@ def get_local_language_names():
@register.simple_tag()
def get_unlocalized_url(page, locale):
return page.get_url().replace(f'/{locale}/', '/', 1)

# Force-relocalize a URL
@register.simple_tag()
Copy link
Contributor Author

@Pomax Pomax Aug 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of a hack, and probably should not be necessary. But... it is? =(

def relocalized_url(url, locale_code):
if locale_code == DEFAULT_LOCALE_CODE:
return url
return url.replace(f'/{DEFAULT_LOCALE_CODE}/', f'/{locale_code}/')


# Overcome a limitation of the routablepageurl tag
@register.simple_tag(takes_context=True)
def localizedroutablepageurl(context, page, url_name, locale_code, *args, **kwargs):
url = relocalized_url(
routablepageurl(context, page, url_name, *args, **kwargs),
locale_code,
)
return url