Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
84d684d
added automatic release svg generation
bhargav-j47 Nov 8, 2025
06511ea
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 8, 2025
2213e0e
Update generate_release_roadmap.py
bhargav-j47 Nov 8, 2025
1b18f70
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 8, 2025
0a42640
Update tools/template.svg.jinja
bhargav-j47 Nov 17, 2025
c4d691f
Merge branch 'django:main' into release-imp
bhargav-j47 Nov 17, 2025
4a49490
Update tools/generate_release_roadmap.py
bhargav-j47 Nov 18, 2025
c9599a3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 18, 2025
6f75394
added suggested changes
bhargav-j47 Nov 18, 2025
8a06d6f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 18, 2025
7c5aa13
Update generate_release_roadmap.py
bhargav-j47 Nov 18, 2025
5e140e9
making svg more similar to png and adding fade
bhargav-j47 Nov 19, 2025
9feb2fc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 19, 2025
90d5ba3
update generate_release_roadmap.py
bhargav-j47 Nov 19, 2025
c791ec1
improving fade
bhargav-j47 Nov 19, 2025
c9a3e88
size changes to make svg similar to old png
bhargav-j47 Nov 19, 2025
23fb4b2
Update tools/generate_release_roadmap.py
bhargav-j47 Nov 19, 2025
4500278
Update tools/generate_release_roadmap.py
bhargav-j47 Nov 19, 2025
460c01b
Merge branch 'django:main' into release-imp
bhargav-j47 Nov 19, 2025
c4c15f6
commiting latest svg
bhargav-j47 Nov 19, 2025
2479366
updated og:image:type and appereance changes to match original png
bhargav-j47 Nov 20, 2025
3a1d1b2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 20, 2025
1c5c6c5
adding generate roadmap as management command in releases app
bhargav-j47 Nov 20, 2025
5f6f840
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 20, 2025
db1d6e7
minor visual changes
bhargav-j47 Nov 20, 2025
421d607
removing tools directory and minor changes in generate_release_roadma…
bhargav-j47 Nov 22, 2025
81af293
minor changes in generate_release_roadmap.py
bhargav-j47 Nov 22, 2025
e11be98
Update djangoproject/templates/releases/download.html
bhargav-j47 Nov 29, 2025
faae676
Add python-dateutil to requirements/common.txt
bhargav-j47 Nov 29, 2025
213bc4d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 29, 2025
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ SCSS = djangoproject/scss
STATIC = djangoproject/static

ci: compilemessages test
@python tools/generate_release_roadmap.py
@python -m coverage report

compilemessages:
Expand Down
2 changes: 1 addition & 1 deletion djangoproject/templates/releases/download.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ <h2 id="supported-versions">Supported Versions</h2>
<p>See the <a href="https://docs.djangoproject.com/en/dev/internals/release-process/#supported-versions">
supported versions policy</a> for detailed guidelines about what fixes will be backported.</p>

<img src="{% static "img/release-roadmap.png" %}" class='img-release' style="max-width:100%;" alt="Django release roadmap">
<img src="{% static "img/release-roadmap.svg" %}" class='img-release' style="max-width:100%;" alt="Django release roadmap">
<hr style="margin-bottom: 20px;">

<table class='django-supported-versions'>
Expand Down
248 changes: 248 additions & 0 deletions tools/generate_release_roadmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import datetime as dtime
import json

from jinja2 import Environment, FileSystemLoader


def load_release_data(json_file):
with open(json_file, encoding="utf-8") as f:
data = json.load(f)

processed_data = []
for item in data:
item["release_date"] = dtime.datetime.strptime(
item["release_date"], "%Y-%m-%d"
).date()
item["mainstream_end"] = dtime.datetime.strptime(
item["mainstream_end"], "%Y-%m-%d"
).date()
item["extended_end"] = dtime.datetime.strptime(
item["extended_end"], "%Y-%m-%d"
).date()
processed_data.append(item)
return processed_data


COLORS = {
"mainstream": "#0C4B33",
"extended": "#CBFDE9",
"grid": "#333333",
"text": "#FFFFFF",
"text_lts": "#0C4B33",
"bg": "#000000",
}

CONFIG = {
"pixels_per_year": 120,
"bar_height": 20,
"bar_v_spacing": 20,
"padding_top": 30,
"padding_bottom": 20,
"padding_left": 20,
"padding_right": 10,
"font_family": "'Segoe UI', 'Arial'",
"font_size": 12,
"font_size_small": 10,
"font_weight": "bold",
"font_weight_lts": "600",
"font_style_lts": "italic",
"legend_box_size": 14,
"legend_spacing": 150,
"text_padding_x": 10,
"year_line_width": 3,
"month_line_width": 1,
}


def get_chart_timeline(data, config):

start_year = data[0]["release_date"].year

max_end_date = max(d["extended_end"] for d in data)

end_year = max_end_date.year + 1

total_years = end_year - start_year
chart_width = total_years * config["pixels_per_year"]
svg_width = chart_width + config["padding_left"] + config["padding_right"]

return start_year, end_year, int(svg_width)


def calculate_dimensions(config, num_releases):

chart_height = (
config["padding_top"]
+ config["padding_bottom"]
+ (num_releases * config["bar_height"])
+ ((num_releases - 1) * config["bar_v_spacing"])
)
return int(chart_height)


def date_to_x(date, start_year, config):

pixels_per_year = config["pixels_per_year"]
pixels_per_block = pixels_per_year / 3.0
start_x = config["padding_left"]

year_offset = (date.year - start_year) * pixels_per_year

if 1 <= date.month <= 4:

block_num = 0
elif 5 <= date.month <= 8:

block_num = 1
else:

block_num = 2

block_x_end = year_offset + ((block_num + 1) * pixels_per_block)

return start_x + block_x_end


def generate_grids(start_year, end_year, config):

grid_lines = []
pixels_per_year = config["pixels_per_year"]
pixels_per_block = pixels_per_year / 3.0

for i, year in enumerate(range(start_year, end_year + 1)):
year_x_start = config["padding_left"] + (i * pixels_per_year)

for i in range(3):
grid_lines.append(
{
"x": year_x_start + (i * pixels_per_block),
"width": (
config["year_line_width"]
if i == 0
else config["month_line_width"]
),
"label": str(year) if i == 0 else None,
}
)
return grid_lines


def generate_releases(data, start_year, config):

releases_processed = []
for i, release in enumerate(data):
bar_y = config["padding_top"] + (
i * (config["bar_height"] + config["bar_v_spacing"])
)
text_y_center = bar_y + (config["bar_height"] / 2) + (config["font_size"] / 3)

x_start = date_to_x(release["release_date"], start_year, config)
x_end_mainstream = date_to_x(release["mainstream_end"], start_year, config)
x_end_extended = date_to_x(release["extended_end"], start_year, config)

mainstream_bar = {
"x": x_start,
"y": bar_y,
"width": x_end_mainstream - x_start,
"height": config["bar_height"],
"fill": COLORS["mainstream"],
}

extended_bar = {
"x": x_end_mainstream,
"y": bar_y,
"width": x_end_extended - x_end_mainstream,
"height": config["bar_height"],
"fill": COLORS["extended"],
}

version_text = {
"x": x_start + config["text_padding_x"],
"y": text_y_center,
"text": release["name"],
}

lts_text = None
if release.get("is_lts", False):
lts_text = {
"x": x_end_mainstream + config["text_padding_x"],
"y": text_y_center,
"text": "LTS",
}

releases_processed.append(
{
"mainstream_bar": mainstream_bar,
"extended_bar": extended_bar,
"version_text": version_text,
"lts_text": lts_text,
}
)
return releases_processed


def generate_legend(config, svg_height):

legend_y = svg_height - (config["padding_bottom"] / 2)
legend2_x = config["padding_left"] + config["legend_spacing"]

legend = {
"mainstream_box": {
"x": config["padding_left"],
"y": legend_y - config["legend_box_size"] + 2,
"size": config["legend_box_size"],
"fill": COLORS["mainstream"],
},
"mainstream_text": {
"x": config["padding_left"] + config["legend_box_size"] + 5,
"y": legend_y,
"text": "Mainstream Support",
},
"extended_box": {
"x": legend2_x,
"y": legend_y - config["legend_box_size"] + 2,
"size": config["legend_box_size"],
"fill": COLORS["extended"],
},
"extended_text": {
"x": legend2_x + config["legend_box_size"] + 5,
"y": legend_y,
"text": "Extended Support",
},
}
return legend


def render_svg():

data = load_release_data("release-data.json")

start_year, end_year, svg_width = get_chart_timeline(data, CONFIG)
svg_height = calculate_dimensions(CONFIG, len(data))

grid_lines = generate_grids(start_year, end_year, CONFIG)
releases_processed = generate_releases(data, start_year, CONFIG)

legend = generate_legend(CONFIG, svg_height)

env = Environment(loader=FileSystemLoader("."))
template = env.get_template("template.svg.jinja")

output_svg = template.render(
svg_width=svg_width,
svg_height=svg_height,
config=CONFIG,
colors=COLORS,
grid_lines=grid_lines,
releases=releases_processed,
legend=legend,
)

outfile = "../djangoproject/static/img/release-roadmap.svg"

with open(outfile, "w", encoding="utf-8") as f:
f.write(output_svg)


if __name__ == "__main__":
render_svg()
58 changes: 58 additions & 0 deletions tools/release-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
[
{
"name": "4.2",
"is_lts": true,
"release_date": "2023-04-03",
"mainstream_end": "2023-12-04",
"extended_end": "2026-04-01"
},
{
"name": "5.0",
"is_lts": false,
"release_date": "2023-12-04",
"mainstream_end": "2024-08-01",
"extended_end": "2025-04-01"
},
{
"name": "5.1",
"is_lts": false,
"release_date": "2024-08-01",
"mainstream_end": "2025-04-01",
"extended_end": "2025-12-01"
},
{
"name": "5.2",
"is_lts": true,
"release_date": "2025-04-01",
"mainstream_end": "2025-12-01",
"extended_end": "2028-04-01"
},
{
"name": "6.0",
"is_lts": false,
"release_date": "2025-12-01",
"mainstream_end": "2026-08-01",
"extended_end": "2027-04-01"
},
{
"name": "6.1",
"is_lts": false,
"release_date": "2026-08-01",
"mainstream_end": "2027-04-01",
"extended_end": "2027-12-01"
},
{
"name": "6.2",
"is_lts": true,
"release_date": "2027-04-01",
"mainstream_end": "2027-12-01",
"extended_end": "2030-04-01"
},
{
"name": "7.0",
"is_lts": false,
"release_date": "2027-12-01",
"mainstream_end": "2028-08-01",
"extended_end": "2029-04-01"
}
]
Loading