Should we adjust Django's versioning to use a form of CalVer?

And yet those environments wouldn’t pay for a commercial Django license with longer security updates either, would they? I work in a highly regulated industry and frequent updates not being acceptable is imo most of the times just a cheap excuse.

I really appreciate that. Practically speaking, it seems that your magic wand doesn’t exist, barring simply making our release cycle slower. In practice it seems that we can’t reasonably add new features without also breaking compatibility in enough ways that make us unable to recommend hopping from LTS to LTS directly.

In your view, does the somewhat-adhered-to compatibility between N.2 and (N+1).2 make a significant difference in whether someone attracted to LTS is likely to use an LTS, or might abandon Django altogether?

I suspect that such attempted compatibility is adding a non-trivial maintenance burden without providing the benefit that would be worth the cost for our fellows and community. However, I suspect that just having the LTS releases, without any additional compatibility guarantees, might reasonably be a significant factor in why Django is attractive to sponsors.

There are some cases where this is true - and some where I don’t think the answer is quite so clear-cut.

For example, and only for discussion purposes, we were directly affected by the following items deprecated in 4.2, available in 5.0 and removed in 5.1:

  • make_random_password() removed
  • UnsaltedMD5PasswordHasher removed
  • length_is template filter removed

Now, were any of these so technically problematic that they couldn’t have been kept until 6.0? (That’s an honest question to which I do not know the answer. If the answer is “yes”, I can accept that.)

To be clear, I would also add that I would have been fine if these specific examples had been dropped in 5.0 - their effects on us were minimal.

I am only pointing these out to identify specific examples where I disagree with the current deprecation policy WRT the “pseudo-SemVer” numbering scheme. These removals feel to me like it breaks the spirit of an X.0 → X.1 transition.

Or, to try and explain it another way, I fail to see a “difference in severity” implied by the X.0 → X.1 transision as compared to a X.2 → X+1.0. I’m not familiar with any other project that would consider those two transitions to be “equal”. (There may be some, my knowledge is far from complete.)

There’s another incongruity between the X.0 → X.1 transition and the X.1 → X.2 transition in that while features can be removed in X.1 they will not be removed in X.2

This does not make it “wrong” in any objective sense, just different.

Speaking for myself as an individual formerly employed by an organization and not as a representative of that organization, the decision to migrate from LTS to LTS has been made based on staffing and funding issues, not technical issues regarding the magnitude of changes.

For certain projects, the actual amount of time spent updating the code is generally quite small compared to the administrative costs associated with deploying new releases. Going through the release process every 8 months simply wouldn’t happen.

So they migrate from LTS to LTS to take advantage of security patches over a 2-year window, keeping the release cadence at an appropriate rate. (I am not aware of any situation where a security release has cause an issue - those are frequently slipstreamed in with little difficulty.)

But this is primarily true for a very small number of projects. There are other projects that are updated regularly, and projects of very limited scope that may never get updated. (Those projects run in a physically isolated equipment “test bench” environment. I wouldn’t be surprised to find a 2.2 project still hanging around somewhere.)

As far as I can all of those could have kept “easily” till 6.0 (and 10.0 [sic] for that matter). The crux is probably in the detail, what does “easily” mean? It is easy to not remove code (sometimes) but it also means more CI time, potentially more bugs to fix, more code to navigate when developing a new feature etc…

Worse, when people only migrate from LTS to LTS we get bug reports about new features very very late after their initial commit.

I agree that the current versioning scheme might be a bit odd about when features are removed wrt LTS, but I don’t have strong feelings on version numbers :smiley:

What would be a way to force those projects to lower those administrative costs and put some of the money gained elsewhere (like Django)? In the end it feels like we are selling Django way to cheap if there is no incentive to lower those administrative costs. Take a look at Spring Boot for example, you get 13 months (iirc) support and then you pay for Spring Enterprise. Now I get that not that many projects have such policies like the ones you mention; but as a larger community how much do we want to “work” (as in: developing Django) for those instead of the other ones that keep up with releases etc?

Agreed completely, if the “meaning” of those numbers can be understood.

Side note: We’re getting pretty far afield from the original topic being the version numbering system used by Django. I think if this discussion is to continue, we might want to move these messages to create a new thread. (Your “friendly local moderator” does know how to do that.)

Not possible - and it’s not at all related to the technology being used.
It wouldn’t matter if the stack being used was Java Spring, Drupal, or even Microsoft Excel. The overhead for getting approvals to update anything can be substantial, but necessary within the context of the larger environment of which these software development tasks are a very small part.

I have always considered myself an outlier in terms of how we use Django. I never expect that anything be done “for” us, but have only ever requested that changes not be made “against” us. (e.g. The thread from a while ago where the idea was floated to eliminate the manage.py file.

As such, we adapt ourselves to the LTS release cycle rather than requesting changes be made to that cycle to benefit us.

This also means I would continue to advocate for some type of “LTS” release that remains supported for security purposes for at least 25 months total. (The 36-month window is perfect for us.)

I can understand that (one of) the DSF or the TSC could decide that the maintenance effort of an LTS release is not justified, and that they would be stopped. Personally, I can understand the decision, but also think that would be a mistake - but that’s from my perspective as an outlier. (What my former employer would do is something they would need to decide.)

At the risk of continuing this off-topic conversation too much, I’m more interested in the idea of having no guarantees about compatibility between LTS releases than I am in abandoning LTS entirely. I think that having the extended support of LTS releases encourages sponsors. I’m not sure that attempting any additional compatibility between those releases does, however.

If we dropped any special compatibility between LTS releases, I wonder how much comparative burden on the fellows it might be to extend all releases to be longer-term supported for security. I assume a notable amount of additional burden, but it could be something to consider.

For example, if we unified extended support to 32 months, then the end of extended support would align with new Django releases. We’d have mainstream support for 1 cycle (8 months) and extended support for an additional 3 cycles.

This would be more similar to Python’s yearly cycles, except that we’d have one less cycle than Python for mainstream (active) support, with the same number of extended (security-only) support cycles. This more predictable pacing could also aid our ecosystem packages in better aligning with our pace.

And just to give it an on-topic flavor: I like that the versions reflect our usual steering council terms. That’s a boundary where priorities for the project could be more likely to shift in notable ways as the governance of the project changes.

1 Like

Just to wrap up here, I had more thoughts on what might work here.

Here’s my proposal to move Django to an annual release cycle.

It’s obviously a bit spicy :hot_pepper::hot_pepper::hot_pepper: so no idea how that’s gonna go but :partying_face:

That’s still the other option.

4 Likes

@carltongibson Great write-up!!! I’m trying to understand why the below is not considered an option? I.e. aligning current release cycle with the calendar year, but skip the version scheme changes?

Existing Proposed Release Date
5.0 5.0 Dec ‘23
5.1 5.1 Sep ‘24
5.2 LTS 5.2 LTS Apr ‘25
6.0 6.0 Jan ‘26
6.1 6.1 April ‘26
6.2 LTS 6.2 LTS Sep ‘26

(the release months are not a concrete proposal, just examples)

Compromises:

  1. We switch to an annual rhythm in the release cycle
  2. LTS releases are the last release of the year
    1. Easy to learn pattern: “6”+20 = 26
    2. All the .2s are still LTS releases
  3. No irreversible changes to versioning (ie. once you go “2026”, you can’t go back)
  4. It’s still SemVer
  5. Allows for irregularities so we never need to shoot ourselves in the foot if something disruptive happens.
  6. We can see how the yearly cycle goes - and if happens to work great for some years, we can decide again if it’s worth using the year as the major digit.

I think a major release every four months would be asking a lot.

I’ve pondered developer friendly preview tags at times, but that’s just an idea — bottom line is I can pip install any given commit for less effort than it’s probably worth.

Happy to go down these side alleys on the social if you want!

1 Like

I’m roughly favorable to your proposal, Carlton. The consistency and alignment with the cadence of Python seems a great choice. I don’t love the years-as-versions, but they’re ok. 2-digits year versions would be better.

One thing that you didn’t address is the steering council alignment. Right now the steering council is aligned with the first version number changes, but it’s not clear how you would see that mapping to the new versions.

2 Likes

Thanks for this detailed write-up Carlton. I’m +1 on an annual release cycle, and going for “green only” or “plus last yellow” on Python support.

I’m still not sold on renumbering to year-based versions, especially since Python has decided not to use such a scheme. But I also don’t have an amazing suggestion here, since I think it’s in our interest to keep bumping the major version number, but doing it annually and keeping a .0 minor version would look a bit silly.

1 Like

If we keep the first version aligned with steering council cycles, it could avoid that problem.

To try to understand the package maintainers’ perspective, I’ve set up a Yearly LTS proposal - Wagtail and Django release timeline that others might find helpful, with pretend yearly LTS from 2023 to 2027 in April, to compare with the current Wagtail and Django release timeline.

The proposed schedule seems like more work to me, simply because 3 LTS to support = 3x 36 months? While in the current release schedule, of the 3 concurrent Django releases to support, one is 36 months support and the other two are 16.

I also worry about the crunch if all package maintainers were to look at 12 months’ worth of compatibility changes all at the same time of the year, vs. the current more staggered 8 months. Feels like packages with package dependencies would have a harder time upgrading? Not sure.


Should this be added on the new features repo? I’ve not heard much complaints about Django’s versioning scheme personally, I’d be interested to know how this fares compared to other possible improvements in :+1: metrics.

1 Like

I like the idea of switching to annual releases, each being 3-year LTS. The slower release pace matches Django being a mature 20-year old core project, where a lot of the innovation happens with 3rd party apps. Fewer releases means apps don’t need to upgrade as frequently. It’s also nice to have the same schedule every year. More frequent LTS releases also gives LTS people the option to upgrade yearly rather than every 2 years, assuming it works with their version of Python.

I think “Green Only Pythons” (currently 3.13 and 3.14) is too limited, and as an Ubuntu LTS user, “Plus Last Yellow” (2-year-old python) (currently 3.12) is pretty key, and would be enough for me. If Django is reaching back 2 years of python and giving 3 years of support, then Django generally doesn’t need to support an EOL python which are supported for 5 years. Aligning Django’s release date (December/January) to be just after Python’s release date (October) really helps with that.

Re versions, if the release was early January, then just do CalVer with month: 27.1, 28.1, etc. (I personally prefer 2-digits, can always switch to 4 later.)

1 Like

I am very much in agreement with the proposal from the blogpost. A few points I want to emphasize:

  1. Removing the .0/.1/.2 pattern is great. Really necessary! Not only we put a lot of care in every single feature release, with the LTS being no different, but also the “.2 is LTS” is very misleading. I can confirm there is real confusion about when to upgrade Django. It is common for enterprise users to plan LTS-to-LTS jumps, but by the time they move, the target LTS is already out of its bugfix phase. The current numbering only reinforces that misunderstanding.
  2. From a Fellows perspective, I agree that the overall workload would likely decrease and, even if it’s stay roughly the same in terms of “time investment”, it’ll surely be simpler. Feature releases take time and attention, and even if we’d automate some steps, they still require careful coordination and add stress. Fewer releases means fewer of those coordination points.
  3. The 8-month cadence has always been difficult to plan around. Every year, the milestones fall in different months, making it harder to align both personal and project schedules. A fixed annual cycle would be much easier to reason about.

Now, for the “but” part: I would avoid planning release candidates and finals right before/after the new year. That period is impractical for many people. Many contributors, companies, and users are taking time off, and some organizations run end-of-year shutdowns. In the southern hemisphere it is also summer and school holidays. It would be better to avoid that window entirely.

As a concrete proposal, I think something similar to the current A.0 roadmap works well, but offset by a few weeks to stay clear of the holiday season and DjangoCon US overlap:

  • Late August / Early September: alpha (feature freeze)
  • Mid October: beta (non-blocking freeze, right after Python release)
  • Mid November: release candidate
  • Early December: final release

That keeps the rhythm familiar while making it easier for everyone to participate fully. It does not collide with major holidays (as far as I could check), and it also allows more time between Alpha and Beta, which I think we need to incorporate as well.

Another interesting data point is that targeting the “202X” release to December means that “during 202X we develop 202X”. In other words, from January to September we work on the release of the same year, which feels intuitive and aligns nicely with yearly planning.

1 Like

I’m coming in on the retain the status quo side of things.

  • Releases more frequent than once per year. I’m hesistant to want to make our releases less frequent. There’s plenty of room for iteration in the framework, and pushing features/changes out another four months on a regular basis is very unappealing.
  • Keep with something like our current SemVer. Like Tom, I didn’t realize ubuntu followed yy.mm.xx versioning :grimacing: To that end, I think the consistency with our existing versioning is worth more than knowing what what year a version was associated and when it’s no longer supported.
  • Improve the LTS identifier? I don’t have a solution, but it seems like the x.2 version being the LTS is confusing for people. While the current supports third-party package maintainers really well, I think that group of people is the most tolerant of changes, and least impacted by changes.

The best parts of the proposal, for me, are:

  • more carrots (everything is an LTS)
  • nudge people away from really old versions (the last four months of the LTS)

We still have the option to do that with our current cadence by:

  • making every 8-month release an LTS (and making every deprecation cycle 24 months instead of sometimes 24, sometimes 16)
  • cutting the “extra” four months that leave people on EOL pythons

If we do that, then I think our current version numbers make sense. Yes, some people will still say, “Ha! Django 13.0 has nothing of interest: classic mistake to have no carrot for your breaking changes!” But this gets about five things wrong with regard to our philosophy and ecosystem.


One thing I’d like to see discussed more is the impact on third-party packages. Carlton’s blog explains the intended workflow:

  • packages support at most two LTSs, and the versions in between (4.2 LTS - 5.2 LTS)

and the social problem(s):

The issue is, though, that Django 4.2 still has several months to run at the point where Django 6.0 is released.

The reality is our users aren’t updating even then.

I think the first issue could be fixed by just removing those extra months. I’m already worried about the “extra 4” coming up in 2026: PyPI has a policy change scheduled in Feb 2026 that will require us to change our version of setuptools to build 4.2 releases. I’m just hoping we don’t have a security release in those six weeks. (We’ll be fine, but it’s just a headache I’d prefer to not to have.)

Re: the second issue (“our users aren’t updating even then”), let’s just make every 8-month release an LTS. Our backport policy is so conservative already, I can’t see this being a problem.


If we move to an annual cadence, I’m expecting this to increase pressure on third-party packages to support even longer spans of Django releases. 2 yr 8 mos (today) will become 3 years, as packages will be expected to support a Django 2026 (feature X deprecated), 2027, and 2028. (feature X removed in 2029? not sure of the plan here.)

I think that’s a harm, making it harder for packages to adopt new features at a time when we’re saying we want innovation to be led by the package ecosystem.

So let’s say some packages get more aggressive and only support two versions, Django 2026 and 2027. Aren’t consumers are in a tough spot where they have to get intimately familiar with the release philosophies of their most important packages to be able to predict what’s coming down the pike for 2028?

3 Likes

Hey @jacobtylerwalls.

I’m interested that you don’t mention the improved story for Python version support as a key positive. :thinking:

As a maintainer, I’m on the hook for three years of
support for the LTS. Nothing changes here. The recommendation would be to support all three active versions, dropping support as they reach EOL. The stability and depreciation policies would continue to operate as now. As ever, when opting into a new feature, I’d just add a shim where needed, and drop that when the time came. This is how we’ve been doing it… well… forever.

I have more to say about innovation. That’s the final piece, but I’ve already spoken to it lots of times (e.g.): tl;dr innovation in Django doesn’t happen in Django itself. It’s in third-party packages, that can make their way to core where appropriate. I believe very strongly in a small core for Django, and then good stories for how you extend for your particular use case. In my DjangoCon Europe talk last year I coined the term “Battery Packs” to try and capture this. It’s giving concrete demonstrations of that which remains in order to put the full story in place.

There are always going to be different possibilities of how we try to grow (and continue to maintain) Django. My vision is going to be just one. I’d love to see other concrete proposals! I think many of the half-thoughts reduce to ideas that are not going to be feasible if fully spelled out — but until that’s done we’ll not know. That’s a kind of general, if not this then what?

Historically, we’ve carried on with the status quo for lack of an answer. Increasingly people express dissatisfaction with that. It works. But the lingering problems get slowly more serious. Years pass. Nothing happens. We stagnate.

… which is why I’m proposing this change. Because I don’t think continuing as we are is actually the way.

2 Likes

I folded that in with “nudge people away from really old versions”–I should have said Python versions. When musing about grafting the benefits onto our current cadence, I was focusing on the few extra months supporting 3.9 in early 2026, but you’re right that the 3.8 support is absurd (18 months EOL). I fully support fixing this.

I just want to consider that “Remove 1-2 more pythons” could be grafted onto our current cadence. 4.2 LTS could have dropped support for Python 3.8 and supported 3.9, 3.10, 3.11 from the start (eventually adding 3.12 during mainstream support). I think the way we cull more Pythons after an LTS instead of at the LTS is mistimed.


As a maintainer, I’m on the hook for three years of
support for the LTS. Nothing changes here.

This is the part I’m hoping you’ll help me understand. Let’s leave shims aside. I’m thinking about using GeneratedField from Django 5.0 in your third-party package, which is too complicated to shim or vendor.

Realistically, I’m waiting until 6.0 is released (Dec 2025), when I can drop all versions prior to 5.2 LTS before I can start developing with 5.0 features that are too inconvenient to vendor (Dec 2023), because the prior version of my package supported 4.2 LTS - 5.2 LTS (2 yrs 8mos).

On the new timeline, that would be something like:
v1 (Django 2023 released Dec '22)
v2 (2024) – GeneratedField released
v3 (2025)
v4 (2026) – packages drop support for v1, can use v2 features

I wait for the release of v4 (Dec 2025), same as the status quo. 2 year wait time for GeneratedField. Good, parity.

But if GeneratedField had come out in Django 5.1 (August 2024), shift the version numbers by
one. Under the new system, still a 2 year wait time. Under the old system, the wait was only 16 months.

That’s one slowdown. The other slowdown is what we consider “a full LTS cycle” for ingesting django-template-partials or django-tasks into core. As of now, that’s either 2 years (interval between LTSes) or 2 years 8 months (full support cycle for third party packages). We’re taking that to 3 years.

In those two cases, don’t those extra months slow down innovation a little bit? (I’m including “merge into Django if proven” as part of “innovation”.)

This isn’t about the deprecation period per se and more about the norms for how much Django-release-time a package is expected to develop against. We’re going from 2 years 8 months to 3 years. Those extra four months hurt a little bit, no?

You mentioned there’s a social problem where packages can’t hold the line on the 2 years 8 months and end up doing more. That’s helpful to know. I wanted to think through whether we can also help them by grafting the main benefits onto the current cadence.

I may be worrying about nothing, but this is what I’m trying to get a handle on.

Under the old system, the wait was only 16 months.

Ah, I realize now to be fair my hypothetical should also test new features released in an x.2, which on the old system would require a longer wait (2 yr 8 months), so on average the new system is just as slow as the old one. Thanks for tolerating me while I worked through that :sweat_smile:

Still interested in your view about the “length of an LTS cycle” for ingesting a package into core. And also just, in general, whether it’s worth grafting the benefits onto an 8-month cycle to continue supporting faster feature delivery.

1 Like