Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
eb8a4d2
new app website-v3
DrJfrost Feb 18, 2026
c8c95d7
documentation of the flag characteristics
DrJfrost Feb 18, 2026
86dff45
Merge pull request #2080 from boostorg/julian-release-v3
DrJfrost Feb 18, 2026
26ad1c2
feat: v3 css foundations and v3 as a context processor
fromagge Feb 18, 2026
7be9604
feat: header v3 implementation and icon component
fromagge Feb 18, 2026
e8b0c35
fix: added moon on lightmode and sun on darkmode as well as avatar
fromagge Feb 18, 2026
da4d9e8
delete website_v3 project and create v3 template file
DrJfrost Feb 18, 2026
86b3c87
delete website v3
DrJfrost Feb 18, 2026
0ceeb66
Merge pull request #2083 from boostorg/julian-release-v3
DrJfrost Feb 18, 2026
b0a32aa
fix: remove unnecesary context processor
fromagge Feb 20, 2026
5e9293c
fix: added border for dark version of avatar
fromagge Feb 20, 2026
788148c
Merge branch 'v3' into feat/main-site-navigation-v3
fromagge Feb 20, 2026
636e812
feat: refactor to all go into the new v3 folder
fromagge Feb 20, 2026
1defd1b
feat: add footer styles and conditional footer inclusion for v3
fromagge Feb 20, 2026
fc1ee4e
Merge pull request #2082 from boostorg/feat/main-site-navigation-v3
fromagge Feb 23, 2026
29d5541
Merge branch 'v3' into feat/footer-v3
fromagge Feb 24, 2026
e78b712
style: update footer CSS for mobile responsiveness, adjusting layout …
fromagge Feb 24, 2026
b073c0d
feat: implement carousel buttons with new styling and HTML structure
fromagge Feb 24, 2026
3840322
Merge pull request #2098 from boostorg/feat/carousel-button-v3
fromagge Feb 24, 2026
c31c271
feat: add tooltip button component and examples section for V3
fromagge Feb 24, 2026
9b640a4
Merge branch 'v3' into feat/footer-v3
fromagge Feb 24, 2026
f93aa4c
Merge pull request #2097 from boostorg/feat/footer-v3
fromagge Feb 24, 2026
fd3687b
feat: add post card component and related styles for V3
fromagge Feb 24, 2026
d028a5f
feat: enhance social icon links and add login option in header for V3
fromagge Feb 24, 2026
b90f43c
Implement input components
gregjkal Feb 24, 2026
a2e25a0
Fixes to hover state, combo, and multi-select inputs
gregjkal Feb 24, 2026
dc93e15
Fixes to combo input styling and animation
gregjkal Feb 24, 2026
b25b23b
Add demo page for form input compoments
gregjkal Feb 24, 2026
de68096
Consolidate forms.css into components.css
gregjkal Feb 24, 2026
f8add36
Fix help text padding
gregjkal Feb 24, 2026
d5da605
Add component demo view and include form inputs
gregjkal Feb 24, 2026
bc3f1d1
Fix: button hover override on post card component
fromagge Feb 25, 2026
12338d8
feat: add button examples and styles for V3
fromagge Feb 25, 2026
5587d31
feat: enhance tooltip button component with unique ID support
fromagge Feb 25, 2026
f9fecad
Fix: Changed examples structure and nitted css
fromagge Feb 25, 2026
9e789eb
Fix: Nit examples section
fromagge Feb 25, 2026
ae4456d
Merge pull request #2102 from boostorg/develop
fromagge Feb 26, 2026
3284bce
Merge branch 'v3' into gk/input-components
fromagge Feb 26, 2026
a7a79cf
Merge pull request #2099 from boostorg/gk/input-components
fromagge Feb 26, 2026
beeac51
implementation of why boost section
DrJfrost Feb 26, 2026
8542afb
implementation of category tags section
DrJfrost Feb 26, 2026
f103923
implementation of event cards section
DrJfrost Feb 26, 2026
8c87338
fix style of category tags
DrJfrost Feb 26, 2026
75ceb8f
Correcting comments and removing unnecessary imports.
DrJfrost Feb 26, 2026
ac78876
feat: add avatar component with initials support and styling
fromagge Feb 26, 2026
9e7950b
fix: return a initial instead of a text
fromagge Feb 26, 2026
276d8d7
feat: add large avatar size and update examples section
fromagge Feb 26, 2026
0da0fbf
fix: unified examples
fromagge Feb 26, 2026
623643e
add imports css components
DrJfrost Feb 26, 2026
3c4e548
add imports css index
DrJfrost Feb 26, 2026
8e4f3fc
refactor: streamline avatar rendering logic in examples and header se…
fromagge Feb 26, 2026
cfc07d2
ending section code block
DrJfrost Feb 26, 2026
9210001
refactor: improve file naming and component structure
DrJfrost Feb 27, 2026
a89834a
refactor: reorganize code structure and stylesheets
DrJfrost Feb 27, 2026
89fef80
Merge pull request #2104 from boostorg/julian-release-v3
fromagge Feb 27, 2026
ca83d00
refactor: migrate avatar initials filter to avatar_tags and update te…
fromagge Feb 27, 2026
acca496
fix: revert to v3/examples
fromagge Feb 27, 2026
22c5a5e
Story: Search card component V3 (#2101)
gregjkal Feb 27, 2026
2001890
Merge branch 'v3' into feat/2092-avatar-component-v3
fromagge Feb 27, 2026
61a76a1
Merge pull request #2106 from boostorg/feat/2092-avatar-component-v3
fromagge Feb 27, 2026
474c92d
Implement library intro card component
gregjkal Feb 26, 2026
b37bb3c
Clean up duplicate right arrow icon
gregjkal Feb 26, 2026
376fc46
Make library intro card data-driven with build_library_intro_context()
gregjkal Feb 26, 2026
05eb310
Add placeholder avatar for authors without profile images
gregjkal Feb 26, 2026
910fdb6
Use avatar_v3 component in library intro card
gregjkal Feb 27, 2026
e1dc1d9
Add medal emoji badges to top three library contributors
gregjkal Feb 27, 2026
2162c71
dark mode for examples section
fromagge Feb 27, 2026
2ecf75f
Fix: button tooltip dark
fromagge Feb 27, 2026
730b494
feat: add author avatar URLs to post cards in example section
fromagge Feb 27, 2026
d765089
Merge pull request #2114 from boostorg/fix/dark-mode-sections
fromagge Mar 2, 2026
87e0c94
Merge branch 'v3' into gk/library-intro-card
fromagge Mar 2, 2026
33036d7
Merge pull request #2112 from boostorg/gk/library-intro-card
fromagge Mar 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions config/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging

from django.conf import settings
from django.contrib.admin.views.decorators import staff_member_required
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path, re_path, register_converter, reverse_lazy
Expand All @@ -21,22 +22,23 @@
from config.settings import DEBUG_TOOLBAR
from core.views import (
BSLView,
BoostDevelopmentView,
CalendarView,
ClearCacheView,
DocLibsTemplateView,
ImageView,
MarkdownTemplateView,
V3ComponentDemoView,
ModernizedDocsView,
RedirectToDocsView,
RedirectToHTMLDocsView,
RedirectToHTMLToolsView,
RedirectToLibrariesView,
RedirectToLibraryDetailView,
RedirectToReleaseView,
RedirectToToolsView,
StaticContentTemplateView,
UserGuideTemplateView,
BoostDevelopmentView,
ModernizedDocsView,
RedirectToLibraryDetailView,
)
from marketing.views import PlausibleRedirectView, WhitePaperView
from libraries.api import LibrarySearchView
Expand Down Expand Up @@ -241,6 +243,11 @@
TemplateView.as_view(template_name="style_guide.html"),
name="style-guide",
),
path(
"v3/demo/components/",
staff_member_required(V3ComponentDemoView.as_view()),
name="v3-demo-components",
),
path("libraries/", LibraryListDispatcher.as_view(), name="libraries"),
path(
"libraries/<boostversionslug:version_slug>/<str:library_view_str>/",
Expand Down
49 changes: 49 additions & 0 deletions core/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import os
import re

Expand Down Expand Up @@ -1034,3 +1035,51 @@ def get(self, request: HttpRequest, campaign_identifier: str, main_path: str = "
redirect_path = f"{redirect_path}?{qs}"

return HttpResponseRedirect(redirect_path)


class V3ComponentDemoView(TemplateView):
"""Demo page for V3 design system components."""

template_name = "base.html"

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["popular_terms"] = [
{"label": "Networking"},
{"label": "Math"},
{"label": "Data processing"},
{"label": "Concurrency"},
{"label": "File systems"},
{"label": "Testing"},
]
context["demo_libs_json"] = json.dumps(
[
{"value": "asio", "label": "Asio"},
{"value": "beast", "label": "Beast"},
{"value": "filesystem", "label": "Filesystem"},
{"value": "json", "label": "JSON"},
{"value": "spirit", "label": "Spirit"},
]
)
context["demo_cats_json"] = json.dumps(
[
{"value": "algorithms", "label": "Algorithms"},
{"value": "containers", "label": "Containers"},
{"value": "io", "label": "I/O"},
{"value": "math", "label": "Math & Numerics"},
{"value": "networking", "label": "Networking"},
]
)
from libraries.models import LibraryVersion
from libraries.utils import build_library_intro_context

latest = Version.objects.most_recent()
if latest:
lv = (
LibraryVersion.objects.filter(version=latest, library__slug="beast")
.select_related("library")
.first()
)
if lv:
context["library_intro"] = build_library_intro_context(lv)
return context
133 changes: 133 additions & 0 deletions docs/django-waffle-v3-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Django Waffle — Feature flags

This document describes how **feature flags** work in the Boost website project (django-waffle), how to assign and manage them, and how the **v3** flag is set up.

---

## How feature flags work in this project

- **Library:** [django-waffle](https://github.com/jazzband/django-waffle) (v5.0.0). Flags are stored in the database and evaluated per request (and optionally per user, group, or session).
- **Middleware:** `waffle.middleware.WaffleMiddleware` runs on each request so that flag state is available in templates and views.
- **Settings** (`config/settings.py`):
- **`WAFFLE_CREATE_MISSING_FLAGS = True`** — The first time a flag name is used in code (e.g. `{% flag "v3" %}` or `flag_is_active(request, "v3")`), it is auto-created in the database with default **off**.
- **`WAFFLE_FLAG_DEFAULT = False`** — Newly created flags have “Everyone” = No; they only become active when explicitly enabled for users, groups, percentage, etc.
- **Evaluation:** For each request, waffle checks the flag’s rules (everyone, superusers, staff, authenticated, groups, users, percentage, testing cookie). If any rule matches, the flag is **active** for that request; otherwise it is **inactive**. Anonymous users only get a flag if “Everyone” is Yes or a percentage/testing rule applies; group-based activation applies only to authenticated users in the chosen groups.

So in short: **flags are off by default**, and you turn them on by assigning **groups**, **users**, or other options in the Django admin.

---

## How to assign feature flags

Feature flags are managed in the **Django Admin** under **Waffle**.

### 1. Create or open a flag

- Go to **Django Admin** → **Waffle** → **Flags** (`/admin/waffle/flag/`).
- Either:
- **Create** a new flag (Name = e.g. `v3`), or
- **Open** an existing flag (e.g. `v3`).

With `WAFFLE_CREATE_MISSING_FLAGS = True`, the flag may already exist because it was used in code; you only need to edit it.

### 2. Choose who gets the flag

On the flag’s edit page you can enable the flag for:

- **Everyone** — Yes/No/Unknown. “Yes” turns the flag on for all requests (use with care).
- **Superusers** — If checked, the flag is always on for superusers.
- **Staff** — If checked, the flag is on for staff users.
- **Authenticated** — If checked, the flag is on for any logged-in user.
- **Groups (Chosen groups)** — The flag is on only for users who belong to at least one of the selected groups. This is the usual way to target testers (e.g. `v3_testers`).
- **Users (Chosen users)** — The flag is on only for the selected users.
- **Percentage** — Roll out to a percentage of users (0.0–99.9).
- **Testing** — When enabled, the flag can be toggled via a query/cookie for testing (see waffle docs).

For a **group-based** flag (e.g. v3):

1. In **Chosen groups**, move the desired group (e.g. **v3_testers**) from “Available groups” to “Chosen groups”.
2. Leave **Everyone** as “No” (or “Unknown”) so only the chosen group sees the flag.
3. Click **Save**.

### 3. Put users into the group

Group-based flags only apply to **authenticated** users who are **members** of one of the chosen groups.

- Go to **Users** (or **Auth** → **Users** / **Users** → **Users**, depending on your project).
- Open the **user** that should see the flag.
- In **Groups** (or “User groups”), add the group (e.g. **v3_testers**).
- Save.

After that, when that user is logged in, the flag is active for their requests. Log out and back in (or use a fresh session) if you don’t see the change.

---

## The “v3” flag and banner

The **v3** flag is used to show a banner to users who are part of the v3 rollout.

### What was added in the repo

- **django-waffle** in `requirements.txt` (v5.0.0).
- **Config** in `config/settings.py`: `waffle` in `INSTALLED_APPS`, `waffle.middleware.WaffleMiddleware` in `MIDDLEWARE`, `WAFFLE_CREATE_MISSING_FLAGS = True`, `WAFFLE_FLAG_DEFAULT = False`.
- **Banner** in `templates/base.html`: `{% load waffle_tags %}` and a block `{% flag "v3" %} ... {% endflag %}` that shows a grey “v3 flag enabled” bar at the top of the page when the flag is active for the requesting user.
- **Data migration** `users/migrations/0021_add_v3_testers_group.py` creates the Django auth group **v3_testers**, which can be assigned to the v3 flag.

### How to enable the v3 banner for yourself

1. **Apply migrations** (so the `v3_testers` group exists):
```bash
just migrate
# or: docker compose run --rm web python manage.py migrate
```
2. **Admin** → **Waffle** → **Flags** → open (or create) the **v3** flag.
3. In **Chosen groups**, add **v3_testers** and save.
4. **Admin** → **Users** → open **your user** → add **v3_testers** to Groups → save.
5. Log in on the site and reload; the **“v3 flag enabled”** banner should appear at the top.

### If the “v3 flag enabled” banner does not appear

- You must be **logged in**; group-based flags do not apply to anonymous users.
- Your **user** must be in the **v3_testers** group (Users → your user → Groups).
- **Log out and log back in** (or use a new incognito session) so the session reflects the group.
- Confirm the **v3** flag is saved with **v3_testers** in Chosen groups.

---

## Where it is in the codebase

| What | Where |
|------|--------|
| Conditional banner | `templates/base.html`: `{% load waffle_tags %}` and `{% flag "v3" %} ... {% endflag %}` |
| Waffle config | `config/settings.py`: `INSTALLED_APPS`, `MIDDLEWARE`, `WAFFLE_*` |
| v3_testers group | `users/migrations/0021_add_v3_testers_group.py` |
| Package | `requirements.txt`: `django-waffle==5.0.0` |

---

## Using flags in Python (views)

```python
from waffle import flag_is_active

def my_view(request):
if flag_is_active(request, "v3"):
# Flag is active for this request (e.g. user in v3_testers)
...
else:
...
```

---

## Using flags in templates

```django
{% load waffle_tags %}

{% flag "v3" %}
<div>Content only shown when the v3 flag is active for this user.</div>
{% endflag %}
```

You can use the same pattern for any flag name (e.g. `{% flag "my_feature" %}`) and assign it via groups or users in the admin as described above.
22 changes: 2 additions & 20 deletions libraries/mixins.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import re

import structlog
from types import SimpleNamespace

from django.db.models import Count, Exists, OuterRef
from django.db.models.functions import Lower
from django.shortcuts import get_object_or_404
from django.urls import reverse

Expand All @@ -17,11 +15,11 @@
from libraries.models import (
Commit,
CommitAuthor,
CommitAuthorEmail,
Library,
LibraryVersion,
)
from libraries.path_matcher.utils import determine_latest_url
from libraries.utils import patch_commit_authors
from versions.models import Version

logger = structlog.get_logger()
Expand Down Expand Up @@ -232,23 +230,7 @@ def get_related(self, library_version, relation="maintainers", exclude_ids=None)
if exclude_ids:
qs = qs.exclude(id__in=exclude_ids)
qs = list(qs)
commit_authors = {
author_email.email: author_email
for author_email in CommitAuthorEmail.objects.annotate(
email_lower=Lower("email")
)
.filter(email_lower__in=[x.email.lower() for x in qs])
.select_related("author")
}
for user in qs:
if author_email := commit_authors.get(user.email.lower(), None):
user.commitauthor = author_email.author
else:
user.commitauthor = SimpleNamespace(
github_profile_url="",
avatar_url="",
display_name=f"{user.display_name}",
)
patch_commit_authors(qs)
return qs

def get_author_tag(self, library_version):
Expand Down
Loading