Merge branch 'main' into 9647-nav
This commit is contained in:
Коммит
29e9157323
|
@ -53,7 +53,7 @@ jobs:
|
||||||
python-version: 3.9.9
|
python-version: 3.9.9
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 18
|
||||||
|
|
||||||
- name: Install Python Dependencies
|
- name: Install Python Dependencies
|
||||||
run: pip install -r requirements.txt -r dev-requirements.txt
|
run: pip install -r requirements.txt -r dev-requirements.txt
|
||||||
|
|
|
@ -61,7 +61,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 18
|
||||||
|
|
||||||
- name: Configure AWS Credentials for visual diffing
|
- name: Configure AWS Credentials for visual diffing
|
||||||
uses: aws-actions/configure-aws-credentials@v1
|
uses: aws-actions/configure-aws-credentials@v1
|
||||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 18
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
- name: Install Node Dependencies
|
- name: Install Node Dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
@ -73,7 +73,7 @@ jobs:
|
||||||
cache: "pip"
|
cache: "pip"
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 18
|
||||||
- name: Install Python Dependencies
|
- name: Install Python Dependencies
|
||||||
run: pip install -r requirements.txt -r dev-requirements.txt
|
run: pip install -r requirements.txt -r dev-requirements.txt
|
||||||
- name: Install Node Dependencies
|
- name: Install Node Dependencies
|
||||||
|
@ -122,7 +122,7 @@ jobs:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||||
env:
|
env:
|
||||||
ALLOWED_HOSTS: localhost,mozfest.localhost,default-site.com,secondary-site.com
|
ALLOWED_HOSTS: 127.0.0.1,localhost,mozfest.localhost,default-site.com,secondary-site.com
|
||||||
CONTENT_TYPE_NO_SNIFF: True
|
CONTENT_TYPE_NO_SNIFF: True
|
||||||
CORS_ALLOWED_ORIGINS: "*"
|
CORS_ALLOWED_ORIGINS: "*"
|
||||||
DATABASE_URL: postgres://postgres:postgres@localhost:5432/network
|
DATABASE_URL: postgres://postgres:postgres@localhost:5432/network
|
||||||
|
@ -149,7 +149,7 @@ jobs:
|
||||||
cache: "pip"
|
cache: "pip"
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 18
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
- name: Install Python Dependencies
|
- name: Install Python Dependencies
|
||||||
run: pip install -r requirements.txt -r dev-requirements.txt
|
run: pip install -r requirements.txt -r dev-requirements.txt
|
||||||
|
@ -187,7 +187,7 @@ jobs:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||||
env:
|
env:
|
||||||
ALLOWED_HOSTS: localhost,mozfest.localhost,default-site.com,secondary-site.com
|
ALLOWED_HOSTS: 127.0.0.1,localhost,mozfest.localhost,default-site.com,secondary-site.com
|
||||||
CONTENT_TYPE_NO_SNIFF: True
|
CONTENT_TYPE_NO_SNIFF: True
|
||||||
CORS_ALLOWED_ORIGINS: "*"
|
CORS_ALLOWED_ORIGINS: "*"
|
||||||
DATABASE_URL: postgres://postgres:postgres@localhost:5432/network
|
DATABASE_URL: postgres://postgres:postgres@localhost:5432/network
|
||||||
|
@ -217,7 +217,7 @@ jobs:
|
||||||
cache: "pip"
|
cache: "pip"
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 18
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
- name: Install Python Dependencies
|
- name: Install Python Dependencies
|
||||||
run: pip install -r requirements.txt -r dev-requirements.txt
|
run: pip install -r requirements.txt -r dev-requirements.txt
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile with Python 3.9
|
# This file is autogenerated by pip-compile with python 3.9
|
||||||
# by the following command:
|
# To update, run:
|
||||||
#
|
#
|
||||||
# pip-compile dev-requirements.in
|
# pip-compile dev-requirements.in
|
||||||
#
|
#
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
FROM node:14.13.1-stretch-slim
|
FROM node:18-bullseye-slim
|
||||||
|
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
|
@ -13,6 +13,14 @@
|
||||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="static/images/apple-touch-icon-180x180@2x.png">
|
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="static/images/apple-touch-icon-180x180@2x.png">
|
||||||
<link rel="icon" type="image/png" sizes="196x196" href="static/images/favicon-196x196@2x.png">
|
<link rel="icon" type="image/png" sizes="196x196" href="static/images/favicon-196x196@2x.png">
|
||||||
<link rel="shortcut icon" href="static/images/favicon.ico">
|
<link rel="shortcut icon" href="static/images/favicon.ico">
|
||||||
|
<!-- Fundraiseup Script -->
|
||||||
|
<script>(function(w,d,s,n,a){if(!w[n]){var l='call,catch,on,once,set,then,track'
|
||||||
|
.split(','),i,o=function(n){return'function'==typeof n?o.l.push([arguments])&&o
|
||||||
|
:function(){return o.l.push([n,arguments])&&o}},t=d.getElementsByTagName(s)[0],
|
||||||
|
j=d.createElement(s);j.async=!0;j.src='https://cdn.fundraiseup.com/widget/'+a;
|
||||||
|
t.parentNode.insertBefore(j,t);o.s=Date.now();o.v=4;o.h=w.location.href;o.l=[];
|
||||||
|
for(i=0;i<7;i++)o[l[i]]=o(l[i]);w[n]=o}
|
||||||
|
})(window,document,'script','FundraiseUp','ADCYPWMX');</script>
|
||||||
<title>Mozilla Foundation - Under Maintenance</title>
|
<title>Mozilla Foundation - Under Maintenance</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -75,9 +83,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="donate-btn-wrapper">
|
<div class="donate-btn-wrapper">
|
||||||
<a id="donate-header-btn" class="tw-btn-pop"
|
<a id="donate-header-btn" class="tw-btn-pop" href="?form=donate">Donate</a>
|
||||||
href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=header"
|
|
||||||
target="_blank" rel="noopener noreferrer">Donate</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -107,7 +113,7 @@
|
||||||
<h3 class="tw-h3-heading mb-0">We all love the Web. Join Mozilla in defending it.</h3>
|
<h3 class="tw-h3-heading mb-0">We all love the Web. Join Mozilla in defending it.</h3>
|
||||||
<h4 class="tw-h3-heading mb-4">Let’s protect the world’s largest resource for future generations.</h4>
|
<h4 class="tw-h3-heading mb-4">Let’s protect the world’s largest resource for future generations.</h4>
|
||||||
<div>
|
<div>
|
||||||
<a href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=maintenance_cta" target="_blank" rel="noopener noreferrer" c class="tw-btn-secondary dark-theme">Donate now</a>
|
<a href="?form=donate" class="tw-btn-secondary dark-theme">Donate now</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,7 +138,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-6 col-xl-7">
|
<div class="col-lg-6 col-xl-7">
|
||||||
<ul class="link-list list-unstyled mb-0">
|
<ul class="link-list list-unstyled mb-0">
|
||||||
<li class="mb-2"><a id="donate-footer-btn" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=footer" target="_blank" rel="noopener noreferrer" class="dark-theme">Donate</a></li>
|
<li class="mb-2"><a id="donate-footer-btn" href="?form=donate" class="dark-theme">Donate</a></li>
|
||||||
<li class="mb-2"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="dark-theme">Cookies</a></li>
|
<li class="mb-2"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="dark-theme">Cookies</a></li>
|
||||||
<li class="mb-2"><a href="https://www.mozilla.org/about/legal/terms/mozilla/" class="dark-theme">Legal</a></li>
|
<li class="mb-2"><a href="https://www.mozilla.org/about/legal/terms/mozilla/" class="dark-theme">Legal</a></li>
|
||||||
<li class="mb-2"><a href="https://www.mozilla.org/about/governance/policies/participation/" class="dark-theme">Participation Guidelines</a></li>
|
<li class="mb-2"><a href="https://www.mozilla.org/about/governance/policies/participation/" class="dark-theme">Participation Guidelines</a></li>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import sys
|
||||||
|
|
||||||
# Django runs twice to support live-reloading, so check Django's internal settings to determine whether or not
|
# Django runs twice to support live-reloading, so check Django's internal settings to determine whether or not
|
||||||
# to start the debugger.
|
# to start the debugger.
|
||||||
if (os.environ.get("RUN_MAIN") or os.environ.get("WERKZEUG_RUN_MAIN")) and os.environ.get("VSCODE_DEBUGGER"):
|
if (os.environ.get("RUN_MAIN") or os.environ.get("WERKZEUG_RUN_MAIN")) and os.environ.get("VSCODE_DEBUGGER") == "True":
|
||||||
import ptvsd # noqa
|
import ptvsd # noqa
|
||||||
|
|
||||||
ptvsd_port = 8001
|
ptvsd_port = 8001
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
from django.contrib.auth.models import Group, User
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteNonStaffTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
User.objects.create(username="Alex"),
|
||||||
|
|
||||||
|
def test_non_staff_is_deleted(self):
|
||||||
|
"""
|
||||||
|
Simple users are deleted
|
||||||
|
"""
|
||||||
|
|
||||||
|
call_command("delete_non_staff", "--now")
|
||||||
|
|
||||||
|
self.assertEqual(User.objects.count(), 0)
|
||||||
|
|
||||||
|
|
||||||
|
class IsStaffNotDeletedTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
User.objects.create(username="Alex", is_staff=True)
|
||||||
|
|
||||||
|
def test_is_staff_not_deleted(self):
|
||||||
|
"""
|
||||||
|
Users with 'is_staff' flag at True are not deleted
|
||||||
|
"""
|
||||||
|
|
||||||
|
call_command("delete_non_staff", "--now")
|
||||||
|
|
||||||
|
self.assertEqual(User.objects.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
|
class InGroupNotDeletedTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
group = Group.objects.create(name="TestGroup")
|
||||||
|
group.user_set.create(username="Alex")
|
||||||
|
|
||||||
|
def test_in_group_not_deleted(self):
|
||||||
|
"""
|
||||||
|
Users in a group are not deleted
|
||||||
|
"""
|
||||||
|
|
||||||
|
call_command("delete_non_staff", "--now")
|
||||||
|
|
||||||
|
self.assertEqual(User.objects.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
|
class MozillaFoundationUsersNotDeletedTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
User.objects.create(username="Alex", email="alex@mozillafoundation.org")
|
||||||
|
|
||||||
|
def test_mozilla_foundation_users_not_deleted(self):
|
||||||
|
"""
|
||||||
|
Mozilla Foundation Users are not deleted
|
||||||
|
"""
|
||||||
|
|
||||||
|
call_command("delete_non_staff", "--now")
|
||||||
|
|
||||||
|
self.assertEqual(User.objects.count(), 1)
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -32,7 +32,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block general_links %}
|
{% block general_links %}
|
||||||
<li><a id="donate-footer-btn" href="https://donate.mozilla.org/?utm_source=mozillafestival.org&utm_medium=referral&utm_campaign=fmonav&utm_content=footer" rel="noopener noreferrer" class="tw-dark">{% trans "Donate" %}</a></li>
|
<li><a id="donate-footer-btn" href="?form=donate" class="tw-dark">{% trans "Donate" %}</a></li>
|
||||||
<!-- the rest of the links should be listed alphabetically -->
|
<!-- the rest of the links should be listed alphabetically -->
|
||||||
<li><a href="https://www.mozillafestival.org/sponsor" class="tw-dark">{% trans "Support MozFest" %}</a></li>
|
<li><a href="https://www.mozillafestival.org/sponsor" class="tw-dark">{% trans "Support MozFest" %}</a></li>
|
||||||
<li><a href="https://careers.mozilla.org/listings/?team=Mozilla%20Foundation" class="tw-dark">{% trans "Careers" %}</a></li>
|
<li><a href="https://careers.mozilla.org/listings/?team=Mozilla%20Foundation" class="tw-dark">{% trans "Careers" %}</a></li>
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
<div class="tw-flex tw-flex-col tw-h-full tw-justify-between">
|
<div class="tw-flex tw-flex-col tw-h-full tw-justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div class="tw-flex tw-flex-wrap">
|
<div class="tw-flex tw-flex-wrap tw-align-center">
|
||||||
{% include "fragments/buyersguide/content_category_links.html" with page=page %}
|
{% include "fragments/buyersguide/content_category_links.html" with content_categories=page.get_content_categories %}
|
||||||
<span class="tw-h6-heading tw-text-gray-40 tw-py-1 tw-mb-0">{{ page.first_published_at|date:"DATE_FORMAT" }}</span>
|
<span class="tw-h6-heading tw-text-gray-40 tw-mb-0">{{ page.first_published_at|date:"DATE_FORMAT" }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="{% relocalized_url page.localized.url %}" class="tw-group tw-block hover:tw-no-underline">
|
<a href="{% relocalized_url page.localized.url %}" class="tw-group tw-block hover:tw-no-underline">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% load i18n wagtailcore_tags %}
|
{% load i18n wagtailcore_tags %}
|
||||||
|
|
||||||
<div class="tw-grid tw-grid-cols-2 {% if columns_above_large == 3 %} large:tw-grid-cols-3 {% endif %} tw-gap-6">
|
<div class="tw-grid tw-grid-cols-2 {% if columns_above_large == 3 %} large:tw-grid-cols-3 {% endif %} tw-gap-6">
|
||||||
<div class="tw-col-span-full tw-flex tw-flex-row tw-justify-between large:tw-items-baseline tw-gap-5">
|
<div class="tw-col-span-full tw-flex tw-flex-row tw-justify-between tw-items-center tw-gap-5">
|
||||||
<div class="tw-h3-heading tw-m-0 tw-whitespace-nowrap">
|
<div class="tw-h3-heading tw-m-0 tw-whitespace-nowrap">
|
||||||
{{ heading }}
|
{{ heading }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{% load static wagtailcore_tags wagtailimages_tags %}
|
{% load static wagtailcore_tags wagtailimages_tags %}
|
||||||
|
|
||||||
<div class="
|
<div class="
|
||||||
|
tw-w-full
|
||||||
tw-bg-gradient-to-b tw-from-yellow-10 tw-to-purple-05
|
tw-bg-gradient-to-b tw-from-yellow-10 tw-to-purple-05
|
||||||
tw-rounded-2xl
|
tw-rounded-2xl
|
||||||
tw-p-[24px] {% if not large %} medium:tw-p-[32px] {% else %} medium:tw-p-[40px] {% endif %}
|
tw-p-[24px] {% if not large %} medium:tw-p-[32px] {% else %} medium:tw-p-[40px] {% endif %}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
{% with content_categories=page.get_content_categories editorial_content_index=page.get_editorial_content_index %}
|
{% if content_categories %}
|
||||||
|
<div class="tw-h6-heading tw-mr-2 tw-mb-0 {{ extra_classes }}">
|
||||||
{% for content_category in content_categories %}
|
{% for content_category in content_categories %}
|
||||||
{% with classes="tw-h6-heading tw-text-blue-80 tw-mr-2 tw-py-1 tw-mb-0" %}
|
<span>
|
||||||
<a class="{{ classes }} {{ extra_classes }}" href="/privacynotincluded/articles/">{{ content_category.title|title }}</a>
|
{{ content_category.title }}
|
||||||
|
</span>
|
||||||
{% if not forloop.last %}
|
{% if not forloop.last %}
|
||||||
<span class="{{ classes }} {{ extra_classes }}">/</span>
|
<span>/</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endwith %}
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
|
@ -8,12 +8,6 @@
|
||||||
tw-justify-between
|
tw-justify-between
|
||||||
tw-m-0
|
tw-m-0
|
||||||
|
|
||||||
{# We need to unset the order rule at the next breakpoint, because the nth-child pseudo-class changes. #}
|
|
||||||
tw-order-none
|
|
||||||
[&:nth-child(n+10)]:tw-order-2 medium:[&:nth-child(n+10)]:tw-order-[unset]
|
|
||||||
medium:[&:nth-child(n+9)]:tw-order-2 large:[&:nth-child(n+9)]:tw-order-[unset]
|
|
||||||
large:[&:nth-child(n+12)]:tw-order-2
|
|
||||||
|
|
||||||
{% if product.draft %}draft-product{% endif %}
|
{% if product.draft %}draft-product{% endif %}
|
||||||
{% if product.adult_content %}adult-content{% endif %}
|
{% if product.adult_content %}adult-content{% endif %}
|
||||||
{% if product.privacy_ding %}privacy-ding{% endif%}
|
{% if product.privacy_ding %}privacy-ding{% endif%}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% load bg_nav_tags localization i18n static wagtailroutablepage_tags wagtailmetadata_tags %}
|
{% load bg_nav_tags localization i18n static wagtailroutablepage_tags wagtailmetadata_tags %}
|
||||||
<nav id="multipage-nav" class="pni-category-nav text-center d-none d-md-block tw-no-scrollbar" title="{% trans "site navigation" context "Tooltip on menu items" %}">
|
<nav id="multipage-nav" class="pni-category-nav text-center d-none d-md-block tw-no-scrollbar tw-border-b tw-border-blue-10" title="{% trans "site navigation" context "Tooltip on menu items" %}">
|
||||||
<div class="container tw-py-2" id="product-review">
|
<div class="container tw-py-2" id="product-review">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% load bg_nav_tags localization i18n static wagtailroutablepage_tags %}
|
{% load bg_nav_tags localization i18n static wagtailroutablepage_tags %}
|
||||||
{% get_bg_home_page as home_page %}
|
{% get_bg_home_page as home_page %}
|
||||||
|
|
||||||
<div class="d-md-none mt-0 mb-0" id="pni-nav-mobile">
|
<div class="d-md-none mt-0 mb-0 tw-bg-white tw-border-b tw-border-blue-10" id="pni-nav-mobile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row px-3 px-sm-0">
|
<div class="row px-3 px-sm-0">
|
||||||
<div class="col-12 tw-border-0">
|
<div class="col-12 tw-border-0">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% load wagtailcore_tags i18n static %}
|
{% load wagtailcore_tags i18n static %}
|
||||||
|
|
||||||
<div class="tw-relative tw-py-5 tw-px-5 tw-rounded-lg medium:tw-px-7 medium:tw-py-6 tw-bg-gradient-to-r tw-from-purple-05 tw-to-blue-05">
|
<div id="buyersguide-newsletter-box" class="tw-hidden tw-relative tw-py-5 tw-px-5 tw-rounded-lg medium:tw-px-7 medium:tw-py-6 tw-bg-gradient-to-r tw-from-purple-05 tw-to-blue-05">
|
||||||
<img
|
<img
|
||||||
class="tw-absolute tw-right-0 tw-top-0 tw-hidden large:tw-block"
|
class="tw-absolute tw-right-0 tw-top-0 tw-hidden large:tw-block"
|
||||||
src="{% static "_images/buyers-guide/asterick.svg" %}"
|
src="{% static "_images/buyers-guide/asterick.svg" %}"
|
||||||
|
@ -16,3 +16,4 @@
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
{% block donate_and_newsletter %}
|
{% block donate_and_newsletter %}
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
{% include "fragments/buyersguide/pni_nav_links.html" with class="primary-nav-special-link pni-nav-link tw-hidden large:tw-block" %}
|
{% include "fragments/buyersguide/pni_nav_links.html" with class="primary-nav-special-link pni-nav-link tw-hidden large:tw-block" %}
|
||||||
<a id="donate-header-btn" class="primary-nav-special-link pni-nav-link tw-hidden large:tw-block" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=header" target="_blank" rel="noopener noreferrer">{% trans "Donate" %}</a>
|
<a id="donate-header-btn" class="primary-nav-special-link pni-nav-link tw-hidden large:tw-block" href="?form=donate">{% trans "Donate" %}</a>
|
||||||
{% if page.signup == None %}<button class="tw-btn-secondary btn-newsletter d-none d-lg-block ml-md-3">{% trans "Newsletter" %}</button>{% endif %}
|
{% if page.signup == None %}<button class="tw-btn-secondary btn-newsletter d-none d-lg-block ml-md-3">{% trans "Newsletter" %}</button>{% endif %}
|
||||||
<button class="tw-bg-transparent" id="mobile-search">
|
<button class="tw-bg-transparent" id="mobile-search">
|
||||||
<img
|
<img
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{% with tab_class="tw-bg-white tw-text-black tw-font-zilla tw-text-3xl tw-py-2 tw-px-5 tw-flex tw-justify-center tw-items-center medium:tw-flex-1 tw-shrink-0 tw-text-center tw-relative tw-cursor-pointer tw-ease-in tw-transform tw-duration-150 medium:tw-w-auto" %}
|
{% with tab_class="tw-bg-white tw-text-black tw-font-zilla tw-text-3xl tw-py-2 tw-px-5 tw-flex tw-justify-center tw-items-center medium:tw-flex-1 tw-shrink-0 tw-text-center tw-relative tw-cursor-pointer tw-ease-in tw-transform tw-duration-150 medium:tw-w-auto" %}
|
||||||
<div id="product-tab-group" class="tw-bg-white tw-px-4 medium:tw-px-6 tw-flex tw-border-b-4 tw-justify-center tw-mb-7 tw-no-scrollbar tw-overflow-auto tw-sticky medium:tw-static tw-top-0 tw-z-[1000] tw--mx-4 medium:tw--mx-6">
|
<div id="product-tab-group" class="tw-bg-white tw-px-4 medium:tw-px-6 tw-flex tw-border-b-4 tw-justify-center tw-mb-7 tw-no-scrollbar tw-overflow-auto tw-sticky medium:tw-static tw-top-0 tw-z-[1000] tw--mx-4 medium:tw--mx-6">
|
||||||
<!-- Need this span for x-scrolling on smaller devices so the first tab doesn't get cut off -->
|
<!-- Need this span for x-scrolling on smaller devices so the first tab doesn't get cut off -->
|
||||||
<span class="tw-py-2 tw-px-7 medium:tw-flex-1 tw-shrink-0 tw-relative medium:tw-w-auto tw-w-[100px] tw-opacity-0 small:tw-hidden">mobile</span>
|
<span class="tw-py-2 tw-px-7 tw-shrink-0 tw-relative tw-opacity-0 min-[330px]:tw-w-[50px] min-[400px]:tw-hidden">mobile</span>
|
||||||
<span
|
<span
|
||||||
data-product-label="0"
|
data-product-label="0"
|
||||||
class="{{tab_class}} "
|
class="{{tab_class}} "
|
||||||
|
|
|
@ -153,7 +153,7 @@
|
||||||
|
|
||||||
<p class="tw-body">
|
<p class="tw-body">
|
||||||
<em>
|
<em>
|
||||||
{% blocktrans with mofo_url='href="https://foundation.mozilla.org" target="_blank"' donate_url='href="https://donate.mozilla.org" target="_blank"' mozilla_url='href="https://www.mozilla.org/" target="_blank"' trimmed %}
|
{% blocktrans with mofo_url='href="https://foundation.mozilla.org" target="_blank"' donate_url='href="?form=donate"' mozilla_url='href="https://www.mozilla.org/" target="_blank"' trimmed %}
|
||||||
Mozilla fights daily for a healthier internet as a non-profit tech company that puts people
|
Mozilla fights daily for a healthier internet as a non-profit tech company that puts people
|
||||||
before profit. This guide was created by the <a {{mofo_url}}>Mozilla Foundation</a> which
|
before profit. This guide was created by the <a {{mofo_url}}>Mozilla Foundation</a> which
|
||||||
relies on <a {{donate_url}}>donations</a> from people like you to do our work. You can also
|
relies on <a {{donate_url}}>donations</a> from people like you to do our work. You can also
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-5 pt-md-5 pb-4 col-lg-8">
|
<div class="pt-5 pt-md-5 pb-4 col-lg-8">
|
||||||
<div class="cms {% if show_comments %}mb-5{% endif %}">
|
<div class="cms {% if show_comments %}mb-5{% endif %}">
|
||||||
{% include "fragments/buyersguide/content_category_links.html" with page=page extra_classes="tw-text-base" %}
|
{% include "fragments/buyersguide/content_category_links.html" with content_categories=page.get_content_categories extra_classes="tw-text-base" %}
|
||||||
<h1 class="tw-h1-heading">{{ page.title }}</h1>
|
<h1 class="tw-h1-heading">{{ page.title }}</h1>
|
||||||
{% include "fragments/byline.html" with authors=page.get_author_profiles publication_date=page.first_published_at show_full_info=True %}
|
{% include "fragments/byline.html" with authors=page.get_author_profiles publication_date=page.first_published_at show_full_info=True %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,27 +49,13 @@
|
||||||
{% include_block block with parent_page=page page_type="blog" %}
|
{% include_block block with parent_page=page page_type="blog" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% with related_articles=page.get_primary_related_articles %}
|
|
||||||
{% if related_articles %}
|
|
||||||
<div class="tw-container tw-my-4">
|
|
||||||
<div class="tw-row">
|
|
||||||
<div id="article-primary-related-articles" class="tw-px-4 tw-w-8/12 tw-mx-auto">
|
|
||||||
{% include "fragments/buyersguide/related_reading.html" with articles=related_articles %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<div class="tw-container tw-mt-6">
|
|
||||||
<div class="tw-row tw-justify-center">
|
|
||||||
<div class="tw-border-4 tw-border-black col-lg-8"></div>
|
|
||||||
</div>
|
|
||||||
<div class="tw-row tw-justify-center tw-divide-y tw-divide-gray-20">
|
|
||||||
{% with author_profiles=page.get_author_profiles %}
|
{% with author_profiles=page.get_author_profiles %}
|
||||||
{% if author_profiles %}
|
{% if author_profiles %}
|
||||||
|
<div class="tw-container tw-mt-7">
|
||||||
|
<div class="tw-row tw-px-4 tw-justify-center">
|
||||||
|
<div class="large:tw-w-2/3 tw-border-t-4 tw-border-black tw-divide-y tw-divide-gray-20">
|
||||||
{% for author_profile in author_profiles %}
|
{% for author_profile in author_profiles %}
|
||||||
<div class="col-lg-8 tw-flex tw-py-4">
|
<div class="tw-flex tw-py-4 last:tw-pb-0">
|
||||||
{% if author_profile.image %}
|
{% if author_profile.image %}
|
||||||
<div class="tw-mr-4 tw-flex tw-items-start tw-flex-shrink-0">
|
<div class="tw-mr-4 tw-flex tw-items-start tw-flex-shrink-0">
|
||||||
<img
|
<img
|
||||||
|
@ -83,27 +69,28 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="">
|
<div class="">
|
||||||
<p class="tw-font-zilla tw-mb-2 tw-font-medium tw-text-2xl tw-leading-7">{{ author_profile.name }}</p>
|
<p class="tw-font-zilla tw-mb-2 tw-font-medium tw-text-2xl tw-leading-7">{{ author_profile.name }}</p>
|
||||||
<p class="tw-font-sans tw-font-normal tw-text-lg tw-leading-6">{{ author_profile.introduction }}</p>
|
<p class="tw-font-sans tw-font-normal tw-text-lg tw-leading-6 tw-m-0">{{ author_profile.introduction }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="article-newsletter-signup" class="tw-container">
|
<div id="article-newsletter-signup" class="tw-container tw-mt-[2.75rem]">
|
||||||
<div class="tw-row tw-justify-center">
|
<div class="tw-row tw-px-4 tw-justify-center">
|
||||||
<div class="col-12 col-lg-8">
|
<div class="tw-w-full large:tw-w-2/3">
|
||||||
{% include "fragments/buyersguide/pni_newsletter_box.html" %}
|
{% include "fragments/buyersguide/pni_newsletter_box.html" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% with related_articles=page.get_secondary_related_articles %}
|
{% with related_articles=page.get_related_articles %}
|
||||||
{% if related_articles %}
|
{% if related_articles %}
|
||||||
{% get_bg_home_page as bg_home_page %}
|
{% get_bg_home_page as bg_home_page %}
|
||||||
<div id="article-secondary-related-articles" class="tw-container">
|
<div class="tw-container tw-mt-7" data-gtm="related-articles">
|
||||||
{% include "fragments/buyersguide/article_listing_what_to_read_next.html" with articles=related_articles index_page=bg_home_page.get_editorial_content_index use_wide_above="medium" %}
|
{% include "fragments/buyersguide/article_listing_what_to_read_next.html" with articles=related_articles index_page=bg_home_page.get_editorial_content_index use_wide_above="medium" %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -76,13 +76,13 @@
|
||||||
|
|
||||||
{% get_bg_home_page as home_page %}
|
{% get_bg_home_page as home_page %}
|
||||||
|
|
||||||
<div class="primary-nav-container-wrapper tw-border-b tw-border-blue-10">
|
<div class="primary-nav-container-wrapper">
|
||||||
{% include "fragments/buyersguide/primary_nav.html" %}
|
{% include "fragments/buyersguide/primary_nav.html" %}
|
||||||
|
</div>
|
||||||
{% include "fragments/buyersguide/pni_mobile_nav.html" with pagetype=pagetype categories=categories current_category=current_category %}
|
{% include "fragments/buyersguide/pni_mobile_nav.html" with pagetype=pagetype categories=categories current_category=current_category %}
|
||||||
{% block category_nav %}
|
{% block category_nav %}
|
||||||
{% include "fragments/buyersguide/pni_category_nav.html" with show_all_reviews_as_active_category=False categories=categories current_category=current_category %}
|
{% include "fragments/buyersguide/pni_category_nav.html" with show_all_reviews_as_active_category=False categories=categories current_category=current_category %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Need to position relatives to deal with potential CSS transformations that change some stacking context of elements -->
|
<!-- Need to position relatives to deal with potential CSS transformations that change some stacking context of elements -->
|
||||||
<div class="tw-relative">
|
<div class="tw-relative">
|
||||||
|
|
|
@ -3,32 +3,6 @@
|
||||||
|
|
||||||
{% block body_id %}{{ category.slug }}{% endblock %}
|
{% block body_id %}{{ category.slug }}{% endblock %}
|
||||||
|
|
||||||
{% block guts %}
|
|
||||||
|
|
||||||
{{ block.super }}
|
|
||||||
|
|
||||||
{% for category in categories %}
|
|
||||||
{% with secondary_related_articles=category.get_secondary_related_articles %}
|
|
||||||
{% if secondary_related_articles %}
|
|
||||||
<div
|
|
||||||
id="category-secondary-related-articles"
|
|
||||||
data-show-for-category="{{ category.name }}"
|
|
||||||
class="
|
|
||||||
{% if category != current_category %}
|
|
||||||
tw-hidden
|
|
||||||
{% endif %}
|
|
||||||
tw-container
|
|
||||||
tw-mt-[4rem]
|
|
||||||
tw-mb-[120px]
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{% include "fragments/buyersguide/article_listing_what_to_read_next.html" with articles=secondary_related_articles index_page=page.get_editorial_content_index use_wide_above="medium" %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock guts %}
|
|
||||||
|
|
||||||
{% block extra_product_box_list_items %}
|
{% block extra_product_box_list_items %}
|
||||||
{% if featured_cta %}
|
{% if featured_cta %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
|
@ -42,7 +16,7 @@
|
||||||
data-show-for-categories="{% for category in categories %}{% if category.show_cta %}{{ category.name }}, {% endif %}{% endfor %}"
|
data-show-for-categories="{% for category in categories %}{% if category.show_cta %}{{ category.name }}, {% endif %}{% endfor %}"
|
||||||
>
|
>
|
||||||
{% with cta=featured_cta %}
|
{% with cta=featured_cta %}
|
||||||
{% include "fragments/buyersguide/call_to_action_box.html" with icon=cta.sticker_image heading=cta.title body=cta.content link_text=cta.link_label link_href=cta.get_target_url %}
|
{% include "fragments/buyersguide/call_to_action_box.html" with icon=cta.sticker_image heading=cta.title body=cta.content link_text=cta.link_label link_href=cta.get_target_url large=True %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -74,3 +48,30 @@
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock extra_product_box_list_items %}
|
{% endblock extra_product_box_list_items %}
|
||||||
|
|
||||||
|
{% block guts %}
|
||||||
|
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
{% for category in categories %}
|
||||||
|
{% with secondary_related_articles=category.get_secondary_related_articles %}
|
||||||
|
{% if secondary_related_articles %}
|
||||||
|
<div
|
||||||
|
id="category-secondary-related-articles"
|
||||||
|
data-show-for-category="{{ category.name }}"
|
||||||
|
class="
|
||||||
|
{% if category != current_category %}
|
||||||
|
tw-hidden
|
||||||
|
{% endif %}
|
||||||
|
tw-container
|
||||||
|
tw-mt-[4rem]
|
||||||
|
tw-mb-[120px]
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{% include "fragments/buyersguide/article_listing_what_to_read_next.html" with articles=secondary_related_articles index_page=page.get_editorial_content_index use_wide_above="medium" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock guts %}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
{% with hero_featured_article=page.get_hero_featured_article %}
|
{% with hero_featured_article=page.get_hero_featured_article %}
|
||||||
{% if hero_featured_article %}
|
{% if hero_featured_article %}
|
||||||
<div class="tw-container">
|
<div class="tw-container tw-mb-7">
|
||||||
{% include "fragments/buyersguide/featured_article.html" with featured_article=hero_featured_article supporting_articles=page.get_hero_supporting_articles supporting_articles_heading=page.hero_supporting_articles_heading %}
|
{% include "fragments/buyersguide/featured_article.html" with featured_article=hero_featured_article supporting_articles=page.get_hero_supporting_articles supporting_articles_heading=page.hero_supporting_articles_heading %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -47,6 +47,12 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
We want to be able to use the CTA and have it show up on other pages, except for the homepage.
|
||||||
|
Displaying the CTA on the homepage should not happen until the redesign has been implemented.
|
||||||
|
See also: https://github.com/mozilla/foundation.mozilla.org/issues/9758.
|
||||||
|
{% endcomment %}
|
||||||
|
{% comment %}
|
||||||
{% with cta=featured_cta %}
|
{% with cta=featured_cta %}
|
||||||
{% if cta %}
|
{% if cta %}
|
||||||
<div class="{{row_item_classes}}">
|
<div class="{{row_item_classes}}">
|
||||||
|
@ -54,6 +60,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -67,11 +74,10 @@
|
||||||
<div
|
<div
|
||||||
id="product-grid-newsletter-signup"
|
id="product-grid-newsletter-signup"
|
||||||
class="
|
class="
|
||||||
tw-col-span-full medium:tw-col-span-2
|
tw-col-[span_2_/_-1]
|
||||||
tw-col-end-5
|
tw-row-start-5 medium:tw-row-start-4 large:tw-row-start-3
|
||||||
tw-order-1
|
|
||||||
tw-flex
|
|
||||||
tw-items-stretch
|
tw-items-stretch
|
||||||
|
tw-hidden
|
||||||
">
|
">
|
||||||
{% include "fragments/buyersguide/pni_newsletter_box.html" %}
|
{% include "fragments/buyersguide/pni_newsletter_box.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% load settings_value i18n static %}
|
{% load settings_value i18n static %}
|
||||||
|
|
||||||
{% with wrapper_class="tw-py-7" background_class="tw-bg-cover tw-bg-center" btn_class="tw-text-white tw-border-black tw-bg-blue-40 hover:tw-bg-blue-80 tw-shadow-black"%}
|
{% with wrapper_class="tw-py-7 tw-shadow-[0_0_3px_2px_rgba(9,32,77,.12)_inset]" background_class="tw-bg-[url(../_images/donate-banner-giving.jpg)] tw-bg-[100%_0] tw-bg-no-repeat tw-bg-cover medium:tw-bg-contain" btn_class="tw-text-white tw-border-black tw-bg-blue-40 hover:tw-bg-blue-80 tw-shadow-black"%}
|
||||||
<div class="donate-banner tw-relative tw-z-20 {{background_class}} tw-bg-[url(../_images/mozilla-donate-dear-internet-mobile.png)] large:tw-bg-[url(../_images/mozilla-donate-dear-internet.png)] {{wrapper_class}} print:tw-hidden tw-w-full tw-hidden">
|
<div class="donate-banner {{background_class}} {{wrapper_class}} tw-relative tw-z-20 tw-w-full tw-hidden print:tw-hidden">
|
||||||
<a href="#"
|
<a href="#"
|
||||||
class="tw-p-4
|
class="tw-p-4
|
||||||
tw-box-content
|
tw-box-content
|
||||||
|
@ -15,40 +15,29 @@
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="tw-container">
|
<div class="tw-container">
|
||||||
<div class="tw-row">
|
<div class="tw-row tw-px-3">
|
||||||
|
|
||||||
{% block left_banner_column %}
|
{% block left_banner_column %}
|
||||||
<div class="medium:tw-w-6/12 large:tw-w-6/12 tw-px-4">
|
<div class="tw-px-5 tw-py-6 tw-bg-[hsla(0,0%,100%,.7)] tw-backdrop-blur-sm large:tw-w-8/12 xlarge:tw-w-6/12">
|
||||||
<p class="tw-h2-heading tw-mb-0 tw-text-blue-80 tw-font-semibold tw-italic">
|
<p class="tw-h2-heading tw-mb-0 tw-text-blue-80 tw-font-semibold tw-italic">
|
||||||
{% trans "Help Mozilla fight for a better internet this holiday season" %}
|
{% trans "Help Mozilla fight for a better internet this holiday season" %}
|
||||||
</p>
|
</p>
|
||||||
<p class="tw-body-large tw-mt-4 tw-leading-normal">
|
<p class="tw-body-large tw-mt-4 tw-leading-normal">
|
||||||
{% trans "We're proudly nonprofit, working to keep the web healthy." %}
|
{% blocktrans trimmed %}
|
||||||
|
We're proudly nonprofit, working to keep the web healthy. Your contributions help build a safe and open internet.
|
||||||
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<p class="tw-body-large">
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block right_banner_column %}
|
|
||||||
<div
|
|
||||||
class=" medium:tw-w-6/12 large:tw-w-3/12 tw-px-4 medium:tw-ml-auto large:tw-mt-2">
|
|
||||||
<div class="tw-row">
|
|
||||||
<div class="tw-px-4">
|
|
||||||
<p class="tw-body">
|
|
||||||
{% trans "Your contributions help build a safe and open internet." %}
|
|
||||||
</p>
|
|
||||||
<p class="tw-body">
|
|
||||||
{% trans "Can you donate today?" %}
|
{% trans "Can you donate today?" %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<div class="tw-w-full tw-pt-3">
|
||||||
</div>
|
<a class="tw-btn-pop {{btn_class}} tw-shadow-[4px_4px]" tw-w-full tw-text-base active:tw-text-white active:tw-bg-[#0a0c8f] active:tw-border-black" href="?form=donate">
|
||||||
<div class="tw-row">
|
|
||||||
<div class="tw-w-full tw-px-4">
|
|
||||||
<a class="tw-btn-pop {{btn_class}} tw-shadow-[4px_4px]" tw-w-full tw-text-base active:tw-text-white active:tw-bg-[#0a0c8f] active:tw-border-black" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=2021_EOY_bannner_MoFo" target="_blank" rel="noopener noreferrer">
|
|
||||||
{% trans "Support Mozilla" %}
|
{% trans "Support Mozilla" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block right_banner_column %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<p class="tw-h3-heading tw-mb-0">{% trans "We all love the Web. Join Mozilla in defending it." %}</p>
|
<p class="tw-h3-heading tw-mb-0">{% trans "We all love the Web. Join Mozilla in defending it." %}</p>
|
||||||
<p class="tw-h3-heading tw-mb-5">{% trans "Let’s protect the world’s largest resource for future generations." %}</p>
|
<p class="tw-h3-heading tw-mb-5">{% trans "Let’s protect the world’s largest resource for future generations." %}</p>
|
||||||
<div>
|
<div>
|
||||||
<a href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=footer" target="_blank" rel="noopener noreferrer" class="tw-btn-secondary tw-dark" id="donate-banner-cta">{% trans "Donate now" %}</a>
|
<a href="?form=donate" class="tw-btn-secondary tw-dark" id="donate-banner-cta">{% trans "Donate now" %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
<div class="large:tw-w-6/12 xlarge:tw-w-7/12 tw-px-4">
|
<div class="large:tw-w-6/12 xlarge:tw-w-7/12 tw-px-4">
|
||||||
<ul class="tw-p-0 tw-ml-0 tw-list-none tw-mb-0 link-list large:tw-columns-2">
|
<ul class="tw-p-0 tw-ml-0 tw-list-none tw-mb-0 link-list large:tw-columns-2">
|
||||||
{% block general_links %}
|
{% block general_links %}
|
||||||
<li class="tw-mb-2"><a id="donate-footer-btn" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=footer" target="_blank" rel="noopener noreferrer" class="tw-dark tw-text-white hover:tw-text-blue-20 hover:tw-underline">{% trans "Donate" %}</a></li>
|
<li class="tw-mb-2"><a id="donate-footer-btn" href="?form=donate" class="tw-dark tw-text-white hover:tw-text-blue-20 hover:tw-underline">{% trans "Donate" %}</a></li>
|
||||||
<!-- the rest of the links should be listed alphabetically -->
|
<!-- the rest of the links should be listed alphabetically -->
|
||||||
<li class="tw-mb-2"><a href="https://careers.mozilla.org/listings/?team=Mozilla%20Foundation" class="tw-dark tw-text-white hover:tw-text-blue-20 hover:tw-underline">{% trans "Careers" %}</a></li>
|
<li class="tw-mb-2"><a href="https://careers.mozilla.org/listings/?team=Mozilla%20Foundation" class="tw-dark tw-text-white hover:tw-text-blue-20 hover:tw-underline">{% trans "Careers" %}</a></li>
|
||||||
<li class="tw-mb-2"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="tw-dark tw-text-white hover:tw-text-blue-20 hover:tw-underline">{% trans "Cookies" %}</a></li>
|
<li class="tw-mb-2"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="tw-dark tw-text-white hover:tw-text-blue-20 hover:tw-underline">{% trans "Cookies" %}</a></li>
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
|
|
||||||
{% block donate_and_newsletter %}
|
{% block donate_and_newsletter %}
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<a id="donate-header-btn" class="primary-nav-special-link tw-heart-glyph tw-flex" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=header" target="_blank" rel="noopener noreferrer">{% trans "Donate" %}</a>
|
<a id="donate-header-btn" class="primary-nav-special-link tw-heart-glyph tw-flex" href="?form=donate">{% trans "Donate" %}</a>
|
||||||
{% if page.signup == None %}<button class="tw-btn-secondary btn-newsletter d-none d-lg-block ml-md-3">{% trans "Newsletter" %}</button>{% endif %}
|
{% if page.signup == None %}<button class="tw-btn-secondary btn-newsletter d-none d-lg-block ml-md-3">{% trans "Newsletter" %}</button>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class MissingMigrationsTests(TestCase):
|
||||||
|
def test_no_migrations_missing(self):
|
||||||
|
"""
|
||||||
|
Ensure we didn't forget a migration
|
||||||
|
"""
|
||||||
|
output = StringIO()
|
||||||
|
call_command("makemigrations", interactive=False, dry_run=True, stdout=output)
|
||||||
|
|
||||||
|
if output.getvalue() != "No changes detected\n":
|
||||||
|
raise AssertionError("Missing migrations detected:\n" + output.getvalue())
|
|
@ -83,6 +83,10 @@ def get_random_objects(
|
||||||
random.shuffle(primary_keys)
|
random.shuffle(primary_keys)
|
||||||
|
|
||||||
count = len(primary_keys)
|
count = len(primary_keys)
|
||||||
|
if not count:
|
||||||
|
# No items available, return empty queryset.
|
||||||
|
return queryset.none()
|
||||||
|
|
||||||
if exact_count:
|
if exact_count:
|
||||||
return_max = min(count, exact_count)
|
return_max = min(count, exact_count)
|
||||||
elif max_count:
|
elif max_count:
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from networkapi.utility.middleware import ReferrerMiddleware, XRobotsTagMiddleware
|
||||||
|
|
||||||
|
|
||||||
|
class ReferrerMiddlewareTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
referrer_middleware = ReferrerMiddleware("response")
|
||||||
|
self.assertEqual(referrer_middleware.get_response, "response")
|
||||||
|
|
||||||
|
def test_requestProcessing(self):
|
||||||
|
"""
|
||||||
|
Ensure that the middleware assigns a Referrer-Policy header to the response object
|
||||||
|
"""
|
||||||
|
|
||||||
|
referrer_middleware = ReferrerMiddleware(MagicMock())
|
||||||
|
response = referrer_middleware(MagicMock())
|
||||||
|
response.__setitem__.assert_called_with("Referrer-Policy", "same-origin")
|
||||||
|
|
||||||
|
|
||||||
|
class XRobotsTagMiddlewareTest(TestCase):
|
||||||
|
def test_returns_response(self):
|
||||||
|
xrobotstag_middleware = XRobotsTagMiddleware("response")
|
||||||
|
self.assertEqual(xrobotstag_middleware.get_response, "response")
|
||||||
|
|
||||||
|
def test_sends_x_robots_tag(self):
|
||||||
|
"""
|
||||||
|
Ensure that the middleware assigns an X-Robots-Tag to the response
|
||||||
|
"""
|
||||||
|
|
||||||
|
xrobotstag_middleware = XRobotsTagMiddleware(MagicMock())
|
||||||
|
response = xrobotstag_middleware(MagicMock())
|
||||||
|
response.__setitem__.assert_called_with("X-Robots-Tag", "noindex")
|
|
@ -0,0 +1,57 @@
|
||||||
|
from unittest import skip
|
||||||
|
|
||||||
|
from django.test import RequestFactory, TestCase
|
||||||
|
from wagtail.core.models import Site
|
||||||
|
from wagtail_factories import SiteFactory
|
||||||
|
|
||||||
|
from networkapi.utility.redirects import redirect_to_default_cms_site
|
||||||
|
|
||||||
|
|
||||||
|
class RedirectDefaultSiteDecoratorTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
# Change the default site away from localhost
|
||||||
|
self.original_default_site = Site.objects.get(is_default_site=True, hostname="localhost")
|
||||||
|
self.original_default_site.is_default_site = False
|
||||||
|
self.original_default_site.save()
|
||||||
|
# Add a default site, and a secondary site.
|
||||||
|
self.default_site = SiteFactory(hostname="default-site.com", is_default_site=True)
|
||||||
|
self.secondary_site = SiteFactory(hostname="secondary-site.com")
|
||||||
|
|
||||||
|
def test_redirect_decorator(self):
|
||||||
|
"""
|
||||||
|
Test that the decorator redirects.
|
||||||
|
"""
|
||||||
|
decorated_view = redirect_to_default_cms_site(lambda request: None)
|
||||||
|
response = decorated_view(self.factory.get("/example/", HTTP_HOST="secondary-site.com"))
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
def test_redirect_decorator_doesnt_redirect(self):
|
||||||
|
"""
|
||||||
|
Test that the redirect is triggered only when needed.
|
||||||
|
"""
|
||||||
|
decorated_view = redirect_to_default_cms_site(lambda request: "untouched response")
|
||||||
|
response = decorated_view(self.factory.get("/example/"))
|
||||||
|
self.assertEqual(response, "untouched response")
|
||||||
|
|
||||||
|
@skip("TODO: REENABLE: TEMPORARY SKIP TO MAKE PNI-AS-WAGTAIL LAUNCH POSSIBLE")
|
||||||
|
# @override_settings(STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage")
|
||||||
|
def test_PNI_homepage_redirect_to_foundation_site(self):
|
||||||
|
"""
|
||||||
|
Test that users gets redirected to PNI on the foundation site when they visit it from a non-default CMS site
|
||||||
|
"""
|
||||||
|
response = self.client.get("/en/privacynotincluded/", HTTP_HOST="secondary-site.com")
|
||||||
|
self.assertRedirects(
|
||||||
|
response,
|
||||||
|
"https://default-site.com/en/privacynotincluded/",
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# Re-instate localhost as the default site
|
||||||
|
self.original_default_site.is_default_site = True
|
||||||
|
self.original_default_site.save()
|
||||||
|
|
||||||
|
# Remove the Site Factories
|
||||||
|
self.default_site.delete()
|
||||||
|
self.secondary_site.delete()
|
|
@ -1,10 +1,10 @@
|
||||||
# TODO: Move these factories to the wagtailpages app.
|
|
||||||
# To avoid too many code conflicts, this should happen after PR 6433 is merged
|
|
||||||
from datetime import date, datetime, timedelta, timezone
|
from datetime import date, datetime, timedelta, timezone
|
||||||
from random import choice, randint, random, randrange, shuffle
|
from random import choice, randint, random, randrange, shuffle
|
||||||
|
|
||||||
|
from django.utils import text as text_utils
|
||||||
from factory import Faker, LazyFunction, SubFactory, post_generation
|
from factory import Faker, LazyFunction, SubFactory, post_generation
|
||||||
from factory.django import DjangoModelFactory
|
from factory.django import DjangoModelFactory
|
||||||
|
from wagtail.core.models import Locale
|
||||||
from wagtail.images.models import Image
|
from wagtail.images.models import Image
|
||||||
from wagtail_factories import PageFactory
|
from wagtail_factories import PageFactory
|
||||||
|
|
||||||
|
@ -261,6 +261,11 @@ class BuyersGuideContentCategoryFactory(DjangoModelFactory):
|
||||||
model = pagemodels.BuyersGuideContentCategory
|
model = pagemodels.BuyersGuideContentCategory
|
||||||
|
|
||||||
title = Faker("word")
|
title = Faker("word")
|
||||||
|
locale = LazyFunction(lambda: Locale.get_default())
|
||||||
|
|
||||||
|
@post_generation
|
||||||
|
def set_slug(obj, created, extracted, **kwargs):
|
||||||
|
obj.slug = text_utils.slugify(obj.title)
|
||||||
|
|
||||||
|
|
||||||
class BuyersGuideArticlePageAuthorProfileRelationFactory(DjangoModelFactory):
|
class BuyersGuideArticlePageAuthorProfileRelationFactory(DjangoModelFactory):
|
||||||
|
@ -404,30 +409,40 @@ def generate(seed):
|
||||||
|
|
||||||
print("Generating buyers guide editorial content")
|
print("Generating buyers guide editorial content")
|
||||||
editorial_content_index = BuyersGuideEditorialContentIndexPageFactory(parent=pni_homepage)
|
editorial_content_index = BuyersGuideEditorialContentIndexPageFactory(parent=pni_homepage)
|
||||||
|
# Create content categories
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
BuyersGuideContentCategoryFactory()
|
BuyersGuideContentCategoryFactory()
|
||||||
articles = []
|
# Create articles
|
||||||
for _ in range(12):
|
for _ in range(12):
|
||||||
article = BuyersGuideArticlePageFactory(parent=editorial_content_index)
|
article = BuyersGuideArticlePageFactory(parent=editorial_content_index)
|
||||||
for profile in get_random_objects(pagemodels.Profile, max_count=3):
|
for index, profile in enumerate(get_random_objects(pagemodels.Profile, max_count=3), start=1):
|
||||||
BuyersGuideArticlePageAuthorProfileRelationFactory(
|
BuyersGuideArticlePageAuthorProfileRelationFactory(
|
||||||
page=article,
|
page=article,
|
||||||
author_profile=profile,
|
author_profile=profile,
|
||||||
|
sort_order=index,
|
||||||
)
|
)
|
||||||
if article.id % 2 == 0:
|
if article.id % 2 == 0:
|
||||||
# Articles with even id get the content category
|
# Articles with even id get the content category
|
||||||
for category in get_random_objects(pagemodels.BuyersGuideContentCategory, max_count=2):
|
for index, category in enumerate(
|
||||||
|
get_random_objects(pagemodels.BuyersGuideContentCategory, max_count=2),
|
||||||
|
start=1,
|
||||||
|
):
|
||||||
BuyersGuideArticlePageContentCategoryRelationFactory(
|
BuyersGuideArticlePageContentCategoryRelationFactory(
|
||||||
page=article,
|
page=article,
|
||||||
content_category=category,
|
content_category=category,
|
||||||
|
sort_order=index,
|
||||||
)
|
)
|
||||||
# Add all previously existing articles as related articles
|
# Add up to 3 previously existing articles as related articles (but not the article itself)
|
||||||
for existing_article in articles:
|
existing_articles = get_random_objects(
|
||||||
|
pagemodels.BuyersGuideArticlePage.objects.exclude(id=article.id),
|
||||||
|
max_count=3,
|
||||||
|
)
|
||||||
|
for index, existing_article in enumerate(existing_articles, start=1):
|
||||||
BuyersGuideArticlePageRelatedArticleRelationFactory(
|
BuyersGuideArticlePageRelatedArticleRelationFactory(
|
||||||
page=article,
|
page=article,
|
||||||
article=existing_article,
|
article=existing_article,
|
||||||
|
sort_order=index,
|
||||||
)
|
)
|
||||||
articles.append(article)
|
|
||||||
|
|
||||||
# Creating Buyersguide Campaign pages and accompanying donation modals
|
# Creating Buyersguide Campaign pages and accompanying donation modals
|
||||||
for _ in range(5):
|
for _ in range(5):
|
||||||
|
@ -446,28 +461,34 @@ def generate(seed):
|
||||||
article=article,
|
article=article,
|
||||||
sort_order=index,
|
sort_order=index,
|
||||||
)
|
)
|
||||||
# Buyerguide homepage featured advice article
|
|
||||||
pni_homepage.featured_advice_article = pagemodels.BuyersGuideArticlePage.objects.last()
|
# The following section is commented, because the homepage redesign needs to be implemented.
|
||||||
pni_homepage.full_clean()
|
# Until then, we want the generated homepage to be closer to the production site, where none of these
|
||||||
pni_homepage.save()
|
# relations are used.
|
||||||
# Buyersguide homepage featured articles
|
# See also: https://github.com/mozilla/foundation.mozilla.org/issues/9758
|
||||||
featured_articles = get_random_objects(
|
#
|
||||||
source=pagemodels.BuyersGuideArticlePage.objects.exclude(id__in=supporting_articles),
|
# # Buyerguide homepage featured advice article
|
||||||
exact_count=3,
|
# pni_homepage.featured_advice_article = pagemodels.BuyersGuideArticlePage.objects.last()
|
||||||
)
|
# pni_homepage.full_clean()
|
||||||
for index, article in enumerate(featured_articles):
|
# pni_homepage.save()
|
||||||
BuyersGuidePageFeaturedArticleRelationFactory(
|
# # Buyersguide homepage featured articles
|
||||||
page=pni_homepage,
|
# featured_articles = get_random_objects(
|
||||||
article=article,
|
# source=pagemodels.BuyersGuideArticlePage.objects.exclude(id__in=supporting_articles),
|
||||||
sort_order=index,
|
# exact_count=3,
|
||||||
)
|
# )
|
||||||
# Buyersguide homepage featured product updates
|
# for index, article in enumerate(featured_articles):
|
||||||
for index, update in enumerate(get_random_objects(pagemodels.Update, exact_count=3)):
|
# BuyersGuidePageFeaturedArticleRelationFactory(
|
||||||
BuyersGuidePageFeaturedUpdateRelationFactory(
|
# page=pni_homepage,
|
||||||
page=pni_homepage,
|
# article=article,
|
||||||
update=update,
|
# sort_order=index,
|
||||||
sort_order=index,
|
# )
|
||||||
)
|
# # Buyersguide homepage featured product updates
|
||||||
|
# for index, update in enumerate(get_random_objects(pagemodels.Update, exact_count=3)):
|
||||||
|
# BuyersGuidePageFeaturedUpdateRelationFactory(
|
||||||
|
# page=pni_homepage,
|
||||||
|
# update=update,
|
||||||
|
# sort_order=index,
|
||||||
|
# )
|
||||||
|
|
||||||
# Adding related articles to the Editorial Content Index Page
|
# Adding related articles to the Editorial Content Index Page
|
||||||
for index, article in enumerate(get_random_objects(pagemodels.BuyersGuideArticlePage, exact_count=3)):
|
for index, article in enumerate(get_random_objects(pagemodels.BuyersGuideArticlePage, exact_count=3)):
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 3.2.16 on 2022-12-14 00:42
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("wagtailpages", "0070_adds_url_field_for_listing_card"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="buyersguidepage",
|
||||||
|
name="dark_theme",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="buyersguidepage",
|
||||||
|
name="header",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="buyersguidepage",
|
||||||
|
name="hero_image",
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Generated by Django 3.2.16 on 2022-12-21 19:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("wagtailcore", "0066_collection_management_permissions"),
|
||||||
|
("wagtailpages", "0071_buyersguidepage_remove_outdated_fields"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="buyersguidecontentcategory",
|
||||||
|
name="slug",
|
||||||
|
field=models.SlugField(
|
||||||
|
help_text="The slug is auto-generated from the title, but can be customized if needed. It needs to be unique per locale. ",
|
||||||
|
max_length=100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="buyersguidecontentcategory",
|
||||||
|
unique_together={("locale", "slug"), ("translation_key", "locale")},
|
||||||
|
),
|
||||||
|
]
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -82,13 +82,13 @@ class BuyersGuideArticlePage(foundation_metadata.FoundationMetadataPageMixin, wa
|
||||||
label="Content category",
|
label="Content category",
|
||||||
max_num=2,
|
max_num=2,
|
||||||
),
|
),
|
||||||
|
panels.StreamFieldPanel("body"),
|
||||||
panels.InlinePanel(
|
panels.InlinePanel(
|
||||||
"related_article_relations",
|
"related_article_relations",
|
||||||
heading="Related articles",
|
heading="What to read next (related articles)",
|
||||||
label="Article",
|
label="Article",
|
||||||
max_num=6,
|
max_num=3,
|
||||||
),
|
),
|
||||||
panels.StreamFieldPanel("body"),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
settings_panels = [
|
settings_panels = [
|
||||||
|
@ -120,16 +120,34 @@ class BuyersGuideArticlePage(foundation_metadata.FoundationMetadataPageMixin, wa
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_author_profiles(self) -> list["Profile"]:
|
def get_author_profiles(self) -> list["Profile"]:
|
||||||
return orderables.get_related_items(
|
author_profiles = orderables.get_related_items(
|
||||||
self.author_profile_relations.all(),
|
self.author_profile_relations.all(),
|
||||||
"author_profile",
|
"author_profile",
|
||||||
)
|
)
|
||||||
|
# The relations are synchronized from the default locale on non-default
|
||||||
|
# locales. But, then working with a page of a non-default locale we are of
|
||||||
|
# course interested in the related articles in that same locale.
|
||||||
|
#
|
||||||
|
# FIXME: This version is bad, because it uses 1+n queries to generate the list
|
||||||
|
# of author profiles in the correct locale. We should create something
|
||||||
|
# more elegant to retrieve the author profiles of the correct locale
|
||||||
|
# directly from the database.
|
||||||
|
return [ap.localized for ap in author_profiles]
|
||||||
|
|
||||||
def get_content_categories(self) -> list["BuyersGuideContentCategory"]:
|
def get_content_categories(self) -> list["BuyersGuideContentCategory"]:
|
||||||
return orderables.get_related_items(
|
content_categories = orderables.get_related_items(
|
||||||
self.content_category_relations.all(),
|
self.content_category_relations.all(),
|
||||||
"content_category",
|
"content_category",
|
||||||
)
|
)
|
||||||
|
# The relations are synchronized from the default locale on non-default
|
||||||
|
# locales. But, then working with a page of a non-default locale we are of
|
||||||
|
# course interested in the related articles in that same locale.
|
||||||
|
#
|
||||||
|
# FIXME: This version is bad, because it uses 1+n queries to generate the list
|
||||||
|
# content categories in the correct locale. We should create something
|
||||||
|
# more elegant to retrieve the content categories of the correct locale
|
||||||
|
# directly from the database.
|
||||||
|
return [cc.localized for cc in content_categories]
|
||||||
|
|
||||||
def get_related_articles(self) -> list["BuyersGuideArticlePage"]:
|
def get_related_articles(self) -> list["BuyersGuideArticlePage"]:
|
||||||
related_articles = orderables.get_related_items(
|
related_articles = orderables.get_related_items(
|
||||||
|
@ -146,12 +164,6 @@ class BuyersGuideArticlePage(foundation_metadata.FoundationMetadataPageMixin, wa
|
||||||
# directly from the database.
|
# directly from the database.
|
||||||
return [a.localized for a in related_articles]
|
return [a.localized for a in related_articles]
|
||||||
|
|
||||||
def get_primary_related_articles(self) -> list["BuyersGuideArticlePage"]:
|
|
||||||
return self.get_related_articles()[:3]
|
|
||||||
|
|
||||||
def get_secondary_related_articles(self) -> list["BuyersGuideArticlePage"]:
|
|
||||||
return self.get_related_articles()[3:]
|
|
||||||
|
|
||||||
|
|
||||||
class BuyersGuideArticlePageAuthorProfileRelation(
|
class BuyersGuideArticlePageAuthorProfileRelation(
|
||||||
wagtail_models.TranslatableMixin,
|
wagtail_models.TranslatableMixin,
|
||||||
|
|
|
@ -95,28 +95,6 @@ class BuyersGuidePage(RoutablePageMixin, FoundationMetadataPageMixin, Page):
|
||||||
related_name="+",
|
related_name="+",
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Remove this field
|
|
||||||
hero_image = models.ForeignKey(
|
|
||||||
"wagtailimages.Image",
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
related_name="pni_hero_image",
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: Remove this field
|
|
||||||
header = models.CharField(
|
|
||||||
max_length=120,
|
|
||||||
blank=True,
|
|
||||||
help_text="The header text for the PNI homepage",
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: Remove this field
|
|
||||||
dark_theme = models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Does the intro need to be white text (for dark backgrounds)?",
|
|
||||||
)
|
|
||||||
|
|
||||||
content_panels = [
|
content_panels = [
|
||||||
FieldPanel("title"),
|
FieldPanel("title"),
|
||||||
MultiFieldPanel(
|
MultiFieldPanel(
|
||||||
|
|
|
@ -1,22 +1,44 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import text as text_utils
|
from wagtail.admin import edit_handlers as admin_panels
|
||||||
from wagtail.core import models as wagtail_models
|
from wagtail.core import models as wagtail_models
|
||||||
from wagtail.snippets import models as snippet_models
|
from wagtail.snippets import models as snippet_models
|
||||||
|
from wagtail_localize import fields as localize_fields
|
||||||
|
|
||||||
|
|
||||||
@snippet_models.register_snippet
|
@snippet_models.register_snippet
|
||||||
class BuyersGuideContentCategory(wagtail_models.TranslatableMixin, models.Model):
|
class BuyersGuideContentCategory(wagtail_models.TranslatableMixin, models.Model):
|
||||||
title = models.CharField(max_length=100, null=False, blank=False)
|
title = models.CharField(max_length=100, null=False, blank=False)
|
||||||
slug = models.SlugField(max_length=100, null=False, blank=True, unique=True)
|
slug = models.SlugField(
|
||||||
|
max_length=100,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
help_text=(
|
||||||
|
"The slug is auto-generated from the title, but can be customized if needed. "
|
||||||
|
"It needs to be unique per locale. "
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
panels = [
|
||||||
|
admin_panels.FieldPanel("title"),
|
||||||
|
admin_panels.FieldPanel("slug"),
|
||||||
|
]
|
||||||
|
|
||||||
|
translatable_fields = [
|
||||||
|
localize_fields.TranslatableField("title"),
|
||||||
|
localize_fields.SynchronizedField("slug"),
|
||||||
|
]
|
||||||
|
|
||||||
class Meta(wagtail_models.TranslatableMixin.Meta):
|
class Meta(wagtail_models.TranslatableMixin.Meta):
|
||||||
verbose_name = "Buyers Guide Content Category"
|
verbose_name = "Buyers Guide Content Category"
|
||||||
verbose_name_plural = "Buyers Guide Content Categories"
|
verbose_name_plural = "Buyers Guide Content Categories"
|
||||||
|
unique_together = wagtail_models.TranslatableMixin.Meta.unique_together + [
|
||||||
|
("locale", "slug"),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
def save(self, *args, **kwargs) -> None:
|
def validate_unique(self, exclude=None):
|
||||||
if not self.slug:
|
if exclude and "locale" in exclude:
|
||||||
self.slug = text_utils.slugify(self.title)
|
exclude.remove("locale")
|
||||||
super().save(*args, **kwargs)
|
return super().validate_unique(exclude)
|
||||||
|
|
|
@ -44,4 +44,5 @@ base_fields = [
|
||||||
("image_teaser_block", customblocks.ImageTeaserBlock()),
|
("image_teaser_block", customblocks.ImageTeaserBlock()),
|
||||||
("text_only_teaser", customblocks.TextOnlyTeaserBlock()),
|
("text_only_teaser", customblocks.TextOnlyTeaserBlock()),
|
||||||
("block_with_aside", customblocks.BlockWithAside()),
|
("block_with_aside", customblocks.BlockWithAside()),
|
||||||
|
("accordion", customblocks.AccordionBlock()),
|
||||||
]
|
]
|
||||||
|
|
|
@ -82,7 +82,8 @@ class RecentBlogEntries(blocks.StructBlock):
|
||||||
entries = blog_page.get_entries(context)
|
entries = blog_page.get_entries(context)
|
||||||
|
|
||||||
# Updates the href for the 'More from our blog' button
|
# Updates the href for the 'More from our blog' button
|
||||||
url = f"/{blog_page.slug}/{type}/{query}"
|
blog_page_url = blog_page.get_url()
|
||||||
|
url = f"{blog_page_url}{type}/{query}"
|
||||||
context["more_entries_link"] = url
|
context["more_entries_link"] = url
|
||||||
|
|
||||||
# We only want to grab no more than the first 6 entries
|
# We only want to grab no more than the first 6 entries
|
||||||
|
|
|
@ -12,6 +12,14 @@
|
||||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="{% static 'images/apple-touch-icon-180x180@2x.png' %}">
|
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="{% static 'images/apple-touch-icon-180x180@2x.png' %}">
|
||||||
<link rel="icon" type="image/png" sizes="196x196" href="{% static 'images/favicon-196x196@2x.png' %}">
|
<link rel="icon" type="image/png" sizes="196x196" href="{% static 'images/favicon-196x196@2x.png' %}">
|
||||||
<link rel="shortcut icon" href="{% static 'images/favicon.ico' %}">
|
<link rel="shortcut icon" href="{% static 'images/favicon.ico' %}">
|
||||||
|
<!-- Fundraiseup Script -->
|
||||||
|
<script>(function(w,d,s,n,a){if(!w[n]){var l='call,catch,on,once,set,then,track'
|
||||||
|
.split(','),i,o=function(n){return'function'==typeof n?o.l.push([arguments])&&o
|
||||||
|
:function(){return o.l.push([n,arguments])&&o}},t=d.getElementsByTagName(s)[0],
|
||||||
|
j=d.createElement(s);j.async=!0;j.src='https://cdn.fundraiseup.com/widget/'+a;
|
||||||
|
t.parentNode.insertBefore(j,t);o.s=Date.now();o.v=4;o.h=w.location.href;o.l=[];
|
||||||
|
for(i=0;i<7;i++)o[l[i]]=o(l[i]);w[n]=o}
|
||||||
|
})(window,document,'script','FundraiseUp','ADCYPWMX');</script>
|
||||||
<title>Mozilla Foundation - Under Maintenance</title>
|
<title>Mozilla Foundation - Under Maintenance</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -74,9 +82,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="donate-btn-wrapper">
|
<div class="donate-btn-wrapper">
|
||||||
<a id="donate-header-btn" class="tw-btn-pop"
|
<a id="donate-header-btn" class="tw-btn-pop" href="?form=donate">Donate</a>
|
||||||
href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=header"
|
|
||||||
target="_blank" rel="noopener noreferrer">Donate</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,7 +112,7 @@
|
||||||
<h3 class="tw-h3-heading mb-0">We all love the Web. Join Mozilla in defending it.</h3>
|
<h3 class="tw-h3-heading mb-0">We all love the Web. Join Mozilla in defending it.</h3>
|
||||||
<h4 class="tw-h3-heading mb-4">Let’s protect the world’s largest resource for future generations.</h4>
|
<h4 class="tw-h3-heading mb-4">Let’s protect the world’s largest resource for future generations.</h4>
|
||||||
<div>
|
<div>
|
||||||
<a href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=maintenance_cta" target="_blank" rel="noopener noreferrer" c class="tw-btn-secondary dark-theme">Donate now</a>
|
<a href="?form=donate" class="tw-btn-secondary dark-theme">Donate now</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -131,7 +137,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-6 col-xl-7">
|
<div class="col-lg-6 col-xl-7">
|
||||||
<ul class="link-list list-unstyled mb-0">
|
<ul class="link-list list-unstyled mb-0">
|
||||||
<li class="mb-2"><a id="donate-footer-btn" href="https://donate.mozilla.org/?utm_source=foundation.mozilla.org&utm_medium=referral&utm_campaign=fmonav&utm_content=footer" target="_blank" rel="noopener noreferrer" class="dark-theme">Donate</a></li>
|
<li class="mb-2"><a id="donate-footer-btn" href="?form=donate" class="dark-theme">Donate</a></li>
|
||||||
<li class="mb-2"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="dark-theme">Cookies</a></li>
|
<li class="mb-2"><a href="https://www.mozilla.org/privacy/websites/#cookies" class="dark-theme">Cookies</a></li>
|
||||||
<li class="mb-2"><a href="https://www.mozilla.org/about/legal/terms/mozilla/" class="dark-theme">Legal</a></li>
|
<li class="mb-2"><a href="https://www.mozilla.org/about/legal/terms/mozilla/" class="dark-theme">Legal</a></li>
|
||||||
<li class="mb-2"><a href="https://www.mozilla.org/about/governance/policies/participation/" class="dark-theme">Participation Guidelines</a></li>
|
<li class="mb-2"><a href="https://www.mozilla.org/about/governance/policies/participation/" class="dark-theme">Participation Guidelines</a></li>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block slides %}
|
{% block slides %}
|
||||||
<div class="swiper-wrapper">
|
<div class="swiper-wrapper tw-items-stretch">
|
||||||
{% for current_event in self.current_events %}
|
{% for current_event in self.current_events %}
|
||||||
{% image current_event.value.image fill-445x185 as img %}
|
{% image current_event.value.image fill-445x185 as img %}
|
||||||
<div class="swiper-slide">
|
<div class="swiper-slide">
|
||||||
|
|
|
@ -12,19 +12,28 @@
|
||||||
{% with card=block %}
|
{% with card=block %}
|
||||||
<li>
|
<li>
|
||||||
{% image card.image fill-690x388 as img %}
|
{% image card.image fill-690x388 as img %}
|
||||||
{% if card.url %}
|
|
||||||
<a href="{{ card.url }}" class="tw-block tw-group hover:tw-no-underline focus:tw-no-underline hover:tw-text-black focus:tw-text-black" aria-label="{{ card.title }}">
|
|
||||||
{% endif %}
|
|
||||||
<div class="tw-px-4 tw-grid tw-grid-cols-1 large:tw-grid-cols-3 tw-gap-4 large:tw-gap-6">
|
<div class="tw-px-4 tw-grid tw-grid-cols-1 large:tw-grid-cols-3 tw-gap-4 large:tw-gap-6">
|
||||||
<div class="tw-col-span-1">
|
<div class="tw-col-span-1">
|
||||||
|
{% if card.url %}
|
||||||
|
<a href="{{ card.url }}" class="tw-block tw-group" aria-label="{{ card.title }}">
|
||||||
|
{% endif %}
|
||||||
<img class="tw-w-full xlarge:tw-max-w-[345px]" src="{{ img.url }}" alt="{{ card.alt_text }}" width="{{ img.width }}" height="{{ img.height }}"/>
|
<img class="tw-w-full xlarge:tw-max-w-[345px]" src="{{ img.url }}" alt="{{ card.alt_text }}" width="{{ img.width }}" height="{{ img.height }}"/>
|
||||||
|
{% if card.url %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-col-span-1 large:tw-col-span-2 large:tw-items-center large:tw-flex">
|
<div class="tw-col-span-1 large:tw-col-span-2 large:tw-items-center large:tw-flex">
|
||||||
<div class="tw-bg-white tw-relative tw-w-full tw-flex tw-flex-col">
|
<div class="tw-bg-white tw-relative tw-w-full tw-flex tw-flex-col">
|
||||||
<span class="tw-h6-heading tw-text-gray-40 tw-py-1 tw-mb-0">{{ card.meta_data }}</span>
|
<span class="tw-h6-heading tw-text-gray-40 tw-py-1 tw-mb-0">{{ card.meta_data }}</span>
|
||||||
|
{% if card.url %}
|
||||||
|
<a href="{{ card.url }}" class="tw-block tw-group hover:tw-no-underline focus:tw-no-underline hover:tw-text-black focus:tw-text-black" aria-label="{{ card.title }}">
|
||||||
|
{% endif %}
|
||||||
<h3 class="tw-h3-heading tw-mb-2 group-hover:tw-underline group-focus:tw-underline group-hover:tw-text-inherit group-focus:tw-text-inherit group-hover:tw-font-inherit">
|
<h3 class="tw-h3-heading tw-mb-2 group-hover:tw-underline group-focus:tw-underline group-hover:tw-text-inherit group-focus:tw-text-inherit group-hover:tw-font-inherit">
|
||||||
{{ card.title }}
|
{{ card.title }}
|
||||||
</h3>
|
</h3>
|
||||||
|
{% if card.url %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
{# Adjust font sizes of rich text elements using [&_p,li,ul] to select them #}
|
{# Adjust font sizes of rich text elements using [&_p,li,ul] to select them #}
|
||||||
<div class="[&_p,li,ul]:tw-text-sm [&_p,li,ul]:tw-text-gray-80">
|
<div class="[&_p,li,ul]:tw-text-sm [&_p,li,ul]:tw-text-gray-80">
|
||||||
{{ card.body|richtext }}
|
{{ card.body|richtext }}
|
||||||
|
@ -32,7 +41,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if card.url %}</a>{% endif %}
|
|
||||||
</li>
|
</li>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,225 +1,12 @@
|
||||||
from datetime import date, datetime, timezone
|
from datetime import date, datetime, timezone
|
||||||
from io import StringIO
|
|
||||||
from os.path import abspath, dirname, join
|
|
||||||
from unittest import skip
|
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
from django.contrib.auth.models import Group, User
|
from django.test import TestCase
|
||||||
from django.core.management import call_command
|
|
||||||
from django.test import RequestFactory, TestCase
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils import translation
|
|
||||||
from django.utils.translation.trans_real import (
|
|
||||||
parse_accept_lang_header as django_parse_accept_lang_header,
|
|
||||||
)
|
|
||||||
from django.utils.translation.trans_real import to_language as django_to_language
|
|
||||||
from wagtail.core.models import Collection, Site
|
|
||||||
from wagtail.images.models import Image
|
|
||||||
from wagtail_factories import SiteFactory
|
|
||||||
|
|
||||||
from networkapi.utility.middleware import ReferrerMiddleware, XRobotsTagMiddleware
|
|
||||||
from networkapi.utility.redirects import redirect_to_default_cms_site
|
|
||||||
from networkapi.wagtailpages import (
|
|
||||||
language_code_to_iso_3166,
|
|
||||||
parse_accept_lang_header,
|
|
||||||
to_language,
|
|
||||||
)
|
|
||||||
from networkapi.wagtailpages.factory.buyersguide import (
|
from networkapi.wagtailpages.factory.buyersguide import (
|
||||||
BuyersGuidePageFactory,
|
BuyersGuidePageFactory,
|
||||||
GeneralProductPageFactory,
|
GeneralProductPageFactory,
|
||||||
)
|
)
|
||||||
from networkapi.wagtailpages.pagemodels.base import Homepage
|
from networkapi.wagtailpages.pagemodels.base import Homepage
|
||||||
from networkapi.wagtailpages.utils import create_wagtail_image
|
|
||||||
|
|
||||||
# from django.test.utils import override_settings
|
|
||||||
|
|
||||||
|
|
||||||
class ReferrerMiddlewareTests(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
referrer_middleware = ReferrerMiddleware("response")
|
|
||||||
self.assertEqual(referrer_middleware.get_response, "response")
|
|
||||||
|
|
||||||
def test_requestProcessing(self):
|
|
||||||
"""
|
|
||||||
Ensure that the middleware assigns a Referrer-Policy header to the response object
|
|
||||||
"""
|
|
||||||
|
|
||||||
referrer_middleware = ReferrerMiddleware(MagicMock())
|
|
||||||
response = referrer_middleware(MagicMock())
|
|
||||||
response.__setitem__.assert_called_with("Referrer-Policy", "same-origin")
|
|
||||||
|
|
||||||
|
|
||||||
class MissingMigrationsTests(TestCase):
|
|
||||||
def test_no_migrations_missing(self):
|
|
||||||
"""
|
|
||||||
Ensure we didn't forget a migration
|
|
||||||
"""
|
|
||||||
output = StringIO()
|
|
||||||
call_command("makemigrations", interactive=False, dry_run=True, stdout=output)
|
|
||||||
|
|
||||||
if output.getvalue() != "No changes detected\n":
|
|
||||||
raise AssertionError("Missing migrations detected:\n" + output.getvalue())
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteNonStaffTest(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
User.objects.create(username="Alex"),
|
|
||||||
|
|
||||||
def test_non_staff_is_deleted(self):
|
|
||||||
"""
|
|
||||||
Simple users are deleted
|
|
||||||
"""
|
|
||||||
|
|
||||||
call_command("delete_non_staff", "--now")
|
|
||||||
|
|
||||||
self.assertEqual(User.objects.count(), 0)
|
|
||||||
|
|
||||||
|
|
||||||
class IsStaffNotDeletedTest(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
User.objects.create(username="Alex", is_staff=True)
|
|
||||||
|
|
||||||
def test_is_staff_not_deleted(self):
|
|
||||||
"""
|
|
||||||
Users with 'is_staff' flag at True are not deleted
|
|
||||||
"""
|
|
||||||
|
|
||||||
call_command("delete_non_staff", "--now")
|
|
||||||
|
|
||||||
self.assertEqual(User.objects.count(), 1)
|
|
||||||
|
|
||||||
|
|
||||||
class InGroupNotDeletedTest(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
group = Group.objects.create(name="TestGroup")
|
|
||||||
group.user_set.create(username="Alex")
|
|
||||||
|
|
||||||
def test_in_group_not_deleted(self):
|
|
||||||
"""
|
|
||||||
Users in a group are not deleted
|
|
||||||
"""
|
|
||||||
|
|
||||||
call_command("delete_non_staff", "--now")
|
|
||||||
|
|
||||||
self.assertEqual(User.objects.count(), 1)
|
|
||||||
|
|
||||||
|
|
||||||
class MozillaFoundationUsersNotDeletedTest(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
User.objects.create(username="Alex", email="alex@mozillafoundation.org")
|
|
||||||
|
|
||||||
def test_mozilla_foundation_users_not_deleted(self):
|
|
||||||
"""
|
|
||||||
Mozilla Foundation Users are not deleted
|
|
||||||
"""
|
|
||||||
|
|
||||||
call_command("delete_non_staff", "--now")
|
|
||||||
|
|
||||||
self.assertEqual(User.objects.count(), 1)
|
|
||||||
|
|
||||||
|
|
||||||
class RedirectDefaultSiteDecoratorTests(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.factory = RequestFactory()
|
|
||||||
# Change the default site away from localhost
|
|
||||||
self.original_default_site = Site.objects.get(is_default_site=True, hostname="localhost")
|
|
||||||
self.original_default_site.is_default_site = False
|
|
||||||
self.original_default_site.save()
|
|
||||||
# Add a default site, and a secondary site.
|
|
||||||
self.default_site = SiteFactory(hostname="default-site.com", is_default_site=True)
|
|
||||||
self.secondary_site = SiteFactory(hostname="secondary-site.com")
|
|
||||||
|
|
||||||
def test_redirect_decorator(self):
|
|
||||||
"""
|
|
||||||
Test that the decorator redirects.
|
|
||||||
"""
|
|
||||||
decorated_view = redirect_to_default_cms_site(lambda request: None)
|
|
||||||
response = decorated_view(self.factory.get("/example/", HTTP_HOST="secondary-site.com"))
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
|
|
||||||
def test_redirect_decorator_doesnt_redirect(self):
|
|
||||||
"""
|
|
||||||
Test that the redirect is triggered only when needed.
|
|
||||||
"""
|
|
||||||
decorated_view = redirect_to_default_cms_site(lambda request: "untouched response")
|
|
||||||
response = decorated_view(self.factory.get("/example/"))
|
|
||||||
self.assertEqual(response, "untouched response")
|
|
||||||
|
|
||||||
@skip("TODO: REENABLE: TEMPORARY SKIP TO MAKE PNI-AS-WAGTAIL LAUNCH POSSIBLE")
|
|
||||||
# @override_settings(STATICFILES_STORAGE="django.contrib.staticfiles.storage.StaticFilesStorage")
|
|
||||||
def test_PNI_homepage_redirect_to_foundation_site(self):
|
|
||||||
"""
|
|
||||||
Test that users gets redirected to PNI on the foundation site when they visit it from a non-default CMS site
|
|
||||||
"""
|
|
||||||
response = self.client.get("/en/privacynotincluded/", HTTP_HOST="secondary-site.com")
|
|
||||||
self.assertRedirects(
|
|
||||||
response,
|
|
||||||
"https://default-site.com/en/privacynotincluded/",
|
|
||||||
fetch_redirect_response=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
# Re-instante localhost as the default site
|
|
||||||
self.original_default_site.is_default_site = True
|
|
||||||
self.original_default_site.save()
|
|
||||||
|
|
||||||
# Remove the Site Factories
|
|
||||||
self.default_site.delete()
|
|
||||||
self.secondary_site.delete()
|
|
||||||
|
|
||||||
|
|
||||||
class WagtailPagesTestCase(TestCase):
|
|
||||||
def test_get_language_code_to_iso_3166(self):
|
|
||||||
self.assertEqual(language_code_to_iso_3166("en-gb"), "en-GB")
|
|
||||||
self.assertEqual(language_code_to_iso_3166("en-us"), "en-US")
|
|
||||||
self.assertEqual(language_code_to_iso_3166("fr"), "fr")
|
|
||||||
|
|
||||||
def test_to_language(self):
|
|
||||||
self.assertEqual(to_language("en_US"), "en-US")
|
|
||||||
|
|
||||||
def test_parse_accept_lang_header_returns_iso_3166_language(self):
|
|
||||||
self.assertEqual(
|
|
||||||
parse_accept_lang_header("en-GB,en;q=0.5"),
|
|
||||||
(("en-GB", 1.0), ("en", 0.5)),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WagtailPagesIntegrationTestCase(TestCase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Test that our overrides to Django translation functions work.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_to_language(self):
|
|
||||||
self.assertEqual(django_to_language("fy_NL"), "fy-NL")
|
|
||||||
|
|
||||||
def test_parse_accept_lang_header_returns_iso_3166_language(self):
|
|
||||||
self.assertEqual(
|
|
||||||
django_parse_accept_lang_header("fy-NL,fy;q=0.5"),
|
|
||||||
(("fy-NL", 1.0), ("fy", 0.5)),
|
|
||||||
)
|
|
||||||
|
|
||||||
@skip("TODO: REMOVE: NOW DONE BY WAGTAIL")
|
|
||||||
def test_reverse_produces_correct_url_prefix(self):
|
|
||||||
translation.activate("fy-NL")
|
|
||||||
url = reverse("buyersguide-home")
|
|
||||||
self.assertTrue(url.startswith("/fy-NL/"))
|
|
||||||
translation.deactivate()
|
|
||||||
|
|
||||||
|
|
||||||
class XRobotsTagMiddlewareTest(TestCase):
|
|
||||||
def test_returns_response(self):
|
|
||||||
xrobotstag_middleware = XRobotsTagMiddleware("response")
|
|
||||||
self.assertEqual(xrobotstag_middleware.get_response, "response")
|
|
||||||
|
|
||||||
def test_sends_x_robots_tag(self):
|
|
||||||
"""
|
|
||||||
Ensure that the middleware assigns an X-Robots-Tag to the response
|
|
||||||
"""
|
|
||||||
|
|
||||||
xrobotstag_middleware = XRobotsTagMiddleware(MagicMock())
|
|
||||||
response = xrobotstag_middleware(MagicMock())
|
|
||||||
response.__setitem__.assert_called_with("X-Robots-Tag", "noindex")
|
|
||||||
|
|
||||||
|
|
||||||
class TestPNIAirtableConnections(TestCase):
|
class TestPNIAirtableConnections(TestCase):
|
||||||
|
@ -236,7 +23,6 @@ class TestPNIAirtableConnections(TestCase):
|
||||||
parent=Homepage.objects.first(),
|
parent=Homepage.objects.first(),
|
||||||
title="* Privacy not included",
|
title="* Privacy not included",
|
||||||
slug="privacynotincluded",
|
slug="privacynotincluded",
|
||||||
header="Be Smart. Shop Safe.",
|
|
||||||
)
|
)
|
||||||
self.general_product_page = GeneralProductPageFactory.create(
|
self.general_product_page = GeneralProductPageFactory.create(
|
||||||
title="General Percy Product",
|
title="General Percy Product",
|
||||||
|
@ -403,48 +189,3 @@ class TestPNIAirtableConnections(TestCase):
|
||||||
self.assertIn("Uses AI", export_fields)
|
self.assertIn("Uses AI", export_fields)
|
||||||
self.assertIn("AI is transparent", export_fields)
|
self.assertIn("AI is transparent", export_fields)
|
||||||
self.assertIn("AI help text", export_fields)
|
self.assertIn("AI help text", export_fields)
|
||||||
|
|
||||||
|
|
||||||
class TestCreateWagtailImageUtility(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.image_path = abspath(join(dirname(__file__), "../../media/images/placeholders/products/teddy.jpg"))
|
|
||||||
|
|
||||||
def create_new_image(self):
|
|
||||||
"""A generic test to ensure the image is created properly."""
|
|
||||||
new_image = create_wagtail_image(self.image_path, image_name="fake teddy.jpg", collection_name="pni products")
|
|
||||||
# Image was created
|
|
||||||
self.assertIsNotNone(new_image)
|
|
||||||
# Image has a collection and is in the proper collection
|
|
||||||
self.assertIsNotNone(new_image.collection_id)
|
|
||||||
self.assertEqual(new_image.collection.name, "pni products")
|
|
||||||
|
|
||||||
def test_empty_image_name_and_no_collection(self):
|
|
||||||
new_image = create_wagtail_image(
|
|
||||||
self.image_path,
|
|
||||||
)
|
|
||||||
self.assertEqual(new_image.title, "teddy.jpg")
|
|
||||||
self.assertEqual(new_image.collection.name, "Root")
|
|
||||||
|
|
||||||
def test_new_collection(self):
|
|
||||||
collection_name = "brand new collection"
|
|
||||||
new_image = create_wagtail_image(
|
|
||||||
self.image_path,
|
|
||||||
image_name="fake teddy.jpg",
|
|
||||||
collection_name=collection_name,
|
|
||||||
)
|
|
||||||
self.assertEqual(new_image.collection.name, collection_name)
|
|
||||||
|
|
||||||
def test_existing_collection(self):
|
|
||||||
new_collection_name = "first collection"
|
|
||||||
|
|
||||||
root_collection = Collection.get_first_root_node()
|
|
||||||
new_collection = root_collection.add_child(name=new_collection_name)
|
|
||||||
total_images_in_new_collection = Image.objects.filter(collection=new_collection).count()
|
|
||||||
self.assertEqual(total_images_in_new_collection, 0)
|
|
||||||
|
|
||||||
new_image = create_wagtail_image(
|
|
||||||
self.image_path,
|
|
||||||
image_name="fake teddy.jpg",
|
|
||||||
collection_name=new_collection_name,
|
|
||||||
)
|
|
||||||
self.assertEqual(new_image.collection.name, new_collection_name)
|
|
|
@ -143,66 +143,3 @@ class BuyersGuideArticlePageTest(test_base.WagtailpagesTestCase):
|
||||||
related_articles_fr = article_page_fr.get_related_articles()
|
related_articles_fr = article_page_fr.get_related_articles()
|
||||||
|
|
||||||
self.assertIn(related_article_fr, related_articles_fr)
|
self.assertIn(related_article_fr, related_articles_fr)
|
||||||
|
|
||||||
def test_primary_related_articles(self):
|
|
||||||
"""First three related articles are primary."""
|
|
||||||
article_page = buyersguide_factories.BuyersGuideArticlePageFactory(
|
|
||||||
parent=self.content_index,
|
|
||||||
)
|
|
||||||
related_articles = []
|
|
||||||
for _ in range(4):
|
|
||||||
related_article = buyersguide_factories.BuyersGuideArticlePageFactory(
|
|
||||||
parent=self.content_index,
|
|
||||||
)
|
|
||||||
buyersguide_factories.BuyersGuideArticlePageRelatedArticleRelationFactory(
|
|
||||||
page=article_page,
|
|
||||||
article=related_article,
|
|
||||||
)
|
|
||||||
related_articles.append(related_article)
|
|
||||||
|
|
||||||
result = article_page.get_primary_related_articles()
|
|
||||||
|
|
||||||
for related_article in related_articles[:3]:
|
|
||||||
self.assertIn(related_article, result)
|
|
||||||
self.assertNotIn(related_articles[-1], result)
|
|
||||||
|
|
||||||
def test_primary_related_articles_no_related_articles(self):
|
|
||||||
article_page = buyersguide_factories.BuyersGuideArticlePageFactory(
|
|
||||||
parent=self.content_index,
|
|
||||||
)
|
|
||||||
|
|
||||||
result = article_page.get_primary_related_articles()
|
|
||||||
|
|
||||||
self.assertListEqual(result, [])
|
|
||||||
|
|
||||||
def test_secondary_related_articles(self):
|
|
||||||
"""Second three related articles are secondary."""
|
|
||||||
article_page = buyersguide_factories.BuyersGuideArticlePageFactory(
|
|
||||||
parent=self.content_index,
|
|
||||||
)
|
|
||||||
related_articles = []
|
|
||||||
for _ in range(6):
|
|
||||||
related_article = buyersguide_factories.BuyersGuideArticlePageFactory(
|
|
||||||
parent=self.content_index,
|
|
||||||
)
|
|
||||||
buyersguide_factories.BuyersGuideArticlePageRelatedArticleRelationFactory(
|
|
||||||
page=article_page,
|
|
||||||
article=related_article,
|
|
||||||
)
|
|
||||||
related_articles.append(related_article)
|
|
||||||
|
|
||||||
result = article_page.get_secondary_related_articles()
|
|
||||||
|
|
||||||
for related_article in related_articles[:3]:
|
|
||||||
self.assertNotIn(related_article, result)
|
|
||||||
for related_article in related_articles[3:]:
|
|
||||||
self.assertIn(related_article, result)
|
|
||||||
|
|
||||||
def test_secondary_related_articles_no_related_articles(self):
|
|
||||||
article_page = buyersguide_factories.BuyersGuideArticlePageFactory(
|
|
||||||
parent=self.content_index,
|
|
||||||
)
|
|
||||||
|
|
||||||
result = article_page.get_secondary_related_articles()
|
|
||||||
|
|
||||||
self.assertListEqual(result, [])
|
|
||||||
|
|
|
@ -1,71 +1,40 @@
|
||||||
import unittest
|
|
||||||
|
|
||||||
from django.core import exceptions
|
from django.core import exceptions
|
||||||
|
|
||||||
from networkapi.wagtailpages.factory import buyersguide as buyersguide_factories
|
from networkapi.wagtailpages.factory import buyersguide as buyersguide_factories
|
||||||
from networkapi.wagtailpages.tests import base as test_base
|
from networkapi.wagtailpages.tests import base as test_base
|
||||||
|
|
||||||
|
TEST_CATEGORY_TITLE = "Test category"
|
||||||
|
|
||||||
|
|
||||||
class TestBuyersGuideContentCategory(test_base.WagtailpagesTestCase):
|
class TestBuyersGuideContentCategory(test_base.WagtailpagesTestCase):
|
||||||
def test_factory(self):
|
def test_factory(self):
|
||||||
buyersguide_factories.BuyersGuideContentCategoryFactory()
|
buyersguide_factories.BuyersGuideContentCategoryFactory()
|
||||||
|
|
||||||
def test_slug_set_during_save(self):
|
def test_factory_sets_slug(self):
|
||||||
category = buyersguide_factories.BuyersGuideContentCategoryFactory.build()
|
category = buyersguide_factories.BuyersGuideContentCategoryFactory.build(
|
||||||
self.assertEqual(category.slug, "")
|
title=TEST_CATEGORY_TITLE,
|
||||||
|
|
||||||
category.save()
|
|
||||||
|
|
||||||
self.assertNotEqual(category.slug, "")
|
|
||||||
|
|
||||||
def test_exisiting_slug_is_kept_during_save(self):
|
|
||||||
category = buyersguide_factories.BuyersGuideContentCategoryFactory(
|
|
||||||
title="Test category",
|
|
||||||
slug="not-the-slugified-title",
|
|
||||||
)
|
)
|
||||||
|
self.assertEqual(category.slug, "test-category")
|
||||||
|
|
||||||
category.save()
|
def test_full_clean_raises_if_category_with_same_title_and_locale_in_db(self):
|
||||||
|
|
||||||
self.assertEqual(category.title, "Test category")
|
|
||||||
self.assertEqual(category.slug, "not-the-slugified-title")
|
|
||||||
|
|
||||||
def test_slug_has_to_be_unique(self):
|
|
||||||
buyersguide_factories.BuyersGuideContentCategoryFactory(
|
buyersguide_factories.BuyersGuideContentCategoryFactory(
|
||||||
title="Test category",
|
title=TEST_CATEGORY_TITLE,
|
||||||
slug="test-category",
|
|
||||||
)
|
)
|
||||||
category = buyersguide_factories.BuyersGuideContentCategoryFactory.build(
|
category = buyersguide_factories.BuyersGuideContentCategoryFactory.build(
|
||||||
title="Test category",
|
title=TEST_CATEGORY_TITLE,
|
||||||
slug="test-category",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.assertRaises(exceptions.ValidationError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
category.full_clean()
|
category.full_clean()
|
||||||
|
|
||||||
@unittest.skip(
|
def test_can_clean_and_save_copy_for_translation(self):
|
||||||
"We would really want the slugs to be only unique per locale. "
|
|
||||||
"If we implement that with a UniqueContraint or unique_together, "
|
|
||||||
"Wagtail will crash if the constraint is violated. "
|
|
||||||
"Wagtail does handle the simple unique requirement on the slug field gracefully. "
|
|
||||||
"Therefore, we are using slugs that are unique regardless of locale. "
|
|
||||||
"We need to manually create unique slugs if there are clashes between the locales. "
|
|
||||||
"Because we are using slugs that need to be unique regradless of locale, this "
|
|
||||||
"test would fail. Therefore it is skipped."
|
|
||||||
"See also: https://github.com/wagtail/wagtail/issues/8918"
|
|
||||||
)
|
|
||||||
def test_same_slug_allowed_on_different_locale(self):
|
|
||||||
category_default_locale = buyersguide_factories.BuyersGuideContentCategoryFactory(
|
category_default_locale = buyersguide_factories.BuyersGuideContentCategoryFactory(
|
||||||
title="Test category",
|
title=TEST_CATEGORY_TITLE,
|
||||||
slug="test-category",
|
|
||||||
locale=self.default_locale,
|
|
||||||
)
|
|
||||||
category_fr_locale = buyersguide_factories.BuyersGuideContentCategoryFactory.build(
|
|
||||||
title="Test category",
|
|
||||||
slug="test-category",
|
|
||||||
locale=self.fr_locale,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
category_fr_locale.save()
|
fr_copy = category_default_locale.copy_for_translation(locale=self.fr_locale)
|
||||||
|
fr_copy.full_clean()
|
||||||
|
fr_copy.save()
|
||||||
|
|
||||||
self.assertEqual(category_fr_locale.slug, category_default_locale.slug)
|
self.assertEqual(category_default_locale.slug, "test-category")
|
||||||
self.assertNotEqual(category_fr_locale.locale, category_default_locale.locale)
|
self.assertEqual(fr_copy.slug, "test-category")
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
from os.path import abspath, dirname, join
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils.translation.trans_real import (
|
||||||
|
parse_accept_lang_header as django_parse_accept_lang_header,
|
||||||
|
)
|
||||||
|
from django.utils.translation.trans_real import to_language as django_to_language
|
||||||
|
from wagtail.core.models import Collection
|
||||||
|
from wagtail.images.models import Image
|
||||||
|
|
||||||
|
from networkapi.wagtailpages import (
|
||||||
|
language_code_to_iso_3166,
|
||||||
|
parse_accept_lang_header,
|
||||||
|
to_language,
|
||||||
|
)
|
||||||
|
from networkapi.wagtailpages.utils import create_wagtail_image
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreateWagtailImageUtility(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.image_path = abspath(join(dirname(__file__), "../../../media/images/placeholders/products/teddy.jpg"))
|
||||||
|
|
||||||
|
def create_new_image(self):
|
||||||
|
"""A generic test to ensure the image is created properly."""
|
||||||
|
new_image = create_wagtail_image(self.image_path, image_name="fake teddy.jpg", collection_name="pni products")
|
||||||
|
# Image was created
|
||||||
|
self.assertIsNotNone(new_image)
|
||||||
|
# Image has a collection and is in the proper collection
|
||||||
|
self.assertIsNotNone(new_image.collection_id)
|
||||||
|
self.assertEqual(new_image.collection.name, "pni products")
|
||||||
|
|
||||||
|
def test_empty_image_name_and_no_collection(self):
|
||||||
|
new_image = create_wagtail_image(
|
||||||
|
self.image_path,
|
||||||
|
)
|
||||||
|
self.assertEqual(new_image.title, "teddy.jpg")
|
||||||
|
self.assertEqual(new_image.collection.name, "Root")
|
||||||
|
|
||||||
|
def test_new_collection(self):
|
||||||
|
collection_name = "brand new collection"
|
||||||
|
new_image = create_wagtail_image(
|
||||||
|
self.image_path,
|
||||||
|
image_name="fake teddy.jpg",
|
||||||
|
collection_name=collection_name,
|
||||||
|
)
|
||||||
|
self.assertEqual(new_image.collection.name, collection_name)
|
||||||
|
|
||||||
|
def test_existing_collection(self):
|
||||||
|
new_collection_name = "first collection"
|
||||||
|
|
||||||
|
root_collection = Collection.get_first_root_node()
|
||||||
|
new_collection = root_collection.add_child(name=new_collection_name)
|
||||||
|
total_images_in_new_collection = Image.objects.filter(collection=new_collection).count()
|
||||||
|
self.assertEqual(total_images_in_new_collection, 0)
|
||||||
|
|
||||||
|
new_image = create_wagtail_image(
|
||||||
|
self.image_path,
|
||||||
|
image_name="fake teddy.jpg",
|
||||||
|
collection_name=new_collection_name,
|
||||||
|
)
|
||||||
|
self.assertEqual(new_image.collection.name, new_collection_name)
|
||||||
|
|
||||||
|
|
||||||
|
class TestLanguageUtilities(TestCase):
|
||||||
|
def test_get_language_code_to_iso_3166(self):
|
||||||
|
self.assertEqual(language_code_to_iso_3166("en-gb"), "en-GB")
|
||||||
|
self.assertEqual(language_code_to_iso_3166("en-us"), "en-US")
|
||||||
|
self.assertEqual(language_code_to_iso_3166("fr"), "fr")
|
||||||
|
|
||||||
|
def test_to_language(self):
|
||||||
|
self.assertEqual(to_language("en_US"), "en-US")
|
||||||
|
|
||||||
|
def test_parse_accept_lang_header_returns_iso_3166_language(self):
|
||||||
|
self.assertEqual(
|
||||||
|
parse_accept_lang_header("en-GB,en;q=0.5"),
|
||||||
|
(("en-GB", 1.0), ("en", 0.5)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDjangoTranslationUtilityOverrides(TestCase):
|
||||||
|
"""
|
||||||
|
Test that our overrides to Django translation functions work.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_to_language(self):
|
||||||
|
self.assertEqual(django_to_language("fy_NL"), "fy-NL")
|
||||||
|
|
||||||
|
def test_parse_accept_lang_header_returns_iso_3166_language(self):
|
||||||
|
self.assertEqual(
|
||||||
|
django_parse_accept_lang_header("fy-NL,fy;q=0.5"),
|
||||||
|
(("fy-NL", 1.0), ("fy", 0.5)),
|
||||||
|
)
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
28
package.json
28
package.json
|
@ -3,7 +3,7 @@
|
||||||
"issues": "https://github.com/mozilla/foundation.mozilla.org/issues",
|
"issues": "https://github.com/mozilla/foundation.mozilla.org/issues",
|
||||||
"description": "Mozilla Foundation site",
|
"description": "Mozilla Foundation site",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.4.0"
|
"node": "18.x"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "run-s build:clean && run-p build:js:prod build:common",
|
"build": "run-s build:clean && run-p build:js:prod build:common",
|
||||||
|
@ -47,10 +47,10 @@
|
||||||
"playwright:install": "playwright install chromium firefox webkit",
|
"playwright:install": "playwright install chromium firefox webkit",
|
||||||
"playwright:wait:dev": "wait-on -i 3000 http://localhost:8000/static/_css/tailwind.compiled.css",
|
"playwright:wait:dev": "wait-on -i 3000 http://localhost:8000/static/_css/tailwind.compiled.css",
|
||||||
"playwright:percy": "run-p --race server playwright:visual",
|
"playwright:percy": "run-p --race server playwright:visual",
|
||||||
"playwright:urls": "wait-on -i 3000 http://localhost:8000/cms && playwright test ./tests/urls.spec.js",
|
"playwright:urls": "wait-on -i 3000 -t 120000 -v http://127.0.0.1:8000/cms && playwright test ./tests/urls.spec.js",
|
||||||
"playwright:visual": "wait-on -i 3000 http://localhost:8000/cms && playwright test ./tests/visual.spec.js",
|
"playwright:visual": "wait-on -i 3000 -t 120000 -v http://127.0.0.1:8000/cms && playwright test ./tests/visual.spec.js",
|
||||||
"playwright:ci": "run-p --race server playwright:integration",
|
"playwright:ci": "run-p --race server playwright:integration",
|
||||||
"playwright:integration": "wait-on -i 3000 http://localhost:8000/cms && playwright test ./tests/integration.spec.js",
|
"playwright:integration": "wait-on -i 3000 -t 120000 -v http://127.0.0.1:8000/cms && playwright test ./tests/integration.spec.js",
|
||||||
"precommit": "npm run test",
|
"precommit": "npm run test",
|
||||||
"prettier:js": "prettier \"source/js/**/*.js\" \"source/js/**/*.jsx\" \"network-api/networkapi/wagtailcustomization/**/*.js\" \"tests/**/*.js\" ./*.js",
|
"prettier:js": "prettier \"source/js/**/*.js\" \"source/js/**/*.jsx\" \"network-api/networkapi/wagtailcustomization/**/*.js\" \"tests/**/*.js\" ./*.js",
|
||||||
"prettier:scss": "prettier \"source/sass/**/*.scss\" \"source/js/**/*.scss\" \"network-api/networkapi/{,!(frontend)/**/}*.css\"",
|
"prettier:scss": "prettier \"source/sass/**/*.scss\" \"source/js/**/*.scss\" \"network-api/networkapi/{,!(frontend)/**/}*.css\"",
|
||||||
|
@ -81,28 +81,28 @@
|
||||||
"axe-core": "^4.1.4",
|
"axe-core": "^4.1.4",
|
||||||
"bootstrap": "^4.6.0",
|
"bootstrap": "^4.6.0",
|
||||||
"chart.js": "3.5.1",
|
"chart.js": "3.5.1",
|
||||||
"classnames": "2.3.1",
|
"classnames": "2.3.2",
|
||||||
"countup.js": "^2.0.7",
|
"countup.js": "^2.3.2",
|
||||||
"cssnano": "^5.0.8",
|
"cssnano": "^5.0.8",
|
||||||
"esbuild": "^0.12.24",
|
"esbuild": "^0.12.24",
|
||||||
"event-stream": "3.3.4",
|
"event-stream": "3.3.4",
|
||||||
"gsap": "^3.9.1",
|
"gsap": "^3.9.1",
|
||||||
"htmx.org": "^1.8.0",
|
"htmx.org": "^1.8.0",
|
||||||
"locomotive-scroll": "^4.1.4",
|
"locomotive-scroll": "^4.1.4",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.4",
|
||||||
"npm-run-all": "^4.1.3",
|
"npm-run-all": "^4.1.3",
|
||||||
"postcss": "^8.4.5",
|
"postcss": "^8.4.21",
|
||||||
"postcss-cli": "^8.3.1",
|
"postcss-cli": "^8.3.1",
|
||||||
"postcss-import": "^14.0.2",
|
"postcss-import": "^14.0.2",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-axe": "^3.5.4",
|
"react-axe": "^3.5.4",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-ga": "3.3.0",
|
"react-ga": "3.3.1",
|
||||||
"sass": "^1.38.2",
|
"sass": "^1.38.2",
|
||||||
"shx": "^0.3.3",
|
"shx": "^0.3.3",
|
||||||
"swiper": "^6.8.4",
|
"swiper": "^6.8.4",
|
||||||
"tailwindcss": "^3.2.1",
|
"tailwindcss": "^3.2.4",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"whatwg-fetch": "^3.6.2"
|
"whatwg-fetch": "^3.6.2"
|
||||||
},
|
},
|
||||||
|
@ -111,19 +111,19 @@
|
||||||
"@percy/playwright": "^1.0.1",
|
"@percy/playwright": "^1.0.1",
|
||||||
"@playwright/test": "^1.17.0",
|
"@playwright/test": "^1.17.0",
|
||||||
"browser-sync": "^2.27.5",
|
"browser-sync": "^2.27.5",
|
||||||
"browserslist": "^4.17.0",
|
"browserslist": "^4.21.4",
|
||||||
"chokidar-cli": "^2.1.0",
|
"chokidar-cli": "^2.1.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||||
"eslint-plugin-prettier": "^3.3.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-react": "^7.25.1",
|
"eslint-plugin-react": "^7.25.1",
|
||||||
"optipng-bin": "^7.0.0",
|
"optipng-bin": "^9.0.0",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"stylelint": "^13.13.1",
|
"stylelint": "^13.13.1",
|
||||||
"stylelint-config-standard": "^20.0.0",
|
"stylelint-config-standard": "^20.0.0",
|
||||||
"stylelint-prettier": "^1.2.0",
|
"stylelint-prettier": "^1.2.0",
|
||||||
"svgo": "^2.5.0",
|
"svgo": "^2.5.0",
|
||||||
"wait-on": "^5.2.1"
|
"wait-on": "^6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ cached_property==1.5.2
|
||||||
Django==3.2.16
|
Django==3.2.16
|
||||||
dj-database-url
|
dj-database-url
|
||||||
djangorestframework
|
djangorestframework
|
||||||
django-admin-sortable==2.2.3
|
django-admin-sortable==2.3
|
||||||
django-cors-headers
|
django-cors-headers
|
||||||
django_csp
|
django_csp
|
||||||
django-environ
|
django-environ
|
||||||
|
@ -29,7 +29,7 @@ wagtail==2.16.3
|
||||||
wagtail-color-panel
|
wagtail-color-panel
|
||||||
wagtail-factories
|
wagtail-factories
|
||||||
wagtail-localize==1.1
|
wagtail-localize==1.1
|
||||||
wagtail-localize-git==0.12.0
|
wagtail-localize-git==0.13.0
|
||||||
wagtail-inventory
|
wagtail-inventory
|
||||||
wagtailmedia
|
wagtailmedia
|
||||||
wagtail-metadata
|
wagtail-metadata
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile with Python 3.9
|
# This file is autogenerated by pip-compile with python 3.9
|
||||||
# by the following command:
|
# To update, run:
|
||||||
#
|
#
|
||||||
# pip-compile
|
# pip-compile
|
||||||
#
|
#
|
||||||
|
@ -68,9 +68,9 @@ django==3.2.16
|
||||||
# wagtail-footnotes
|
# wagtail-footnotes
|
||||||
# wagtail-localize
|
# wagtail-localize
|
||||||
# wagtail-localize-git
|
# wagtail-localize-git
|
||||||
django-admin-sortable==2.2.3
|
django-admin-sortable==2.3
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-cors-headers==3.11.0
|
django-cors-headers==3.13.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-csp==3.7
|
django-csp==3.7
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
@ -135,7 +135,7 @@ jmespath==0.10.0
|
||||||
# botocore
|
# botocore
|
||||||
l18n==2021.3
|
l18n==2021.3
|
||||||
# via wagtail
|
# via wagtail
|
||||||
oauthlib==3.2.0
|
oauthlib==3.2.1
|
||||||
# via
|
# via
|
||||||
# requests-oauthlib
|
# requests-oauthlib
|
||||||
# social-auth-core
|
# social-auth-core
|
||||||
|
@ -244,7 +244,7 @@ wagtail==2.16.3
|
||||||
# wagtailmedia
|
# wagtailmedia
|
||||||
wagtail-airtable==0.2.1
|
wagtail-airtable==0.2.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
wagtail-color-panel==1.3.1
|
wagtail-color-panel==1.4.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
wagtail-factories==2.0.1
|
wagtail-factories==2.0.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
@ -256,7 +256,7 @@ wagtail-localize==1.1
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# wagtail-localize-git
|
# wagtail-localize-git
|
||||||
wagtail-localize-git==0.12.0
|
wagtail-localize-git==0.13.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
wagtail-metadata==3.5.0
|
wagtail-metadata==3.5.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 98 KiB |
|
@ -13,7 +13,7 @@ import injectMultipageNav from "../multipage-nav.js";
|
||||||
import primaryNav from "../primary-nav.js";
|
import primaryNav from "../primary-nav.js";
|
||||||
|
|
||||||
import HomepageSlider from "./homepage-c-slider.js";
|
import HomepageSlider from "./homepage-c-slider.js";
|
||||||
import RelatedArticles from "./related-articles.js";
|
import NewsletterBox from "./newsletter-box.js";
|
||||||
import AnalyticsEvents from "./analytics-events.js";
|
import AnalyticsEvents from "./analytics-events.js";
|
||||||
import initializeSentry from "../common/sentry-config.js";
|
import initializeSentry from "../common/sentry-config.js";
|
||||||
import PNIMobileNav from "./pni-mobile-nav.js";
|
import PNIMobileNav from "./pni-mobile-nav.js";
|
||||||
|
@ -114,9 +114,7 @@ let main = {
|
||||||
if (document.querySelector(`body.pni.catalog`)) {
|
if (document.querySelector(`body.pni.catalog`)) {
|
||||||
HomepageSlider.init();
|
HomepageSlider.init();
|
||||||
}
|
}
|
||||||
if (document.querySelector("#view-article")) {
|
NewsletterBox.toggleVisibilityClasses();
|
||||||
RelatedArticles.floatRelatedArticlesNextToThirdElement();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
const NewsletterBox = {
|
||||||
|
toggleVisibilityClasses: () => {
|
||||||
|
const buyersGuideNewsletterBox = document.querySelector(
|
||||||
|
"#buyersguide-newsletter-box"
|
||||||
|
);
|
||||||
|
// Used for toggle visibility for buyersguide newsletter container
|
||||||
|
if (buyersGuideNewsletterBox) {
|
||||||
|
buyersGuideNewsletterBox.classList.remove("tw-hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for buyersguide product review grid toggle visibility
|
||||||
|
const buyersGuideGridContainer = document.querySelector(
|
||||||
|
"#product-grid-newsletter-signup"
|
||||||
|
);
|
||||||
|
if (buyersGuideGridContainer) {
|
||||||
|
buyersGuideGridContainer.classList.add("tw-flex");
|
||||||
|
buyersGuideGridContainer.classList.remove("tw-hidden");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,28 +0,0 @@
|
||||||
const RelatedArticles = {
|
|
||||||
floatRelatedArticlesNextToThirdElement: () => {
|
|
||||||
const firstParagraph = document.querySelector(
|
|
||||||
".paragraph-block .rich-text"
|
|
||||||
);
|
|
||||||
const relatedContainer = document.querySelector(
|
|
||||||
"#article-primary-related-articles"
|
|
||||||
);
|
|
||||||
if (!firstParagraph || !relatedContainer) return;
|
|
||||||
const relatedContent = relatedContainer.querySelector("div");
|
|
||||||
/*
|
|
||||||
* if there is at least 3 children inside the rich body container.
|
|
||||||
* Usually there is a header(h1-6) and a few paragraphs blocks to start most articles.
|
|
||||||
* If we do not have at least that many elements we leave the related articles at the bottom of the article page
|
|
||||||
*/
|
|
||||||
const relatedPlace = firstParagraph.querySelector("*:nth-child(3)");
|
|
||||||
if (!relatedPlace) return;
|
|
||||||
firstParagraph.insertBefore(relatedContent, relatedPlace);
|
|
||||||
relatedContent.classList.add(
|
|
||||||
"large:tw-float-right",
|
|
||||||
"tw-p-4",
|
|
||||||
"tw-pr-0",
|
|
||||||
"large:tw-mr-[-20%]"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RelatedArticles;
|
|
|
@ -71,15 +71,7 @@ class DonationModal extends Component {
|
||||||
|
|
||||||
getModalContent() {
|
getModalContent() {
|
||||||
if (!this.donateURL) {
|
if (!this.donateURL) {
|
||||||
let base = `https://donate.mozilla.org/?`,
|
this.donateURL = `?form=donate`;
|
||||||
query = [
|
|
||||||
`utm_source=foundation.mozilla.org`,
|
|
||||||
`utm_medium=petitionmodal`,
|
|
||||||
`utm_campaign=${this.props.slug}`,
|
|
||||||
`utm_content=${this.props.name}`,
|
|
||||||
].join(`&`);
|
|
||||||
|
|
||||||
this.donateURL = `${base}${query}`;
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="modal-content" role="dialog">
|
<div className="modal-content" role="dialog">
|
||||||
|
@ -107,7 +99,6 @@ class DonationModal extends Component {
|
||||||
ref={(e) => (this.userElectedToDonateLink = e)}
|
ref={(e) => (this.userElectedToDonateLink = e)}
|
||||||
className="tw-btn-primary"
|
className="tw-btn-primary"
|
||||||
href={this.donateURL}
|
href={this.donateURL}
|
||||||
target="_blank"
|
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
>
|
>
|
||||||
{this.props.donateText}
|
{this.props.donateText}
|
||||||
|
|
2
tasks.py
2
tasks.py
|
@ -203,7 +203,7 @@ def npm(ctx, command):
|
||||||
def npm_install(ctx):
|
def npm_install(ctx):
|
||||||
"""Install Node dependencies"""
|
"""Install Node dependencies"""
|
||||||
with ctx.cd(ROOT):
|
with ctx.cd(ROOT):
|
||||||
ctx.run("docker-compose run --rm watch-static-files npm install")
|
ctx.run("docker-compose run --rm watch-static-files npm ci")
|
||||||
|
|
||||||
|
|
||||||
@task(aliases=["copy-stage-db"])
|
@task(aliases=["copy-stage-db"])
|
||||||
|
|
Загрузка…
Ссылка в новой задаче