├── .nvmrc ├── ietf ├── __init__.py ├── blog │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0008_alter_blogpageauthor_author.py │ │ ├── 0002_auto_20210325_0442.py │ │ ├── 0003_auto_20211101_0113.py │ │ └── 0004_alter_blogpage_body.py │ ├── js │ │ └── index.js │ ├── factories.py │ └── templates │ │ ├── includes │ │ ├── blog_authors.html │ │ └── blog_sidebar.html │ │ └── blog │ │ ├── blog_index_by_author.html │ │ └── blog_index_page.html ├── events │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0003_auto_20210704_2343.py │ │ ├── 0002_auto_20210325_0442.py │ │ └── 0004_auto_20211101_0113.py │ ├── templates │ │ ├── events │ │ │ └── includes │ │ │ │ └── link_block.html │ │ └── includes │ │ │ └── key_detail_section.html │ ├── styles │ │ ├── event_listing_page.scss │ │ └── event_page.scss │ ├── factories.py │ └── tests.py ├── forms │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0004_convert_unicode_to_text.py │ │ ├── 0002_formfield_clean_name.py │ │ └── 0003_auto_20220722_0302.py │ ├── templatetags │ │ ├── __init__.py │ │ └── form_tags.py │ ├── styles │ │ └── forms.scss │ ├── templates │ │ └── forms │ │ │ ├── form_page_landing.html │ │ │ └── form_page.html │ ├── factories.py │ ├── tests.py │ └── models.py ├── health │ ├── __init__.py │ ├── views.py │ └── tests.py ├── home │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0003_remove_bottom_content.py │ │ └── 0002_iabhomepage.py │ ├── wagtail_hooks.py │ ├── factories.py │ ├── templates │ │ └── includes │ │ │ ├── post_iab.html │ │ │ ├── announcement_iab.html │ │ │ └── home_event.html │ └── styles │ │ └── home.scss ├── images │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_alter_ietfimage_file_hash.py │ │ ├── 0005_ietfimage_description.py │ │ ├── 0004_django_42_rendition_storage.py │ │ └── 0003_wagtail_42_wagtailimagefield.py │ └── models.py ├── search │ ├── __init__.py │ └── views.py ├── topics │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── templates │ │ └── topics │ │ │ ├── styles │ │ │ └── topics_page.scss │ │ │ └── topic_index_page.html │ ├── factories.py │ └── test.py ├── utils │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0004_alter_menuitem_options.py │ │ ├── 0003_auto_20211105_0019.py │ │ ├── 0006_textchunk.py │ │ ├── 0005_layoutsettings.py │ │ ├── 0007_auto_20230524_0551.py │ │ └── 0008_socialmediasettings_github_and_more.py │ ├── templatetags │ │ ├── __init__.py │ │ └── ietf_tags.py │ ├── templates │ │ ├── previews │ │ │ ├── footer_column.html │ │ │ └── main_menu_item.html │ │ └── blocks │ │ │ └── note_well_block.html │ ├── static │ │ └── utils │ │ │ └── css │ │ │ └── page_editor.css │ ├── apps.py │ ├── factories.py │ ├── tests │ │ ├── test_500_page.py │ │ ├── test_iab_main_menu.py │ │ └── test_secondary_menu.py │ ├── signal_handlers.py │ ├── management │ │ └── commands │ │ │ └── update_nonprod_hostnames.py │ └── wagtail_hooks.py ├── documents │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_wagtail_upgrade_peturbations.py │ │ └── 0001_initial.py │ ├── apps.py │ ├── models.py │ └── templates │ │ └── wagtaildocs │ │ └── documents │ │ └── list.html ├── glossary │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── factories.py │ └── tests.py ├── settings │ ├── __init__.py │ ├── docker │ │ ├── dev.py │ │ ├── base.py │ │ ├── grains │ │ │ ├── database.py │ │ │ └── logging.py │ │ └── __init__.py │ └── dev.py ├── snippets │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0003_alter_workinggroup_list_subscribe.py │ │ ├── 0004_merge_20231215_0352.py │ │ ├── 0003_person_slug.py │ │ └── 0002_auto_20200414_2027.py │ ├── templates │ │ └── snippets │ │ │ ├── styles │ │ │ ├── index.scss │ │ │ └── mailing_list_signup.scss │ │ │ ├── call_to_action.html │ │ │ ├── group.html │ │ │ ├── disclaimer.html │ │ │ ├── mailing_list_signup.html │ │ │ ├── working_group.html │ │ │ ├── rfc.html │ │ │ ├── area_charter.html │ │ │ └── includes │ │ │ └── _results.html │ ├── urls.py │ ├── tests │ │ ├── test_charter.py │ │ └── test_mailing_list_signup.py │ ├── views.py │ └── factories.py ├── standard │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── templatetags │ │ ├── __init__.py │ │ └── has_tabs.py │ ├── factories.py │ └── tests.py ├── static │ └── img │ │ ├── .gitkeep │ │ ├── buenos.jpg │ │ ├── Belfast.jpg │ │ ├── Florence.jpg │ │ ├── Kathleen.gif │ │ ├── group-01.jpg │ │ ├── ietf-logo.gif │ │ ├── yokohama.jpg │ │ ├── yokohama2.jpg │ │ ├── buenos-big.jpg │ │ ├── sponsors │ │ ├── bbt.jpg │ │ ├── ctc.jpg │ │ ├── iij.jpg │ │ ├── intec.jpg │ │ ├── jpnic.jpg │ │ ├── jprs.png │ │ ├── kddi.png │ │ ├── nec.jpg │ │ ├── nttc.jpg │ │ ├── devnet.jpg │ │ ├── equinix.png │ │ ├── extreme.png │ │ ├── fujitsu.jpg │ │ ├── hitachi.jpg │ │ ├── huawei.jpg │ │ ├── netone.jpg │ │ ├── otsuka.png │ │ ├── sakura.jpg │ │ ├── toshiba.jpg │ │ ├── WIDE_logo.png │ │ └── softbank.jpg │ │ ├── background-dark.jpg │ │ ├── stephen-farrell.gif │ │ ├── cropped-iab-fav-32x32.png │ │ ├── cropped-iab-fav-180x180.png │ │ ├── cropped-iab-fav-192x192.png │ │ ├── twitter-icon.svg │ │ ├── linkedin-icon.svg │ │ ├── youtube-icon.svg │ │ ├── mastodon-icon.svg │ │ ├── mag.svg │ │ ├── IRTF.svg │ │ ├── routing.svg │ │ ├── operations.svg │ │ └── iab-logo.svg ├── announcements │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── factories.py │ └── templates │ │ └── announcements │ │ ├── iab_announcement_page.html │ │ └── iab_announcement_index_page.html ├── iesg_statement │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_auto_20210325_0442.py │ │ ├── 0003_auto_20211101_0113.py │ │ └── 0004_alter_iesgstatementpage_body.py │ ├── factories.py │ └── templates │ │ └── iesg_statement │ │ └── iesg_statement_index_page.html ├── bibliography │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ └── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── templatetags │ │ ├── __init__.py │ │ └── bibliography.py │ ├── templates │ │ └── bibliography │ │ │ ├── item_charter.html │ │ │ ├── item_internetdraft.html │ │ │ ├── item_glossaryitem.html │ │ │ ├── item_rfc.html │ │ │ ├── referencing_pages.html │ │ │ ├── referenced_types.html │ │ │ ├── referenced_objects.html │ │ │ └── bibliography.html │ ├── apps.py │ ├── __init__.py │ ├── styles │ │ └── bibliography.scss │ ├── wagtail_hooks.py │ ├── urls.py │ └── views.py ├── static_src │ ├── js │ │ ├── pages.js │ │ ├── public-path.js │ │ └── init.js │ ├── css │ │ ├── icons.scss │ │ ├── no-js.scss │ │ ├── focus.scss │ │ ├── typography.scss │ │ ├── bs-configure.scss │ │ ├── images.scss │ │ ├── pages.scss │ │ ├── utilities.scss │ │ ├── custom-functions.scss │ │ ├── iab-colors.scss │ │ ├── main.scss │ │ ├── bs-override.scss │ │ ├── datepicker.scss │ │ └── fonts.scss │ └── index.js ├── templates │ ├── includes │ │ ├── social_fields.html │ │ ├── optional-introduction.html │ │ ├── imageblock.html │ │ ├── streamfield.html │ │ ├── children_in_this_section.html │ │ ├── related_links.html │ │ ├── styles │ │ │ ├── footer.scss │ │ │ └── index.scss │ │ ├── breadcrumbs.html │ │ ├── highlight.html │ │ ├── note_well.html │ │ ├── row_siblings_in_section.html │ │ └── social_share.html │ ├── blocks │ │ └── float_block.html │ └── 500.html ├── views.py ├── wsgi.py ├── .editorconfig ├── conftest.py ├── context_processors.py └── urls.py ├── media └── .gitkeep ├── docker ├── database │ ├── .gitkeep │ ├── 01_continue_on_error.sh │ └── 02_convert_native_dump_to_sql_and_restore.sh ├── dev.env ├── init.sh ├── init-dev.sh ├── scripts │ └── db-import.sh ├── init-test.sh ├── supervisord-sandbox.conf ├── nginx-sandbox.conf ├── gunicorn.py └── db.Dockerfile ├── .eslintignore ├── .prettierignore ├── requirements ├── docker.in ├── dev.in ├── compile ├── base.in └── docker.txt ├── dev └── deploy-to-container │ ├── .npmrc │ ├── .editorconfig │ ├── package.json │ └── README.md ├── bin ├── hourly ├── monthly ├── weekly └── daily ├── deploy.sh ├── .babelrc ├── .browserslistrc ├── .prettierrc ├── manage.py ├── .gitignore ├── .eslintrc.js ├── .dockerignore ├── k8s ├── iabweb │ ├── kustomization.yaml │ ├── supervisord.conf │ ├── secrets.yaml │ ├── nginx.conf │ ├── nginx-default.conf │ └── cron.yaml └── ietfweb │ ├── supervisord.conf │ ├── kustomization.yaml │ ├── secrets.yaml │ ├── nginx.conf │ └── cron.yaml ├── tsconfig.json ├── .github └── workflows │ ├── ci-pre-commit.yml │ ├── accessibility-test.yml │ └── ci-run-tests.yml ├── pyproject.toml ├── .pre-commit-config.yaml ├── db_reset.sh ├── webpack.fix-django-paths.js ├── docker-compose.yml └── LICENSE /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /ietf/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /media/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/blog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/events/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/forms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/health/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/home/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/images/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/search/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/topics/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker/database/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/documents/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/glossary/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/snippets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/standard/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/static/img/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/announcements/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/blog/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/forms/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/home/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/iesg_statement/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/utils/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/documents/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/events/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/forms/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/glossary/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/images/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/snippets/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/standard/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/standard/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/topics/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/utils/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/announcements/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/bibliography/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/bibliography/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/bibliography/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ietf/iesg_statement/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | public/ 2 | node_modules/ 3 | 4 | -------------------------------------------------------------------------------- /ietf/bibliography/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.html 2 | **/*.ss 3 | **/*.yml 4 | -------------------------------------------------------------------------------- /ietf/static_src/js/pages.js: -------------------------------------------------------------------------------- 1 | import '../../blog/js/index'; 2 | -------------------------------------------------------------------------------- /requirements/docker.in: -------------------------------------------------------------------------------- 1 | -c base.txt 2 | 3 | gunicorn>=20.1.0 4 | -------------------------------------------------------------------------------- /docker/database/01_continue_on_error.sh: -------------------------------------------------------------------------------- 1 | psql+=( -v ON_ERROR_STOP=0 ) 2 | -------------------------------------------------------------------------------- /ietf/templates/includes/social_fields.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dev/deploy-to-container/.npmrc: -------------------------------------------------------------------------------- 1 | audit = false 2 | fund = false 3 | save-exact = true 4 | -------------------------------------------------------------------------------- /ietf/static_src/css/icons.scss: -------------------------------------------------------------------------------- 1 | @import '~bootstrap-icons/font/bootstrap-icons.min.css'; 2 | -------------------------------------------------------------------------------- /ietf/templates/blocks/float_block.html: -------------------------------------------------------------------------------- 1 | {% if value is not None %}{{ value }}{% endif %} 2 | -------------------------------------------------------------------------------- /ietf/static_src/css/no-js.scss: -------------------------------------------------------------------------------- 1 | .no-js .no-js-hide { 2 | display: none !important; 3 | } 4 | -------------------------------------------------------------------------------- /bin/hourly: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Commands here will be executed hourly by cron on the host system 4 | -------------------------------------------------------------------------------- /bin/monthly: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Commands here will be executed monthly by cron on the host system 4 | -------------------------------------------------------------------------------- /ietf/static/img/buenos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/buenos.jpg -------------------------------------------------------------------------------- /ietf/static/img/Belfast.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/Belfast.jpg -------------------------------------------------------------------------------- /ietf/static/img/Florence.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/Florence.jpg -------------------------------------------------------------------------------- /ietf/static/img/Kathleen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/Kathleen.gif -------------------------------------------------------------------------------- /ietf/static/img/group-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/group-01.jpg -------------------------------------------------------------------------------- /ietf/static/img/ietf-logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/ietf-logo.gif -------------------------------------------------------------------------------- /ietf/static/img/yokohama.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/yokohama.jpg -------------------------------------------------------------------------------- /ietf/static/img/yokohama2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/yokohama2.jpg -------------------------------------------------------------------------------- /ietf/bibliography/templates/bibliography/item_charter.html: -------------------------------------------------------------------------------- 1 |

{{ object.abstract|truncatechars:300 }}

2 | -------------------------------------------------------------------------------- /ietf/static/img/buenos-big.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/buenos-big.jpg -------------------------------------------------------------------------------- /ietf/utils/templates/previews/footer_column.html: -------------------------------------------------------------------------------- 1 | {% extends settings.utils.LayoutSettings.base_template %} 2 | -------------------------------------------------------------------------------- /ietf/utils/templates/previews/main_menu_item.html: -------------------------------------------------------------------------------- 1 | {% extends settings.utils.LayoutSettings.base_template %} 2 | -------------------------------------------------------------------------------- /docker/dev.env: -------------------------------------------------------------------------------- 1 | APP_SECRET_KEY=xxxx 2 | DATABASE_URL=postgres://postgres:password@database/app 3 | ADDRESSES="*" 4 | -------------------------------------------------------------------------------- /ietf/bibliography/templates/bibliography/item_internetdraft.html: -------------------------------------------------------------------------------- 1 |

{{ object.abstract|truncatechars:300 }}

2 | -------------------------------------------------------------------------------- /ietf/static/img/sponsors/bbt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/bbt.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/ctc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/ctc.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/iij.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/iij.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/intec.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/intec.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/jpnic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/jpnic.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/jprs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/jprs.png -------------------------------------------------------------------------------- /ietf/static/img/sponsors/kddi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/kddi.png -------------------------------------------------------------------------------- /ietf/static/img/sponsors/nec.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/nec.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/nttc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/nttc.jpg -------------------------------------------------------------------------------- /ietf/static/img/background-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/background-dark.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/devnet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/devnet.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/equinix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/equinix.png -------------------------------------------------------------------------------- /ietf/static/img/sponsors/extreme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/extreme.png -------------------------------------------------------------------------------- /ietf/static/img/sponsors/fujitsu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/fujitsu.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/hitachi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/hitachi.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/huawei.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/huawei.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/netone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/netone.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/otsuka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/otsuka.png -------------------------------------------------------------------------------- /ietf/static/img/sponsors/sakura.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/sakura.jpg -------------------------------------------------------------------------------- /ietf/static/img/sponsors/toshiba.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/toshiba.jpg -------------------------------------------------------------------------------- /ietf/static/img/stephen-farrell.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/stephen-farrell.gif -------------------------------------------------------------------------------- /ietf/settings/docker/dev.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa: F403 2 | 3 | DEBUG = True 4 | CACHE_MIDDLEWARE_ALIAS = "dummy" 5 | -------------------------------------------------------------------------------- /ietf/static/img/sponsors/WIDE_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/WIDE_logo.png -------------------------------------------------------------------------------- /ietf/static/img/sponsors/softbank.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/sponsors/softbank.jpg -------------------------------------------------------------------------------- /ietf/health/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | 3 | 4 | def healthz(request): 5 | return HttpResponse("OK") 6 | -------------------------------------------------------------------------------- /ietf/snippets/templates/snippets/styles/index.scss: -------------------------------------------------------------------------------- 1 | // Styles for snippet templates. 2 | @import './mailing_list_signup.scss'; 3 | -------------------------------------------------------------------------------- /ietf/static/img/cropped-iab-fav-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/cropped-iab-fav-32x32.png -------------------------------------------------------------------------------- /bin/weekly: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Commands here will be executed hourly by cron on the host system 4 | 5 | /code/manage.py update_index 6 | -------------------------------------------------------------------------------- /ietf/static/img/cropped-iab-fav-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/cropped-iab-fav-180x180.png -------------------------------------------------------------------------------- /ietf/static/img/cropped-iab-fav-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ietf-tools/www/HEAD/ietf/static/img/cropped-iab-fav-192x192.png -------------------------------------------------------------------------------- /bin/daily: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Commands here will be executed daily by cron on the host system 4 | 5 | /code/manage.py datatracker_import 6 | -------------------------------------------------------------------------------- /ietf/bibliography/templates/bibliography/item_glossaryitem.html: -------------------------------------------------------------------------------- 1 | {% load wagtailcore_tags %} 2 | 3 |

{{ object.body|striptags }}

4 | -------------------------------------------------------------------------------- /ietf/documents/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DocumentsConfig(AppConfig): 5 | name = "ietf.documents" 6 | -------------------------------------------------------------------------------- /ietf/events/templates/events/includes/link_block.html: -------------------------------------------------------------------------------- 1 | {% if link %}{{ title }}{% else %}{{ title }}{% endif %} 2 | -------------------------------------------------------------------------------- /ietf/bibliography/templates/bibliography/item_rfc.html: -------------------------------------------------------------------------------- 1 |

{{ object.abstract|truncatechars:300 }}

2 |

{{ object.author_names|join:", " }}

3 | -------------------------------------------------------------------------------- /ietf/forms/styles/forms.scss: -------------------------------------------------------------------------------- 1 | .forms__fieldset_legend { 2 | font-size: inherit; 3 | display: inline-block; 4 | margin-bottom: 0; 5 | } 6 | -------------------------------------------------------------------------------- /requirements/dev.in: -------------------------------------------------------------------------------- 1 | -c base.txt 2 | 3 | black 4 | pip-tools 5 | pre-commit 6 | pytest-cov 7 | pytest-django 8 | ruff 9 | wagtail-factories 10 | -------------------------------------------------------------------------------- /ietf/settings/docker/base.py: -------------------------------------------------------------------------------- 1 | from . import * # noqa: F403 2 | from .grains.database import * # noqa: F403 3 | from .grains.logging import * # noqa: F403 4 | -------------------------------------------------------------------------------- /ietf/topics/templates/topics/styles/topics_page.scss: -------------------------------------------------------------------------------- 1 | .topic-list__item { 2 | max-width: 920px; 3 | margin-left: auto; 4 | margin-right: auto; 5 | } 6 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | cd $(dirname $(echo $0)) 4 | 5 | python manage.py collectstatic --no-input 6 | python manage.py migrate --no-input 7 | python manage.py update_index 8 | -------------------------------------------------------------------------------- /docker/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Running migrate..." 4 | python /app/manage.py migrate 5 | 6 | echo "Starting supervisor..." 7 | /usr/bin/supervisord -c /app/supervisord.conf 8 | -------------------------------------------------------------------------------- /ietf/settings/docker/grains/database.py: -------------------------------------------------------------------------------- 1 | import dj_database_url 2 | 3 | from .. import DATABASE_URL 4 | 5 | DATABASES = {"default": dj_database_url.parse(DATABASE_URL, conn_max_age=600)} 6 | -------------------------------------------------------------------------------- /dev/deploy-to-container/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_size = 2 3 | indent_style = space 4 | charset = utf-8 5 | trim_trailing_whitespace = false 6 | end_of_line = lf 7 | insert_final_newline = true -------------------------------------------------------------------------------- /ietf/bibliography/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BibliographyAppConfig(AppConfig): 5 | name = "ietf.bibliography" 6 | verbose_name = "Bibliography items" 7 | -------------------------------------------------------------------------------- /ietf/documents/models.py: -------------------------------------------------------------------------------- 1 | from wagtail.documents.models import Document 2 | 3 | 4 | class IetfDocument(Document): 5 | 6 | @property 7 | def url(self): 8 | return self.file.url 9 | -------------------------------------------------------------------------------- /ietf/snippets/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import re_path 2 | 3 | from .views import disclaimer 4 | 5 | urlpatterns = [ 6 | re_path(r"^disclaimer/(\d+)/$", disclaimer, name="disclaimer"), 7 | ] 8 | -------------------------------------------------------------------------------- /ietf/events/styles/event_listing_page.scss: -------------------------------------------------------------------------------- 1 | .event_listing_page__promoted-image { 2 | max-height: 6rem; 3 | 4 | @include media-breakpoint-up(md) { 5 | max-height: none; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ietf/static_src/css/focus.scss: -------------------------------------------------------------------------------- 1 | *:focus { 2 | outline: 3px solid #b53cde !important; 3 | outline-offset: 3px; 4 | 5 | .using-mouse & { 6 | outline: none !important; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docker/init-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | python /app/manage.py migrate --no-input 4 | python /app/manage.py createcachetable 5 | exec /usr/local/bin/gunicorn --config /app/docker/gunicorn.py --reload ietf.wsgi 6 | -------------------------------------------------------------------------------- /ietf/utils/templates/blocks/note_well_block.html: -------------------------------------------------------------------------------- 1 | {% load wagtailcore_tags %} 2 | 3 | {% block content %} 4 | {% include "includes/note_well.html" with note_well_git_url=note_well_git_url %} 5 | {% endblock %} -------------------------------------------------------------------------------- /requirements/compile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | cd "$( dirname "${BASH_SOURCE[0]}" )" 5 | 6 | set -x 7 | pip-compile base.in "$@" 8 | pip-compile docker.in "$@" 9 | pip-compile dev.in "$@" 10 | -------------------------------------------------------------------------------- /ietf/standard/templatetags/has_tabs.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | 6 | @register.simple_tag 7 | def has_tabs(key_info, in_depth): 8 | return bool(key_info and in_depth) 9 | -------------------------------------------------------------------------------- /ietf/bibliography/__init__.py: -------------------------------------------------------------------------------- 1 | # TODO: 2 | # X bibliography item model with generic foreign key 3 | # item rendering method 4 | # X model mixin with pre-parser method 5 | # item rendering template tag 6 | # itemS rendering template tag 7 | -------------------------------------------------------------------------------- /ietf/static_src/css/typography.scss: -------------------------------------------------------------------------------- 1 | blockquote { 2 | color: #666666; 3 | border-left: 5px solid #bebebe; 4 | padding-left: 1rem; 5 | font-weight: 500; 6 | 7 | p:last-child { 8 | padding-bottom: 0; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ietf/forms/templates/forms/form_page_landing.html: -------------------------------------------------------------------------------- 1 | {% extends settings.utils.LayoutSettings.base_template %} 2 | {% load wagtailcore_tags %} 3 | 4 | {% block content %} 5 |
{{ self.thank_you_text|richtext }}
6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "entry", 7 | "corejs": "3.6.5", 8 | "modules": false 9 | } 10 | ], 11 | "@babel/preset-typescript" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # https://github.com/browserslist/browserslist#readme 2 | 3 | >= 1% 4 | last 1 major version 5 | not dead 6 | Chrome >= 45 7 | Firefox >= 38 8 | Edge >= 12 9 | Explorer >= 10 10 | iOS >= 9 11 | Safari >= 9 12 | Android >= 4.4 13 | Opera >= 30 14 | -------------------------------------------------------------------------------- /ietf/blog/js/index.js: -------------------------------------------------------------------------------- 1 | $('#date_from-input').datepicker(); 2 | $('#date_to-input').datepicker(); 3 | $('#date_from-desktop-input').datepicker(); 4 | $('#date_to-desktop-input').datepicker(); 5 | $('.ui-datepicker-trigger').html(''); 6 | -------------------------------------------------------------------------------- /requirements/base.in: -------------------------------------------------------------------------------- 1 | dj-database-url 2 | django>=5.2,<5.3 3 | django_analytical 4 | html5lib 5 | psycopg2 6 | pymemcache 7 | tqdm 8 | typed-environment-configuration>=0.1.3,<0.2 9 | wagtail-markdown 10 | wagtail-modeladmin 11 | wagtail-orderable 12 | wagtail>=7.0,<7.1 13 | -------------------------------------------------------------------------------- /ietf/static_src/index.js: -------------------------------------------------------------------------------- 1 | import './js/public-path'; // MUST be first (yes, before absolute imports) 2 | 3 | import './js/init'; 4 | import './js/datepicker'; 5 | import './js/pages'; 6 | 7 | /** Import Sass entry point for Webpack to bundle styles */ 8 | import './css/main.scss'; 9 | -------------------------------------------------------------------------------- /ietf/bibliography/styles/bibliography.scss: -------------------------------------------------------------------------------- 1 | .bibliography-reference::after { 2 | content: '[' attr(data-ordering) ']'; 3 | vertical-align: super; 4 | font-size: 60%; 5 | } 6 | 7 | .bibliography__order { 8 | vertical-align: super; 9 | font-size: 60%; 10 | } 11 | -------------------------------------------------------------------------------- /ietf/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponseServerError 2 | from django.template.loader import get_template 3 | 4 | 5 | def server_error(request, template_name="500.html"): 6 | t = get_template(template_name) 7 | return HttpResponseServerError(t.render(locals(), request)) 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 4, 4 | "semi": true, 5 | "singleQuote": true, 6 | "arrowParens": "always", 7 | "overrides": [ 8 | { 9 | "files": [".travis.yml", ".*"], 10 | "options": { "tabWidth": 2 } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /ietf/utils/static/utils/css/page_editor.css: -------------------------------------------------------------------------------- 1 | /* Make "Heading" blocks look like the h2 headings in the rich text area */ 2 | 3 | .blockname-heading .fieldname-heading input{ 4 | font-size: 2.5em; 5 | font-weight: 600; 6 | font-family: Roboto Slab, Georgia, serif; 7 | color: #666; 8 | } -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ietf.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /docker/scripts/db-import.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Drop dummy app DB if it exists..." 5 | dropdb -U postgres --if-exists app 6 | 7 | echo "Import DB dump into app..." 8 | pg_restore -xO --clean --if-exists --create -U "$POSTGRES_USER" -d postgres ietfwww.dump 9 | 10 | echo "Done!" 11 | -------------------------------------------------------------------------------- /dev/deploy-to-container/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deploy-to-container", 3 | "type": "module", 4 | "dependencies": { 5 | "dockerode": "4.0.5", 6 | "nanoid": "5.0.9", 7 | "slugify": "1.6.5", 8 | "yargs": "17.5.1" 9 | }, 10 | "engines": { 11 | "node": ">=16" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ietf/templates/includes/optional-introduction.html: -------------------------------------------------------------------------------- 1 | {% if value.introduction %} 2 |

{{ value.title }}

3 | {% else %} 4 |

{{ value.title }}

5 | {% endif %} 6 | 7 | {% if value.introduction %} 8 |

{{ value.introduction }}

9 | {% endif %} 10 | -------------------------------------------------------------------------------- /ietf/utils/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UtilsAppConfig(AppConfig): 5 | name = "ietf.utils" 6 | verbose_name = "IETF Website Utils" 7 | 8 | def ready(self): 9 | from .signal_handlers import register_signal_handlers 10 | 11 | register_signal_handlers() 12 | -------------------------------------------------------------------------------- /requirements/docker.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.12 3 | # by the following command: 4 | # 5 | # pip-compile docker.in 6 | # 7 | gunicorn==23.0.0 8 | # via -r docker.in 9 | packaging==25.0 10 | # via 11 | # -c /app/requirements/base.txt 12 | # gunicorn 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Be sure to update .dockerignore to match .gitignore 2 | /venv/ 3 | /static/ 4 | /media/ 5 | /ietf/templates/base.html 6 | /ietf/static/dist 7 | /ietf/settings/local.py 8 | /docker/database/*.gz 9 | *.pyc 10 | *.log 11 | *.swp 12 | .DS_Store 13 | .vscode 14 | .coverage 15 | node_modules 16 | coverage.xml 17 | -------------------------------------------------------------------------------- /ietf/utils/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | import wagtail_factories 3 | 4 | from . import blocks 5 | 6 | 7 | class StandardBlockFactory(wagtail_factories.StreamBlockFactory): 8 | heading = factory.SubFactory(wagtail_factories.CharBlockFactory) 9 | 10 | class Meta: 11 | model = blocks.StandardBlock 12 | -------------------------------------------------------------------------------- /ietf/static_src/css/bs-configure.scss: -------------------------------------------------------------------------------- 1 | $custom-spacers: ( 2 | 200pc: ( 3 | $spacer * 2, 4 | ), 5 | 250pc: ( 6 | $spacer * 2.5, 7 | ), 8 | 400pc: ( 9 | $spacer * 4, 10 | ), 11 | ); 12 | 13 | $spacers: map-merge($spacers, $custom-spacers); 14 | 15 | $enable-negative-margins: true; 16 | -------------------------------------------------------------------------------- /ietf/glossary/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | import wagtail_factories 3 | 4 | from .models import GlossaryPage 5 | 6 | 7 | class GlossaryPageFactory(wagtail_factories.PageFactory): 8 | title = factory.Faker("name") 9 | introduction = factory.Faker("paragraph") 10 | 11 | class Meta: # type: ignore 12 | model = GlossaryPage 13 | -------------------------------------------------------------------------------- /ietf/templates/includes/imageblock.html: -------------------------------------------------------------------------------- 1 | {% load wagtailimages_tags %}{% spaceless %} 2 | {% if value.caption %} 3 |
4 | {% image value original %} 5 |
{{ value.caption }}
6 |
7 | {% else %} 8 | {% image value original %} 9 | {% endif %} 10 | {% endspaceless %} 11 | -------------------------------------------------------------------------------- /ietf/static_src/css/images.scss: -------------------------------------------------------------------------------- 1 | .thumb-img { 2 | width: 150px; 3 | height: 150px; 4 | object-fit: cover; 5 | 6 | @include media-breakpoint-up(md) { 7 | width: 200px; 8 | height: 200px; 9 | } 10 | 11 | @include media-breakpoint-up(lg) { 12 | width: 270px; 13 | height: 270px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ietf/health/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import Client, TestCase 2 | 3 | 4 | class HealthTests(TestCase): 5 | def setUp(self): 6 | self.client = Client() 7 | 8 | def test_healthz(self): 9 | resp = self.client.get("/healthz") 10 | self.assertEqual(resp.status_code, 200) 11 | self.assertEqual(resp.content.decode(), "OK") 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint'], 5 | extends: [ 6 | 'eslint:recommended', 7 | 'plugin:@typescript-eslint/eslint-recommended', 8 | 'plugin:@typescript-eslint/recommended', 9 | 'plugin:prettier/recommended', 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /ietf/snippets/templates/snippets/call_to_action.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ snippet.title }}

3 | {% if snippet.blurb %}

{{ snippet.blurb }}

{% endif %} 4 | {{ snippet.button_text }} 5 |
6 | -------------------------------------------------------------------------------- /ietf/templates/includes/streamfield.html: -------------------------------------------------------------------------------- 1 | {% load wagtailcore_tags %} 2 | 3 |
4 | {% for child in content %} 5 | {% if child.block_type == 'paragraph' %} 6 |
{{ child.value|richtext }}
7 | {% else %} 8 | {{ child }} 9 | {% endif %} 10 | {% endfor %} 11 |
12 | -------------------------------------------------------------------------------- /ietf/static_src/css/pages.scss: -------------------------------------------------------------------------------- 1 | // A place to import page styles 2 | @import '../../home/styles/home.scss'; 3 | @import '../../bibliography/styles/bibliography.scss'; 4 | @import '../../events/styles/event_listing_page.scss'; 5 | @import '../../events/styles/event_page.scss'; 6 | @import '../../topics/templates/topics/styles/topics_page.scss'; 7 | @import '../../forms/styles/forms.scss'; 8 | -------------------------------------------------------------------------------- /ietf/forms/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | import wagtail_factories 3 | 4 | from .models import FormPage 5 | 6 | 7 | class FormPageFactory(wagtail_factories.PageFactory): 8 | title = factory.Faker("name") 9 | intro = factory.Faker("paragraph") 10 | thank_you_text = factory.Faker("paragraph") 11 | 12 | class Meta: # type: ignore 13 | model = FormPage 14 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # The lines below are copied from .gitignore and should be kept in sync 2 | /venv/ 3 | /static/ 4 | /media/ 5 | /ietf/templates/base.html 6 | /ietf/static/dist 7 | /ietf/settings/local.py 8 | /docker/database/*.gz 9 | *.pyc 10 | *.log 11 | *.swp 12 | .DS_Store 13 | .vscode 14 | .coverage 15 | node_modules 16 | coverage.xml 17 | 18 | # Docker-only ignore rules 19 | .git 20 | -------------------------------------------------------------------------------- /ietf/snippets/tests/test_charter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ietf.snippets.factories import CharterFactory, WorkingGroupFactory 4 | 5 | pytestmark = pytest.mark.django_db 6 | 7 | 8 | def test_link_working_group(): 9 | working_group = WorkingGroupFactory() 10 | snippet = CharterFactory(working_group=working_group) 11 | assert snippet.url == working_group.charter_url 12 | -------------------------------------------------------------------------------- /ietf/snippets/templates/snippets/styles/mailing_list_signup.scss: -------------------------------------------------------------------------------- 1 | .mailing_list_signup { 2 | &__container { 3 | position: relative; 4 | } 5 | &__background { 6 | position: absolute; 7 | top: -30%; 8 | bottom: 0; 9 | right: 10%; 10 | font-size: 18em; 11 | overflow: hidden; 12 | color: rgba(255,255,255, 0.05); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docker/init-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | set -x 4 | 5 | python /app/manage.py makemigrations --dry-run --no-input 6 | python /app/manage.py makemigrations --check --no-input 7 | python /app/manage.py migrate --no-input 8 | python /app/manage.py createcachetable 9 | python /app/manage.py collectstatic --no-input 10 | pytest --cov --cov-report=xml 11 | if [ -d /coverage ]; then cp .coverage coverage.xml /coverage/; fi 12 | -------------------------------------------------------------------------------- /ietf/bibliography/wagtail_hooks.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | from wagtail import hooks 3 | from wagtail.admin.menu import MenuItem 4 | 5 | 6 | @hooks.register("register_admin_menu_item") 7 | def register_references_menu_item(): 8 | return MenuItem( 9 | "References", 10 | reverse("referenced_types"), 11 | classname="icon icon-folder-inverse", 12 | order=10000, 13 | ) 14 | -------------------------------------------------------------------------------- /ietf/home/wagtail_hooks.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | from wagtail import hooks 3 | from wagtail.admin.menu import MenuItem 4 | 5 | 6 | @hooks.register("register_admin_menu_item") 7 | def register_resource_menu_item(): 8 | return MenuItem( 9 | "Documentation", 10 | reverse("django-admindocs-docroot"), 11 | classname="icon icon-folder-inverse", 12 | order=10000, 13 | ) 14 | -------------------------------------------------------------------------------- /docker/supervisord-sandbox.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:nginx] 5 | command=nginx -g "daemon off;" 6 | stdout_logfile=/dev/stdout 7 | stdout_logfile_maxbytes=0 8 | redirect_stderr=true 9 | 10 | [program:gunicorn] 11 | command=/usr/local/bin/gunicorn --config /app/docker/gunicorn.py ietf.wsgi 12 | directory=/app 13 | stdout_logfile=/dev/stdout 14 | stdout_logfile_maxbytes=0 15 | redirect_stderr=true 16 | -------------------------------------------------------------------------------- /ietf/snippets/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.shortcuts import get_object_or_404, render 3 | 4 | from .models import MailingListSignup 5 | 6 | 7 | def disclaimer(request, signup_id): 8 | signup = get_object_or_404(MailingListSignup, pk=signup_id) 9 | 10 | return render( 11 | request, 12 | "snippets/disclaimer.html", 13 | {"url": signup.link, "note_well_git_url": settings.NOTE_WELL_REPO}, 14 | ) 15 | -------------------------------------------------------------------------------- /ietf/events/styles/event_page.scss: -------------------------------------------------------------------------------- 1 | .event_page__jumbotron { 2 | background-size: cover; 3 | background-position: center; 4 | background-repeat: no-repeat; 5 | position: relative; 6 | &::before { 7 | content: ''; 8 | position: absolute; 9 | top: 0; 10 | bottom: 0; 11 | left: 0; 12 | right: 0; 13 | pointer-events: none; 14 | background-color: rgba($body-color, 0.7); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /k8s/iabweb/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: iabwww 2 | labels: 3 | - pairs: 4 | app.kubernetes.io/part-of: iabwww 5 | io.portainer.kubernetes.application.stack: iabwww 6 | includeTemplates: true 7 | configMapGenerator: 8 | - name: iabwww-files-cfgmap 9 | files: 10 | - local.py 11 | - supervisord.conf 12 | - nginx-default.conf 13 | - nginx.conf 14 | resources: 15 | - memcached.yaml 16 | - wagtail.yaml 17 | - cron.yaml 18 | -------------------------------------------------------------------------------- /k8s/iabweb/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | logfile=/dev/stdout 4 | logfile_maxbytes=0 5 | 6 | [program:nginx] 7 | command=nginx -g "daemon off;" 8 | stdout_logfile=/dev/stdout 9 | stdout_logfile_maxbytes=0 10 | redirect_stderr=true 11 | 12 | [program:gunicorn] 13 | command=/usr/local/bin/gunicorn --config /app/docker/gunicorn.py ietf.wsgi 14 | directory=/app 15 | stdout_logfile=/dev/stdout 16 | stdout_logfile_maxbytes=0 17 | redirect_stderr=true 18 | -------------------------------------------------------------------------------- /k8s/ietfweb/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | logfile=/dev/stdout 4 | logfile_maxbytes=0 5 | 6 | [program:nginx] 7 | command=nginx -g "daemon off;" 8 | stdout_logfile=/dev/stdout 9 | stdout_logfile_maxbytes=0 10 | redirect_stderr=true 11 | 12 | [program:gunicorn] 13 | command=/usr/local/bin/gunicorn --config /app/docker/gunicorn.py ietf.wsgi 14 | directory=/app 15 | stdout_logfile=/dev/stdout 16 | stdout_logfile_maxbytes=0 17 | redirect_stderr=true 18 | -------------------------------------------------------------------------------- /k8s/ietfweb/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: ietfwww 2 | labels: 3 | - pairs: 4 | app.kubernetes.io/part-of: ietfwww 5 | io.portainer.kubernetes.application.stack: ietfwww 6 | includeTemplates: true 7 | configMapGenerator: 8 | - name: ietfwww-files-cfgmap 9 | files: 10 | - local.py 11 | - supervisord.conf 12 | - nginx-default.conf 13 | - nginx.conf 14 | resources: 15 | - memcached.yaml 16 | - wagtail.yaml 17 | - cron.yaml 18 | -------------------------------------------------------------------------------- /ietf/utils/migrations/0004_alter_menuitem_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.10 on 2022-01-06 21:31 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("utils", "0003_auto_20211105_0019"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="menuitem", 15 | options={"verbose_name_plural": "Secondary Menu"}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /ietf/templates/includes/children_in_this_section.html: -------------------------------------------------------------------------------- 1 | {% with page.get_children as children %} 2 | {% if children %} 3 |
4 | 5 | 6 |
7 | 12 | {% endif %} 13 | {% endwith %} 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, // Only enabled from compatibility with "tagged-template-noop". Potentially can be refactored away. 4 | "outDir": ".tscache", 5 | "incremental": true, 6 | "allowSyntheticDefaultImports": true, 7 | "resolveJsonModule": true, 8 | "jsx": "react", 9 | "baseUrl": "./", 10 | "strict": true, 11 | "lib": ["dom", "es6", "esnext.asynciterable"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ietf/documents/migrations/0002_wagtail_upgrade_peturbations.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-04-27 19:30 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("documents", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="ietfdocument", 15 | options={"verbose_name": "document", "verbose_name_plural": "documents"}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /ietf/static_src/js/public-path.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Webpack does not know the 'static' URL of the CDN it will be served from at build time. 3 | * 4 | * The template file that 'bundle.js' is called from *does* know though, so writes that to 5 | * window.staticRoot. 6 | * 7 | * This is the recommended approach as per webpack's documentation, here: 8 | * https://webpack.js.org/concepts/output/#advanced 9 | */ 10 | 11 | // eslint-disable-next-line camelcase, no-undef 12 | __webpack_public_path__ = window.staticRoot; 13 | -------------------------------------------------------------------------------- /ietf/bibliography/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import re_path 2 | 3 | from .views import ( 4 | referenced_objects, 5 | referenced_types, 6 | referencing_pages, 7 | ) 8 | 9 | urlpatterns = [ 10 | re_path(r"^referenced_types/$", referenced_types, name="referenced_types"), 11 | re_path( 12 | r"^referenced_objects/(\d+)/$", referenced_objects, name="referenced_objects" 13 | ), 14 | re_path( 15 | r"^referencing_pages/(\d+)/(\d+)/$", referencing_pages, name="referencing_pages" 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /ietf/snippets/migrations/0003_alter_workinggroup_list_subscribe.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.7 on 2023-12-11 09:19 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("snippets", "0002_auto_20200414_2027"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="workinggroup", 15 | name="list_subscribe", 16 | field=models.URLField(blank=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /docker/nginx-sandbox.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server; 4 | server_name _; 5 | gzip on; 6 | access_log /dev/stdout; 7 | error_log /dev/stdout warn; 8 | location / { 9 | proxy_pass http://127.0.0.1:8000; 10 | proxy_set_header Host $host; 11 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 12 | } 13 | location /media/ { 14 | alias /app/media/; 15 | } 16 | location /static/ { 17 | alias /app/static/; 18 | } 19 | } -------------------------------------------------------------------------------- /ietf/snippets/templates/snippets/group.html: -------------------------------------------------------------------------------- 1 | {% load wagtailimages_tags %} 2 |
  • 3 |
    4 | {% image snippet.image max-50x50 as image %} 5 | {{ image.title }} 6 |
    7 |

    {{ snippet.name }}{% if snippet.role %}{{ snippet.role }}{% endif %}

    8 | {% if snippet.summary %}

    {{ snippet.summary }}

    {% endif %} 9 | {% if snippet.email %}{{ snippet.email }}{% endif %} 10 |
  • 11 | -------------------------------------------------------------------------------- /.github/workflows/ci-pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: Run pre-commit 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - '*' 9 | 10 | jobs: 11 | check: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Install Python 3.12 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: '3.12' 22 | 23 | - name: Run pre-commit 24 | uses: pre-commit/action@v3.0.1 25 | -------------------------------------------------------------------------------- /ietf/snippets/templates/snippets/disclaimer.html: -------------------------------------------------------------------------------- 1 | {% extends settings.utils.LayoutSettings.base_template %} 2 | 3 | {% block main_content %} 4 |
    5 |
    6 |
    7 |
    8 | {% include "includes/note_well.html" with note_well_git_url=note_well_git_url %} 9 | I understand. 10 |
    11 |
    12 |
    13 |
    14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /ietf/templates/includes/related_links.html: -------------------------------------------------------------------------------- 1 |
    2 | {% if related_links %} 3 |

    you might be interested in

    4 | {% for link in related_links %} 5 |

    6 | 7 | {{ link.title }} 8 | 9 |

    10 | {% endfor %} 11 | {% endif %} 12 |
    13 | -------------------------------------------------------------------------------- /ietf/snippets/migrations/0004_merge_20231215_0352.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.7 on 2023-12-15 03:52 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("snippets", "0003_alter_workinggroup_list_subscribe"), 10 | ("snippets", "0003_person_slug"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="person", 16 | name="slug", 17 | field=models.SlugField(max_length=511, unique=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /ietf/images/migrations/0002_alter_ietfimage_file_hash.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-07-22 02:02 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("images", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="ietfimage", 15 | name="file_hash", 16 | field=models.CharField( 17 | blank=True, db_index=True, editable=False, max_length=40 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /ietf/images/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from wagtail.images.models import AbstractImage, AbstractRendition, Image 3 | 4 | 5 | class IETFImage(AbstractImage): 6 | caption = models.CharField(max_length=255, null=True, blank=True) 7 | 8 | admin_form_fields = Image.admin_form_fields + ("caption",) 9 | 10 | 11 | class IETFRendition(AbstractRendition): 12 | image = models.ForeignKey( 13 | IETFImage, related_name="renditions", on_delete=models.CASCADE 14 | ) 15 | 16 | class Meta: 17 | unique_together = (("image", "filter_spec", "focal_point_key"),) 18 | -------------------------------------------------------------------------------- /ietf/static_src/css/utilities.scss: -------------------------------------------------------------------------------- 1 | .u-obj-cover { 2 | object-fit: cover; 3 | } 4 | 5 | .u-obj-contain { 6 | object-fit: contain; 7 | } 8 | 9 | // Use this to limit the width of text to improve readability 10 | .u-max-text-width { 11 | max-width: 50rem; 12 | } 13 | 14 | .u-text-xs { 15 | font-size: 13px; 16 | } 17 | 18 | .fw-medium { 19 | font-weight: 500 !important; 20 | } 21 | .fw-semibold { 22 | font-weight: 600 !important; 23 | } 24 | 25 | .u-border-lg-bottom-0 { 26 | @include media-breakpoint-up(lg) { 27 | border-bottom: 0 !important; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ietf/static_src/js/init.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import 'jquery-ui/ui/widgets/datepicker'; 3 | import * as Popper from '@popperjs/core'; 4 | 5 | import * as bootstrap from 'bootstrap'; 6 | 7 | window.$ = $; 8 | window.jQuery = $; 9 | window.Popper = Popper; 10 | window.bootstrap = bootstrap; 11 | 12 | document.documentElement.classList.remove('no-js'); 13 | 14 | document.body.addEventListener('mousedown', () => { 15 | document.body.classList.add('using-mouse'); 16 | }); 17 | document.body.addEventListener('keydown', () => { 18 | document.body.classList.remove('using-mouse'); 19 | }); 20 | -------------------------------------------------------------------------------- /ietf/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for ietf project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ietf.settings.production") 15 | 16 | 17 | def application(environ, start_response): 18 | django_application = get_wsgi_application() 19 | 20 | return django_application(environ, start_response) 21 | -------------------------------------------------------------------------------- /ietf/home/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | import wagtail_factories 3 | 4 | from .models import HomePage, IABHomePage 5 | 6 | 7 | class HomePageFactory(wagtail_factories.PageFactory): 8 | title = factory.Faker("name") 9 | heading = factory.Faker("name") 10 | introduction = factory.Faker("name") 11 | 12 | class Meta: # type: ignore 13 | model = HomePage 14 | 15 | 16 | class IABHomePageFactory(wagtail_factories.PageFactory): 17 | title = factory.Faker("name") 18 | heading = factory.Faker("name") 19 | 20 | class Meta: # type: ignore 21 | model = IABHomePage 22 | -------------------------------------------------------------------------------- /ietf/snippets/templates/snippets/mailing_list_signup.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 |

    {{ snippet.title }}

    6 | {% if snippet.blurb %}

    {{ snippet.blurb }}

    {% endif %} 7 | 8 | {{ snippet.button_text }} 9 | 10 |
    11 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.pytest.ini_options] 2 | addopts = "--reuse-db" 3 | python_files = "ietf/*/test*.py" 4 | filterwarnings = [ 5 | "error", 6 | "ignore::django.utils.deprecation.RemovedInDjango60Warning", 7 | "ignore::UserWarning", 8 | ] 9 | 10 | [tool.ruff] 11 | target-version = "py312" 12 | 13 | [tool.ruff.lint] 14 | extend-select = [ 15 | "E", # pycodestyle 16 | "F", # Pyflakes 17 | "UP", # pyupgrade 18 | "B", # flake8-bugbear 19 | "SIM", # flake8-simplify 20 | "I", # isort 21 | ] 22 | 23 | extend-ignore = [ 24 | "E501", # no line length errors 25 | ] 26 | -------------------------------------------------------------------------------- /ietf/snippets/templates/snippets/working_group.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |

    {{ working_group.name }}

    4 |

    {{ working_group.description|truncatechars:200 }}

    5 | 6 | {{ working_group.acronym }} 7 | {% if working_group.list_email %} 8 | {{ working_group.list_email }} 9 | {% endif %} 10 | 11 |
    12 | -------------------------------------------------------------------------------- /ietf/images/migrations/0005_ietfimage_description.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.17 on 2024-12-16 07:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("images", "0004_django_42_rendition_storage"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="ietfimage", 15 | name="description", 16 | field=models.CharField( 17 | blank=True, default="", max_length=255, verbose_name="description" 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /ietf/topics/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | import wagtail_factories 3 | 4 | from .models import PrimaryTopicPage, TopicIndexPage 5 | 6 | 7 | class PrimaryTopicPageFactory(wagtail_factories.PageFactory): 8 | title = factory.Faker("name") 9 | introduction = factory.Faker("paragraph") 10 | 11 | class Meta: # type: ignore 12 | model = PrimaryTopicPage 13 | 14 | 15 | class TopicIndexPageFactory(wagtail_factories.PageFactory): 16 | title = factory.Faker("name") 17 | introduction = factory.Faker("paragraph") 18 | 19 | class Meta: # type: ignore 20 | model = TopicIndexPage 21 | -------------------------------------------------------------------------------- /ietf/settings/dev.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | 3 | from .base import * # noqa: F403 4 | 5 | # SECURITY WARNING: don't run with debug turned on in production! 6 | DEBUG = True 7 | 8 | # SECURITY WARNING: keep the secret key used in production secret! 9 | SECRET_KEY = "CHANGEME!!!" 10 | 11 | 12 | EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" 13 | 14 | 15 | # Process all tasks synchronously. 16 | # Helpful for local development and running tests 17 | CELERY_EAGER_PROPAGATES_EXCEPTIONS = True 18 | CELERY_ALWAYS_EAGER = True 19 | 20 | with contextlib.suppress(ImportError): 21 | from .local import * # noqa: F403 22 | -------------------------------------------------------------------------------- /ietf/events/migrations/0003_auto_20210704_2343.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-07-04 23:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("events", "0002_auto_20210325_0442"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="eventpage", 15 | name="introduction", 16 | field=models.CharField( 17 | help_text="The introduction for the event page. Limited to 511 characters.", 18 | max_length=511, 19 | ), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /ietf/static_src/css/custom-functions.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | // ============================================================================= 3 | // Functions 4 | // ============================================================================= 5 | 6 | // Used by `rem` function 7 | @function strip-unit($number) { 8 | @if type-of($number) == 'number' and not unitless($number) { 9 | @return math.div($number, ($number * 0 + 1)); 10 | } 11 | 12 | @return $number; 13 | } 14 | 15 | // Convert pixels to rems 16 | @function rem($size) { 17 | $rem-size: math.div(strip-unit($size), strip-unit(16)); 18 | @return #{$rem-size}rem; 19 | } 20 | -------------------------------------------------------------------------------- /ietf/static/img/twitter-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /ietf/forms/migrations/0004_convert_unicode_to_text.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | 3 | 4 | def convert_unicode_to_text(apps, schema_editor): 5 | if apps.is_installed("wagtailforms"): 6 | Submissions = apps.get_model("wagtailforms", "formsubmission") 7 | for submission in Submissions.objects.all(): 8 | submission.form_data = str(submission.form_data.replace("\\u0000", "")) 9 | submission.save() 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("forms", "0003_auto_20220722_0302"), 16 | ] 17 | 18 | operations = [migrations.RunPython(convert_unicode_to_text)] 19 | -------------------------------------------------------------------------------- /ietf/blog/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | import wagtail_factories 3 | 4 | from ietf.utils.factories import StandardBlockFactory 5 | 6 | from .models import BlogIndexPage, BlogPage 7 | 8 | 9 | class BlogPageFactory(wagtail_factories.PageFactory): 10 | title = factory.Faker("name") 11 | introduction = factory.Faker("paragraph") 12 | body = wagtail_factories.StreamFieldFactory(StandardBlockFactory) 13 | 14 | class Meta: # type: ignore 15 | model = BlogPage 16 | 17 | 18 | class BlogIndexPageFactory(wagtail_factories.PageFactory): 19 | title = factory.Faker("name") 20 | 21 | class Meta: # type: ignore 22 | model = BlogIndexPage 23 | -------------------------------------------------------------------------------- /docker/database/02_convert_native_dump_to_sql_and_restore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /usr/local/bin/docker-entrypoint.sh 4 | 5 | cd /docker-entrypoint-initdb.d 6 | 7 | set +e 8 | FILE=$(ls -1 *.dump | head) 9 | 10 | set -e 11 | 12 | restore_dump() { 13 | # use docker_process_sql function from docker-entrypoint.sh in the Postgres container 14 | # pg_restore -xO means restore no owner, no permissions 15 | cat "$1" | pg_restore -xO -f - | sed -e '/CREATE SCHEMA public/d' | docker_process_sql 16 | } 17 | 18 | if [ -s "${FILE}" ]; then 19 | echo "Restoring the archive..." 20 | restore_dump "$FILE" 21 | else 22 | echo "No dump, starting fresh." 23 | fi 24 | 25 | -------------------------------------------------------------------------------- /ietf/snippets/templates/snippets/rfc.html: -------------------------------------------------------------------------------- 1 |
    2 |

    {{ rfc.title }}

    3 |

    {{ rfc.abstract|truncatechars:200 }}

    4 | 5 | RFC {{ rfc.rfc }} 6 | was: 7 | {{ rfc.name }} 8 | {# we include the WG email here #} 9 | {% if rfc.working_group.list_email %} 10 | {{ rfc.working_group.list_email }} 11 | {% endif %} 12 | 13 |
    14 | -------------------------------------------------------------------------------- /ietf/utils/tests/test_500_page.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock 2 | 3 | import pytest 4 | from django.test import Client 5 | 6 | pytestmark = pytest.mark.django_db 7 | 8 | 9 | def test_500_page(client: Client, monkeypatch: pytest.MonkeyPatch, settings, home): 10 | settings.DEBUG = False 11 | monkeypatch.setattr( 12 | "ietf.home.models.HomePage.serve", Mock(side_effect=RuntimeError) 13 | ) 14 | client.raise_request_exception = False 15 | response = client.get("/") 16 | assert response.status_code == 500 17 | expect = 'If the matter is urgent, please email ' 18 | assert expect in response.content.decode() 19 | -------------------------------------------------------------------------------- /ietf/events/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | import wagtail_factories 3 | 4 | from ietf.utils.factories import StandardBlockFactory 5 | 6 | from .models import EventListingPage, EventPage 7 | 8 | 9 | class EventPageFactory(wagtail_factories.PageFactory): 10 | title = factory.Faker("name") 11 | introduction = factory.Faker("paragraph") 12 | body = wagtail_factories.StreamFieldFactory(StandardBlockFactory) 13 | 14 | class Meta: # type: ignore 15 | model = EventPage 16 | 17 | 18 | class EventListingPageFactory(wagtail_factories.PageFactory): 19 | title = factory.Faker("name") 20 | 21 | class Meta: # type: ignore 22 | model = EventListingPage 23 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_language_version: 2 | node: system 3 | python: python3.12 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v4.5.0 7 | hooks: 8 | - id: check-toml 9 | - id: check-merge-conflict 10 | - id: check-byte-order-marker 11 | - id: check-case-conflict 12 | - id: debug-statements 13 | - id: detect-private-key 14 | - repo: https://github.com/astral-sh/ruff-pre-commit 15 | rev: v0.4.9 16 | hooks: 17 | - id: ruff 18 | args: [--fix, --exit-non-zero-on-fix] 19 | - repo: https://github.com/psf/black-pre-commit-mirror 20 | rev: 24.4.2 21 | hooks: 22 | - id: black 23 | -------------------------------------------------------------------------------- /ietf/forms/templatetags/form_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from wagtail.coreutils import camelcase_to_underscore 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.filter 8 | def widgettype(bound_field): 9 | return camelcase_to_underscore(bound_field.field.widget.__class__.__name__) 10 | 11 | 12 | @register.filter(name="add_attr") 13 | def add_attr(bound_field, value): 14 | attrs = {} 15 | definition = value.split(",") 16 | 17 | for d in definition: 18 | if ":" not in d: 19 | attrs["class"] = d 20 | else: 21 | key, val = d.split(":") 22 | attrs[key] = val 23 | 24 | return bound_field.as_widget(attrs=attrs) 25 | -------------------------------------------------------------------------------- /ietf/templates/includes/styles/footer.scss: -------------------------------------------------------------------------------- 1 | footer section { 2 | h4[role=button] { 3 | @include media-breakpoint-up(lg) { 4 | cursor: text; 5 | } 6 | } 7 | 8 | h4 .bi-chevron-down { 9 | display: block; 10 | float: right; 11 | @include media-breakpoint-up(lg) { 12 | display: none; 13 | } 14 | } 15 | 16 | &.expanded h4 .bi-chevron-down { 17 | transform: rotate(180deg); 18 | } 19 | 20 | ul { 21 | display: none; 22 | @include media-breakpoint-up(lg) { 23 | display: block; 24 | } 25 | } 26 | 27 | &.expanded ul { 28 | display: block; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ietf/templates/includes/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import 'header.scss'; 2 | @import 'footer.scss'; 3 | 4 | .block-paragraph { 5 | .h2, h2 { 6 | font-size: 1.75rem; 7 | margin-bottom: 0.6rem; 8 | margin-top: 2rem; 9 | &:first-child { 10 | margin-top: 0; 11 | } 12 | } 13 | 14 | .h3, h3 { 15 | font-size: 1.5rem; 16 | margin-bottom: 0.5rem; 17 | } 18 | 19 | .h4, h4 { 20 | font-size: 1.25rem; 21 | margin-bottom: 0.5rem; 22 | } 23 | 24 | .h5, h5 { 25 | font-size: 1.25rem; 26 | margin-bottom: 0.5rem; 27 | } 28 | 29 | p, 30 | li { 31 | font-weight: 300; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dev/deploy-to-container/README.md: -------------------------------------------------------------------------------- 1 | # WWW Deploy to Container Tool 2 | 3 | This tool takes a docker image and deploys it to a container, along with its own database container. 4 | 5 | ## Requirements 6 | 7 | - Node `16.x` or later 8 | - Docker 9 | 10 | ## Usage 11 | 12 | 1. From the `dev/deploy-to-container` directory, run the command: 13 | ```sh 14 | npm install 15 | ``` 16 | 3. From the project root directory (back up 2 levels), run the command: (replacing the `branch` and `domain` arguments) 17 | ```sh 18 | node ./dev/deploy-to-container/cli.js --branch main --domain something.com 19 | ``` 20 | 21 | A container named `ws-app-BRANCH` and `ws-db-BRANCH` (where BRANCH is the argument provided above) will be created. 22 | -------------------------------------------------------------------------------- /ietf/static/img/linkedin-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /ietf/iesg_statement/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | import wagtail_factories 3 | 4 | from ietf.utils.factories import StandardBlockFactory 5 | 6 | from .models import IESGStatementIndexPage, IESGStatementPage 7 | 8 | 9 | class IESGStatementPageFactory(wagtail_factories.PageFactory): 10 | title = factory.Faker("name") 11 | introduction = factory.Faker("paragraph") 12 | body = wagtail_factories.StreamFieldFactory(StandardBlockFactory) 13 | 14 | class Meta: # type: ignore 15 | model = IESGStatementPage 16 | 17 | 18 | class IESGStatementIndexPageFactory(wagtail_factories.PageFactory): 19 | title = factory.Faker("name") 20 | 21 | class Meta: # type: ignore 22 | model = IESGStatementIndexPage 23 | -------------------------------------------------------------------------------- /ietf/static_src/css/iab-colors.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-iab-brand-blue: #3e96c8; // (62,150,200) 3 | --color-iab-blue-2: #004f73; // (0, 79, 115) 4 | --color-iab-blue-3: #0a77b0; // (10, 119, 176) AKA Blue (accessible) per the figma 5 | --color-iab-blue: #0b8cc5; // (11, 140, 97) 6 | --color-iab-navy: #002d3c; // (0, 45, 60) 7 | --color-iab-yellow: #ffd13e; // (255, 209, 62) 8 | 9 | --color-iab-grey-1: #f7fafc; // (247, 250, 252) 10 | --color-iab-grey-2: #f0f6fa; // (240, 246, 250) 11 | --color-iab-grey-3: #e6edf2; // (230, 237, 242) 12 | --color-iab-grey-4: #d9e0e5; // (217, 224, 229) 13 | --color-iab-grey-5: #cbd2d6; // (230, 210, 214) 14 | --color-iab-grey-6: #616466; // (97, 100, 102) 15 | } 16 | -------------------------------------------------------------------------------- /ietf/forms/migrations/0002_formfield_clean_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-03-25 05:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("forms", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="formfield", 15 | name="clean_name", 16 | field=models.CharField( 17 | blank=True, 18 | default="", 19 | help_text="Safe name of the form field, the label converted to ascii_snake_case", 20 | max_length=255, 21 | verbose_name="name", 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /ietf/home/templates/includes/post_iab.html: -------------------------------------------------------------------------------- 1 | {% load wagtailimages_tags %} 2 | 3 |
  • 4 | {# col-* classes include positioning, position static cancels that and allows the stretched link to cover the image #} 5 |
    6 |

    {{ post.title }}

    7 |

    {{ post.description|truncatechars:250 }}

    8 |
    9 | 10 |

    {{ post.published_date|date:"DATE_FORMAT" }}

    11 |
    12 |
    13 |
    14 |
  • 15 | -------------------------------------------------------------------------------- /ietf/blog/migrations/0008_alter_blogpageauthor_author.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.20 on 2023-11-24 13:34 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("snippets", "0002_auto_20200414_2027"), 11 | ("blog", "0007_alter_blogpage_body"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="blogpageauthor", 17 | name="author", 18 | field=models.ForeignKey( 19 | on_delete=django.db.models.deletion.CASCADE, 20 | related_name="+", 21 | to="snippets.person", 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /.github/workflows/accessibility-test.yml: -------------------------------------------------------------------------------- 1 | name: Run accessibility tests 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | workflow_dispatch: 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Set up Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: '18' 19 | 20 | - name: Install Yarn 21 | run: | 22 | npm install --global yarn 23 | 24 | - name: Install dependencies 25 | run: | 26 | yarn install 27 | 28 | - name: Build project 29 | run: | 30 | yarn build 31 | 32 | - name: Run tests 33 | run: | 34 | yarn test 35 | -------------------------------------------------------------------------------- /ietf/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | # 4 space indentation 8 | [*] 9 | end_of_line = lf 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # Matches multiple files with brace expansion notation 15 | # Set default charset 16 | [*.{js,py}] 17 | charset = utf-8 18 | 19 | # Tab indentation (no size specified) 20 | [Makefile] 21 | indent_style = tab 22 | 23 | # Indentation override for all JS under lib directory 24 | [lib/**.js] 25 | indent_style = space 26 | indent_size = 4 27 | 28 | # Matches the exact files either package.json or .travis.yml 29 | [{package.json,.travis.yml}] 30 | indent_style = space 31 | indent_size = 2 32 | -------------------------------------------------------------------------------- /ietf/images/migrations/0004_django_42_rendition_storage.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.7 on 2023-11-29 12:17 2 | 3 | import wagtail.images.models 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("images", "0003_wagtail_42_wagtailimagefield"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="ietfrendition", 16 | name="file", 17 | field=wagtail.images.models.WagtailImageField( 18 | height_field="height", 19 | storage=wagtail.images.models.get_rendition_storage, 20 | upload_to=wagtail.images.models.get_rendition_upload_to, 21 | width_field="width", 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /ietf/snippets/templates/snippets/area_charter.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |

    {{ charter.name }}

    4 |

    {{ charter.abstract }}

    5 | 6 | {% if charter.active %} 7 | Active 8 | {% else %} 9 | Not active 10 | {% endif %} 11 | {{ charter.area.acronym }} 12 | 13 | working groups: 14 | {% for working_group in charter.working_groups %} 15 | {{ working_group.acronym }} 16 | {% endfor %} 17 | {# See all… #} 18 | 19 |
    20 | -------------------------------------------------------------------------------- /ietf/standard/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | import wagtail_factories 3 | 4 | from .models import IABStandardPage, StandardIndexPage, StandardPage 5 | 6 | 7 | class StandardPageFactory(wagtail_factories.PageFactory): 8 | title = factory.Faker("name") 9 | introduction = factory.Faker("paragraph") 10 | 11 | class Meta: # type: ignore 12 | model = StandardPage 13 | 14 | 15 | class StandardIndexPageFactory(wagtail_factories.PageFactory): 16 | title = factory.Faker("name") 17 | 18 | class Meta: # type: ignore 19 | model = StandardIndexPage 20 | 21 | 22 | class IABStandardPageFactory(wagtail_factories.PageFactory): 23 | title = factory.Faker("name") 24 | introduction = factory.Faker("paragraph") 25 | 26 | class Meta: # type: ignore 27 | model = IABStandardPage 28 | -------------------------------------------------------------------------------- /docker/gunicorn.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | accesslog = "-" 4 | errorlog = "-" 5 | access_log_format = json.dumps( 6 | { 7 | "remote_host": "%(h)s", 8 | "remote_logname": "%(l)s", 9 | "remote_user": "%(u)s", 10 | "timestamp": "%(t)s", 11 | "request": "%(r)s", 12 | "status_code": "%(s)s", 13 | "response_size": "%(b)s", 14 | "referrer": "%(f)s", 15 | "user_agent": "%(a)s", 16 | "x_forwarded_for": "%({x-forwarded-for}i)s", 17 | } 18 | ) 19 | capture_output = True 20 | forwarded_allow_ips = "*" 21 | # set request limit to django limit - len("/admin/login/?next=") - 1 22 | limit_request_line = 2028 23 | # setting workers + threads = 2 * number of cores 24 | workers = 4 25 | threads = 4 26 | worker_class = "gthread" 27 | bind = ":8000" 28 | chdir = "/app" 29 | -------------------------------------------------------------------------------- /ietf/bibliography/templates/bibliography/referencing_pages.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailadmin/base.html" %} 2 | 3 | {% block titletag %}{{ title }}{% endblock %} 4 | 5 | {% block content %} 6 | {% include "wagtailadmin/shared/header.html" with title=title icon="folder" %} 7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for page in pages %} 16 | 17 | 18 | 19 | {% endfor %} 20 | 21 |
    name
    {{ page.title }}
    22 |
    23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /ietf/utils/migrations/0003_auto_20211105_0019.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-11-05 00:19 2 | 3 | from django.db import migrations 4 | 5 | 6 | def add_missing_menu(apps, schema_editor): 7 | MenuItem = apps.get_model("utils", "MenuItem") 8 | Page = apps.get_model("wagtailcore", "Page") 9 | page = Page.objects.filter(pk=42).first() 10 | if page: 11 | MenuItem.objects.create(page=page, sort_order=0, text="News & blog") 12 | tools_menu = MenuItem.objects.filter(page__title="Links").first() 13 | if tools_menu: 14 | tools_menu.text = "Tools" 15 | tools_menu.save() 16 | 17 | 18 | class Migration(migrations.Migration): 19 | 20 | dependencies = [ 21 | ("utils", "0002_auto_20211101_0113"), 22 | ] 23 | 24 | operations = [migrations.RunPython(add_missing_menu)] 25 | -------------------------------------------------------------------------------- /ietf/announcements/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | import wagtail_factories 3 | 4 | from ietf.utils.factories import StandardBlockFactory 5 | 6 | from .models import IABAnnouncementIndexPage, IABAnnouncementPage 7 | 8 | 9 | class IABAnnouncementPageFactory(wagtail_factories.PageFactory): 10 | title = factory.Faker("name") 11 | date = factory.Faker("date") 12 | introduction = factory.Faker("paragraph") 13 | body = wagtail_factories.StreamFieldFactory(StandardBlockFactory) 14 | 15 | class Meta: # type: ignore 16 | model = IABAnnouncementPage 17 | 18 | 19 | class IABAnnouncementIndexPageFactory(wagtail_factories.PageFactory): 20 | title = factory.Faker("name") 21 | introduction = factory.Faker("paragraph") 22 | 23 | class Meta: # type: ignore 24 | model = IABAnnouncementIndexPage 25 | -------------------------------------------------------------------------------- /ietf/templates/includes/breadcrumbs.html: -------------------------------------------------------------------------------- 1 | {% load wagtailcore_tags %} 2 | 3 | {% if self.get_ancestors|length > 1 %} 4 | 14 | {% endif %} 15 | -------------------------------------------------------------------------------- /ietf/templates/includes/highlight.html: -------------------------------------------------------------------------------- 1 | {% if self.call_to_action or self.mailing_list_signup or self.related_links.exists %} 2 |
    3 | {% if self.call_to_action %} 4 |
    5 | {{ self.call_to_action.render }} 6 |
    7 | {% endif %} 8 | {% if self.mailing_list_signup %} 9 |
    10 | {{ self.mailing_list_signup.render }} 11 |
    12 | {% endif %} 13 | {% if self.related_links.exists %} 14 |
    15 | {% include "includes/related_links.html" with related_links=self.related_links.all %} 16 |
    17 | {% endif %} 18 |
    19 | {% endif %} 20 | -------------------------------------------------------------------------------- /ietf/home/templates/includes/announcement_iab.html: -------------------------------------------------------------------------------- 1 | {% load wagtailimages_tags %} 2 | 3 |
  • 4 | {# col-* classes include positioning, position static cancels that and allows the stretched link to cover the image #} 5 |
    6 |

    {{ announcement.title }}

    7 |

    {{ announcement.introduction|safe|truncatechars:200 }}

    8 |
    9 | 10 | {% if announcement.date %} 11 |

    {{ announcement.date|date:"DATE_FORMAT" }}

    12 | {% endif %} 13 |
    14 |
    15 |
    16 |
  • 17 | -------------------------------------------------------------------------------- /k8s/iabweb/secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: iabwww-secrets-env 5 | type: Opaque 6 | stringData: 7 | IABWWW_ADMINS: null 8 | 9 | IABWWW_ALLOWED_HOSTS: ".iab.org" # newline-separated list also allowed 10 | WAGTAILADMIN_BASE_URL: "https://www.iab.org" 11 | 12 | # Outgoing email details 13 | IABWWW_EMAIL_HOST: null 14 | IABWWW_EMAIL_PORT: null 15 | 16 | IABWWW_MATOMO_SITE_ID: null # must be present to enable Matomo 17 | 18 | # Can also be a newline-separated list 19 | IABWWW_CSRF_TRUSTED_ORIGINS: "https://www.iab.org" 20 | 21 | # Database connection details - to be fetched from Vault 22 | # IABWWW_DB_HOST: "" 23 | # IABWWW_DB_NAME: "" 24 | # IABWWW_DB_PASS: "" 25 | # IABWWW_DB_PORT: "" 26 | # IABWWW_DB_USER: "" 27 | 28 | # Django secret key - to be fetched from Vault 29 | # IABWWW_DJANGO_SECRET_KEY: "" 30 | -------------------------------------------------------------------------------- /ietf/utils/migrations/0006_textchunk.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2023-04-13 23:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("utils", "0005_layoutsettings"), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="TextChunk", 15 | fields=[ 16 | ( 17 | "id", 18 | models.AutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("slug", models.SlugField()), 26 | ("text", models.TextField()), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /db_reset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | echo "Getting backups from ${BACKUPS_BUCKET}" 7 | 8 | # basically what happens here is: 9 | # 1. Get the psql binary backup from the bucket 10 | # 2. Ungzip it 11 | # 3. Run pg_restore to turn it into an SQL file 12 | # 4. Insert drop schema to the beginning 13 | # 5. Run the commands in a single transaction and exit on error 14 | # So if anything of the above fails, the database should be left untouched 15 | #aws s3 cp s3://${BACKUPS_BUCKET}/ietfa.torchbox.gz - | gzip -d -c | pg_restore -xO -f - | sed "1s/^/DROP SCHEMA public CASCADE; CREATE SCHEMA public;/" | psql "$DATABASE_URL" -v ON_ERROR_STOP=1 --single-transaction -f - 16 | 17 | #python manage.py migrate --noinput 18 | 19 | #aws s3 sync --delete s3://${BACKUPS_BUCKET}/media/ s3://${AWS_STORAGE_BUCKET_NAME}/media/ 20 | 21 | #python manage.py update_nonprod_hostnames 22 | -------------------------------------------------------------------------------- /k8s/ietfweb/secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: ietfwww-secrets-env 5 | type: Opaque 6 | stringData: 7 | IETFWWW_ADMINS: null 8 | 9 | IETFWWW_ALLOWED_HOSTS: ".ietf.org" # newline-separated list also allowed 10 | WAGTAILADMIN_BASE_URL: "https://www.ietf.org" 11 | 12 | # Outgoing email details 13 | IETFWWW_EMAIL_HOST: null 14 | IETFWWW_EMAIL_PORT: null 15 | 16 | IETFWWW_MATOMO_SITE_ID: null # must be present to enable Matomo 17 | 18 | # Can also be a newline-separated list 19 | IETFWWW_CSRF_TRUSTED_ORIGINS: "https://www.ietf.org" 20 | 21 | # Database connection details - to be fetched from Vault 22 | # IETFWWW_DB_HOST: "" 23 | # IETFWWW_DB_NAME: "" 24 | # IETFWWW_DB_PASS: "" 25 | # IETFWWW_DB_PORT: "" 26 | # IETFWWW_DB_USER: "" 27 | 28 | # Django secret key - to be fetched from Vault 29 | # IETFWWW_DJANGO_SECRET_KEY: "" 30 | -------------------------------------------------------------------------------- /ietf/glossary/tests.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.test import Client 3 | 4 | from ietf.home.models import HomePage 5 | 6 | from .factories import GlossaryPageFactory 7 | from .models import GlossaryPage 8 | 9 | pytestmark = pytest.mark.django_db 10 | 11 | 12 | class TestGlossaryPage: 13 | @pytest.fixture(autouse=True) 14 | def set_up(self, home: HomePage, client: Client): 15 | self.home = home 16 | self.client = client 17 | self.glossary_page: GlossaryPage = GlossaryPageFactory( 18 | parent=self.home, 19 | ) # type: ignore 20 | 21 | def test_glossary_page(self): 22 | response = self.client.get(path=self.glossary_page.url) 23 | assert response.status_code == 200 24 | html = response.content.decode() 25 | 26 | assert self.glossary_page.title in html 27 | assert self.glossary_page.introduction in html 28 | -------------------------------------------------------------------------------- /ietf/iesg_statement/templates/iesg_statement/iesg_statement_index_page.html: -------------------------------------------------------------------------------- 1 | {% extends settings.utils.LayoutSettings.base_template %} 2 | 3 | 4 | {% block main_content %} 5 | 6 |
    7 |
    8 |
    9 | {% include 'includes/breadcrumbs.html' %} 10 |
    11 |

    {{self.title}}

    12 |
    13 |
    14 |
    15 | 16 |
    17 | 18 | {% for statement in statements %} 19 | 20 | 21 | 22 | 23 | {% endfor %} 24 |
    {{statement.date_published|date:"Y-m-d"}}{{statement.title}}
    25 |
    26 |
    27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /ietf/snippets/migrations/0003_person_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.7 on 2023-12-12 18:52 2 | 3 | from django.db import migrations, models 4 | from wagtail.coreutils import slugify 5 | 6 | 7 | def generate_person_slugs(apps, schema_editor): 8 | Person = apps.get_model("snippets.Person") 9 | for person in Person.objects.all(): 10 | person.slug = slugify(person.name) 11 | person.save() 12 | 13 | 14 | class Migration(migrations.Migration): 15 | 16 | dependencies = [ 17 | ("snippets", "0002_auto_20200414_2027"), 18 | ] 19 | 20 | operations = [ 21 | migrations.AddField( 22 | model_name="person", 23 | name="slug", 24 | field=models.SlugField(default="", max_length=511), 25 | preserve_default=False, 26 | ), 27 | migrations.RunPython(generate_person_slugs, migrations.RunPython.noop), 28 | ] 29 | -------------------------------------------------------------------------------- /docker/db.Dockerfile: -------------------------------------------------------------------------------- 1 | # ===================== 2 | # --- Builder Stage --- 3 | # ===================== 4 | FROM postgres:16 AS builder 5 | 6 | ENV POSTGRES_PASSWORD=password 7 | ENV POSTGRES_USER=postgres 8 | ENV POSTGRES_DB=app 9 | ENV POSTGRES_HOST_AUTH_METHOD=trust 10 | ENV PGDATA=/data 11 | 12 | COPY docker/scripts/db-import.sh /docker-entrypoint-initdb.d/ 13 | COPY docker/database/ietfwww.dump / 14 | 15 | RUN ["sed", "-i", "s/exec \"$@\"/echo \"skipping...\"/", "/usr/local/bin/docker-entrypoint.sh"] 16 | RUN ["/usr/local/bin/docker-entrypoint.sh", "postgres"] 17 | 18 | # =================== 19 | # --- Final Image --- 20 | # =================== 21 | FROM postgres:16 22 | LABEL maintainer="IETF Tools Team " 23 | 24 | COPY --from=builder /data $PGDATA 25 | 26 | ENV POSTGRES_PASSWORD=password 27 | ENV POSTGRES_USER=postgres 28 | ENV POSTGRES_DB=app 29 | ENV POSTGRES_HOST_AUTH_METHOD=trust 30 | -------------------------------------------------------------------------------- /ietf/announcements/templates/announcements/iab_announcement_page.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends settings.utils.LayoutSettings.base_template %} 3 | {% load bibliography %} 4 | {% load has_tabs %} 5 | 6 | {% block main_content %} 7 | 8 |
    9 |
    10 |
    11 | 12 | {% include 'includes/breadcrumbs.html' %} 13 | {% include "includes/social_fields.html" %} 14 |
    15 |
    16 | 17 |
    18 |
    19 |

    {{ page.title }}

    20 | {{ page.date }} 21 |
    22 | {{ page.introduction }} 23 |
    24 |
    25 | {{ page.body|safe }} 26 |
    27 |
    28 |
    29 |
    30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /ietf/static/img/youtube-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /ietf/blog/templates/includes/blog_authors.html: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /ietf/bibliography/templates/bibliography/referenced_types.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailadmin/base.html" %} 2 | 3 | {% block titletag %}References{% endblock %} 4 | 5 | {% block content %} 6 | {% include "wagtailadmin/shared/header.html" with title="References" icon="folder" %} 7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for type in types %} 17 | 18 | 19 | 20 | 21 | {% endfor %} 22 | 23 |
    namecitation count
    {{ type.0 }}{{ type.1 }}
    24 |
    25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /ietf/home/styles/home.scss: -------------------------------------------------------------------------------- 1 | .home_page__jumbotron { 2 | background-size: cover; 3 | background-position: center; 4 | background-repeat: no-repeat; 5 | position: relative; 6 | &::before { 7 | content: ''; 8 | position: absolute; 9 | top: 0; 10 | bottom: 0; 11 | left: 0; 12 | right: 0; 13 | pointer-events: none; 14 | background-color: rgba($body-color, 0.7); 15 | } 16 | } 17 | 18 | .home_page__cta-container { 19 | position: relative; 20 | &__background { 21 | position: absolute; 22 | top: -30%; 23 | bottom: 0; 24 | right: 10%; 25 | font-size: 18em; 26 | overflow: hidden; 27 | color: rgba(255,255,255, 0.05); 28 | pointer-events: none; 29 | } 30 | } 31 | 32 | .home_event__image { 33 | max-height: 6rem; 34 | 35 | @include media-breakpoint-up(md) { 36 | max-height: none; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ietf/bibliography/templates/bibliography/referenced_objects.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailadmin/base.html" %} 2 | 3 | {% block titletag %}{{ title }}{% endblock %} 4 | 5 | {% block content %} 6 | {% include "wagtailadmin/shared/header.html" with title=title icon="folder" %} 7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for object in objects %} 17 | 18 | 19 | 20 | 21 | {% endfor %} 22 | 23 |
    namecitation count
    {{ object.0 }}{{ object.1 }}
    24 |
    25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /ietf/bibliography/templatetags/bibliography.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | 6 | @register.inclusion_tag("bibliography/bibliography.html") 7 | def bibliography(page): 8 | """ 9 | Render the bibliography of a page. 10 | 11 | Uses the template "bibliography/bibliography.html" as its framework. The items are put into a variable "items", no 12 | other variables are available. 13 | """ 14 | 15 | items = [] 16 | if page.pk is not None: 17 | for item in page.bibliography_items.all().order_by("ordering"): 18 | items.append( 19 | { 20 | "title": item.render_title(), 21 | "long_title": item.content_long_title, 22 | "content": item.render(), 23 | "ordering": item.ordering, 24 | "uri": item.render_uri, 25 | } 26 | ) 27 | 28 | return {"items": items} 29 | -------------------------------------------------------------------------------- /ietf/announcements/templates/announcements/iab_announcement_index_page.html: -------------------------------------------------------------------------------- 1 | {% extends settings.utils.LayoutSettings.base_template %} 2 | {% load wagtailcore_tags %} 3 | {% load has_tabs %} 4 | 5 | {% block head_content %} 6 | {% include 'includes/optional-introduction.html' with value=self %} 7 | {% include "includes/social_fields.html" %} 8 | {% endblock head_content %} 9 | 10 | {% block content %} 11 |
    12 |
    13 | {{ page.body|safe }} 14 |
    15 |
    16 |
      17 | {% for child in self.children %} 18 |
    • 19 |

      {{ child.title }}

      20 | {{ child.date }} 21 |

      {{ child.introduction }}

      22 |
    • 23 | {% endfor %} 24 |
    25 |
    26 |
    27 | 28 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /ietf/events/templates/includes/key_detail_section.html: -------------------------------------------------------------------------------- 1 |
    2 | 14 | 15 | 20 |
    21 | -------------------------------------------------------------------------------- /ietf/static/img/mastodon-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /ietf/snippets/templates/snippets/includes/_results.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% if items %} 3 | {% if is_searching %} 4 |

    5 | {% blocktrans count counter=items.paginator.count %} 6 | There is one match 7 | {% plural %} 8 | There are {{ counter }} matches 9 | {% endblocktrans %} 10 |

    11 | {% else %} 12 |

    {% trans snippet_type.name %}

    13 | {% endif %} 14 | 15 | 16 | 17 | {% for item in items %} 18 | 19 | {% endfor %} 20 | 21 |
    {{ item }}
    22 | 23 | {% include "wagtailadmin/shared/pagination_nav.html" with items=items is_ajax=1 %} 24 | {% else %} 25 |

    {% blocktrans %}Sorry, no matches for "{{ query_string }}"{% endblocktrans %}

    26 | {% endif %} 27 | -------------------------------------------------------------------------------- /ietf/utils/templatetags/ietf_tags.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import quote 2 | 3 | from django.template import Library 4 | 5 | from ..models import PromoteMixin, SocialMediaSettings 6 | 7 | register = Library() 8 | 9 | 10 | @register.simple_tag(takes_context=False) 11 | def social_text(page, site, encode=False): 12 | text = "" 13 | 14 | if isinstance(page, PromoteMixin): 15 | text = page.get_social_text() 16 | 17 | if not text: 18 | text = SocialMediaSettings.for_site(site).default_sharing_text 19 | 20 | if encode: 21 | text = quote(text) 22 | 23 | return text 24 | 25 | 26 | @register.simple_tag(takes_context=False) 27 | def social_image(page, site): 28 | image = None 29 | 30 | if isinstance(page, PromoteMixin): 31 | image = page.get_social_image() 32 | 33 | if image is None: 34 | image = SocialMediaSettings.for_site(site).default_sharing_image 35 | 36 | if image is not None: 37 | return image.get_rendition("original").url 38 | 39 | return "" 40 | -------------------------------------------------------------------------------- /ietf/blog/templates/blog/blog_index_by_author.html: -------------------------------------------------------------------------------- 1 | {% extends settings.utils.LayoutSettings.base_template %} 2 | {% load wagtailroutablepage_tags %} 3 | 4 | {% block main_content %} 5 | 6 |
    7 |
    8 |
    9 | {% include 'includes/breadcrumbs.html' %} 10 |
    11 |

    {{ self.title }}

    12 |
    13 |
    14 |
    15 | 16 |
    17 |
    18 |
    19 | 20 | {% for entry in entries %} 21 | 22 | 23 | 24 | 25 | {% endfor %} 26 |
    {{entry.coalesced_published_date|date:"Y-m-d"}}{{entry.title}}
    27 |
    28 | 29 |
    30 |
    31 |
    32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /ietf/templates/includes/note_well.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Note Well

    3 |

    An error occured fetching the note well.

    4 |

    See https://github.com/ietf/note-well/ for the note well.

    5 |

    Please email us to report this.

    6 |
    7 | 10 | -------------------------------------------------------------------------------- /ietf/static/img/mag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ietf/bibliography/templates/bibliography/bibliography.html: -------------------------------------------------------------------------------- 1 | {% if items %} 2 | 3 |
    4 |
    5 |

    Bibliography

    6 | 24 |
    25 | 26 | 27 |
    28 | {% endif %} -------------------------------------------------------------------------------- /ietf/static/img/IRTF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /k8s/iabweb/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes auto; 2 | pid /var/lib/nginx/nginx.pid; 3 | error_log /dev/stdout; 4 | include /etc/nginx/modules-enabled/*.conf; 5 | 6 | events { 7 | worker_connections 768; 8 | # multi_accept on; 9 | } 10 | 11 | http { 12 | 13 | ## 14 | # Basic Settings 15 | ## 16 | 17 | sendfile on; 18 | tcp_nopush on; 19 | types_hash_max_size 2048; 20 | # server_tokens off; 21 | 22 | # server_names_hash_bucket_size 64; 23 | # server_name_in_redirect off; 24 | 25 | include /etc/nginx/mime.types; 26 | default_type application/octet-stream; 27 | 28 | ## 29 | # SSL Settings 30 | ## 31 | 32 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE 33 | ssl_prefer_server_ciphers on; 34 | 35 | ## 36 | # Logging Settings 37 | ## 38 | 39 | access_log /dev/stdout; 40 | 41 | ## 42 | # Gzip Settings 43 | ## 44 | 45 | gzip on; 46 | 47 | ## 48 | # Virtual Host Configs 49 | ## 50 | 51 | include /etc/nginx/conf.d/*.conf; 52 | include /etc/nginx/sites-enabled/*; 53 | } 54 | -------------------------------------------------------------------------------- /k8s/ietfweb/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes auto; 2 | pid /var/lib/nginx/nginx.pid; 3 | error_log /dev/stdout; 4 | include /etc/nginx/modules-enabled/*.conf; 5 | 6 | events { 7 | worker_connections 768; 8 | # multi_accept on; 9 | } 10 | 11 | http { 12 | 13 | ## 14 | # Basic Settings 15 | ## 16 | 17 | sendfile on; 18 | tcp_nopush on; 19 | types_hash_max_size 2048; 20 | # server_tokens off; 21 | 22 | # server_names_hash_bucket_size 64; 23 | # server_name_in_redirect off; 24 | 25 | include /etc/nginx/mime.types; 26 | default_type application/octet-stream; 27 | 28 | ## 29 | # SSL Settings 30 | ## 31 | 32 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE 33 | ssl_prefer_server_ciphers on; 34 | 35 | ## 36 | # Logging Settings 37 | ## 38 | 39 | access_log /dev/stdout; 40 | 41 | ## 42 | # Gzip Settings 43 | ## 44 | 45 | gzip on; 46 | 47 | ## 48 | # Virtual Host Configs 49 | ## 50 | 51 | include /etc/nginx/conf.d/*.conf; 52 | include /etc/nginx/sites-enabled/*; 53 | } 54 | -------------------------------------------------------------------------------- /ietf/images/migrations/0003_wagtail_42_wagtailimagefield.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.10 on 2023-11-29 10:19 2 | 3 | import wagtail.images.models 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("images", "0002_alter_ietfimage_file_hash"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="ietfimage", 16 | name="file", 17 | field=wagtail.images.models.WagtailImageField( 18 | height_field="height", 19 | upload_to=wagtail.images.models.get_upload_to, 20 | verbose_name="file", 21 | width_field="width", 22 | ), 23 | ), 24 | migrations.AlterField( 25 | model_name="ietfrendition", 26 | name="file", 27 | field=wagtail.images.models.WagtailImageField( 28 | height_field="height", 29 | upload_to=wagtail.images.models.get_rendition_upload_to, 30 | width_field="width", 31 | ), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /ietf/static/img/routing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ietf/templates/includes/row_siblings_in_section.html: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /ietf/static_src/css/main.scss: -------------------------------------------------------------------------------- 1 | @import './custom-functions'; 2 | @import 'bootstrap/scss/functions'; 3 | @import 'bootstrap/scss/variables'; 4 | 5 | @import './bs-configure.scss'; 6 | 7 | @import '@ietf-tools/common-bootstrap-theme/scss/ietf-theme.scss'; 8 | @import 'bootstrap/scss/bootstrap'; 9 | 10 | @import './bs-override.scss'; 11 | @import './icons.scss'; 12 | @import './pages.scss'; // Styles for page templates 13 | @import '../../templates/includes/styles/index.scss'; // Styles for global includes template files 14 | @import '../../snippets/templates/snippets/styles/index.scss'; // Styles for snippet templates 15 | @import './images'; // Styles for images that can't be managed with bootstrap classes alone 16 | @import './utilities'; // Utility classes that don't exist in Bootstrap yet 17 | @import './streamfield'; // Styles for streamfield blocks 18 | @import './typography'; // Styles for text 19 | @import './datepicker'; // Styles for jquery-ui datepicker 20 | @import './no-js'; // Styles for when javascript is disabled 21 | @import './focus'; 22 | 23 | // IAB Specific Styles 24 | 25 | @import './iab-colors.scss'; 26 | @import './iab.scss'; 27 | -------------------------------------------------------------------------------- /ietf/documents/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 1.11.29 on 2020-04-10 18:46 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ("wagtaildocs", "0008_document_file_size"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="IetfDocument", 18 | fields=[ 19 | ( 20 | "document_ptr", 21 | models.OneToOneField( 22 | auto_created=True, 23 | on_delete=django.db.models.deletion.CASCADE, 24 | parent_link=True, 25 | primary_key=True, 26 | serialize=False, 27 | to="wagtaildocs.Document", 28 | ), 29 | ), 30 | ], 31 | options={ 32 | "verbose_name": "document", 33 | "abstract": False, 34 | }, 35 | bases=("wagtaildocs.document",), 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /ietf/static/img/operations.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /webpack.fix-django-paths.js: -------------------------------------------------------------------------------- 1 | // Webpack likes to make relative paths between its 2 | // generated HTML and its bundles(etc), but the paths 3 | // it generates aren't how Wagtail serves files so we 4 | // need to rewrite the paths 5 | 6 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 7 | 8 | const AUTOGENERATED_COMMENT = 9 | '\n{% comment %}\n\n DEV WARNING\n\n THIS FILE IS AUTO-GENERATED FROM "*_src" DIRECTORY. DO NOT EDIT!\n\n{% endcomment %}\n'; 10 | 11 | class FixPaths { 12 | apply(compiler) { 13 | compiler.hooks.compilation.tap('replaceStaticPath', (compilation) => { 14 | // Static Plugin interface |compilation |HOOK NAME | register listener 15 | HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync( 16 | 'replaceStaticPath', 17 | (data, cb) => { 18 | // Manipulate the content 19 | data.html = 20 | AUTOGENERATED_COMMENT + 21 | data.html.replace( 22 | /\.\.\/static\/(.*?)(["'])/gi, 23 | "{% static '$1' %}$2", 24 | ); 25 | // Tell webpack to move on 26 | cb(null, data); 27 | }, 28 | ); 29 | }); 30 | } 31 | } 32 | 33 | module.exports = FixPaths; 34 | -------------------------------------------------------------------------------- /ietf/static_src/css/bs-override.scss: -------------------------------------------------------------------------------- 1 | // Overriding IETF base themes and bootstrap. 2 | 3 | h1, 4 | h2, 5 | h3, 6 | h4, 7 | h5, 8 | h6 { 9 | font-weight: 600; 10 | } 11 | 12 | $grid-breakpoints: ( 13 | xs: 0, 14 | sm: 440px, 15 | md: 768px, 16 | lg: 992px, 17 | xl: 1200px, 18 | xxl: 1400px, 19 | ); 20 | 21 | p { 22 | //same as _reboot.scss in bootstrap 23 | margin-top: 0; 24 | margin-bottom: $paragraph-margin-bottom; 25 | 26 | //change margin to padding for last child, so the bottom space uses parent background color 27 | &:last-child { 28 | margin-bottom: 0; 29 | padding-bottom: $paragraph-margin-bottom; 30 | } 31 | } 32 | 33 | :root { 34 | --bs-body-font-family: 'Inter', Helvetica, arial, sans-serif; 35 | } 36 | 37 | // Adjust headings from BS defaults 38 | 39 | .h1, h1 { 40 | font-size: 2rem; 41 | } 42 | 43 | .h2, h2 { 44 | font-size: 1.75rem; 45 | margin-bottom: 0.6rem; 46 | } 47 | 48 | .h3, h3 { 49 | font-size: 1.5rem; 50 | margin-bottom: 0.5rem; 51 | } 52 | 53 | .h4, h4 { 54 | font-size: 1.25rem; 55 | margin-bottom: 0.5rem; 56 | } 57 | 58 | .h5, h5 { 59 | font-size: 1.25rem; 60 | margin-bottom: 0.5rem; 61 | } 62 | -------------------------------------------------------------------------------- /ietf/settings/docker/__init__.py: -------------------------------------------------------------------------------- 1 | from typed_environment_configuration import ( 2 | BoolVariable, 3 | FillVars, 4 | StringListVariable, 5 | StringVariable, 6 | ) 7 | 8 | from ..base import * # noqa: F403 9 | 10 | _ENVVARS = [ 11 | StringVariable( 12 | "APPLICATION_VERSION" 13 | ), # The Application version used across project 14 | StringListVariable("ADDRESSES", default=""), # list of allowed addresses 15 | StringVariable("APP_SECRET_KEY", prefix="APP_"), 16 | StringVariable("PROJECT"), # Project namespace 17 | StringVariable( 18 | "ENVIRONMENT" 19 | ), # Application environment i.e. development, production, etc. 20 | StringVariable("AWS_STORAGE_BUCKET_NAME", default=""), # S3 Bucket Name 21 | StringVariable("AWS_S3_CUSTOM_DOMAIN", default=""), # S3 Domain 22 | StringVariable("DATABASE_URL"), # e.g. postgres URL 23 | StringListVariable("WAGTAILADMIN_BASE_URL", default=""), 24 | ] 25 | 26 | _DJANGO_ENVVARS = [ 27 | BoolVariable("DJANGO_DEBUG", default=False), 28 | StringVariable("DJANGO_SERVER_ENV", default="Nonprod"), 29 | ] 30 | 31 | 32 | FillVars(_ENVVARS, vars()) 33 | FillVars(_DJANGO_ENVVARS, vars(), "DJANGO_") 34 | ALLOWED_HOSTS = ADDRESSES # noqa: F405 35 | -------------------------------------------------------------------------------- /ietf/forms/tests.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.core import mail 3 | from django.test import Client 4 | 5 | from ietf.home.models import HomePage 6 | 7 | from .factories import FormPageFactory 8 | from .models import FormPage 9 | 10 | pytestmark = pytest.mark.django_db 11 | 12 | 13 | class TestFormPage: 14 | FORM_ADDRESS = "forms@example.com" 15 | 16 | @pytest.fixture(autouse=True) 17 | def set_up(self, home: HomePage, client: Client): 18 | self.home = home 19 | self.client = client 20 | 21 | self.form_page: FormPage = FormPageFactory( 22 | parent=self.home, 23 | to_address=self.FORM_ADDRESS, 24 | ) # type: ignore 25 | 26 | def test_form_page(self): 27 | response = self.client.get(path=self.form_page.url) 28 | assert response.status_code == 200 29 | html = response.content.decode() 30 | 31 | assert self.form_page.title in html 32 | assert self.form_page.intro in html 33 | 34 | def test_submit(self): 35 | response = self.client.post(self.form_page.url, {}) 36 | assert response.status_code == 200 37 | assert len(mail.outbox) == 1 38 | message = mail.outbox[0] 39 | assert message.to == [self.FORM_ADDRESS] 40 | -------------------------------------------------------------------------------- /ietf/blog/templates/includes/blog_sidebar.html: -------------------------------------------------------------------------------- 1 | {% load wagtailroutablepage_tags %} 2 | {% if filter_text %} 3 |
    4 |

    {{ filter_text }}

    5 |
    6 | {% endif %} 7 | 30 | -------------------------------------------------------------------------------- /ietf/static_src/css/datepicker.scss: -------------------------------------------------------------------------------- 1 | .ui-datepicker-trigger { 2 | @extend .btn; 3 | @extend .btn-outline-dark; 4 | @extend .px-2; 5 | position: relative; 6 | } 7 | 8 | #ui-datepicker-div { 9 | padding: 1em; 10 | background-color: $white; 11 | border: 1px solid $primary; 12 | min-width: 17em; 13 | 14 | .ui-state-active { 15 | background-color: $primary; 16 | color: $white; 17 | } 18 | 19 | td a { 20 | display: block; 21 | } 22 | button { 23 | @extend .btn; 24 | @extend .btn-outline-primary; 25 | } 26 | .ui-state-highlight { 27 | outline: 2px solid $primary; 28 | } 29 | } 30 | 31 | .ui-datepicker-header { 32 | display: flex; 33 | justify-content: space-between; 34 | } 35 | 36 | .ui-datepicker-prev { 37 | order: 1; 38 | } 39 | .ui-datepicker-title { 40 | order: 2; 41 | margin-left: 1em; 42 | } 43 | .ui-datepicker-next { 44 | order: 3; 45 | margin-left: 1em; 46 | } 47 | 48 | .ui-datepicker-prev, 49 | .ui-datepicker-next { 50 | &:focus, 51 | &:active { 52 | text-decoration: underline; 53 | } 54 | } 55 | 56 | .ui-datepicker-calendar { 57 | min-width: 100%; 58 | text-align: center; 59 | } 60 | 61 | .ui-datepicker-buttonpane { 62 | text-align: right; 63 | } 64 | -------------------------------------------------------------------------------- /ietf/home/templates/includes/home_event.html: -------------------------------------------------------------------------------- 1 | {% load wagtailimages_tags %} 2 | 3 |
  • 4 |
    5 | {% image event.main_image height-245 as event_image %} 6 | {{ event.main_image.title }} 7 |
    8 | {# col-* classes include positioning, position static cancels that and allows the stretched link to cover the image #} 9 |
    10 |
    11 |

    {{ event.title }}

    12 |

    {{ event.introduction }}

    13 |
    14 | 15 | {{ event.date }} 16 | {{ event.listing_location }} 17 | {% if event.host %} 18 | {{ event.host.title }} 19 | {% endif %} 20 | 21 |
    22 |
    23 |
    24 |
  • 25 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | application: 3 | restart: on-failure 4 | build: 5 | context: . 6 | dockerfile: docker/Dockerfile 7 | target: app-dev 8 | volumes: 9 | - ".:/app" 10 | ports: 11 | - "${DOCKER_APPLICATION_PORT:-8001}:8000" 12 | env_file: 13 | - docker/dev.env 14 | environment: 15 | WAGTAILADMIN_BASE_URL: "http://localhost:${DOCKER_APPLICATION_PORT:-8001}/" 16 | depends_on: 17 | - database 18 | - memcached 19 | 20 | frontend: 21 | restart: on-failure 22 | build: 23 | context: . 24 | dockerfile: docker/Dockerfile 25 | target: frontend 26 | volumes: 27 | - ".:/app" 28 | environment: 29 | APP_SERVER_URL: "http://application:8000" 30 | depends_on: 31 | - application 32 | 33 | database: 34 | restart: on-failure 35 | image: postgres:16 36 | volumes: 37 | - "./docker/database:/docker-entrypoint-initdb.d/" 38 | environment: 39 | POSTGRES_DB: "app" 40 | POSTGRES_USER: "postgres" 41 | POSTGRES_PASSWORD: "password" 42 | 43 | memcached: 44 | image: memcached:latest 45 | hostname: memcached 46 | -------------------------------------------------------------------------------- /.github/workflows/ci-run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - '*' 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Docker build app-test 19 | uses: docker/build-push-action@v6 20 | with: 21 | context: . 22 | file: docker/Dockerfile 23 | target: app-test 24 | push: false 25 | tags: app-test:latest 26 | 27 | - name: Start database 28 | run: docker run --name=database -e POSTGRES_DB=app -e POSTGRES_PASSWORD=password -d postgres:14.6 29 | 30 | - name: Start memcached 31 | run: docker run --name=memcached -d memcached:latest 32 | 33 | - name: Run tests 34 | run: | 35 | set -x 36 | docker run -i --rm --name=app-test -u root --link=database --link=memcached -e DATABASE_URL="postgres://postgres:password@database/app" -e APP_SECRET_KEY=xxxx -e ENVIRONMENT=test -v $(pwd)/coverage:/coverage app-test 37 | cp coverage/.coverage coverage/coverage.xml ./ 38 | 39 | - name: Upload coverage reports to Codecov 40 | uses: codecov/codecov-action@v4-beta 41 | env: 42 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 43 | -------------------------------------------------------------------------------- /ietf/search/views.py: -------------------------------------------------------------------------------- 1 | from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator 2 | from django.http import HttpResponseBadRequest 3 | from django.shortcuts import render 4 | from wagtail.contrib.search_promotions.models import Query 5 | from wagtail.models import Page 6 | 7 | MAX_SEARCH_TERMS = 150 8 | 9 | 10 | def search(request): 11 | search_query = request.GET.get("query", None) 12 | page = request.GET.get("page", 1) 13 | 14 | # Search 15 | if search_query and ( 16 | search_query.count(" ") > MAX_SEARCH_TERMS or "\x00" in search_query 17 | ): 18 | return HttpResponseBadRequest("Invalid search query") 19 | elif search_query: 20 | search_results = Page.objects.live().search(search_query) 21 | Query.get(search_query).add_hit() 22 | else: 23 | search_results = Page.objects.none() 24 | 25 | # Pagination 26 | paginator = Paginator(search_results, 10) 27 | try: 28 | search_results = paginator.page(page) 29 | except PageNotAnInteger: 30 | search_results = paginator.page(1) 31 | except EmptyPage: 32 | search_results = paginator.page(paginator.num_pages) 33 | 34 | return render( 35 | request, 36 | "search/search.html", 37 | { 38 | "search_query": search_query, 39 | "search_results": search_results, 40 | }, 41 | ) 42 | -------------------------------------------------------------------------------- /ietf/snippets/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from django.utils.text import slugify 3 | from factory.django import DjangoModelFactory 4 | 5 | from .models import Charter, MailingListSignup, Person, Topic, WorkingGroup 6 | 7 | 8 | class PersonFactory(DjangoModelFactory): 9 | name = factory.Faker("name") 10 | slug = factory.LazyAttribute(lambda obj: slugify(obj.name)) 11 | link = factory.Faker("url") 12 | 13 | class Meta: # type: ignore 14 | model = Person 15 | 16 | 17 | class TopicFactory(DjangoModelFactory): 18 | title = factory.Faker("name") 19 | slug = factory.LazyAttribute(lambda obj: slugify(obj.title)) 20 | 21 | class Meta: # type: ignore 22 | model = Topic 23 | 24 | 25 | class CharterFactory(DjangoModelFactory): 26 | name = factory.Faker("name") 27 | 28 | class Meta: # type: ignore 29 | model = Charter 30 | 31 | 32 | class WorkingGroupFactory(DjangoModelFactory): 33 | name = factory.Faker("name") 34 | list_subscribe = factory.Faker("url") 35 | 36 | class Meta: # type: ignore 37 | model = WorkingGroup 38 | 39 | 40 | class MailingListSignupFactory(DjangoModelFactory): 41 | title = factory.Faker("name") 42 | blurb = factory.Faker("paragraph") 43 | button_text = factory.Faker("name") 44 | sign_up = factory.Faker("url") 45 | 46 | class Meta: # type: ignore 47 | model = MailingListSignup 48 | -------------------------------------------------------------------------------- /k8s/iabweb/nginx-default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080 default_server; 3 | listen [::]:8080 default_server; 4 | server_name _; 5 | gzip on; 6 | access_log /dev/stdout; 7 | error_log /dev/stdout warn; 8 | location / { 9 | proxy_pass http://127.0.0.1:8000; 10 | proxy_set_header Host $${keepempty}host; 11 | proxy_set_header X-Forwarded-For $${keepempty}proxy_add_x_forwarded_for; 12 | proxy_set_header Connection close; 13 | client_max_body_size 0; # disable size check 14 | # Set timeouts longer than Cloudflare proxy limits 15 | proxy_connect_timeout 60; # nginx default (Cf = 15) 16 | proxy_read_timeout 120; # nginx default = 60 (Cf = 100) 17 | proxy_send_timeout 60; # nginx default = 60 (Cf = 30) 18 | } 19 | location /media/ { 20 | alias /app/media/; 21 | 22 | error_page 404 = @error_redirect; 23 | } 24 | location /static/ { 25 | alias /app/static/; 26 | 27 | error_page 404 = @error_redirect; 28 | } 29 | location /robots.txt { 30 | add_header Content-Type text/plain; 31 | return 200 "User-agent: *\nDisallow: /admin/\nDisallow: /search/\n"; 32 | } 33 | location @error_redirect { 34 | proxy_pass http://127.0.0.1:8000; 35 | proxy_set_header Host $${keepempty}host; 36 | proxy_set_header X-Forwarded-For $${keepempty}proxy_add_x_forwarded_for; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ietf/settings/docker/grains/logging.py: -------------------------------------------------------------------------------- 1 | LOGGING = { 2 | "version": 1, 3 | "disable_existing_loggers": False, 4 | "formatters": { 5 | "verbose": { 6 | "format": "%(asctime)s | %(levelname)s [%(name)s.%(filename)s:%(lineno)s] %(message)s", 7 | "datefmt": "%Y-%m-%d %H:%M:%S%z", 8 | }, 9 | "simple": {"format": "%(levelname)s %(message)s"}, 10 | }, 11 | "handlers": { 12 | "stdout": { 13 | "level": "DEBUG", 14 | "class": "logging.StreamHandler", 15 | "formatter": "verbose", 16 | }, 17 | "mail_admins": { 18 | "level": "ERROR", 19 | "class": "django.utils.log.AdminEmailHandler", 20 | }, 21 | }, 22 | "loggers": { 23 | "django": {"handlers": ["stdout"], "propagate": True, "level": "ERROR"}, 24 | "core": {"handlers": ["stdout"], "propagate": True, "level": "INFO"}, 25 | "accounts": {"handlers": ["stdout"], "propagate": True, "level": "INFO"}, 26 | "shop": {"handlers": ["stdout"], "propagate": True, "level": "INFO"}, 27 | "certificates": {"handlers": ["stdout"], "propagate": True, "level": "INFO"}, 28 | "forms": {"handlers": ["stdout"], "propagate": True, "level": "INFO"}, 29 | "elearning": {"handlers": ["stdout"], "propagate": True, "level": "INFO"}, 30 | "shop_apps": {"handlers": ["stdout"], "propagate": True, "level": "INFO"}, 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /ietf/topics/test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.test import Client 3 | 4 | from ietf.home.models import HomePage 5 | 6 | from .factories import PrimaryTopicPageFactory, TopicIndexPageFactory 7 | from .models import PrimaryTopicPage, TopicIndexPage 8 | 9 | pytestmark = pytest.mark.django_db 10 | 11 | 12 | class TestTopicPage: 13 | @pytest.fixture(autouse=True) 14 | def set_up(self, home: HomePage, client: Client): 15 | self.home = home 16 | self.client = client 17 | 18 | self.topic_index: TopicIndexPage = TopicIndexPageFactory( 19 | parent=self.home, 20 | ) # type: ignore 21 | 22 | self.topic_page: PrimaryTopicPage = PrimaryTopicPageFactory( 23 | parent=self.topic_index, 24 | ) # type: ignore 25 | 26 | def test_index_page(self): 27 | response = self.client.get(path=self.topic_index.url) 28 | assert response.status_code == 200 29 | html = response.content.decode() 30 | 31 | assert self.topic_page.title in html 32 | assert f'href="{self.topic_page.url}"' in html 33 | 34 | def test_topic_page(self): 35 | response = self.client.get(path=self.topic_page.url) 36 | assert response.status_code == 200 37 | html = response.content.decode() 38 | 39 | assert self.topic_page.title in html 40 | assert self.topic_page.introduction in html 41 | assert f'href="{self.topic_index.url}"' in html 42 | -------------------------------------------------------------------------------- /ietf/utils/signal_handlers.py: -------------------------------------------------------------------------------- 1 | from django.db.models.signals import post_delete, post_save 2 | from wagtail.contrib.frontend_cache.utils import purge_pages_from_cache 3 | from wagtail.models import Page, ReferenceIndex 4 | from wagtail.signals import page_published, page_unpublished 5 | 6 | from ietf.utils.models import MainMenuItem 7 | 8 | 9 | def register_signal_handlers(): 10 | def page_published_or_unpublished_handler(instance, **kwargs): 11 | home_page = instance.get_site().root_page 12 | purge_pages = set() 13 | 14 | if instance.pk != home_page.pk: 15 | parent = instance.get_parent() 16 | purge_pages.add(parent) 17 | 18 | for obj, _ in ReferenceIndex.get_grouped_references_to(instance): 19 | if isinstance(obj, Page) and obj.live: 20 | purge_pages.add(obj) 21 | 22 | if isinstance(obj, MainMenuItem): 23 | purge_pages.add(home_page) 24 | 25 | purge_pages_from_cache(purge_pages) 26 | 27 | def main_menu_item_saved_or_deleted_handler(instance, **kwargs): 28 | home_page = instance.page.get_site().root_page 29 | purge_pages_from_cache({home_page}) 30 | 31 | page_published.connect(page_published_or_unpublished_handler) 32 | page_unpublished.connect(page_published_or_unpublished_handler) 33 | post_save.connect(main_menu_item_saved_or_deleted_handler, sender=MainMenuItem) 34 | post_delete.connect(main_menu_item_saved_or_deleted_handler, sender=MainMenuItem) 35 | -------------------------------------------------------------------------------- /ietf/home/migrations/0003_remove_bottom_content.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.7 on 2023-12-11 12:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("home", "0002_iabhomepage"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="workinggroupssectionlinks", 15 | name="link_document", 16 | ), 17 | migrations.RemoveField( 18 | model_name="workinggroupssectionlinks", 19 | name="link_page", 20 | ), 21 | migrations.RemoveField( 22 | model_name="workinggroupssectionlinks", 23 | name="page", 24 | ), 25 | migrations.RemoveField( 26 | model_name="homepage", 27 | name="highlighted_request_for_comment", 28 | ), 29 | migrations.RemoveField( 30 | model_name="homepage", 31 | name="highlighted_working_group", 32 | ), 33 | migrations.RemoveField( 34 | model_name="homepage", 35 | name="request_for_comments_section_body", 36 | ), 37 | migrations.RemoveField( 38 | model_name="homepage", 39 | name="working_groups_section_body", 40 | ), 41 | migrations.DeleteModel( 42 | name="RequestForCommentsSectionLinks", 43 | ), 44 | migrations.DeleteModel( 45 | name="WorkingGroupsSectionLinks", 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /ietf/utils/management/commands/update_nonprod_hostnames.py: -------------------------------------------------------------------------------- 1 | import os 2 | from logging import Logger 3 | 4 | from django.core.management.base import BaseCommand 5 | from wagtail.models import Site 6 | 7 | logger = Logger(__name__) 8 | 9 | 10 | ALLOWED_ENVIRONMENTS = [ 11 | "preview", 12 | "staging", 13 | ] 14 | 15 | HOSTNAME_TRANSLATIONS = { 16 | "www.ietf.org": { 17 | "preview": "wwwdev.ietf.org", 18 | "staging": "wwwstaging.ietf.org", 19 | }, 20 | "www.iab.org": { 21 | "preview": "iabdev.ietf.org", 22 | "staging": "iabstaging.ietf.org", 23 | }, 24 | } 25 | 26 | 27 | class Command(BaseCommand): 28 | help = "Updates Site objects to have appropriate hostnames for select non-production environments" 29 | 30 | def handle(self, *args, **options): 31 | # First, make sure we're on the appropriate enviornment. 32 | environment = os.getenv("ENVIRONMENT") 33 | if environment not in ALLOWED_ENVIRONMENTS: 34 | logger.warning("This command is not allowed for this environment.") 35 | exit() 36 | 37 | # Now adjust hostnames 38 | all_sites = Site.objects.all() 39 | for site in all_sites: 40 | if site.hostname not in HOSTNAME_TRANSLATIONS: 41 | logger.warning("Site hostname in database is not able to be updated.") 42 | continue 43 | new_hostname = HOSTNAME_TRANSLATIONS[site.hostname][environment] 44 | site.hostname = new_hostname 45 | site.save() 46 | -------------------------------------------------------------------------------- /ietf/utils/wagtail_hooks.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.utils.html import format_html 3 | from wagtail import hooks 4 | from wagtail.snippets.models import register_snippet 5 | from wagtail.snippets.views.snippets import SnippetViewSet 6 | from wagtail_modeladmin.options import ModelAdmin, modeladmin_register 7 | from wagtailorderable.modeladmin.mixins import OrderableMixin 8 | 9 | from .models import FooterColumn, MainMenuItem, SecondaryMenuItem 10 | 11 | 12 | @hooks.register("insert_global_admin_css") # type: ignore 13 | def editor_css(): 14 | return format_html( 15 | '' 18 | ) 19 | 20 | 21 | class MainMenuViewSet(SnippetViewSet): 22 | list_display = [ 23 | "__str__", 24 | "sort_order", 25 | ] # type: ignore 26 | 27 | 28 | register_snippet(MainMenuItem, viewset=MainMenuViewSet) 29 | 30 | 31 | class MenuItemAdmin(OrderableMixin, ModelAdmin): # type: ignore 32 | model = SecondaryMenuItem 33 | menu_order = 900 34 | menu_label = "Secondary Menu" 35 | menu_icon = "list-ul" 36 | add_to_settings_menu = True 37 | list_display = ("title",) 38 | 39 | ordering = ["sort_order"] 40 | 41 | 42 | modeladmin_register(MenuItemAdmin) 43 | 44 | 45 | class FooterColumnViewSet(SnippetViewSet): 46 | list_display = [ 47 | "__str__", 48 | "sort_order", 49 | ] # type: ignore 50 | 51 | 52 | register_snippet(FooterColumn, viewset=MainMenuViewSet) 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2019-2022 IETF Trust 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /ietf/blog/migrations/0002_auto_20210325_0442.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.16 on 2021-03-25 04:42 2 | 3 | import wagtail.blocks 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.embeds.blocks 6 | import wagtail.fields 7 | import wagtail.images.blocks 8 | from django.db import migrations 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ("blog", "0001_initial"), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name="blogpage", 20 | name="body", 21 | field=wagtail.fields.StreamField( 22 | [ 23 | ("heading", wagtail.blocks.CharBlock(icon="title")), 24 | ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), 25 | ( 26 | "image", 27 | wagtail.images.blocks.ImageChooserBlock( 28 | icon="image", template="includes/imageblock.html" 29 | ), 30 | ), 31 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), 32 | ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), 33 | ( 34 | "table", 35 | wagtail.contrib.table_block.blocks.TableBlock( 36 | table_options={"renderer": "html"}, 37 | template="includes/tableblock.html", 38 | ), 39 | ), 40 | ] 41 | ), 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /ietf/events/migrations/0002_auto_20210325_0442.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.16 on 2021-03-25 04:42 2 | 3 | import wagtail.blocks 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.embeds.blocks 6 | import wagtail.fields 7 | import wagtail.images.blocks 8 | from django.db import migrations 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ("events", "0001_initial"), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name="eventpage", 20 | name="body", 21 | field=wagtail.fields.StreamField( 22 | [ 23 | ("heading", wagtail.blocks.CharBlock(icon="title")), 24 | ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), 25 | ( 26 | "image", 27 | wagtail.images.blocks.ImageChooserBlock( 28 | icon="image", template="includes/imageblock.html" 29 | ), 30 | ), 31 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), 32 | ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), 33 | ( 34 | "table", 35 | wagtail.contrib.table_block.blocks.TableBlock( 36 | table_options={"renderer": "html"}, 37 | template="includes/tableblock.html", 38 | ), 39 | ), 40 | ] 41 | ), 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /ietf/iesg_statement/migrations/0002_auto_20210325_0442.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.16 on 2021-03-25 04:42 2 | 3 | import wagtail.blocks 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.embeds.blocks 6 | import wagtail.fields 7 | import wagtail.images.blocks 8 | from django.db import migrations 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ("iesg_statement", "0001_initial"), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name="iesgstatementpage", 20 | name="body", 21 | field=wagtail.fields.StreamField( 22 | [ 23 | ("heading", wagtail.blocks.CharBlock(icon="title")), 24 | ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), 25 | ( 26 | "image", 27 | wagtail.images.blocks.ImageChooserBlock( 28 | icon="image", template="includes/imageblock.html" 29 | ), 30 | ), 31 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), 32 | ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), 33 | ( 34 | "table", 35 | wagtail.contrib.table_block.blocks.TableBlock( 36 | table_options={"renderer": "html"}, 37 | template="includes/tableblock.html", 38 | ), 39 | ), 40 | ] 41 | ), 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /ietf/blog/templates/blog/blog_index_page.html: -------------------------------------------------------------------------------- 1 | {% extends settings.utils.LayoutSettings.base_template %} 2 | {% load wagtailroutablepage_tags %} 3 | 4 | {% block main_content %} 5 | 6 |
    7 |
    8 |
    9 | {% include 'includes/breadcrumbs.html' %} 10 |
    11 |

    {{self.title}}

    12 | 13 | {% if self.filter_topic %} 14 |

    You have filtered by {{ self.filter_topic.title }}

    15 | {% endif %} 16 |
    17 |
    18 |
    19 | 20 |
    21 |
    22 |
    23 |

    Filter Blog & News by Topic

    24 | 30 |
    31 | 32 |
    33 | 34 | {% for entry in entries %} 35 | 36 | 37 | 38 | 39 | {% endfor %} 40 |
    {{entry.coalesced_published_date|date:"Y-m-d"}}{{entry.title}}
    41 |
    42 | 43 |
    44 |
    45 |
    46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /ietf/snippets/migrations/0002_auto_20200414_2027.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 1.11.29 on 2020-04-14 20:27 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("snippets", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="group", 16 | name="image", 17 | field=models.ForeignKey( 18 | blank=True, 19 | help_text="An image to represent this group.", 20 | null=True, 21 | on_delete=django.db.models.deletion.SET_NULL, 22 | related_name="+", 23 | to="images.IETFImage", 24 | ), 25 | ), 26 | migrations.AlterField( 27 | model_name="group", 28 | name="role", 29 | field=models.ForeignKey( 30 | blank=True, 31 | help_text="This group's role within the IETF.", 32 | null=True, 33 | on_delete=django.db.models.deletion.SET_NULL, 34 | related_name="+", 35 | to="snippets.Role", 36 | ), 37 | ), 38 | migrations.AlterField( 39 | model_name="rfc", 40 | name="working_group", 41 | field=models.ForeignKey( 42 | blank=True, 43 | help_text="The working group that produced this RFC", 44 | null=True, 45 | on_delete=django.db.models.deletion.SET_NULL, 46 | related_name="+", 47 | to="snippets.WorkingGroup", 48 | ), 49 | ), 50 | ] 51 | -------------------------------------------------------------------------------- /ietf/conftest.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock 2 | 3 | import pytest 4 | from wagtail.models import Page, Site 5 | 6 | from ietf.home.factories import HomePageFactory, IABHomePageFactory 7 | from ietf.utils.models import IAB_BASE, LayoutSettings 8 | 9 | 10 | @pytest.fixture(autouse=True) 11 | def disable_caches(settings): 12 | """ 13 | Tests run with the "dev" settings, which use memcached. We override them 14 | with the dummy cache so we don't pollute our local development cache. 15 | """ 16 | 17 | settings.CACHES = { 18 | "default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}, 19 | "sessions": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}, 20 | "dummy": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}, 21 | } 22 | 23 | 24 | @pytest.fixture 25 | def home(): 26 | site = Site.objects.get() 27 | site.root_page = HomePageFactory(parent=Page.get_first_root_node()) 28 | site.save(update_fields=["root_page"]) 29 | return site.root_page 30 | 31 | 32 | @pytest.fixture 33 | def iab_home(): 34 | site = Site.objects.get() 35 | site.root_page = IABHomePageFactory(parent=Page.get_first_root_node()) 36 | site.hostname = "iab.org" 37 | site.save(update_fields=["root_page", "hostname"]) 38 | layout_settings = LayoutSettings.for_site(site) 39 | layout_settings.base_template = IAB_BASE 40 | layout_settings.save(update_fields=["base_template"]) 41 | return site.root_page 42 | 43 | 44 | @pytest.fixture(autouse=True) 45 | def iab_blog_feed(monkeypatch: pytest.MonkeyPatch): 46 | mock_get = Mock() 47 | mock_get.return_value.text = "" 48 | monkeypatch.setattr("ietf.home.models.get_request", mock_get) 49 | return mock_get 50 | -------------------------------------------------------------------------------- /ietf/forms/templates/forms/form_page.html: -------------------------------------------------------------------------------- 1 | {% extends settings.utils.LayoutSettings.base_template %} 2 | {% load wagtailcore_tags %} 3 | 4 | {% block content %} 5 |
    6 |
    7 |
    8 |

    {{ self.title }}

    9 | {{ self.intro|richtext }} 10 |
    11 |
    12 |
    13 | {% csrf_token %} 14 | 15 | {% if form.errors %} 16 |

    There were some errors with your form. Please amend the fields highlighted below.

    17 | {% endif %} 18 | 19 | {% for field in form %} 20 |
    21 | {% if field.errors %} 22 |

    {{ field.errors }}

    23 | {% endif %} 24 | 25 | 26 | {% if field.id_for_label %} 27 | {% include "includes/form_field.html" with field=field field_id=field.id_for_label %} 28 | {% else %} 29 | 30 | {% include "includes/form_field.html" with field=field field_id=field.auto_id %} 31 | {% endif %} 32 |
    33 | {% endfor %} 34 | 35 |
    36 |
    37 |
    38 |
    39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /ietf/blog/migrations/0003_auto_20211101_0113.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-11-01 01:13 2 | 3 | import wagtail.blocks 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.embeds.blocks 6 | import wagtail.fields 7 | import wagtail.images.blocks 8 | import wagtailmarkdown.blocks 9 | from django.db import migrations 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("blog", "0002_auto_20210325_0442"), 16 | ] 17 | 18 | operations = [ 19 | migrations.AlterField( 20 | model_name="blogpage", 21 | name="body", 22 | field=wagtail.fields.StreamField( 23 | [ 24 | ("heading", wagtail.blocks.CharBlock(icon="title")), 25 | ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), 26 | ( 27 | "image", 28 | wagtail.images.blocks.ImageChooserBlock( 29 | icon="image", template="includes/imageblock.html" 30 | ), 31 | ), 32 | ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), 33 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), 34 | ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), 35 | ( 36 | "table", 37 | wagtail.contrib.table_block.blocks.TableBlock( 38 | table_options={"renderer": "html"}, 39 | template="includes/tableblock.html", 40 | ), 41 | ), 42 | ] 43 | ), 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /ietf/utils/migrations/0005_layoutsettings.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2023-02-09 00:34 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wagtailcore", "0078_referenceindex"), 11 | ("utils", "0004_alter_menuitem_options"), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="LayoutSettings", 17 | fields=[ 18 | ( 19 | "id", 20 | models.AutoField( 21 | auto_created=True, 22 | primary_key=True, 23 | serialize=False, 24 | verbose_name="ID", 25 | ), 26 | ), 27 | ( 28 | "base_template", 29 | models.CharField( 30 | blank=True, 31 | choices=[ 32 | ("base.html", "IETF (default)"), 33 | ("iab_base.html", "IAB"), 34 | ], 35 | default="base.html", 36 | max_length=255, 37 | ), 38 | ), 39 | ( 40 | "site", 41 | models.OneToOneField( 42 | editable=False, 43 | on_delete=django.db.models.deletion.CASCADE, 44 | to="wagtailcore.site", 45 | ), 46 | ), 47 | ], 48 | options={ 49 | "abstract": False, 50 | }, 51 | ), 52 | ] 53 | -------------------------------------------------------------------------------- /ietf/events/migrations/0004_auto_20211101_0113.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-11-01 01:13 2 | 3 | import wagtail.blocks 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.embeds.blocks 6 | import wagtail.fields 7 | import wagtail.images.blocks 8 | import wagtailmarkdown.blocks 9 | from django.db import migrations 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("events", "0003_auto_20210704_2343"), 16 | ] 17 | 18 | operations = [ 19 | migrations.AlterField( 20 | model_name="eventpage", 21 | name="body", 22 | field=wagtail.fields.StreamField( 23 | [ 24 | ("heading", wagtail.blocks.CharBlock(icon="title")), 25 | ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), 26 | ( 27 | "image", 28 | wagtail.images.blocks.ImageChooserBlock( 29 | icon="image", template="includes/imageblock.html" 30 | ), 31 | ), 32 | ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), 33 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), 34 | ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), 35 | ( 36 | "table", 37 | wagtail.contrib.table_block.blocks.TableBlock( 38 | table_options={"renderer": "html"}, 39 | template="includes/tableblock.html", 40 | ), 41 | ), 42 | ] 43 | ), 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /ietf/utils/migrations/0007_auto_20230524_0551.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2023-05-24 04:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("utils", "0006_textchunk"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="socialmediasettings", 15 | name="linkedin", 16 | field=models.CharField( 17 | blank="True", 18 | help_text="Link to linkedin profile", 19 | max_length=255, 20 | verbose_name="LinkedIn link", 21 | ), 22 | ), 23 | migrations.AddField( 24 | model_name="socialmediasettings", 25 | name="mastodon", 26 | field=models.CharField( 27 | blank="True", 28 | help_text="Link to mastodon profile", 29 | max_length=255, 30 | verbose_name="Mastodon link", 31 | ), 32 | ), 33 | migrations.AddField( 34 | model_name="socialmediasettings", 35 | name="twitter", 36 | field=models.CharField( 37 | blank="True", 38 | help_text="Link to twitter profile", 39 | max_length=255, 40 | verbose_name="Twitter link", 41 | ), 42 | ), 43 | migrations.AddField( 44 | model_name="socialmediasettings", 45 | name="youtube", 46 | field=models.CharField( 47 | blank="True", 48 | help_text="Link to youtube account", 49 | max_length=255, 50 | verbose_name="Youtube link", 51 | ), 52 | ), 53 | ] 54 | -------------------------------------------------------------------------------- /ietf/iesg_statement/migrations/0003_auto_20211101_0113.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-11-01 01:13 2 | 3 | import wagtail.blocks 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.embeds.blocks 6 | import wagtail.fields 7 | import wagtail.images.blocks 8 | import wagtailmarkdown.blocks 9 | from django.db import migrations 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("iesg_statement", "0002_auto_20210325_0442"), 16 | ] 17 | 18 | operations = [ 19 | migrations.AlterField( 20 | model_name="iesgstatementpage", 21 | name="body", 22 | field=wagtail.fields.StreamField( 23 | [ 24 | ("heading", wagtail.blocks.CharBlock(icon="title")), 25 | ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), 26 | ( 27 | "image", 28 | wagtail.images.blocks.ImageChooserBlock( 29 | icon="image", template="includes/imageblock.html" 30 | ), 31 | ), 32 | ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), 33 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), 34 | ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), 35 | ( 36 | "table", 37 | wagtail.contrib.table_block.blocks.TableBlock( 38 | table_options={"renderer": "html"}, 39 | template="includes/tableblock.html", 40 | ), 41 | ), 42 | ] 43 | ), 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /ietf/blog/migrations/0004_alter_blogpage_body.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-09-02 04:24 2 | 3 | import wagtail.blocks 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.embeds.blocks 6 | import wagtail.fields 7 | import wagtail.images.blocks 8 | import wagtailmarkdown.blocks 9 | from django.db import migrations 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("blog", "0003_auto_20211101_0113"), 16 | ] 17 | 18 | operations = [ 19 | migrations.AlterField( 20 | model_name="blogpage", 21 | name="body", 22 | field=wagtail.fields.StreamField( 23 | [ 24 | ("heading", wagtail.blocks.CharBlock(icon="title")), 25 | ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), 26 | ( 27 | "image", 28 | wagtail.images.blocks.ImageChooserBlock( 29 | icon="image", template="includes/imageblock.html" 30 | ), 31 | ), 32 | ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), 33 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), 34 | ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), 35 | ( 36 | "table", 37 | wagtail.contrib.table_block.blocks.TableBlock( 38 | table_options={"renderer": "html"}, 39 | template="includes/tableblock.html", 40 | ), 41 | ), 42 | ], 43 | use_json_field=True, 44 | ), 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /ietf/iesg_statement/migrations/0004_alter_iesgstatementpage_body.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-12-01 21:39 2 | 3 | import wagtail.blocks 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.embeds.blocks 6 | import wagtail.fields 7 | import wagtail.images.blocks 8 | import wagtailmarkdown.blocks 9 | from django.db import migrations 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("iesg_statement", "0003_auto_20211101_0113"), 16 | ] 17 | 18 | operations = [ 19 | migrations.AlterField( 20 | model_name="iesgstatementpage", 21 | name="body", 22 | field=wagtail.fields.StreamField( 23 | [ 24 | ("heading", wagtail.blocks.CharBlock(icon="title")), 25 | ("paragraph", wagtail.blocks.RichTextBlock(icon="pilcrow")), 26 | ( 27 | "image", 28 | wagtail.images.blocks.ImageChooserBlock( 29 | icon="image", template="includes/imageblock.html" 30 | ), 31 | ), 32 | ("markdown", wagtailmarkdown.blocks.MarkdownBlock(icon="code")), 33 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="code")), 34 | ("raw_html", wagtail.blocks.RawHTMLBlock(icon="placeholder")), 35 | ( 36 | "table", 37 | wagtail.contrib.table_block.blocks.TableBlock( 38 | table_options={"renderer": "html"}, 39 | template="includes/tableblock.html", 40 | ), 41 | ), 42 | ], 43 | use_json_field=True, 44 | ), 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /ietf/context_processors.py: -------------------------------------------------------------------------------- 1 | from operator import itemgetter 2 | 3 | from wagtail.models import Site 4 | 5 | from ietf.home.models import HomePage, IABHomePage 6 | from ietf.utils.context_processors import get_footer, get_main_menu 7 | from ietf.utils.models import SecondaryMenuItem, SocialMediaSettings 8 | 9 | 10 | def home_page(site): 11 | if "iab" in site.hostname: 12 | return IABHomePage.objects.filter(depth=2).first() 13 | return HomePage.objects.filter(depth=2).first() 14 | 15 | 16 | def secondary_menu(site): 17 | if "iab" in site.hostname: 18 | return [] 19 | items = ( 20 | SecondaryMenuItem.objects.order_by("sort_order") 21 | .all() 22 | .select_related("page") 23 | .prefetch_related("sub_menu_items") 24 | ) 25 | return items 26 | 27 | 28 | def social_menu(site): 29 | social = SocialMediaSettings.for_site(site) 30 | links = [ 31 | {"url": social.linkedin, "icon": "linkedin", "title": "LinkedIn"}, 32 | {"url": social.twitter, "icon": "twitter", "title": "Twitter"}, 33 | {"url": social.youtube, "icon": "youtube", "title": "YouTube"}, 34 | {"url": social.mastodon, "icon": "mastodon", "title": "Mastodon"}, 35 | {"url": social.github, "icon": "github", "title": "GitHub"}, 36 | ] 37 | return filter(itemgetter("url"), links) 38 | 39 | 40 | def global_pages(request): 41 | site = Site.find_for_request(request) 42 | # XXX Return lazy values. This makes a big difference when a page renders 43 | # multiple templates, e.g. when the wagtail userbar is displayed. 44 | return { 45 | "HOME": lambda: home_page(site), 46 | "MENU": lambda: get_main_menu(site), 47 | "SECONDARY_MENU": lambda: secondary_menu(site), 48 | "SOCIAL_MENU": lambda: social_menu(site), 49 | "FOOTER": lambda: get_footer(), 50 | } 51 | -------------------------------------------------------------------------------- /ietf/utils/tests/test_iab_main_menu.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from bs4 import BeautifulSoup 3 | from django.test import Client 4 | 5 | from ietf.home.models import IABHomePage 6 | from ietf.standard.factories import IABStandardPageFactory 7 | 8 | pytestmark = pytest.mark.django_db 9 | 10 | 11 | class TestIABHome: 12 | @pytest.fixture(autouse=True) 13 | def set_up(self, iab_home: IABHomePage, client: Client): 14 | self.home = iab_home 15 | self.client = client 16 | 17 | def test_pages_in_menu(self): 18 | page1 = IABStandardPageFactory(parent=self.home, show_in_menus=True) 19 | page1a = IABStandardPageFactory(parent=page1, show_in_menus=True) 20 | page1b = IABStandardPageFactory(parent=page1, show_in_menus=True) 21 | page2 = IABStandardPageFactory(parent=self.home, show_in_menus=True) 22 | page2a = IABStandardPageFactory(parent=page2, show_in_menus=True) 23 | page2b = IABStandardPageFactory(parent=page2, show_in_menus=True) 24 | 25 | response = self.client.get(path=self.home.url) 26 | assert response.status_code == 200 27 | html = response.content.decode() 28 | soup = BeautifulSoup(html, "html.parser") 29 | 30 | def get_nav_item(item): 31 | """Get the menu item link, and the links within the menu.""" 32 | [main_link] = item.select("a.nav-link") 33 | child_links = item.select("ul.dropdown-menu > li > a") 34 | return ( 35 | main_link.attrs["href"], 36 | [link.attrs["href"] for link in child_links], 37 | ) 38 | 39 | menu = [get_nav_item(item) for item in soup.select(".navbar-nav > li")] 40 | assert menu == [ 41 | (page1.url, [page1a.url, page1b.url]), 42 | (page2.url, [page2a.url, page2b.url]), 43 | ("/search", []), 44 | ] 45 | -------------------------------------------------------------------------------- /ietf/forms/migrations/0003_auto_20220722_0302.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-07-22 02:02 2 | 3 | import wagtail.contrib.forms.models 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("forms", "0002_formfield_clean_name"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="formfield", 16 | name="choices", 17 | field=models.TextField( 18 | blank=True, 19 | help_text="Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.", 20 | verbose_name="choices", 21 | ), 22 | ), 23 | migrations.AlterField( 24 | model_name="formfield", 25 | name="default_value", 26 | field=models.TextField( 27 | blank=True, 28 | help_text="Default value. Comma or new line separated values supported for checkboxes.", 29 | verbose_name="default value", 30 | ), 31 | ), 32 | migrations.AlterField( 33 | model_name="formpage", 34 | name="from_address", 35 | field=models.EmailField( 36 | blank=True, max_length=255, verbose_name="from address" 37 | ), 38 | ), 39 | migrations.AlterField( 40 | model_name="formpage", 41 | name="to_address", 42 | field=models.CharField( 43 | blank=True, 44 | help_text="Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma.", 45 | max_length=255, 46 | validators=[wagtail.contrib.forms.models.validate_to_address], 47 | verbose_name="to address", 48 | ), 49 | ), 50 | ] 51 | -------------------------------------------------------------------------------- /ietf/templates/includes/social_share.html: -------------------------------------------------------------------------------- 1 | {% load ietf_tags wagtailcore_tags %} 2 | 3 | {% comment %} 4 | You can set the correct heahding level, for example 5 | {% include "includes/social_share.html" with heading="h4" %} 6 | {% endcomment %} 7 | 8 | {% with settings.utils.SocialMediaSettings as social_media_settings %} 9 | 10 | <{{ heading|default:"h2"}} class="h4">Share this page 11 | 12 | {% comment %} 13 | {% spaceless %} 14 | 20 | 21 | 22 | {% endspaceless %} 23 | {% endcomment %} 24 | {% spaceless %} 25 | 30 | 31 | 32 | {% endspaceless %} 33 | {% spaceless %} 34 | 39 | 40 | 41 | {% endspaceless %} 42 | {% endwith %} 43 | -------------------------------------------------------------------------------- /ietf/utils/tests/test_secondary_menu.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.test import Client 3 | from wagtail.test.utils import WagtailTestUtils 4 | 5 | from ietf.events.factories import EventListingPageFactory, EventPageFactory 6 | from ietf.home.models import HomePage 7 | from ietf.utils.models import SecondaryMenuItem 8 | 9 | pytestmark = pytest.mark.django_db 10 | 11 | 12 | class TestMenu(WagtailTestUtils): 13 | @pytest.fixture(autouse=True) 14 | def set_up(self, home: HomePage): 15 | self.home = home 16 | self.eventlisting = EventListingPageFactory( 17 | parent=home, 18 | ) 19 | self.eventpage = EventPageFactory( 20 | parent=self.eventlisting, 21 | ) 22 | 23 | def _build_menu(self): 24 | SecondaryMenuItem.objects.create( 25 | page=self.eventlisting, text="Menu One", sort_order=0 26 | ) 27 | SecondaryMenuItem.objects.create( 28 | page=self.eventpage, text="Menu Two", sort_order=1 29 | ) 30 | 31 | def test_admin_menu_item_index(self, admin_client): 32 | response = admin_client.get("/admin/utils/secondarymenuitem/") 33 | assert response.status_code == 200 34 | 35 | def test_menu_context_loads(self, client: Client): 36 | self._build_menu() 37 | menu_items = SecondaryMenuItem.objects.order_by("sort_order").all() 38 | response = client.get("/") 39 | assert response.status_code == 200 40 | secondary_menu = response.context["SECONDARY_MENU"]() 41 | assert len(secondary_menu) == 2 42 | assert menu_items[0] == secondary_menu[0] 43 | assert menu_items[1] == secondary_menu[1] 44 | 45 | def test_menu_in_template(self, client: Client): 46 | self._build_menu() 47 | response = client.get("/") 48 | html = response.content.decode() 49 | assert "Menu Two" in html 50 | assert "Menu One" in html 51 | -------------------------------------------------------------------------------- /ietf/static_src/css/fonts.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'montserrat'; 3 | src: url('../fonts/montserrat/montserrat-bold-webfont.woff2') 4 | format('woff2'), 5 | url('../fonts/montserrat/montserrat-bold-webfont.woff') format('woff'), 6 | url('../fonts/montserrat/montserrat-bold-webfont.otf') format('otf'), 7 | url('../fonts/montserrat/montserrat-bold-webfont.eot') format('eot'); 8 | font-weight: 700; 9 | font-style: normal; 10 | } 11 | 12 | @font-face { 13 | font-family: 'montserrat'; 14 | src: url('../fonts/montserrat/montserrat-light-webfont.woff2') 15 | format('woff2'), 16 | url('../fonts/montserrat/montserrat-light-webfont.woff') format('woff'), 17 | url('../fonts/montserrat/montserrat-light-webfont.otf') format('otf'), 18 | url('../fonts/montserrat/montserrat-light-webfont.eot') format('eot'); 19 | font-weight: 300; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'montserrat'; 25 | src: url('../fonts/montserrat/montserrat-medium-webfont.woff2') 26 | format('woff2'), 27 | url('../fonts/montserrat/montserrat-medium-webfont.woff') format('woff'), 28 | url('../fonts/montserrat/montserrat-medium-webfont.otf') format('otf'), 29 | url('../fonts/montserrat/montserrat-medium-webfont.eot') format('eot'); 30 | font-weight: 500; 31 | font-style: normal; 32 | } 33 | 34 | @font-face { 35 | font-family: 'montserrat'; 36 | src: url('../fonts/montserrat/montserrat-regular-webfont.woff2') 37 | format('woff2'), 38 | url('../fonts/montserrat/montserrat-regular-webfont.woff') 39 | format('woff'), 40 | url('../fonts/montserrat/montserrat-regular-webfont.otf') format('otf'), 41 | url('../fonts/montserrat/montserrat-regular-webfont.eot') format('eot'); 42 | font-weight: 400; 43 | font-style: normal; 44 | } 45 | 46 | body { 47 | -webkit-font-smoothing: antialiased; 48 | } 49 | -------------------------------------------------------------------------------- /ietf/forms/models.py: -------------------------------------------------------------------------------- 1 | from logging import Logger 2 | 3 | from django.contrib import messages 4 | from modelcluster.fields import ParentalKey 5 | from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel 6 | from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField 7 | from wagtail.fields import RichTextField 8 | 9 | logger = Logger(__name__) 10 | 11 | 12 | class EmailException(Exception): 13 | def __init__(self, message="Error sending email", code=500, params=None): 14 | super().__init__(message, code, params) 15 | self.message = message 16 | self.code = code 17 | 18 | 19 | class FormField(AbstractFormField): 20 | page = ParentalKey("FormPage", related_name="form_fields") 21 | 22 | @classmethod 23 | def _migrate_legacy_clean_name(cls): 24 | return None 25 | 26 | 27 | class FormPage(AbstractEmailForm): 28 | intro = RichTextField(blank=True) 29 | thank_you_text = RichTextField(blank=True) 30 | 31 | def send_mail(self, form): 32 | try: 33 | super().send_mail(form) 34 | except Exception as ex: 35 | logger.error(f"Failed to send email with exception: {ex}") 36 | raise EmailException from ex 37 | 38 | def serve(self, request, *args, **kwargs): 39 | try: 40 | return super().serve(request, *args, **kwargs) 41 | except EmailException: 42 | messages.add_message( 43 | request, messages.ERROR, message="Failed to send email" 44 | ) 45 | raise 46 | 47 | 48 | FormPage.content_panels = [ 49 | FieldPanel("title", classname="title"), 50 | FieldPanel("intro"), 51 | InlinePanel("form_fields", label="Form fields"), 52 | FieldPanel("thank_you_text"), 53 | MultiFieldPanel( 54 | [ 55 | FieldPanel("to_address"), 56 | FieldPanel("from_address"), 57 | FieldPanel("subject"), 58 | ], 59 | "Email", 60 | ), 61 | ] 62 | -------------------------------------------------------------------------------- /ietf/topics/templates/topics/topic_index_page.html: -------------------------------------------------------------------------------- 1 | {% extends settings.utils.LayoutSettings.base_template %} 2 | 3 | {% load wagtailimages_tags wagtailcore_tags %} 4 | 5 | {% block main_content %} 6 |
    7 | 8 |
    9 |
    10 |
    11 | {% include 'includes/breadcrumbs.html' %} 12 |

    {{ self.title }}

    13 |

    {{ self.introduction }}

    14 |
    15 |
    16 |
    17 | 18 | 43 | 44 |
    45 | 46 | {% endblock main_content %} 47 | -------------------------------------------------------------------------------- /k8s/ietfweb/cron.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: CronJob 3 | metadata: 4 | name: ietfwww-cron-hourly 5 | labels: 6 | app.kubernetes.io/name: cron-hourly 7 | app.kubernetes.io/instance: ietfwww-cron-hourly 8 | app.kubernetes.io/component: cronjob 9 | spec: 10 | schedule: "44 * * * *" # "At minute 44 of every hour." 11 | timeZone: "Etc/UTC" 12 | concurrencyPolicy: Forbid # No concurrent runs 13 | jobTemplate: 14 | spec: 15 | backoffLimit: 0 # No retries 16 | template: 17 | spec: 18 | restartPolicy: Never 19 | containers: 20 | - name: ietfwww-cron-hourly 21 | image: "ghcr.io/ietf-tools/www:$APP_IMAGE_TAG" 22 | imagePullPolicy: Always 23 | volumeMounts: 24 | - name: ietfwww-cfg 25 | mountPath: /app/ietf/settings/local.py 26 | subPath: local.py 27 | env: 28 | - name: "DJANGO_SETTINGS_MODULE" 29 | value: "ietf.settings.production" 30 | # ensures the pod gets recreated on every deploy: 31 | - name: "DEPLOY_UID" 32 | value: "$DEPLOY_UID" 33 | envFrom: 34 | - secretRef: 35 | name: ietfwww-secrets-env 36 | securityContext: 37 | allowPrivilegeEscalation: false 38 | capabilities: 39 | drop: 40 | - ALL 41 | readOnlyRootFilesystem: true 42 | runAsUser: 1000 43 | runAsGroup: 1000 44 | command: 45 | - /bin/sh 46 | - -c 47 | - | 48 | python /app/manage.py publish_scheduled && 49 | python /app/manage.py update_index && 50 | python /app/manage.py rebuild_references_index 51 | volumes: 52 | - name: ietfwww-cfg 53 | configMap: 54 | name: ietfwww-files-cfgmap 55 | -------------------------------------------------------------------------------- /ietf/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import include 3 | from django.contrib import admin 4 | from django.urls import path, re_path 5 | from wagtail import urls as wagtail_urls 6 | from wagtail.admin import urls as wagtailadmin_urls 7 | from wagtail.contrib.sitemaps.views import sitemap 8 | from wagtail.documents import urls as wagtaildocs_urls 9 | 10 | from ietf.bibliography import urls as bibliography_urls 11 | from ietf.blog.feeds import BlogFeed 12 | from ietf.health.views import healthz 13 | from ietf.search.views import search 14 | from ietf.snippets import urls as snippet_urls 15 | 16 | handler500 = "ietf.views.server_error" 17 | 18 | 19 | urlpatterns = [ 20 | path("sitemap.xml", sitemap), 21 | path("healthz", healthz, name="healthz"), 22 | re_path(r"^admin/doc/", include("django.contrib.admindocs.urls")), 23 | re_path(r"^bibliography/", include(bibliography_urls)), 24 | re_path(r"^django-admin/", admin.site.urls), 25 | re_path(r"^blog/feed/$", BlogFeed(), name="blog_feed"), 26 | re_path(r"^admin/", include(wagtailadmin_urls)), 27 | re_path(r"^documents/", include(wagtaildocs_urls)), 28 | re_path(r"^search/$", search, name="search"), 29 | ] 30 | 31 | 32 | if settings.DEBUG: # pragma: no cover 33 | from django.conf.urls.static import static 34 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 35 | from django.views.generic import TemplateView 36 | 37 | # Serve static and media files from development server 38 | urlpatterns += staticfiles_urlpatterns() 39 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 40 | 41 | # Add views for testing 404 and 500 templates 42 | urlpatterns += [ 43 | re_path(r"^test404/$", TemplateView.as_view(template_name="404.html")), 44 | re_path(r"^test500/$", TemplateView.as_view(template_name="500.html")), 45 | ] 46 | 47 | 48 | urlpatterns += [ 49 | re_path(r"^misc/", include(snippet_urls)), 50 | re_path(r"", include(wagtail_urls)), 51 | ] 52 | -------------------------------------------------------------------------------- /ietf/events/tests.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | import pytest 4 | from django.test import Client 5 | from django.utils import timezone 6 | 7 | from ietf.home.models import HomePage 8 | 9 | from .factories import EventListingPageFactory, EventPageFactory 10 | from .models import EventListingPage, EventPage 11 | 12 | pytestmark = pytest.mark.django_db 13 | 14 | 15 | class TestEventPage: 16 | @pytest.fixture(autouse=True) 17 | def set_up(self, home: HomePage, client: Client): 18 | self.home = home 19 | self.client = client 20 | 21 | self.event_listing: EventListingPage = EventListingPageFactory( 22 | parent=self.home, 23 | ) # type: ignore 24 | self.event_page: EventPage = EventPageFactory( 25 | parent=self.event_listing, 26 | end_date=timezone.now() + timedelta(days=1), 27 | body__0__heading="Heading in body Streamfield", 28 | ) # type: ignore 29 | 30 | def test_event_listing(self): 31 | response = self.client.get(path=self.event_listing.url) 32 | assert response.status_code == 200 33 | html = response.content.decode() 34 | 35 | assert self.event_page.title in html 36 | assert f'href="{self.event_page.url}"' in html 37 | 38 | def test_event_page(self): 39 | response = self.client.get(path=self.event_page.url) 40 | assert response.status_code == 200 41 | html = response.content.decode() 42 | 43 | assert self.event_page.title in html 44 | assert self.event_page.body[0].value in html 45 | assert self.event_page.introduction in html 46 | assert f'href="{self.event_listing.url}"' in html 47 | 48 | def test_home_page(self): 49 | """The first two upcoming events are shown on the homepage.""" 50 | response = self.client.get(path=self.home.url) 51 | assert response.status_code == 200 52 | html = response.content.decode() 53 | 54 | assert f'href="{self.event_page.url}"' in html 55 | assert self.event_page.title in html 56 | -------------------------------------------------------------------------------- /ietf/snippets/tests/test_mailing_list_signup.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from bs4 import BeautifulSoup 3 | from django.test import Client 4 | from django.urls import reverse 5 | 6 | from ietf.home.models import HomePage 7 | from ietf.snippets.factories import MailingListSignupFactory, WorkingGroupFactory 8 | from ietf.standard.factories import StandardPageFactory 9 | 10 | pytestmark = pytest.mark.django_db 11 | 12 | 13 | def test_disclaimer(client: Client, home: HomePage): 14 | """ 15 | The "note well" disclaimer is a page that is shown when a user clicks on a 16 | mailing list link. It displays an informative text, and the "next" button 17 | is a link to the actual mailing list. 18 | """ 19 | snippet = MailingListSignupFactory() 20 | page = StandardPageFactory(parent=home, mailing_list_signup=snippet) 21 | 22 | page_response = client.get(page.url) 23 | assert page_response.status_code == 200 24 | page_html = page_response.content.decode() 25 | page_soup = BeautifulSoup(page_html, "html.parser") 26 | [link] = page_soup.select(".mailing_list_signup__container a") 27 | disclaimer_url = reverse("disclaimer", args=[snippet.pk]) 28 | assert link.attrs["href"] == disclaimer_url 29 | 30 | disclaimer_response = client.get(disclaimer_url) 31 | assert disclaimer_response.status_code == 200 32 | disclaimer_html = disclaimer_response.content.decode() 33 | disclaimer_soup = BeautifulSoup(disclaimer_html, "html.parser") 34 | 35 | assert 'See ' in disclaimer_html 36 | link = disclaimer_soup.select(".body .container a")[-1] 37 | assert "I understand" in link.get_text() 38 | assert link.attrs["href"] == snippet.sign_up 39 | 40 | 41 | def test_link_mailto(): 42 | snippet = MailingListSignupFactory(sign_up="foo@example.com") 43 | assert snippet.link == "mailto:foo@example.com" 44 | 45 | 46 | def test_link_working_group(): 47 | working_group = WorkingGroupFactory() 48 | snippet = MailingListSignupFactory(sign_up="", working_group=working_group) 49 | assert snippet.link == working_group.list_subscribe 50 | -------------------------------------------------------------------------------- /ietf/documents/templates/wagtaildocs/documents/list.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | 18 | 27 | 28 | 29 | 30 | {% for doc in documents %} 31 | 32 | 39 | 40 | 41 | 42 | {% endfor %} 43 | 44 |
    9 | {% if not is_searching %} 10 | 11 | {% trans "Title" %} 12 | 13 | {% else %} 14 | {% trans "Title" %} 15 | {% endif %} 16 | {% trans "File" %} 19 | {% if not is_searching %} 20 | 21 | {% trans "Uploaded" %} 22 | 23 | {% else %} 24 | {% trans "Uploaded" %} 25 | {% endif %} 26 |
    33 | {% if choosing %} 34 |

    {{ doc.title }}

    35 | {% else %} 36 |

    {{ doc.title }}

    37 | {% endif %} 38 |
    {{ doc.filename }}
    {% blocktrans with time_period=doc.created_at|timesince %}{{ time_period }} ago{% endblocktrans %}
    45 | -------------------------------------------------------------------------------- /ietf/home/migrations/0002_iabhomepage.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2023-02-19 22:04 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wagtailcore", "0078_referenceindex"), 11 | ("images", "0002_alter_ietfimage_file_hash"), 12 | ("home", "0001_initial"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="IABHomePage", 18 | fields=[ 19 | ( 20 | "page_ptr", 21 | models.OneToOneField( 22 | auto_created=True, 23 | on_delete=django.db.models.deletion.CASCADE, 24 | parent_link=True, 25 | primary_key=True, 26 | serialize=False, 27 | to="wagtailcore.page", 28 | ), 29 | ), 30 | ("heading", models.CharField(max_length=255)), 31 | ("button_text", models.CharField(blank=True, max_length=255)), 32 | ( 33 | "button_link", 34 | models.ForeignKey( 35 | blank=True, 36 | null=True, 37 | on_delete=django.db.models.deletion.SET_NULL, 38 | related_name="+", 39 | to="wagtailcore.page", 40 | ), 41 | ), 42 | ( 43 | "main_image", 44 | models.ForeignKey( 45 | blank=True, 46 | null=True, 47 | on_delete=django.db.models.deletion.SET_NULL, 48 | related_name="+", 49 | to="images.ietfimage", 50 | ), 51 | ), 52 | ], 53 | options={ 54 | "verbose_name": "IAB Home Page", 55 | }, 56 | bases=("wagtailcore.page",), 57 | ), 58 | ] 59 | -------------------------------------------------------------------------------- /ietf/utils/migrations/0008_socialmediasettings_github_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.7 on 2023-12-14 09:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("utils", "0007_auto_20230524_0551"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="socialmediasettings", 15 | name="github", 16 | field=models.CharField( 17 | blank="True", 18 | help_text="Link to GitHub profile", 19 | max_length=255, 20 | verbose_name="GitHub link", 21 | ), 22 | ), 23 | migrations.AlterField( 24 | model_name="socialmediasettings", 25 | name="linkedin", 26 | field=models.CharField( 27 | blank="True", 28 | help_text="Link to LinkedIn profile", 29 | max_length=255, 30 | verbose_name="LinkedIn link", 31 | ), 32 | ), 33 | migrations.AlterField( 34 | model_name="socialmediasettings", 35 | name="mastodon", 36 | field=models.CharField( 37 | blank="True", 38 | help_text="Link to Mastodon profile", 39 | max_length=255, 40 | verbose_name="Mastodon link", 41 | ), 42 | ), 43 | migrations.AlterField( 44 | model_name="socialmediasettings", 45 | name="twitter", 46 | field=models.CharField( 47 | blank="True", 48 | help_text="Link to Twitter profile", 49 | max_length=255, 50 | verbose_name="Twitter link", 51 | ), 52 | ), 53 | migrations.AlterField( 54 | model_name="socialmediasettings", 55 | name="youtube", 56 | field=models.CharField( 57 | blank="True", 58 | help_text="Link to YouTube account", 59 | max_length=255, 60 | verbose_name="Youtube link", 61 | ), 62 | ), 63 | ] 64 | -------------------------------------------------------------------------------- /ietf/static/img/iab-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 13 | 15 | 17 | 19 | 21 | 23 | 25 | 27 | 29 | 30 | -------------------------------------------------------------------------------- /k8s/iabweb/cron.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: CronJob 3 | metadata: 4 | name: iabwww-cron-hourly 5 | labels: 6 | app.kubernetes.io/name: cron-hourly 7 | app.kubernetes.io/instance: iabwww-cron-hourly 8 | app.kubernetes.io/component: cronjob 9 | spec: 10 | schedule: "32 * * * *" # "At minute 32 of every hour." 11 | timeZone: "Etc/UTC" 12 | concurrencyPolicy: Forbid # No concurrent runs 13 | jobTemplate: 14 | spec: 15 | backoffLimit: 0 # No retries 16 | template: 17 | spec: 18 | restartPolicy: Never 19 | containers: 20 | - name: iabwww-cron-hourly 21 | image: "ghcr.io/ietf-tools/www:$APP_IMAGE_TAG" 22 | imagePullPolicy: Always 23 | volumeMounts: 24 | - name: iabwww-cfg 25 | mountPath: /app/ietf/settings/local.py 26 | subPath: local.py 27 | env: 28 | - name: "DJANGO_SETTINGS_MODULE" 29 | value: "ietf.settings.production" 30 | # ensures the pod gets recreated on every deploy: 31 | - name: "DEPLOY_UID" 32 | value: "$DEPLOY_UID" 33 | envFrom: 34 | - secretRef: 35 | name: iabwww-secrets-env 36 | securityContext: 37 | allowPrivilegeEscalation: false 38 | capabilities: 39 | drop: 40 | - ALL 41 | readOnlyRootFilesystem: true 42 | runAsUser: 1000 43 | runAsGroup: 1000 44 | resources: 45 | requests: 46 | memory: "64Mi" 47 | cpu: "100m" 48 | limits: 49 | memory: "2Gi" 50 | cpu: "1000m" 51 | command: 52 | - /bin/sh 53 | - -c 54 | - | 55 | python /app/manage.py publish_scheduled && 56 | python /app/manage.py update_index && 57 | python /app/manage.py rebuild_references_index 58 | volumes: 59 | - name: iabwww-cfg 60 | configMap: 61 | name: iabwww-files-cfgmap 62 | -------------------------------------------------------------------------------- /ietf/bibliography/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.contenttypes.models import ContentType 2 | from django.db.models import Count 3 | from django.shortcuts import render 4 | from wagtail.models import Page 5 | 6 | from .models import BibliographyItem 7 | 8 | 9 | def referenced_types(request): 10 | content_types = ( 11 | BibliographyItem.objects.exclude(content_type=None) 12 | .order_by() 13 | .values_list("content_type") 14 | .distinct() 15 | .annotate(num=Count("content_type")) 16 | .order_by("-num") 17 | ) 18 | return render( 19 | request, 20 | "bibliography/referenced_types.html", 21 | { 22 | "types": [ 23 | (ContentType.objects.get(pk=type_id), count) 24 | for type_id, count in content_types 25 | ] 26 | }, 27 | ) 28 | 29 | 30 | def referenced_objects(request, content_type_id): 31 | content_type = ContentType.objects.get(pk=content_type_id) 32 | object_ids = ( 33 | BibliographyItem.objects.filter(content_type=content_type_id) 34 | .order_by() 35 | .values_list("object_id") 36 | .distinct() 37 | .annotate(num=Count("object_id")) 38 | .order_by("-num") 39 | ) 40 | return render( 41 | request, 42 | "bibliography/referenced_objects.html", 43 | { 44 | "title": content_type._meta.verbose_name, 45 | "content_type_id": content_type_id, 46 | "objects": [ 47 | (content_type.get_object_for_this_type(id=object_id), count) 48 | for object_id, count in object_ids 49 | ], 50 | }, 51 | ) 52 | 53 | 54 | def referencing_pages(request, content_type_id, object_id): 55 | content_type = ContentType.objects.get(pk=content_type_id) 56 | obj = content_type.get_object_for_this_type(id=object_id) 57 | page_ids = BibliographyItem.objects.filter( 58 | content_type=content_type_id, object_id=object_id 59 | ).values_list("page", flat=True) 60 | pages = Page.objects.filter(pk__in=page_ids) 61 | return render( 62 | request, 63 | "bibliography/referencing_pages.html", 64 | {"title": obj.__str__(), "pages": pages}, 65 | ) 66 | -------------------------------------------------------------------------------- /ietf/standard/tests.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.test import Client 3 | 4 | from ietf.home.models import HomePage, IABHomePage 5 | 6 | from .factories import ( 7 | IABStandardPageFactory, 8 | StandardIndexPageFactory, 9 | StandardPageFactory, 10 | ) 11 | from .models import IABStandardPage, StandardIndexPage, StandardPage 12 | 13 | pytestmark = pytest.mark.django_db 14 | 15 | 16 | class TestStandardPage: 17 | @pytest.fixture(autouse=True) 18 | def set_up(self, home: HomePage, client: Client): 19 | self.home = home 20 | self.client = client 21 | 22 | self.standard_index: StandardIndexPage = StandardIndexPageFactory( 23 | parent=self.home, 24 | ) # type: ignore 25 | 26 | self.standard_page: StandardPage = StandardPageFactory( 27 | parent=self.standard_index, 28 | ) # type: ignore 29 | 30 | def test_index_page(self): 31 | response = self.client.get(path=self.standard_index.url) 32 | assert response.status_code == 200 33 | html = response.content.decode() 34 | 35 | assert self.standard_page.title in html 36 | assert f'href="{self.standard_page.url}"' in html 37 | 38 | def test_standard_page(self): 39 | response = self.client.get(path=self.standard_page.url) 40 | assert response.status_code == 200 41 | html = response.content.decode() 42 | 43 | assert self.standard_page.title in html 44 | assert self.standard_page.introduction in html 45 | assert f'href="{self.standard_index.url}"' in html 46 | 47 | 48 | class TestIABStandardPage: 49 | @pytest.fixture(autouse=True) 50 | def set_up(self, iab_home: IABHomePage, client: Client): 51 | self.home = iab_home 52 | self.client = client 53 | 54 | self.standard_page: IABStandardPage = IABStandardPageFactory( 55 | parent=self.home, 56 | ) # type: ignore 57 | 58 | def test_standard_page(self): 59 | response = self.client.get(path=self.standard_page.url) 60 | assert response.status_code == 200 61 | html = response.content.decode() 62 | 63 | assert self.standard_page.title in html 64 | assert self.standard_page.introduction in html 65 | assert f'href="{self.home.url}"' in html 66 | -------------------------------------------------------------------------------- /ietf/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends settings.utils.LayoutSettings.base_template %} 2 | 3 | {% block body_class %}template-500{% endblock %} 4 | 5 | {% block title %}- 500 - Internal server error{% endblock %} 6 | 7 | {% block main_content %} 8 |
    9 |
    10 |
    11 | 12 | 21 | 22 | 23 |
    24 |

    HTTP Status Code: 500 Internal Server Error

    25 | 26 |

    Ooops, something went wrong. We are looking into it, please try again soon.

    27 | 28 | {% if messages %} 29 |
      30 | {% for message in messages %} 31 | {{ message }} 32 | {% endfor %} 33 |
    34 | {% endif %} 35 |
    36 | 37 |
    38 |
    39 |
    40 | 41 | 42 |
    43 |
    44 |
    45 | 46 |
    47 |
    48 | 49 |
    50 |
    51 |

    Report problems

    52 |

    If the matter is urgent, please email support@ietf.org .

    53 |
    54 |
    55 | 56 |
    57 | 58 | {% endblock %} 59 | --------------------------------------------------------------------------------