The Stack Report logo

The Stack Report

Subscribe
Archives
May 17, 2025

How we make decisions in Django

Write up of my recent talk at DjangoCon Europe, in Dublin, on How we make decisions in Django.

This month’s Stack Report is running late. It’s a write up of my recent talk at DjangoCon Europe, in Dublin, on How we make decisions in Django.

The conference was top-notch, and a real boost, as always. Will and I did a quick recap episode for Django Chat 🎙️. You can give that a listen.

Inevitably, though, I landed back to a maelstrom of work, family, and Django commitments, which have meant it’s only now that I get a moment of quiet to pull together my thoughts.

As I write, the talks aren’t yet online, but you can subscribe to the DjangoCon Europe channel on YouTube to see them as soon as they are. (There’s an absolute gold-mine of knowledge in the past videos there. Getting into a habit of watching an odd-one from the backlog is a real power-move, if you’re looking for one.)

Finally, before we start proper, this post is best read as the third, and for now final, piece in a series marked by my earlier Thoughts on Django’s Core and then Managing Django’s Queue. Together they give a pretty good idea of my thinking around how we can continue to push Django forwards.

So with that all said, let’s go!

Contributing to Django is hard

The starting point for the discussion is Django’s contributing process. In the run up to the recent Steering Council election there was a lot of concern, several social media threads, and some blog posts, about this. It was this discussion that triggered my Thoughts on Django’s Core.

Several folks ran for the Steering Council specifically looking to address the contributing process, and so I want to try and give a bit of context to that.

As a disclaimer, these are only my thoughts. They’re not unconsidered. And, I don’t think they’re a million miles from things that other Steering Council members might agree with. (But Maybe… 🤷) But, they’re certainly not official Steering Council policy or positions.

Slide saying, "Getting your code into Django has always been difficult".
The challenges of contributing to Django aren’t new.

A standard contributor story might run along these lines…

Tickets for new ideas get opened on Django’s issue tracker, Trac. That’s not the place for them — Django’s established workflow is that new features are discussed on the Forum before we open a ticket — so those tickets closed, and are sent to the Forum.

Those tickets are marked wontfix because pending the discussion that’s how they stand. They can always be reopened, but folks see that wontfix and feel like they’ve been shut down before they’ve begun.

On the Forum then, the idea is proposed, and either no-one responds, or else there’s a massive discussion, that goes on and on and on.

More times than not, the discussion peters out without a clear resolution. And, well, what happens to the idea then?

Normally nothing.

If we do get to an accepted ticket, Django’s coding standings mean it’s hard to a get a PR merged. The code review is tough, particularly for new contributors.

And so on.

If you’ve been around Django a while, these scenarios will (I hope) sound familiar.

§

Certainly it isn’t new.

I gave a talk at DjangoCon Europe in 2018. I’d just started as a Django Fellow.

I talked about getting a patch into Django. How there’d be lots of review. Lots of comments.

We’d get your patch ready, and then mark it Ready for commit. Then there’d be another review. More comments.

The bottom line is there’s a quality bar. It’s never been easy. It’s one of the reasons why Django is so reliable.

There’s always been pushback on new features.We can’t accept everything. Not by a long way.

Sarah Boyce, one of the current Django Fellows, gave the opening keynote of the conference, talking about how we can’t really do much more than we already are with our current capacity. It tied in well with the argument of Managing Django’s Queue.

The long discussions on the Forum are no different from those that used to happen on the Django Developers mailing list. Not really.

So I think the problems we face aren’t new, but rather that they’ve slowly built up as Django’s got older.

Slide text: Once upon a time in Kansas...
Django has come a long way in the last 20 years.

Django’s origin story is quite familiar. A small team got together in the basement of the LAWRENCE JOURNAL-WORLD and created the web framework that we could come to know as Django…

If you don’t know that story, Frank Wiles gave an excellent telling of it at last year’s DjangoCon US.

But Django is now 20! For years, it has been either the leading or amongst the leading Python web frameworks. It has 20 million downloads a month, millions of users, countless deployments.

What worked for a small team, and even a small community online doesn’t necessarily work so well when you scale it up.

Django has always looked for consensus. But, consensus: what exactly is that? What does it even mean?

It can’t be “everyone agrees”.

Django has so many use-cases that there is almost no proposal that is suitable for everybody.

So if you ask everybody’s opinion — and if consensus has to mean everybody agrees — you’re asking the impossible for us to get there.

Scaling the community

As we looked at in Managing Django’s Queue, this is a normal phenomenon — it seemingly happens in every large open source project — so it’s not something we’re doing particularly wrong in Django. It’s is (just) a problem of scaling the community.

In Django we say that Django is first and foremost a community, then an ecosystem, and only finally a framework. So the actual Django bit is the least of it.

That web framework is amazing. It lets us solve the web problem quickly. But — by itself — it’s not the reason why we do half the things that we do.

It’s the community. It's the friendships we make, the relationships we build, that are the point of it.

That community is built on the individual efforts that we each put into it, be that code, or organising a conference like DjangoCon, or a local meet-up, or answering questions on a forum, or whatever it may be.

But the community is much more than just those efforts.

The community is a shared resource. It's a commons. It truly is. And I think in the modern world, that's a wonderful thing. One that you get to be a part of.

But “community” is another big word. Is it any better defined than “consensus”?

Slide text: Community as Trust
In a smaller group I can trust that my view was adequately taken into consideration.

We can think of community in different ways. One is community as trust.

In smaller groups, trust is easier.

Consensus never meant that everybody always agrees about everything.

A better way of thinking about it is that, even though I don’t agree, I know that my view point was adequately taken into consideration.

In a small group, where I know everybody, and everybody knows me, it’s easy to trust that my view point was considered.

That enables me to let go.

As the community grows — not suddenly but, over time — people become anonymous to me, and I to them. I can’t trust that my view point was considered. (How could it be?) And so I’m forced to defend it. (Of course)

What does this mean? Well, every proposal for change ends up in the long grass.

There are other ways we can think about community that are helpful here.

Community as Norms

We can think of community as a set of norms. How we behave.

The code of conduct is the obvious example here. As the community gets bigger, and more anonymous, it’s harder to maintain the CoC. It becomes necessary to sign-post it more aggressively.

People new to the community, they don’t know.

But it’s the same with code.

There a million ways you can solve the web problem. So if you were to ask me Why Django? — like, what’s really unique — I would ultimately point to its support and API stability policies, and so on, — Django’s longterm continuity over time.

Django is the web framework you can rely on.

You build your application on Django and it’s still going to be with you, more or less as-is, in five or even ten years time.

The value of that over the software development lifecycle is immense.

What that means is that we don’t arbitrarily break things without consideration.

Yes, we know the Form API is a bit crusty. (So would you be!)

But we need to evolve it, with sensitivity. Not burn it down and build a new one (problem free, of course) just because it would‘d supposedly be neater (or whatever).

We’ve got the deprecation policy. We use it every release. (Lots of people think too much!)

But we can’t just make radical changes outside of that process. So, please don’t suggest that we do.

That’s a norm, right?

But People new to the community, they don’t know.

Community as reciprocity

We can think of the community as reciprocity.

The community is maintained by what we all put back into it. We all take at times. We call get something from it. New members take more than they give. They need help, orientation.

That’s all totally normal. Healthy. As times goes by, folks begin to feedback, to pay it forward.


But the open contribution model, that’s enabled particularly by GitHub, mean that the level of transitory engagement, that never deepens, which has always been there, is just pumped up until it drowns out the effort that we can all give.

A quote from Tim Shilling: the biggest takeaway for me is that, if we want to see change in the number of contributors or the people who contribute, you have to take time to invest into mentorship.
The space required for mentorship is essential to a sustainable community.

I put up a quote from Tim Schilling, who’s on the Django Steering Council, and is part of the Djangonaut Space organising team.

The point is we need space for mentorship, but that space is drowned out by the barrage of noise, from users that never give back, on the Trac, Discord, Forum, and Github.

Again, this was discussed in Managing Django’s Queue and by Sarah in her keynote.

But it’s the Same for PRs.

Quote from Daniel Stenberg, maintainer of cURL: Once merged, you own it.
Users will add code but don’t stick around to help maintain it.

I quoted Daniel Stenberg, the maintainer of cURL:

Contributors sending improvements and pull requests to an Open Source project could be viewed as your friendly neighbourhood people helping you out shovelling sand into a pile. The actual resulting pile of sand is yours, on your land. You get help to make the pile better and faster, sure, but the people helping you out do not consider the sand to be theirs in any way. It is yours.

A maintainer of the project is someone who actually feels some responsibility for or co-ownership of the pile, but most contributors never end up maintainers.

This was (again) a point from Sarah’s keynote. Most folks make the PR they need, but they don’t stick around to help maintain it.

That’s totally normal — but as the project grows it necessarily becomes more conservative, more guarded. (Do we even want your pile of sand?)

Without reciprocity — without people giving back, and without some limits on the purely extractive interactions — we reach an asymptotic limit of what we can hope to achieve.

Community as context

In the end I see community as the context within which we make our decisions.

Here I think it’s helpful to think about the technology adoption life cycle.

Graph: the Innovation Adoption Lifecycle. A bell-curve from Innovators through Early Adopters, Early Majority, Late majority, and Laggards, capturing the uptake of a new technology or innovation.
What’s the shape of this curve for Django? via Wikipedia, CC BY-SA 3

Here you have a distribution of users, segmented by how quickly they adopt a new or a change in technology.

At the leading edge you’ve got innovators, followed by early adopters, early majority, late majority, and laggards.

The question for us is what is the shape of this curve for Django? Is it weighted towards the early or the late adopters?

It’s difficult to know. We don’t have telemetry. There’s a lot we can’t see here.

But I think we can probably estimate it quite well.

Maybe there are some lone genius coders, but in general, early adoption isn’t something you can do in a vacuum.

It’s a fair bet that the vast majority of innovators and early adopters will part of what we might call the visible Django community — they’ll come to DjangoCons, they’ll subscribe to the Django News newsletter, they’ll listen to the Django Chat podcast, they’ll respond the annual Django Developers Survey. They’ll be on the Forum, or the Discord, or whatever — they’ll pop up somewhere.

Well, Django is downloaded about 20 million times a month. Using that figure and some simple heuristics, Jacob Kaplan-Moss, at the last DjangoCon US, estimated the Django user base as in the low-single millions.

That’s a reasonable estimate of the number of Django users.

Then, it’s no secret that the Django News newsletter has a low-single thousands number of subscribers.

The Django Chat podcast has a low-single thousands number of listeners.

And, when we do the annual developer survey with JetBrains we get a same low-single thousands number of responses.

The overlap on those sets won’t be perfect, but it’ll be pretty high.

What that means is that, to an order of magnitude, the visible part of the Django community is about 0.1% — a thousandth — of the total user base.

Slide text: "The visible part of the Django community is (to an order of magnitude) about 0.1% of the total Django user base.
The Django user base is weighted heavily against early adopters.

Even if those figures are way-off — 3 times, 5 times, 10 times even — it’s clear that the Django user base is weighted heavily around Early Majority, Late Majority, and Laggards — rather than innovators and early adopters.

And even if those people aren’t us — even if we want to push forwards faster — we need to make sure that their needs are given due weight in our decisions.

But the number seems about right.

Whilst I was writing this talk, Thibaud Colas, who’s DSF President, member of the accessibility team, and Wagtail Core Team, put this graph up on Mastodon:

Graph shows the percentage of downloads PyPI downloads for Django that are for a supported version. It ranges between 55% and 75% for recent versions, but was over 75% before Django 1.11 was end of life, which was when support for Python 2 was dropped.
It takes the whole LTS cycle for folks in the community to upgrade.

It shows the percentage of downloads PyPI downloads for Django that are for a supported version — one that’s in mainstream or extended support.

Currently that means Django 5.2 — the latest release, which is the new LTS. Django 5.1 which is now in extended support, and the Django 4.2 LTS — which goes all the way to April next year.

I think this is massively interesting.

First, it shows that — depending where we are in the cycle — between 50 and 75% of all downloads are on a supported version. That’s a good achievement, and one we should be proud of. The effort in making Django stable and easy to upgrade is worth it.

Second, note that the graph drops off after each .0 release. What’s happening there is that the previous LTS is going end of life. Next April, during the 6.0 cycle, we should see the same thing, as Django 4.2 finally goes end of life and folks decide they’d finally better update to Django 5.2.

Finally, note that it’s not a quick recovery — it’s taking what looks like almost the whole LTS cycle for it to recover as people slowly upgrade.

“Django has a great upgrade story” — we like to tell ourselves. And it really does.

But the reality of that — with third party packages and what not — is not the painless release day upgrades that we like to toot about.

It’s a bit more nuanced than that.

As an aside, there’s a question over on the left-hand side, after 3.0? What’s the big drop there?

Well that’s when Django 1.11 finally expired, and we dropped support for Python 2.

Early adopters people are not.

§

Anecdotally, I posted on Mastodon the about a month or so before the Django 5.2 release, about how I had to battle with a pre-Django 5 form template.

We started my current work project with Django 4.2 but moved to Django 5.0 as soon as it was out. There was just this one pre-5.0 form template left. I needed to work on it. It was horrible. I’d forgotten. The changes to form rendering in 5.0 are wonderful.

My point was that Django can’t be going that slowly because there’s no way I’d go back to what was then even the current LTS.

A friend of mine — who is both technical and runs a successful business built on Django — asked me what the new features I was so keen on were?

They didn’t even know.

They’re using Django 4.2. They’re happy there. And they had no intention of even thinking about updating until Django 5.2 — the next LTS was available.

Early adopters people are not.

And that’s totally OK.

But it means that we have to be realistic about the changes we make.

Can we tear up the API Stability Policy? No.

Even with deprecations, we can’t just adjust APIs without a real reason to do so.

A breaking change needs to be “clearly superior”, the Stability Policy says.

We can — and do — make such changes every release… — but those changes have costs, and we need to be aware of them. We need to keep them in mind.

Community as trust. The trust in Django.

What to do?

So, I think that’s more or less how we got here.

The community grew, making consensus based decision making more difficult.

The user base grew — heavily skewed against innovators and early adopters — making the stability policy vital.

We want to push forwards — but we’ve got this whole community coming up behind us that we have a duty to.

Again, it’s important to stress: The tension we feel is just part of being a successful, large, long-lived, open source project. It’s not that Django is doing anything wrong, and we mustn’t buy-in to any narrative that we are.

There are challenges, sure, as always. But they’re a natural outcome for where we are.

The question then is what can we do?

I finished by glossing over a few related strategies that I think are key.

These aren’t the only things we could do. But I didn’t have time for everything. And I just glossed, because we could talk about each one for an age.

Boost the existing ecosystem

The first thing is a point I made in Thoughts on Django’s Core: let’s make more of the third-party ecosystem we already have.

Slide text: "The real power of Django is... the vast, and growing selection of third-party packages" A quote from, Two Scoops of Django (2013)
Django’s Secret Sauce is its massive ecosystem of add-ons.

The quote here is from the classic two scoops of Django book — from 2013! (These problems aren’t new, and neither are the solutions.)

If I primarily use Django because it’s a super-stable foundation for my app, the giant ecosystem is probably a close second.

For almost any idea I have, there’s either a package I can use directly, or prior art that shows the way. That’s enormously valuable.

But for any should we add this to Django? — One simple solution can be not to bother. Put it in a third-party app, and be done with it.

I do this anyway — if I want something new — and if it has to be in Django itself, even if I somehow get it in instantly it’s not going to be available until the next major release — which can be the best part of a year away. (Sarah’s keynote made the point that average time to get a PR merged is something like 300 days, so getting my patch in instantly isn’t likely at all.)

If I’m updating LTS to LTS, I can wait two years for a new feature to become available.

If I put it in a third party package I can have it now — so that’s what I always do.

The question of whether it should go into Django comes later, much later, if at all.

§

Our problem here has been that we almost keep the ecosystem a secret. We’ve historically not mentioned it hardly at all on the website and docs.

The Steering Council are working on ways to make this better, but the key is we have this amazing resource that we’re just not making much of.

I think if we can change that then we can remove (or reduce) this tendency to think that everything needs to be in core.

That’s not to say the third-party app story is perfect. Maybe we can make that better over time — better hooks, better template, better guides, full featured plugins (or whatever).

Who knows… — those are more medium term thoughts and suggestions.

My focus is here, though, because it feels like something we can do immediately, without really changing anything. (And we already said, it’s change that’s hard.)

Batteries Included in 2025

Next then, I think we should have a conversation as a community about what the appropriate scope is for Django itself.

What does Batteries Included mean in 2025?

There was a thread on the forum about this recently. What batteries should be included? — It was really interesting — but it soon grew to include pretty much everything you could ever want.

We need to have that same conversation but with the constraints outlines in Managing Django’s Queue and Sarah’s keynote in mind — holding in mind the idea of what we really have capacity for.

It could go two ways:

Either we think the scope should change — and we’d have a plan for how — and we could all get behind that.

Or we think no — actually — the scope should stay more or less as it is — with third-party packages taking up the slack — and we could all get behind that.

I don’t really mind how it comes out but — I think we need to have the conversation.

It feels like a lot of tensions, and the confusion in messaging, is because we don’t have a settled conception of where we think Django itself is going over the next few years.

What is it we want our web framework to be?

Modularise (Somehow)

Finally then, somehow, I think we need to find a way to modularise our efforts.

It doesn’t matter how many Fellows we might be able to imagine into existence, it can’t be a sustainable solution to keep pushing ever more through the same single bottleneck of the django/django repo.

I see two possible ways here.

Battery Packs

If it can’t be batteries included, then maybe something like Battery Packs.

Django tasks is a good example here.
We hope to get the core interface and dummy backend into Django during the upcoming cycle.

But the database backend is a different question.
Absolutely guaranteed it’s going to need fixes and extra features, and all the rest of it.
No way can it — or should it — be tied to Django’s slow release cycle and stability policy.

But we want to make it easy to use. Can we do it as an add on? What about a pip extra…

pip install django[db-tasks]

I think with Django Tasks we’re going to have to address this question. We’ll have a deferred tasks interface merged but it will require an additional dependency in order to be truly functional. (The immediate backend won’t in fact defer anything.)

If we can find a good solution, can we do this for other things? A new — more exciting — CLI command? A more production ready setup? The auth add-ons you’re looking for? The API story you want to use? The databases or cache extras you need?

I don’t know... The point is that we don’t necessarily have to limit ourselves to just pip install django — and what a user sees as being Django once they’ve downloaded it doesn’t have to map 1-to-1 to how it’s developed back on GitHub.

Other frameworks do this. Maybe we could too.

Break up core?

Then maybe we could break up core.

I wasn’t sure about discussing this because you’ll all say “Carlton said, take out the admin”

I’m not saying that at all but I was pondering a while back if we had extras then maybe this woundn’t be the end of the world...

Slide text: pip install django[admin] (as a pip extra)
What would it take for the Admin to be a separate package?

What would happen if the admin were separated from the core and packaged separately?

Again, I’m not suggesting this.

But I think it’s an interesting thought experiment to consider what would be necessary for that to be the case? Sometimes I think the admin could benefit from it: there’s a desire to push the admin forward, that would be easier to experiment with as a separate package.

I don’t know if that’s possible. But we modularise, somehow?

If we can, then I think a few things can follow.

Maybe we can we parallelise our efforts a little. It doesn’t have to be just the Fellows as the single bottleneck. It can be teams — the ORM, async, admin, … — If so, potentially we can go faster.

But more, we potentially find a way back to smaller working groups.

And if we can have that, we can bring back the space for trust. Trust — just to remind us — that my concerns were at least properly considered — that, as I told it, enables consensus.

That smaller space is one where we can continue to welcome, mentor, and bring-on the new members of our community that are its future.

Exactly how we do all these things, I’m not sure. But that’s where I’d like to see us headed.

Read more:

  • Thoughts on Django’s Core

    To tie-in with the upcoming elections for Django's Steering Council, I wanted think again about Django's Core, let's call it Batteries Included Revisited....

  • Managing Django’s Queue

    With the Django 5.2 prerelease cycle now in play, eyes can turn to what might come in for Django 6.0. Top of that list is the interface part of django-tasks,...

Don't miss what's next. Subscribe to The Stack Report:
This email brought to you by Buttondown, the easiest way to start and grow your newsletter.