Skip to content

Commit

Permalink
Merge pull request #448 from ietf-tools/feat/modern-footer
Browse files Browse the repository at this point in the history
feat: Modern footer
  • Loading branch information
kesara authored May 27, 2024
2 parents 982c825 + e368cf2 commit 271340f
Show file tree
Hide file tree
Showing 74 changed files with 2,281 additions and 811 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci-run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ on:
push:
branches:
- main
- feat/modern-footer
pull_request:
branches:
- '*'
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ name: "CodeQL"

on:
push:
branches: [main, deploy/preview]
branches:
- 'main'
- 'feat/modern-footer'
pull_request:
# The branches below must be a subset of the branches above
branches: [main, deploy/preview]
Expand Down
8 changes: 7 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ See the [installation instructions](README.md#install)

### Testing

Wagtail is based on Django, and there are many Django-style tests typically named `tests.py` to test templates. These verify that the templates can be compiled (that they don't have syntax errors) and that they are inserting variables.
Wagtail is based on Django, and there are many tests, in each app, typically named `tests.py` or `tests/test_*.py`, to test templates and business logic. These verify that the pages render without error and that they contain the expected values.

The testsuite uses [pytest](https://docs.pytest.org/) and [pytest-django](https://pytest-django.readthedocs.io/). You can run the testsuite locally:

```bash
pytest
```

## Frontend Development

Expand Down
2 changes: 1 addition & 1 deletion dev/deploy-to-container/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ async function main () {
`VIRTUAL_PORT=80`,
`DJANGO_SETTINGS_MODULE=ietf.settings.production`,
`PGHOST=ws-db-${branch}`,
`PGDATABASE=torchbox`,
`PGDATABASE=torchbox_temp`,
`PGUSER=postgres`,
`PGPASSWORD=password`,
`SECRET_KEY=${nanoid(36)}`,
Expand Down
23 changes: 23 additions & 0 deletions ietf/announcements/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import factory
import wagtail_factories

from ietf.utils.factories import StandardBlockFactory
from .models import IABAnnouncementIndexPage, IABAnnouncementPage


class IABAnnouncementPageFactory(wagtail_factories.PageFactory):
title = factory.Faker("name")
date = factory.Faker("date")
introduction = factory.Faker("paragraph")
body = wagtail_factories.StreamFieldFactory(StandardBlockFactory)

class Meta: # type: ignore
model = IABAnnouncementPage


class IABAnnouncementIndexPageFactory(wagtail_factories.PageFactory):
title = factory.Faker("name")
introduction = factory.Faker("paragraph")

class Meta: # type: ignore
model = IABAnnouncementIndexPage
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.7 on 2024-04-03 13:40

from django.db import migrations
import wagtail.blocks
import wagtail.contrib.table_block.blocks
import wagtail.contrib.typed_table_block.blocks
import wagtail.embeds.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtailmarkdown.blocks


class Migration(migrations.Migration):

dependencies = [
('announcements', '0003_alter_iabannouncementpage_body'),
]

operations = [
migrations.AlterField(
model_name='iabannouncementpage',
name='body',
field=wagtail.fields.StreamField([('heading', wagtail.blocks.CharBlock(icon='title')), ('paragraph', wagtail.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.images.blocks.ImageChooserBlock(icon='image', template='includes/imageblock.html')), ('markdown', wagtailmarkdown.blocks.MarkdownBlock(icon='code')), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='code')), ('raw_html', wagtail.blocks.RawHTMLBlock(icon='placeholder')), ('table', wagtail.contrib.table_block.blocks.TableBlock(table_options={'renderer': 'html'}, template='includes/tableblock.html')), ('typed_table', wagtail.contrib.typed_table_block.blocks.TypedTableBlock([('text', wagtail.blocks.CharBlock(required=False)), ('numeric', wagtail.blocks.FloatBlock(required=False, template='blocks/float_block.html')), ('rich_text', wagtail.blocks.RichTextBlock(required=False)), ('image', wagtail.images.blocks.ImageChooserBlock(required=False))])), ('note_well', wagtail.blocks.StructBlock([], icon='placeholder', label='Note Well Text'))], use_json_field=True),
),
]
79 changes: 79 additions & 0 deletions ietf/announcements/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from datetime import timedelta
from bs4 import BeautifulSoup
from django.test import Client
from django.utils import timezone

import pytest

from ietf.home.models import IABHomePage
from .factories import IABAnnouncementIndexPageFactory, IABAnnouncementPageFactory
from .models import IABAnnouncementIndexPage, IABAnnouncementPage

pytestmark = pytest.mark.django_db


class TestIABAnnouncement:
@pytest.fixture(autouse=True)
def set_up(self, iab_home: IABHomePage, client: Client):
self.home = iab_home
self.client = client

self.index: IABAnnouncementIndexPage = IABAnnouncementIndexPageFactory(
parent=self.home,
) # type: ignore

now = timezone.now()

self.announcement_1: IABAnnouncementPage = IABAnnouncementPageFactory(
parent=self.index,
date=now - timedelta(days=10),
) # type: ignore

self.announcement_2: IABAnnouncementPage = IABAnnouncementPageFactory(
parent=self.index,
date=now - timedelta(days=8),
) # type: ignore

self.announcement_3: IABAnnouncementPage = IABAnnouncementPageFactory(
parent=self.index,
date=now - timedelta(days=4),
body__0__heading="Heading in body Streamfield",
) # type: ignore

self.announcement_4: IABAnnouncementPage = IABAnnouncementPageFactory(
parent=self.index,
date=now,
) # type: ignore

def test_announcement_page(self):
response = self.client.get(self.announcement_3.url)
assert response.status_code == 200
html = response.content.decode()

assert self.announcement_3.title in html
assert self.announcement_3.body[0].value in html
assert self.announcement_3.introduction in html

def test_homepage(self):
""" The two most recent announcements are shown on the homepage """
response = self.client.get(self.home.url)
assert response.status_code == 200
html = response.content.decode()

assert f'href="{self.announcement_3.url}"' in html
assert self.announcement_3.title in html
assert f'href="{self.announcement_4.url}"' in html
assert self.announcement_4.title in html

def test_index_page(self):
response = self.client.get(self.index.url)
assert response.status_code == 200
html = response.content.decode()
soup = BeautifulSoup(html, "html.parser")
links = [a.get_text().strip() for a in soup.select("#content .container h2 a")]
assert links == [
self.announcement_4.title,
self.announcement_3.title,
self.announcement_2.title,
self.announcement_1.title,
]
12 changes: 6 additions & 6 deletions ietf/bibliography/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
from django.template.loader import get_template
from wagtail.models import Page

from ietf.utils import OrderedSet


class BibliographyItem(models.Model):
"""
Expand Down Expand Up @@ -101,7 +99,7 @@ def render(self, request=None):
else:
return str(object)

def __str__(self):
def __str__(self): # pragma: no cover
return "Bibliography Item #{}: {}".format(self.ordering, self.content_object)


Expand Down Expand Up @@ -159,9 +157,11 @@ def save(self, *args, **kwargs):
)
for content_field, prepared_content_field in self.CONTENT_FIELD_MAP.items()
}
tags = OrderedSet(all_soup.find_all("a", attrs={"data-app": True}))

for tag in tags:
# Look for <a> nodes that are tagged with bibliographic markup,
# create BibliographyItem records, and turn the <a> nodes into
# footnote links.
for index, tag in enumerate(all_soup.find_all("a", attrs={"data-app": True})):
app = tag["data-app"]
model = tag["data-linktype"]
obj_id = tag["data-id"]
Expand All @@ -187,7 +187,7 @@ def save(self, *args, **kwargs):
}
item = BibliographyItem.objects.create(
page=self,
ordering=list(tags).index(tag) + 1,
ordering=index + 1,
content_key=model,
content_identifier=obj_id,
**object_details
Expand Down
137 changes: 137 additions & 0 deletions ietf/bibliography/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import pytest
from django.contrib.contenttypes.models import ContentType
from django.test import Client
from django.urls import reverse

from ietf.bibliography.models import BibliographyItem
from ietf.home.models import HomePage
from ietf.snippets.models import RFC
from ietf.standard.factories import StandardIndexPageFactory, StandardPageFactory
from ietf.standard.models import StandardIndexPage, StandardPage

pytestmark = pytest.mark.django_db


class TestBibliography:
@pytest.fixture(autouse=True)
def set_up(self, home: HomePage, client: Client):
self.home = home
self.client = client

self.rfc_2026 = RFC.objects.create(
name="draft-ietf-poised95-std-proc-3",
title="The Internet Standards Process -- Revision 3",
rfc="2026",
)

self.standard_index: StandardIndexPage = StandardIndexPageFactory(
parent=self.home,
) # type: ignore

self.standard_page: StandardPage = StandardPageFactory(
parent=self.standard_index,
) # type: ignore
self.standard_page.in_depth = [
{
"type": "raw_html",
"value": (
f'<a data-app="snippets" data-id="{self.rfc_2026.pk}"'
' data-linktype="rfc">The Standards RFC</a>'
),
}
]
self.standard_page.save()

def test_bibliography_item_created(self):
"""
Make sure that a BibliographyItem record was created when
`self.standard_page` was created in `set_up()`.
"""
assert BibliographyItem.objects.count() == 1
item = BibliographyItem.objects.get()
assert item.content_object == self.rfc_2026

def test_referenced_types(self, admin_client):
"""
Admin view that shows which object types might be referenced in content
pages.
"""
rfc_content_type = ContentType.objects.get_for_model(RFC)
response = admin_client.get(reverse("referenced_types"))
assert response.status_code == 200
html = response.content.decode()
assert reverse("referenced_objects", args=[rfc_content_type.pk]) in html
assert "snippets | RFC" in html

def test_referenced_objects(self, admin_client):
"""
Admin view that shows which objects are being referenced as
bibliography items in content pages.
"""
rfc_content_type = ContentType.objects.get_for_model(RFC)
response = admin_client.get(
reverse("referenced_objects", args=[rfc_content_type.pk])
)
assert response.status_code == 200
html = response.content.decode()
assert reverse(
"referencing_pages", args=[rfc_content_type.pk, self.rfc_2026.pk]
) in html
assert "RFC 2026" in html

def test_referencing_pages(self, admin_client):
"""
Admin view that shows which pages are referencing a given object.
"""
rfc_content_type = ContentType.objects.get_for_model(RFC)
response = admin_client.get(
reverse("referencing_pages", args=[rfc_content_type.pk, self.rfc_2026.pk])
)
assert response.status_code == 200
html = response.content.decode()
assert self.standard_page.title in html

def test_render_page(self, client):
"""
The title of the referenced object should be displayed in the page.
"""
response = client.get(self.standard_page.url)
assert response.status_code == 200
html = response.content.decode()
assert "RFC 2026" in html

def test_render_page_reference_removed(self, client):
"""
The target of a BibliographyItem was deleted. It should be displayed as
such.
"""
self.rfc_2026.delete()
self.standard_page.save()
response = client.get(self.standard_page.url)
assert response.status_code == 200
html = response.content.decode()
assert "RFC 2026" not in html
assert "(removed)" in html

def test_update_fields_partial_raises_exception(self):
"""
Updating the `key_info` and `in_depth` fields, without also updating
the corresponding `prepared_*` fields, is not allowed. The prepared
fields contain properly formatted footnotes and are meant to be
displayed to the visitor.
"""
with pytest.raises(ValueError) as error:
self.standard_page.save(update_fields=["key_info", "in_depth"])

assert error.match("Either all prepared content fields must be updated or none")

def test_update_fields_with_all_prepared_fields_succeeds(self):
"""
Updating the `key_info` and `in_depth` fields, while also updating
the corresponding `prepared_*` fields, should work fine.
"""
self.standard_page.save(
update_fields=[
"key_info", "in_depth", "prepared_key_info", "prepared_in_depth"
]
)
22 changes: 22 additions & 0 deletions ietf/blog/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import factory
import wagtail_factories

from ietf.utils.factories import StandardBlockFactory

from .models import BlogIndexPage, BlogPage


class BlogPageFactory(wagtail_factories.PageFactory):
title = factory.Faker("name")
introduction = factory.Faker("paragraph")
body = wagtail_factories.StreamFieldFactory(StandardBlockFactory)

class Meta: # type: ignore
model = BlogPage


class BlogIndexPageFactory(wagtail_factories.PageFactory):
title = factory.Faker("name")

class Meta: # type: ignore
model = BlogIndexPage
Loading

0 comments on commit 271340f

Please sign in to comment.