Skip to content
1 change: 1 addition & 0 deletions makeabilitylab/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@
'django.contrib.messages.context_processors.messages',
'website.context_processors.recent_news',
'website.context_processors.admin_version_info',
'website.context_processors.site_scheme',
],
},
},
Expand Down
2 changes: 1 addition & 1 deletion website/admin/person_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class PersonAdmin(ImageCroppingMixin, admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['first_name', 'middle_name', 'last_name', 'image', 'cropping', 'easter_egg', 'easter_egg_crop', 'easter_egg_starwars_choice']}),
('Bio', {'fields': ['bio', 'personal_website', 'github']}),
('Socials', {'fields': ['twitter', 'threads', 'mastodon', 'linkedin']}),
('Socials', {'fields': ['twitter', 'bluesky', 'threads', 'mastodon', 'linkedin', 'google_scholar', 'orcid']}),
('For Alumni (Next Position)', {'fields': ['next_position', 'next_position_url']}),
]

Expand Down
25 changes: 25 additions & 0 deletions website/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,31 @@ def recent_news(request):

return { 'recent_news': news_items, }

def site_scheme(request):
"""
Expose the canonical URL scheme (``http`` / ``https``) to every template.

The site runs behind UW CSE's Apache TLS-terminating proxy, which talks to
the Django container over plain HTTP. Because ``SECURE_PROXY_SSL_HEADER`` is
not (yet) configured, ``request.scheme`` reports ``http`` in production/test
even though visitors arrive over HTTPS. Building absolute URLs (canonical,
Open Graph ``og:url``/``og:image``, Twitter Card images) from
``request.scheme`` therefore advertises ``http://`` links to crawlers and
social scrapers — the root cause of issue #1236.

This processor pins the scheme to ``https`` whenever the site is not in
DEBUG (i.e. on the test/prod servers), while leaving local dev on whatever
``request.scheme`` reports (``http`` over localhost). Templates should build
absolute URLs as ``{{ site_scheme }}://{{ request.get_host }}{{ path }}``.

NOTE: This is the in-repo workaround. The cleaner long-term fix is for IT to
set ``SECURE_PROXY_SSL_HEADER`` on the proxy (tracked in #1329); once that
lands, ``request.scheme`` will be correct and this can fall back to it.
"""
return {
'site_scheme': request.scheme if settings.DEBUG else 'https',
}

def admin_version_info(request):
"""
Make version and debug info available to all templates.
Expand Down
13 changes: 10 additions & 3 deletions website/models/person.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,11 @@ def get_director(cls):
threads = models.URLField(blank=True, null=True)
mastodon = models.URLField(blank=True, null=True)
linkedin = models.URLField(blank=True, null=True)

orcid = models.URLField(blank=True, null=True)
orcid.help_text = 'Full ORCID profile URL. For example, <a href="https://orcid.org/0000-0001-8291-3353">https://orcid.org/0000-0001-8291-3353</a>'
google_scholar = models.URLField(blank=True, null=True)
google_scholar.help_text = 'Full Google Scholar profile URL. For example, <a href="https://scholar.google.com/citations?user=nExKrpsAAAAJ&amp;hl=en&amp;oi=ao">https://scholar.google.com/citations?user=nExKrpsAAAAJ&amp;hl=en&amp;oi=ao</a>'

# If a bio is not added, the member view page will auto-generate one
bio = models.TextField(blank=True, null=True)
bio.help_text = "You can use HTML markup here. If a bio is not added, the member view page will auto-generate one."
Expand Down Expand Up @@ -172,8 +176,11 @@ def get_director(cls):
but you can set it to anything you want and crop it appropriately here")

def has_website_links(self):
"""Returns True if person has a personal website, github, or twitter, etc. False otherwise."""
return self.personal_website or self.github or self.twitter
"""Returns True if person has any external profile/website link set
(personal site, github, twitter, ORCID, Google Scholar, etc.)."""
return (self.personal_website or self.github or self.twitter
or self.bluesky or self.mastodon or self.threads
or self.linkedin or self.orcid or self.google_scholar)

@cached_property
def is_graduated_phd_student(self):
Expand Down
55 changes: 46 additions & 9 deletions website/templates/website/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,54 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="referrer" content="strict-origin-when-cross-origin">
<meta name="description" content="The Makeability Lab is an advanced research lab in Human-Computer Interaction and AI directed by Professor Jon E. Froehlich at University of Washington's Allen School of Computer Science.">
<meta name="author" content="Jon E. Froehlich and Makeability Lab members">

<!-- Open Graph meta tags for social sharing -->
{% block opengraph %}
<meta property="og:title" content="Makeability Lab">
<meta property="og:type" content="website">
<meta property="og:description" content="The Makeability Lab is an advanced research lab in Human-Computer Interaction directed by Professor Jon E. Froehlich at University of Washington's Allen School of Computer Science.">
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'website/img/logos/makelab_logo_v3_white_with_colors_and_text_og_image_ratio_1200w.png' %}">
<meta property="og:url" content="{{ request.scheme }}://{{ request.get_host }}{{ request.path }}">
{% endblock %}
{% comment %}
============================================================================
PAGE METADATA — single source of truth for SEO + social sharing.

Per-page values come from the optional `page_meta` context dict set by the
view (keys: title, description, canonical_path, og_type). Anything not
provided falls back to the lab-wide defaults below, so every page gets a
complete, valid set of tags. The meta description is computed once and reused
by <meta name="description">, og:description, and twitter:description (no
duplication).

Absolute URLs use {{ site_scheme }} (https on the servers, http in local dev)
so they don't advertise http:// behind the TLS proxy — see #1236.

Detail templates customize via:
{% block social_image %} — og:image + twitter:image (default: lab logo)
{% block meta_extra %} — structural OG tags (og:profile:*, og:article:*)
{% block jsonld %} — schema.org JSON-LD structured data
============================================================================
{% endcomment %}
{% with meta_title=page_meta.title|default:"Makeability Lab" og_type=page_meta.og_type|default:"website" canonical_path=page_meta.canonical_path|default:request.path %}
{% firstof page_meta.description "The Makeability Lab is an advanced research lab in Human-Computer Interaction and AI directed by Professor Jon E. Froehlich at University of Washington's Allen School of Computer Science." as meta_description %}
<meta name="description" content="{{ meta_description }}">
<link rel="canonical" href="{{ site_scheme }}://{{ request.get_host }}{{ canonical_path }}">

<!-- Open Graph (Facebook, LinkedIn, Slack, Bluesky, …) -->
<meta property="og:site_name" content="Makeability Lab">
<meta property="og:locale" content="en_US">
<meta property="og:title" content="{{ meta_title }}">
<meta property="og:type" content="{{ og_type }}">
<meta property="og:description" content="{{ meta_description }}">
<meta property="og:url" content="{{ site_scheme }}://{{ request.get_host }}{{ canonical_path }}">
{% block social_image %}
<meta property="og:image" content="{{ site_scheme }}://{{ request.get_host }}{% static 'website/img/logos/makelab_logo_v3_white_with_colors_and_text_og_image_ratio_1200w.png' %}">
<meta property="og:image:alt" content="Makeability Lab logo">
<meta name="twitter:image" content="{{ site_scheme }}://{{ request.get_host }}{% static 'website/img/logos/makelab_logo_v3_white_with_colors_and_text_og_image_ratio_1200w.png' %}">
{% endblock social_image %}
{% block meta_extra %}{% endblock %}

<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ meta_title }}">
<meta name="twitter:description" content="{{ meta_description }}">

{% block jsonld %}{% if jsonld %}<script type="application/ld+json">{{ jsonld }}</script>{% endif %}{% endblock %}
{% endwith %}

<!-- Favicon -->
<link rel="shortcut icon" href="{% static 'website/img/ml-favicon.png' %}">
Expand Down
47 changes: 32 additions & 15 deletions website/templates/website/member.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,23 @@
{% load thumbnail %}
{% load ml_tags %}

{% block opengraph %}
<meta property="og:title" content="{{ person.get_full_name }} - Makeability Lab">
<meta property="og:type" content="profile">
{% if person.bio %}
<meta property="og:description" content="{{ person.bio|striptags|truncatewords:50 }}">
{% elif auto_generated_bio %}
<meta property="og:description" content="{{ auto_generated_bio|striptags|truncatewords:50 }}">
{# Title, description, og:type=profile, and canonical come from page_meta (see
website/views/member.py). Here we override the share image and add the
profile-specific structural OG tags. #}
{% block social_image %}
{% thumbnail person.image '1200x630' box=person.cropping crop=True upscale=True as og_img %}
{% if og_img %}
<meta property="og:image" content="{{ site_scheme }}://{{ request.get_host }}{{ og_img.url }}">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="{{ person.get_full_name }}">
<meta name="twitter:image" content="{{ site_scheme }}://{{ request.get_host }}{{ og_img.url }}">
{% else %}
{% if position %}
<meta property="og:description" content="{{ person.get_full_name }} is a {{ position.title }} at the Makeability Lab, an advanced research lab in Human-Computer Interaction at University of Washington.">
{% else %}
<meta property="og:description" content="{{ person.get_full_name }} — Makeability Lab, an advanced research lab in Human-Computer Interaction at University of Washington.">
{% endif %}
{{ block.super }}
{% endif %}
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% thumbnail person.image '1200x630' box=person.cropping crop=True upscale=True %}">
<meta property="og:url" content="{{ request.scheme }}://{{ request.get_host }}{{ request.path }}">
{% endblock social_image %}

{% block meta_extra %}
<meta property="og:profile:first_name" content="{{ person.first_name }}">
<meta property="og:profile:last_name" content="{{ person.last_name }}">
{% endblock %}
Expand Down Expand Up @@ -196,13 +197,29 @@ <h1 class="person-name-header">{{ person.get_full_name }}</h1>
</a>
{% endif %}
{% if person.linkedin %}
<a href="{{ person.linkedin }}"
<a href="{{ person.linkedin }}"
class="person-website-link"
aria-label="{{ person.first_name }} on LinkedIn">
<i class="fab fa-linkedin" aria-hidden="true"></i>
<span>LinkedIn</span>
</a>
{% endif %}
{% if person.google_scholar %}
<a href="{{ person.google_scholar }}"
class="person-website-link"
aria-label="{{ person.first_name }} on Google Scholar">
<i class="ai ai-google-scholar" aria-hidden="true"></i>
<span>Google Scholar</span>
</a>
{% endif %}
{% if person.orcid %}
<a href="{{ person.orcid }}"
class="person-website-link"
aria-label="{{ person.first_name }} on ORCID">
<i class="ai ai-orcid" aria-hidden="true"></i>
<span>ORCID</span>
</a>
{% endif %}
</nav>
{% endif %}

Expand Down
29 changes: 17 additions & 12 deletions website/templates/website/news_item.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,25 @@
{% load thumbnail %}
{% load ml_tags %}

{% block opengraph %}
<meta property="og:title" content="{{ news_item.title }}">
<meta property="og:type" content="article">
{% if news_item.content %}
<meta property="og:description" content="{{ news_item.content|striptags|truncatewords:50 }}">
{# Title, description, og:type=article, and canonical come from page_meta (see
website/views/news_item.py). Here we override the share image and add the
article-specific structural OG tags. #}
{% block social_image %}
{% if news_item.image %}{% thumbnail news_item.image '1200x630' box=news_item.cropping crop=True upscale=True as og_img %}{% endif %}
{% if og_img %}
<meta property="og:image" content="{{ site_scheme }}://{{ request.get_host }}{{ og_img.url }}">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="{{ news_item.title }}">
<meta name="twitter:image" content="{{ site_scheme }}://{{ request.get_host }}{{ og_img.url }}">
{% else %}
<meta property="og:description" content="News from the Makeability Lab, an advanced research lab in Human-Computer Interaction at University of Washington.">
<meta property="og:image" content="{{ site_scheme }}://{{ request.get_host }}{% static news_item.default_news_image_filename %}">
<meta property="og:image:alt" content="{{ news_item.title }}">
<meta name="twitter:image" content="{{ site_scheme }}://{{ request.get_host }}{% static news_item.default_news_image_filename %}">
{% endif %}
{% if news_item.image %}
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% thumbnail news_item.image '1200x630' box=news_item.cropping crop=True upscale=True %}">
{% else %}
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static news_item.default_news_image_filename %}">
{% endif %}
<meta property="og:url" content="{{ request.scheme }}://{{ request.get_host }}{{ request.path }}">
{% endblock social_image %}

{% block meta_extra %}
<meta property="og:article:published_time" content="{{ news_item.date|date:'c' }}">
{% if news_item.author %}
<meta property="og:article:author" content="{{ news_item.author.get_full_name }}">
Expand Down
21 changes: 12 additions & 9 deletions website/templates/website/project.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,20 @@
{% load thumbnail %}
{% load ml_tags %}

{% block opengraph %}
<meta property="og:title" content="{{ project.name }}">
<meta property="og:type" content="website">
{% if project.summary %}
<meta property="og:description" content="{{ project.summary }}">
{# Title, description, og:type, and canonical come from page_meta (see
website/views/project.py). Here we only override the social share image. #}
{% block social_image %}
{% thumbnail project.gallery_image 1200x630 box=project.cropping crop=True upscale=True as og_img %}
{% if og_img %}
<meta property="og:image" content="{{ site_scheme }}://{{ request.get_host }}{{ og_img.url }}">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="{{ project.name }}">
<meta name="twitter:image" content="{{ site_scheme }}://{{ request.get_host }}{{ og_img.url }}">
{% else %}
<meta property="og:description" content="The Makeability Lab is an advanced research lab in Human-Computer Interaction directed by Professor Jon E. Froehlich at University of Washington's Allen School of Computer Science.">
{{ block.super }}
{% endif %}
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% thumbnail project.gallery_image 1200x630 box=project.cropping crop=True upscale=True %}">
<meta property="og:url" content="{{ request.scheme }}://{{ request.get_host }}{{ request.path }}">
{% endblock %}
{% endblock social_image %}

{% block stylesheets %}
<link rel="stylesheet" href="{% static 'website/css/project.css' %}">
Expand Down
Loading
Loading