Skip to content

Commit cbcf233

Browse files
authored
Merge pull request #1 from boostorg/v3
V3 Website to QA
2 parents 3d38460 + 33036d7 commit cbcf233

78 files changed

Lines changed: 6137 additions & 33 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

config/urls.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22

33
from django.conf import settings
4+
from django.contrib.admin.views.decorators import staff_member_required
45
from django.conf.urls.static import static
56
from django.contrib import admin
67
from django.urls import include, path, re_path, register_converter, reverse_lazy
@@ -21,22 +22,23 @@
2122
from config.settings import DEBUG_TOOLBAR
2223
from core.views import (
2324
BSLView,
25+
BoostDevelopmentView,
2426
CalendarView,
2527
ClearCacheView,
2628
DocLibsTemplateView,
2729
ImageView,
2830
MarkdownTemplateView,
31+
V3ComponentDemoView,
32+
ModernizedDocsView,
2933
RedirectToDocsView,
3034
RedirectToHTMLDocsView,
3135
RedirectToHTMLToolsView,
3236
RedirectToLibrariesView,
37+
RedirectToLibraryDetailView,
3338
RedirectToReleaseView,
3439
RedirectToToolsView,
3540
StaticContentTemplateView,
3641
UserGuideTemplateView,
37-
BoostDevelopmentView,
38-
ModernizedDocsView,
39-
RedirectToLibraryDetailView,
4042
)
4143
from marketing.views import PlausibleRedirectView, WhitePaperView
4244
from libraries.api import LibrarySearchView
@@ -241,6 +243,11 @@
241243
TemplateView.as_view(template_name="style_guide.html"),
242244
name="style-guide",
243245
),
246+
path(
247+
"v3/demo/components/",
248+
staff_member_required(V3ComponentDemoView.as_view()),
249+
name="v3-demo-components",
250+
),
244251
path("libraries/", LibraryListDispatcher.as_view(), name="libraries"),
245252
path(
246253
"libraries/<boostversionslug:version_slug>/<str:library_view_str>/",

core/views.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import os
23
import re
34

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

10361037
return HttpResponseRedirect(redirect_path)
1038+
1039+
1040+
class V3ComponentDemoView(TemplateView):
1041+
"""Demo page for V3 design system components."""
1042+
1043+
template_name = "base.html"
1044+
1045+
def get_context_data(self, **kwargs):
1046+
context = super().get_context_data(**kwargs)
1047+
context["popular_terms"] = [
1048+
{"label": "Networking"},
1049+
{"label": "Math"},
1050+
{"label": "Data processing"},
1051+
{"label": "Concurrency"},
1052+
{"label": "File systems"},
1053+
{"label": "Testing"},
1054+
]
1055+
context["demo_libs_json"] = json.dumps(
1056+
[
1057+
{"value": "asio", "label": "Asio"},
1058+
{"value": "beast", "label": "Beast"},
1059+
{"value": "filesystem", "label": "Filesystem"},
1060+
{"value": "json", "label": "JSON"},
1061+
{"value": "spirit", "label": "Spirit"},
1062+
]
1063+
)
1064+
context["demo_cats_json"] = json.dumps(
1065+
[
1066+
{"value": "algorithms", "label": "Algorithms"},
1067+
{"value": "containers", "label": "Containers"},
1068+
{"value": "io", "label": "I/O"},
1069+
{"value": "math", "label": "Math & Numerics"},
1070+
{"value": "networking", "label": "Networking"},
1071+
]
1072+
)
1073+
from libraries.models import LibraryVersion
1074+
from libraries.utils import build_library_intro_context
1075+
1076+
latest = Version.objects.most_recent()
1077+
if latest:
1078+
lv = (
1079+
LibraryVersion.objects.filter(version=latest, library__slug="beast")
1080+
.select_related("library")
1081+
.first()
1082+
)
1083+
if lv:
1084+
context["library_intro"] = build_library_intro_context(lv)
1085+
return context

docs/django-waffle-v3-flag.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Django Waffle — Feature flags
2+
3+
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.
4+
5+
---
6+
7+
## How feature flags work in this project
8+
9+
- **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).
10+
- **Middleware:** `waffle.middleware.WaffleMiddleware` runs on each request so that flag state is available in templates and views.
11+
- **Settings** (`config/settings.py`):
12+
- **`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**.
13+
- **`WAFFLE_FLAG_DEFAULT = False`** — Newly created flags have “Everyone” = No; they only become active when explicitly enabled for users, groups, percentage, etc.
14+
- **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.
15+
16+
So in short: **flags are off by default**, and you turn them on by assigning **groups**, **users**, or other options in the Django admin.
17+
18+
---
19+
20+
## How to assign feature flags
21+
22+
Feature flags are managed in the **Django Admin** under **Waffle**.
23+
24+
### 1. Create or open a flag
25+
26+
- Go to **Django Admin****Waffle****Flags** (`/admin/waffle/flag/`).
27+
- Either:
28+
- **Create** a new flag (Name = e.g. `v3`), or
29+
- **Open** an existing flag (e.g. `v3`).
30+
31+
With `WAFFLE_CREATE_MISSING_FLAGS = True`, the flag may already exist because it was used in code; you only need to edit it.
32+
33+
### 2. Choose who gets the flag
34+
35+
On the flag’s edit page you can enable the flag for:
36+
37+
- **Everyone** — Yes/No/Unknown. “Yes” turns the flag on for all requests (use with care).
38+
- **Superusers** — If checked, the flag is always on for superusers.
39+
- **Staff** — If checked, the flag is on for staff users.
40+
- **Authenticated** — If checked, the flag is on for any logged-in user.
41+
- **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`).
42+
- **Users (Chosen users)** — The flag is on only for the selected users.
43+
- **Percentage** — Roll out to a percentage of users (0.0–99.9).
44+
- **Testing** — When enabled, the flag can be toggled via a query/cookie for testing (see waffle docs).
45+
46+
For a **group-based** flag (e.g. v3):
47+
48+
1. In **Chosen groups**, move the desired group (e.g. **v3_testers**) from “Available groups” to “Chosen groups”.
49+
2. Leave **Everyone** as “No” (or “Unknown”) so only the chosen group sees the flag.
50+
3. Click **Save**.
51+
52+
### 3. Put users into the group
53+
54+
Group-based flags only apply to **authenticated** users who are **members** of one of the chosen groups.
55+
56+
- Go to **Users** (or **Auth****Users** / **Users****Users**, depending on your project).
57+
- Open the **user** that should see the flag.
58+
- In **Groups** (or “User groups”), add the group (e.g. **v3_testers**).
59+
- Save.
60+
61+
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.
62+
63+
---
64+
65+
## The “v3” flag and banner
66+
67+
The **v3** flag is used to show a banner to users who are part of the v3 rollout.
68+
69+
### What was added in the repo
70+
71+
- **django-waffle** in `requirements.txt` (v5.0.0).
72+
- **Config** in `config/settings.py`: `waffle` in `INSTALLED_APPS`, `waffle.middleware.WaffleMiddleware` in `MIDDLEWARE`, `WAFFLE_CREATE_MISSING_FLAGS = True`, `WAFFLE_FLAG_DEFAULT = False`.
73+
- **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.
74+
- **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.
75+
76+
### How to enable the v3 banner for yourself
77+
78+
1. **Apply migrations** (so the `v3_testers` group exists):
79+
```bash
80+
just migrate
81+
# or: docker compose run --rm web python manage.py migrate
82+
```
83+
2. **Admin****Waffle****Flags** → open (or create) the **v3** flag.
84+
3. In **Chosen groups**, add **v3_testers** and save.
85+
4. **Admin****Users** → open **your user** → add **v3_testers** to Groups → save.
86+
5. Log in on the site and reload; the **“v3 flag enabled”** banner should appear at the top.
87+
88+
### If the “v3 flag enabled” banner does not appear
89+
90+
- You must be **logged in**; group-based flags do not apply to anonymous users.
91+
- Your **user** must be in the **v3_testers** group (Users → your user → Groups).
92+
- **Log out and log back in** (or use a new incognito session) so the session reflects the group.
93+
- Confirm the **v3** flag is saved with **v3_testers** in Chosen groups.
94+
95+
---
96+
97+
## Where it is in the codebase
98+
99+
| What | Where |
100+
|------|--------|
101+
| Conditional banner | `templates/base.html`: `{% load waffle_tags %}` and `{% flag "v3" %} ... {% endflag %}` |
102+
| Waffle config | `config/settings.py`: `INSTALLED_APPS`, `MIDDLEWARE`, `WAFFLE_*` |
103+
| v3_testers group | `users/migrations/0021_add_v3_testers_group.py` |
104+
| Package | `requirements.txt`: `django-waffle==5.0.0` |
105+
106+
---
107+
108+
## Using flags in Python (views)
109+
110+
```python
111+
from waffle import flag_is_active
112+
113+
def my_view(request):
114+
if flag_is_active(request, "v3"):
115+
# Flag is active for this request (e.g. user in v3_testers)
116+
...
117+
else:
118+
...
119+
```
120+
121+
---
122+
123+
## Using flags in templates
124+
125+
```django
126+
{% load waffle_tags %}
127+
128+
{% flag "v3" %}
129+
<div>Content only shown when the v3 flag is active for this user.</div>
130+
{% endflag %}
131+
```
132+
133+
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.

libraries/mixins.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import re
22

33
import structlog
4-
from types import SimpleNamespace
54

65
from django.db.models import Count, Exists, OuterRef
7-
from django.db.models.functions import Lower
86
from django.shortcuts import get_object_or_404
97
from django.urls import reverse
108

@@ -17,11 +15,11 @@
1715
from libraries.models import (
1816
Commit,
1917
CommitAuthor,
20-
CommitAuthorEmail,
2118
Library,
2219
LibraryVersion,
2320
)
2421
from libraries.path_matcher.utils import determine_latest_url
22+
from libraries.utils import patch_commit_authors
2523
from versions.models import Version
2624

2725
logger = structlog.get_logger()
@@ -232,23 +230,7 @@ def get_related(self, library_version, relation="maintainers", exclude_ids=None)
232230
if exclude_ids:
233231
qs = qs.exclude(id__in=exclude_ids)
234232
qs = list(qs)
235-
commit_authors = {
236-
author_email.email: author_email
237-
for author_email in CommitAuthorEmail.objects.annotate(
238-
email_lower=Lower("email")
239-
)
240-
.filter(email_lower__in=[x.email.lower() for x in qs])
241-
.select_related("author")
242-
}
243-
for user in qs:
244-
if author_email := commit_authors.get(user.email.lower(), None):
245-
user.commitauthor = author_email.author
246-
else:
247-
user.commitauthor = SimpleNamespace(
248-
github_profile_url="",
249-
avatar_url="",
250-
display_name=f"{user.display_name}",
251-
)
233+
patch_commit_authors(qs)
252234
return qs
253235

254236
def get_author_tag(self, library_version):

0 commit comments

Comments
 (0)