feat: add people to projects and articles

This commit is contained in:
mohamad
2026-10-28 22:10:43 +08:00
parent 473637f477
commit 7c186b4230
14 changed files with 288 additions and 18 deletions
+9 -2
View File
@@ -37,10 +37,17 @@ class SiteBrandingAdmin(admin.ModelAdmin):
}, },
), ),
( (
"Blog article people section", "Article people section",
{ {
"fields": ("article_people_eyebrow", "article_people_title"), "fields": ("article_people_eyebrow", "article_people_title"),
"description": "These headings are shared by every blog article that has people cards.", "description": "These headings are shared by blog articles and project articles that have people cards.",
},
),
(
"Project people section",
{
"fields": ("project_people_eyebrow", "project_people_title"),
"description": "These headings are shared by main projects and sub-projects that have people cards.",
}, },
), ),
( (
@@ -0,0 +1,33 @@
# Generated by Django 5.2.16 on 2026-10-28 14:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_sitebranding_article_people_eyebrow_and_more'),
]
operations = [
migrations.AddField(
model_name='sitebranding',
name='project_people_eyebrow',
field=models.CharField(blank=True, default='The people behind this project', help_text='Small heading above the people cards on main projects and sub-projects.', max_length=160),
),
migrations.AddField(
model_name='sitebranding',
name='project_people_title',
field=models.CharField(default='Organizers & collaborators', help_text='Heading above the people cards on main projects and sub-projects.', max_length=200),
),
migrations.AlterField(
model_name='sitebranding',
name='article_people_eyebrow',
field=models.CharField(blank=True, default='The people behind this article', help_text='Small heading above the people cards on blog and project articles.', max_length=160),
),
migrations.AlterField(
model_name='sitebranding',
name='article_people_title',
field=models.CharField(default='Organizers & collaborators', help_text='Heading above the people cards on blog and project articles.', max_length=200),
),
]
+10 -2
View File
@@ -34,12 +34,20 @@ class SiteBranding(models.Model):
max_length=160, max_length=160,
blank=True, blank=True,
default="The people behind this article", default="The people behind this article",
help_text="Small heading above the people cards on every blog article.", help_text="Small heading above the people cards on blog and project articles.",
) )
article_people_title = models.CharField( article_people_title = models.CharField(
max_length=200, max_length=200,
default="Organizers & collaborators", default="Organizers & collaborators",
help_text="Heading above the people cards on every blog article.", help_text="Heading above the people cards on blog and project articles.",
)
project_people_eyebrow = models.CharField(
max_length=160, blank=True, default="The people behind this project",
help_text="Small heading above the people cards on main projects and sub-projects.",
)
project_people_title = models.CharField(
max_length=200, default="Organizers & collaborators",
help_text="Heading above the people cards on main projects and sub-projects.",
) )
icon = models.ImageField( icon = models.ImageField(
+25 -4
View File
@@ -3,7 +3,28 @@ from django.contrib import admin
from apps.core.admin_video import video_admin_preview from apps.core.admin_video import video_admin_preview
from apps.core.video import VIDEO_ADMIN_FIELDSET from apps.core.video import VIDEO_ADMIN_FIELDSET
from .models import Article, ArticleCitation, ArticleSection, MainProduct, ProductVideo, SubProduct from .models import (
Article, ArticleCitation, ArticlePerson, ArticleSection, MainProduct,
MainProductPerson, ProductVideo, SubProduct, SubProductPerson,
)
class PersonInline(admin.StackedInline):
extra = 0
fields = ("role", "name", "description", "read_more_description", "image", "image_alt", "url", "order")
ordering = ("order",)
class MainProductPersonInline(PersonInline):
model = MainProductPerson
class SubProductPersonInline(PersonInline):
model = SubProductPerson
class ArticlePersonInline(PersonInline):
model = ArticlePerson
class ArticleCitationInline(admin.TabularInline): class ArticleCitationInline(admin.TabularInline):
@@ -77,7 +98,7 @@ class MainProductAdmin(admin.ModelAdmin):
search_fields = ("name", "description") search_fields = ("name", "description")
prepopulated_fields = {"slug": ("name",)} prepopulated_fields = {"slug": ("name",)}
list_editable = ("order", "is_active", "show_on_homepage", "homepage_order") list_editable = ("order", "is_active", "show_on_homepage", "homepage_order")
inlines = [MainProductArticleInline, ProductVideoInline, SubProductInline] inlines = [MainProductPersonInline, MainProductArticleInline, ProductVideoInline, SubProductInline]
fieldsets = ( fieldsets = (
( (
None, None,
@@ -136,7 +157,7 @@ class SubProductAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("name",)} prepopulated_fields = {"slug": ("name",)}
list_editable = ("order", "is_active", "show_on_homepage", "homepage_order") list_editable = ("order", "is_active", "show_on_homepage", "homepage_order")
raw_id_fields = ("main_product",) raw_id_fields = ("main_product",)
inlines = [SubProductArticleInline, SubProductVideoInline] inlines = [SubProductPersonInline, SubProductArticleInline, SubProductVideoInline]
fieldsets = ( fieldsets = (
( (
None, None,
@@ -164,7 +185,7 @@ class ArticleAdmin(admin.ModelAdmin):
search_fields = ("title", "description", "main_product__name", "sub_product__name") search_fields = ("title", "description", "main_product__name", "sub_product__name")
list_editable = ("order",) list_editable = ("order",)
raw_id_fields = ("main_product", "sub_product") raw_id_fields = ("main_product", "sub_product")
inlines = [ArticleSectionInline, ArticleCitationInline] inlines = [ArticlePersonInline, ArticleSectionInline, ArticleCitationInline]
fieldsets = ( fieldsets = (
(None, {"fields": ("main_product", "sub_product", "badge", "title")}), (None, {"fields": ("main_product", "sub_product", "badge", "title")}),
("Description", {"fields": ("description_format", "description", "read_more_description")}), ("Description", {"fields": ("description_format", "description", "read_more_description")}),
@@ -0,0 +1,77 @@
# Generated by Django 5.2.16 on 2026-10-28 14:09
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0024_articlecitation_read_more_text_and_more'),
]
operations = [
migrations.CreateModel(
name='ArticlePerson',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(default='Collaborator', help_text='Type Organizer, Collaborator, or any other role name.', max_length=100, verbose_name='Role / group')),
('name', models.CharField(max_length=200)),
('description', models.TextField(blank=True)),
('read_more_description', models.BooleanField(default=False, help_text="Collapse this person's description with Read more when it is long.")),
('image', models.ImageField(blank=True, null=True, upload_to='products/people/')),
('image_alt', models.CharField(blank=True, max_length=240)),
('url', models.URLField(blank=True)),
('order', models.PositiveIntegerField(default=0, help_text='Smaller numbers appear first. Organizers appear before collaborators, then custom roles.', verbose_name='Display order')),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='people', to='products.article')),
],
options={
'verbose_name': 'Project article person',
'verbose_name_plural': 'Project article people',
'ordering': [models.Case(models.When(role__iexact='organizer', then=models.Value(0)), models.When(role__iexact='collaborator', then=models.Value(1)), default=models.Value(2), output_field=models.IntegerField()), 'order', 'pk'],
'abstract': False,
},
),
migrations.CreateModel(
name='MainProductPerson',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(default='Collaborator', help_text='Type Organizer, Collaborator, or any other role name.', max_length=100, verbose_name='Role / group')),
('name', models.CharField(max_length=200)),
('description', models.TextField(blank=True)),
('read_more_description', models.BooleanField(default=False, help_text="Collapse this person's description with Read more when it is long.")),
('image', models.ImageField(blank=True, null=True, upload_to='products/people/')),
('image_alt', models.CharField(blank=True, max_length=240)),
('url', models.URLField(blank=True)),
('order', models.PositiveIntegerField(default=0, help_text='Smaller numbers appear first. Organizers appear before collaborators, then custom roles.', verbose_name='Display order')),
('main_product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='people', to='products.mainproduct')),
],
options={
'verbose_name': 'Main project person',
'verbose_name_plural': 'Main project people',
'ordering': [models.Case(models.When(role__iexact='organizer', then=models.Value(0)), models.When(role__iexact='collaborator', then=models.Value(1)), default=models.Value(2), output_field=models.IntegerField()), 'order', 'pk'],
'abstract': False,
},
),
migrations.CreateModel(
name='SubProductPerson',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(default='Collaborator', help_text='Type Organizer, Collaborator, or any other role name.', max_length=100, verbose_name='Role / group')),
('name', models.CharField(max_length=200)),
('description', models.TextField(blank=True)),
('read_more_description', models.BooleanField(default=False, help_text="Collapse this person's description with Read more when it is long.")),
('image', models.ImageField(blank=True, null=True, upload_to='products/people/')),
('image_alt', models.CharField(blank=True, max_length=240)),
('url', models.URLField(blank=True)),
('order', models.PositiveIntegerField(default=0, help_text='Smaller numbers appear first. Organizers appear before collaborators, then custom roles.', verbose_name='Display order')),
('sub_product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='people', to='products.subproduct')),
],
options={
'verbose_name': 'Sub-project person',
'verbose_name_plural': 'Sub-project people',
'ordering': [models.Case(models.When(role__iexact='organizer', then=models.Value(0)), models.When(role__iexact='collaborator', then=models.Value(1)), default=models.Value(2), output_field=models.IntegerField()), 'order', 'pk'],
'abstract': False,
},
),
]
+65
View File
@@ -207,6 +207,10 @@ class Article(models.Model):
def rendered_description(self): def rendered_description(self):
return render_content(self.description, self.description_format) return render_content(self.description, self.description_format)
@property
def people_heading_id(self):
return f"article-{self.pk}-people"
def clean(self): def clean(self):
super().clean() super().clean()
has_main = self.main_product_id is not None has_main = self.main_product_id is not None
@@ -220,6 +224,67 @@ class Article(models.Model):
return self.title return self.title
class ProjectPerson(models.Model):
role = models.CharField(
"Role / group", max_length=100, default="Collaborator",
help_text="Type Organizer, Collaborator, or any other role name.",
)
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
read_more_description = models.BooleanField(
default=False, help_text="Collapse this person's description with Read more when it is long."
)
image = models.ImageField(upload_to="products/people/", blank=True, null=True)
image_alt = models.CharField(max_length=240, blank=True)
url = models.URLField(blank=True)
order = models.PositiveIntegerField(
"Display order", default=0,
help_text="Smaller numbers appear first. Organizers appear before collaborators, then custom roles.",
)
class Meta:
abstract = True
ordering = [
models.Case(
models.When(role__iexact="organizer", then=models.Value(0)),
models.When(role__iexact="collaborator", then=models.Value(1)),
default=models.Value(2), output_field=models.IntegerField(),
),
"order", "pk",
]
@property
def role_label(self):
return self.role.title() if self.role.casefold() in {"organizer", "collaborator"} else self.role
def __str__(self):
return f"{self.name} ({self.role_label})"
class MainProductPerson(ProjectPerson):
main_product = models.ForeignKey(MainProduct, on_delete=models.CASCADE, related_name="people")
class Meta(ProjectPerson.Meta):
verbose_name = "Main project person"
verbose_name_plural = "Main project people"
class SubProductPerson(ProjectPerson):
sub_product = models.ForeignKey(SubProduct, on_delete=models.CASCADE, related_name="people")
class Meta(ProjectPerson.Meta):
verbose_name = "Sub-project person"
verbose_name_plural = "Sub-project people"
class ArticlePerson(ProjectPerson):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name="people")
class Meta(ProjectPerson.Meta):
verbose_name = "Project article person"
verbose_name_plural = "Project article people"
class SubProductVersion(models.Model): class SubProductVersion(models.Model):
main_product = models.ForeignKey( main_product = models.ForeignKey(
MainProduct, MainProduct,
+38 -1
View File
@@ -1,7 +1,10 @@
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from apps.products.models import Article, ArticleSection, MainProduct, SubProduct from apps.products.models import (
Article, ArticlePerson, ArticleSection, MainProduct, MainProductPerson,
SubProduct, SubProductPerson,
)
class ProductViewsSetup(TestCase): class ProductViewsSetup(TestCase):
@@ -56,6 +59,31 @@ class ProductOverviewViewTest(ProductViewsSetup):
class MainProductDetailViewTest(ProductViewsSetup): class MainProductDetailViewTest(ProductViewsSetup):
def test_project_and_article_people_are_independent_and_ordered(self):
MainProductPerson.objects.create(main_product=self.main_product, name="Main collaborator", role="Collaborator", order=1)
MainProductPerson.objects.create(main_product=self.main_product, name="Main organizer", role="Organizer", order=9)
main_article = Article.objects.create(main_product=self.main_product, title="Main article", description="Text")
ArticlePerson.objects.create(article=main_article, name="Article advisor", role="Scientific advisor", order=0)
response = self.client.get(self.main_product.get_absolute_url())
html = response.content.decode()
self.assertLess(html.index("Main organizer"), html.index("Main collaborator"))
self.assertContains(response, "Article advisor")
self.assertNotContains(response, "Sub collaborator")
self.assertContains(response, f'id="article-{main_article.pk}-people"')
def test_people_section_is_hidden_until_created_and_read_more_is_optional(self):
response = self.client.get(self.main_product.get_absolute_url())
self.assertNotContains(response, 'id="main-project-people"')
person = MainProductPerson.objects.create(
main_product=self.main_product, name="Project leader", description="A long biography",
)
response = self.client.get(self.main_product.get_absolute_url())
self.assertContains(response, "Project leader")
self.assertNotContains(response, 'data-readmore="88"')
person.read_more_description = True
person.save(update_fields=["read_more_description"])
self.assertContains(self.client.get(self.main_product.get_absolute_url()), 'data-readmore="88"')
def test_read_more_can_be_set_per_article_and_section(self): def test_read_more_can_be_set_per_article_and_section(self):
url = self.main_product.get_absolute_url() url = self.main_product.get_absolute_url()
article = Article.objects.create(main_product=self.main_product, title="Notes", description="Long notes") article = Article.objects.create(main_product=self.main_product, title="Notes", description="Long notes")
@@ -112,6 +140,15 @@ class MainProductDetailViewTest(ProductViewsSetup):
class SubProductDetailViewTest(ProductViewsSetup): class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_project_and_article_people_render_separately(self):
SubProductPerson.objects.create(sub_product=self.sub_product, name="Sub organizer", role="Organizer")
ArticlePerson.objects.create(article=self.article, name="Article collaborator", role="Collaborator")
response = self.client.get(self.sub_product.get_absolute_url())
self.assertContains(response, "Sub organizer")
self.assertContains(response, "Article collaborator")
self.assertContains(response, 'id="sub-project-people"')
self.assertNotContains(self.client.get(self.main_product.get_absolute_url()), "Sub organizer")
def test_sub_detail_returns_200(self): def test_sub_detail_returns_200(self):
url = reverse( url = reverse(
"products:sub_product_detail", "products:sub_product_detail",
+5 -5
View File
@@ -23,7 +23,7 @@ class ProductOverviewView(ListView):
template_name = "products/overview.html" template_name = "products/overview.html"
context_object_name = "main_products" context_object_name = "main_products"
queryset = MainProduct.objects.filter(is_active=True).prefetch_related( queryset = MainProduct.objects.filter(is_active=True).prefetch_related(
"sub_products" "sub_products", "people"
) )
@@ -33,13 +33,13 @@ class MainProductDetailView(DetailView):
context_object_name = "main_product" context_object_name = "main_product"
slug_url_kwarg = "main_slug" slug_url_kwarg = "main_slug"
queryset = MainProduct.objects.filter(is_active=True).prefetch_related( queryset = MainProduct.objects.filter(is_active=True).prefetch_related(
"sub_products" "sub_products", "people"
) )
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["articles"] = self.object.articles.prefetch_related( context["articles"] = self.object.articles.prefetch_related(
"sections", "citations" "sections", "citations", "people"
).all() ).all()
videos = self.object.videos.filter(is_active=True).order_by("order", "pk") videos = self.object.videos.filter(is_active=True).order_by("order", "pk")
context.update( context.update(
@@ -65,7 +65,7 @@ class SubProductDetailView(TemplateView):
is_active=True, is_active=True,
) )
sub_product = get_object_or_404( sub_product = get_object_or_404(
SubProduct, SubProduct.objects.prefetch_related("people"),
slug=self.kwargs["sub_slug"], slug=self.kwargs["sub_slug"],
main_product=main_product, main_product=main_product,
is_active=True, is_active=True,
@@ -73,7 +73,7 @@ class SubProductDetailView(TemplateView):
context["main_product"] = main_product context["main_product"] = main_product
context["sub_product"] = sub_product context["sub_product"] = sub_product
context["articles"] = sub_product.articles.prefetch_related( context["articles"] = sub_product.articles.prefetch_related(
"sections", "citations" "sections", "citations", "people"
).all() ).all()
videos = sub_product.videos.filter(is_active=True).order_by("order", "pk") videos = sub_product.videos.filter(is_active=True).order_by("order", "pk")
context.update( context.update(
+6 -3
View File
@@ -117,18 +117,21 @@ body::before {
.blog-article { max-width: 900px; margin-inline: auto; padding: clamp(1.5rem, 4vw, 3rem); } .blog-article { max-width: 900px; margin-inline: auto; padding: clamp(1.5rem, 4vw, 3rem); }
.blog-partners { min-width: 0; margin-top: clamp(2.5rem, 5vw, 4rem); } .blog-partners { min-width: 0; margin-top: clamp(2.5rem, 5vw, 4rem); }
.blog-partners--about { margin-top: 0; } .blog-partners--about { margin-top: 0; }
.blog-partners--embedded { margin-top: 2rem; border-top: 1px solid rgba(255,255,255,.12); padding-top: 1.5rem; }
.blog-partners--embedded .blog-partners-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 145px), 1fr)); }
.blog-partners-heading { padding: .3rem .15rem 1.25rem; } .blog-partners-heading { padding: .3rem .15rem 1.25rem; }
.blog-partners-eyebrow { display: block; color: var(--accent-blue-light); font-size: .65rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; } .blog-partners-eyebrow { display: block; color: var(--accent-blue-light); font-size: .65rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }
.blog-partners-heading h2 { margin-top: .45rem; font-size: clamp(1.2rem, 2vw, 1.5rem); line-height: 1.35; } .blog-partners-heading h2 { margin-top: .45rem; font-size: clamp(1.2rem, 2vw, 1.5rem); line-height: 1.35; }
.blog-partners-heading h4 { margin-top: .45rem; font-size: 1.15rem; line-height: 1.35; }
.blog-partners-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1rem; } .blog-partners-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1rem; }
.blog-partner-card { display: flex; flex-direction: column; gap: .85rem; padding: .8rem; min-width: 0; } .blog-partner-card { display: flex; flex-direction: column; gap: .85rem; padding: .8rem; min-width: 0; }
.blog-partner-image { width: 100%; aspect-ratio: 1 / 1; border-radius: 12px; background: rgba(255,255,255,.08); overflow: hidden; display: grid; place-items: center; } .blog-partner-image { width: 100%; aspect-ratio: 1 / 1; border-radius: 12px; background: rgba(255,255,255,.08); overflow: hidden; display: grid; place-items: center; }
.blog-partner-image img { width: 100%; height: 100%; object-fit: cover; object-position: center 25%; } .blog-partner-image img { width: 100%; height: 100%; object-fit: cover; object-position: center 25%; }
.blog-partner-info { min-width: 0; } .blog-partner-info { min-width: 0; }
.blog-partner-role { display: block; color: var(--accent-blue-light); font-size: .62rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; margin-bottom: .35rem; } .blog-partner-role { display: block; color: var(--accent-blue-light); font-size: .62rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; margin-bottom: .35rem; }
.blog-partner-info h3 { font-size: .9rem; line-height: 1.35; margin: 0 0 .4rem; } .blog-partner-info h3, .blog-partner-info h5 { font-size: .9rem; line-height: 1.35; margin: 0 0 .4rem; }
.blog-partner-info h3 a { color: inherit; } .blog-partner-info h3 a, .blog-partner-info h5 a { color: inherit; }
.blog-partner-info h3 a:hover { color: var(--accent-blue-light); } .blog-partner-info h3 a:hover, .blog-partner-info h5 a:hover { color: var(--accent-blue-light); }
.blog-partner-info p { color: var(--text-secondary); font-size: .76rem; line-height: 1.55; margin: 0; } .blog-partner-info p { color: var(--text-secondary); font-size: .76rem; line-height: 1.55; margin: 0; }
@media (max-width: 950px) { .blog-partners-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } } @media (max-width: 950px) { .blog-partners-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 620px) { .blog-partners-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 620px) { .blog-partners-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
+13
View File
@@ -0,0 +1,13 @@
{% if people %}
<section class="blog-partners{% if embedded %} blog-partners--embedded{% endif %}" aria-labelledby="{{ heading_id }}">
<div class="blog-partners-heading">
{% if eyebrow %}<span class="blog-partners-eyebrow">{{ eyebrow }}</span>{% endif %}
{% if embedded %}<h4 id="{{ heading_id }}">{{ title }}</h4>{% else %}<h2 id="{{ heading_id }}">{{ title }}</h2>{% endif %}
</div>
<div class="blog-partners-grid">
{% for person in people %}
{% include "partials/_person_card.html" with person=person body=person.description read_more=person.read_more_description embedded=embedded %}
{% endfor %}
</div>
</section>
{% endif %}
+1 -1
View File
@@ -2,7 +2,7 @@
{% if person.image %}<div class="blog-partner-image"><img src="{{ person.image.url }}" alt="{{ person.image_alt|default:person.name }}" loading="lazy"></div>{% endif %} {% if person.image %}<div class="blog-partner-image"><img src="{{ person.image.url }}" alt="{{ person.image_alt|default:person.name }}" loading="lazy"></div>{% endif %}
<div class="blog-partner-info"> <div class="blog-partner-info">
{% if person.role_label %}<span class="blog-partner-role">{{ person.role_label }}</span>{% endif %} {% if person.role_label %}<span class="blog-partner-role">{{ person.role_label }}</span>{% endif %}
<h3>{% if person.url %}<a href="{{ person.url }}" target="_blank" rel="noopener noreferrer">{{ person.name }}</a>{% else %}{{ person.name }}{% endif %}</h3> {% if embedded %}<h5>{% if person.url %}<a href="{{ person.url }}" target="_blank" rel="noopener noreferrer">{{ person.name }}</a>{% else %}{{ person.name }}{% endif %}</h5>{% else %}<h3>{% if person.url %}<a href="{{ person.url }}" target="_blank" rel="noopener noreferrer">{{ person.name }}</a>{% else %}{{ person.name }}{% endif %}</h3>{% endif %}
{% if body %}<p {% if read_more %}data-readmore="88"{% endif %}>{{ body }}</p>{% endif %} {% if body %}<p {% if read_more %}data-readmore="88"{% endif %}>{{ body }}</p>{% endif %}
</div> </div>
</div> </div>
+1
View File
@@ -62,6 +62,7 @@
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% include "partials/_people_section.html" with people=article.people.all eyebrow=site_branding.article_people_eyebrow title=site_branding.article_people_title heading_id=article.people_heading_id embedded=True %}
</article> </article>
{% endfor %} {% endfor %}
</div> </div>
+4
View File
@@ -101,4 +101,8 @@
{% endif %} {% endif %}
{% endwith %} {% endwith %}
<div class="container">
{% include "partials/_people_section.html" with people=main_product.people.all eyebrow=site_branding.project_people_eyebrow title=site_branding.project_people_title heading_id="main-project-people" %}
</div>
{% endblock %} {% endblock %}
+1
View File
@@ -84,6 +84,7 @@
</aside> </aside>
</div> </div>
{% include "partials/_people_section.html" with people=sub_product.people.all eyebrow=site_branding.project_people_eyebrow title=site_branding.project_people_title heading_id="sub-project-people" %}
</div> </div>
</div> </div>