Comments

Hacker News

For Django's default template language, does it still have these limitations?

1. Brackets aren't allowed to help with boolean expressions like {% if a and (b or c) %}

2. You can't do basic arithmetic like {{ x * 2 }}, but you're allowed to do {{ x | add:"2" }}. There's hacks to multiply using {% widthratio a 1 b %} or division with {% widthratio a b 1 %} though (https://stackoverflow.com/questions/18350630/multiplication-...).

3. You can't assign expressions to variables like {% with x = a or b %}, so you have to repeat yourself.

4. You can't capture HTML generated from template code in a variable to pass into a partial template to write slots-style HTML components e.g. {% capture x %}Hello {{ username }}{% endcapture %}{{ include "partials/header.html" with body_html=x }}.

5. You can't pass variables to model methods.

I understand there's a philosophy that templates shouldn't contain complex logic, but I find the above pretty arbitrary and leads to code that's harder to maintain. Addition is okay but not multiplication? Boolean logic is okay but not with brackets? I often have to puzzle out some way to get my code to work that goes against what I'd normally want to do, some I'm forced to duplicate template code because you can't put expressions in variables or move basic one-off logic into views (which has poor locality https://htmx.org/essays/locality-of-behaviour/ and makes it harder to move template snippets between pages).

It's like hiding the kitchen knives because they might be misused.

Is Jinja2 a practical alternative or there's friction to using it?

by seanwilson

> the site can now pretty easily handle 12 requests per second.

Right prod architecture (nginx -> gunicorn -> django, with nginx serving static assets), decent PRAGMAs for sqlite3 and caching for generic content should improve the RPS one or two orders of magnitude.

by lazyant

My experience from running a django app with thousands of migrations and dozen of apps

- Testing involving models is slow. It runs all your migration. Python's own "Unittest" is fast for functions not involving models.

- The suggested pattern for business logic is "active record", which is putting functions about a model alongside the model itself. Good for clean case. Doesn't really work when your operation involve multiple models.

- But if you put function about a model inside a model, the migration doesn't serialize the function into migration history (I could be wrong)

- "Data" migration is handy when you want enum-like data in database available to all teammates. e.g. list of currencies

- It is very tempting for new team member just to create an django app, with its own view and model, because it feels like starting a fresh without needing to care about existing business context. On the other hand, designing conceptual boundary between apps is very important. Because starting app is easy, and often (not always) wrong.

- The squash migration utils are limited because I guess of relatively low usage. It squashes linear migration history (with manual curation of starting and ending migration node I think? Not sure about latest state). I wrote a util to squash whole migration history by finding linear segments and squash those segment of history one by one. Didn't release it, but wrote some notes https://gist.github.com/kmcheung12/08b4c234b38c23efd43550da9...

by a_c

Not to be a downer since a lot of JEvans’ content is great. People should really give .NET a try. I don’t know if we are just old greybeard curmudgeons who look at this and go “is this a new thing? haven’t we been doing this for decades?”, but .NET out of the box with a few packages for databases, logging, etc is so pleasant you don’t even think about it. Maybe it’s just not a vocal community idk.

by hecreto

Good ole Django. Worked with a number of frameworks (tm), but nothing really quite scratches my itch like Django does. I still find the ORM and database migration system unmatched.

by altbdoor

> Some light load testing (with (ab -n 1000 -c 1) shows that right now we can serve about 2-3 requests per second (on a ~$10/month VM).

> After turning on template caching, it seems like the site can now pretty easily handle 12 requests per second or so without using all of the CPU. I have not carefully benchmarked the before and after but it seems like it’s made a pretty big difference.

That seems crazy low, I think there has to be something else going on here.

by echoangle

I have been using Django since 0.95 and I haven't seen anything which is so flexible with amazing DSLs while also making it easy to understand the magic behind it.

For the last 10 years, even in a Golang stack or Java stack, I still use Django for models and migration. I even have generators which generate Gorm (or other framework) DAO or Java hibernate classes using Django models.

With LLMs, it becomes easier since I can now write all the model, custom querysets in Django, ask the LLM to generate Golang DAO, setters and getters... and test the query against the Django generated queries for completeness.

Atlas, sqlx, sqlc and all other ORM like things in golang cannot do migrations the way Django does.

by ranedk

There are so many footguns[1] in async python but it really has stolen the zeitgeist of modern python web. Synchronous django has a lot to commend it. If you deploy it reasonably well (workers, behind a caching reverse proxy etc) it's easy to operate in production even under duress.

It's not going to be the right stack for long lived websocket connections or whatever but for a CRUD-ish or enterprise app, often very productive.

[1] buffer bloat is too easy to sleep walk into

    queue = asyncio.Queue()  # oops
unbounded concurrency

    await asyncio.gather(*(fetch(item) for item in items))  # look mum! no outbound sockets left or maybe even no file descriptors
accidental blocking

    data = requests.get(url).json()  # oops! we're blocking the event loop
and sure you can setup a separate task to measure the event loop latency and alert on that but this is the kinda thing you'll never find in a tutorial, you just need to experience this stuff and figure out a solution you like

I can go on and on about async python (cancellation bugs, leaking a task - that has a cataclysmic failure mode where if you forget to hold a reference to your asyncio.create_task() then it's all weak refs in that machinery so your task can get garbage collected before it ran or completed in production! super tricky forensics. Then there's all the obvious stuff like race conditions, new ways of creating deadlocks, blah blah i really can go on for days.

by CraigJPerry

Join the discussion

Write your take first — we'll ask for email only when you're ready to publish.

  • Hacker News
  • For Django's default template language, does it still have these limitations?

    1. Brackets aren't allowed to help with boolean expressions like {% if a and (b or c) %}

    2. You can't do basic arithmetic like {{ x * 2 }}, but you're allowed to do {{ x | add:"2" }}. There's hacks to multiply using {% widthratio a 1 b %} or division with {% widthratio a b 1 %} though (https://stackoverflow.com/questions/18350630/multiplication-...).

    3. You can't assign expressions to variables like {% with x = a or b %}, so you have to repeat yourself.

    4. You can't capture HTML generated from template code in a variable to pass into a partial template to write slots-style HTML components e.g. {% capture x %}Hello {{ username }}{% endcapture %}{{ include "partials/header.html" with body_html=x }}.

    5. You can't pass variables to model methods.

    I understand there's a philosophy that templates shouldn't contain complex logic, but I find the above pretty arbitrary and leads to code that's harder to maintain. Addition is okay but not multiplication? Boolean logic is okay but not with brackets? I often have to puzzle out some way to get my code to work that goes against what I'd normally want to do, some I'm forced to duplicate template code because you can't put expressions in variables or move basic one-off logic into views (which has poor locality https://htmx.org/essays/locality-of-behaviour/ and makes it harder to move template snippets between pages).

    It's like hiding the kitchen knives because they might be misused.

    Is Jinja2 a practical alternative or there's friction to using it?

  • > the site can now pretty easily handle 12 requests per second.

    Right prod architecture (nginx -> gunicorn -> django, with nginx serving static assets), decent PRAGMAs for sqlite3 and caching for generic content should improve the RPS one or two orders of magnitude.

  • My experience from running a django app with thousands of migrations and dozen of apps

    - Testing involving models is slow. It runs all your migration. Python's own "Unittest" is fast for functions not involving models.

    - The suggested pattern for business logic is "active record", which is putting functions about a model alongside the model itself. Good for clean case. Doesn't really work when your operation involve multiple models.

    - But if you put function about a model inside a model, the migration doesn't serialize the function into migration history (I could be wrong)

    - "Data" migration is handy when you want enum-like data in database available to all teammates. e.g. list of currencies

    - It is very tempting for new team member just to create an django app, with its own view and model, because it feels like starting a fresh without needing to care about existing business context. On the other hand, designing conceptual boundary between apps is very important. Because starting app is easy, and often (not always) wrong.

    - The squash migration utils are limited because I guess of relatively low usage. It squashes linear migration history (with manual curation of starting and ending migration node I think? Not sure about latest state). I wrote a util to squash whole migration history by finding linear segments and squash those segment of history one by one. Didn't release it, but wrote some notes https://gist.github.com/kmcheung12/08b4c234b38c23efd43550da9...

    by a_c
  • Not to be a downer since a lot of JEvans’ content is great. People should really give .NET a try. I don’t know if we are just old greybeard curmudgeons who look at this and go “is this a new thing? haven’t we been doing this for decades?”, but .NET out of the box with a few packages for databases, logging, etc is so pleasant you don’t even think about it. Maybe it’s just not a vocal community idk.
  • Good ole Django. Worked with a number of frameworks (tm), but nothing really quite scratches my itch like Django does. I still find the ORM and database migration system unmatched.
  • > Some light load testing (with (ab -n 1000 -c 1) shows that right now we can serve about 2-3 requests per second (on a ~$10/month VM).

    > After turning on template caching, it seems like the site can now pretty easily handle 12 requests per second or so without using all of the CPU. I have not carefully benchmarked the before and after but it seems like it’s made a pretty big difference.

    That seems crazy low, I think there has to be something else going on here.

  • I have been using Django since 0.95 and I haven't seen anything which is so flexible with amazing DSLs while also making it easy to understand the magic behind it.

    For the last 10 years, even in a Golang stack or Java stack, I still use Django for models and migration. I even have generators which generate Gorm (or other framework) DAO or Java hibernate classes using Django models.

    With LLMs, it becomes easier since I can now write all the model, custom querysets in Django, ask the LLM to generate Golang DAO, setters and getters... and test the query against the Django generated queries for completeness.

    Atlas, sqlx, sqlc and all other ORM like things in golang cannot do migrations the way Django does.

  • There are so many footguns[1] in async python but it really has stolen the zeitgeist of modern python web. Synchronous django has a lot to commend it. If you deploy it reasonably well (workers, behind a caching reverse proxy etc) it's easy to operate in production even under duress.

    It's not going to be the right stack for long lived websocket connections or whatever but for a CRUD-ish or enterprise app, often very productive.

    [1] buffer bloat is too easy to sleep walk into

        queue = asyncio.Queue()  # oops
    
    unbounded concurrency

        await asyncio.gather(*(fetch(item) for item in items))  # look mum! no outbound sockets left or maybe even no file descriptors
    
    accidental blocking

        data = requests.get(url).json()  # oops! we're blocking the event loop
    
    and sure you can setup a separate task to measure the event loop latency and alert on that but this is the kinda thing you'll never find in a tutorial, you just need to experience this stuff and figure out a solution you like

    I can go on and on about async python (cancellation bugs, leaking a task - that has a cataclysmic failure mode where if you forget to hold a reference to your asyncio.create_task() then it's all weak refs in that machinery so your task can get garbage collected before it ran or completed in production! super tricky forensics. Then there's all the obvious stuff like race conditions, new ways of creating deadlocks, blah blah i really can go on for days.