r/django 12m ago

DSF member of the month - Benjamin Balder Bach

Thumbnail djangoproject.com
Upvotes

r/django 2h ago

walbox: react to PostgreSQL changes from Python

3 Upvotes

I built this because I wanted to react to PostgreSQL changes from Python without polling, without triggers, and without pulling in a whole CDC platform.

It consumes PostgreSQL logical replication and exposes committed transactions as an async stream in Python.

What it does:

  • Keeps a durable checkpoint. If the process dies, it resumes from the last transaction it actually finished, not the last one it started.
  • Bounded delivery queue, so a slow handler doesn't let memory grow without limit.
  • Reconnects automatically after the connection drops.
  • One dependency: psycopg3.

The transactional outbox is one use case, but it works with any published table.

GitHub: https://github.com/mochams/walbox

Curious to hear where this wouldn't fit your setup, or what's missing if you've solved this problem a different way.


r/django 6h ago

Djangonaut Space - Session 7 Accepting Applications

Thumbnail djangoproject.com
6 Upvotes

r/django 10h ago

Help me for this

Thumbnail gallery
0 Upvotes

Hi guys

I'm learning web dev.

When I watch the course, he logout to test the user login and logout template

But I get this error, I searched but can't find the Solution, and can't understand the error

If anyone know the solution, shere it with me


r/django 12h ago

Cursor, Codex, Gemini or Claude Code?

Thumbnail
0 Upvotes

r/django 12h ago

Cursor, Codex, Gemini or Claude Code?

0 Upvotes

What AI tools are the models or tools are you using to help you with your Django applications (writing code/debugging)? Are there models that have a marked strength in python/django? Are there models that could be good in js frameworks or laravel, but relatively suck at Django?


r/django 12h ago

I hate dealing with Frontend so I created GridViewSpec for dashboard apps.

0 Upvotes

Hi Reddit,

I want to introduce a small framework I've been building and using in my own projects for the past few months.

It's called GridViewSpec. The name is not particularly exciting — it's basically a grid/dashboard view described by a spec :) The name stuck, so here we are.

I originally built it because I got tired of implementing the same details every time I needed a table or dashboard:

  • global search
  • per-column search
  • filters that behave consistently
  • saved searches / views
  • PDF and XLSX exports with templates
  • reusable table settings and configurations

The basic idea is to describe the page using a typed Python spec and let GridViewSpec handle the rendering and UI behavior consistently.

For tables, it currently has two backends:

  • AG Grid for large datasets and server-side/lazy loading
  • Simple Table for smaller datasets with regular pagination

Over time I also added charts, tabs, KPI/info boxes and other blocks, so it became useful for building complete dashboard-style pages rather than just tables.

There is also a built-in MCP server, so coding agents can inspect the available components, validate specs and get guidance on how to build pages with it.

And if you're building a more agentic application, the same spec can be used as an interface between AI and the UI: an agent can analyze collected data and generate an appropriate dashboard, table or chart configuration on the fly, without generating frontend code directly.

I made a demo site where you can play with it:

https://gridviewspec.alpi.net.ua

GitHub:

https://github.com/alpiua/grid-view-spec

Would be interested in feedback, especially from people who build a lot of data-heavy internal tools, dashboards, or agent-driven applications.


r/django 14h ago

At what point do you actually reach for Celery instead of just doing the work synchronously?

9 Upvotes

Curious where people actually draw the line. Celery gets recommended pretty reflexively any time "background task" comes up, but it's also a real piece of infrastructure — a broker, worker processes, monitoring, retry/failure handling you now own. For something like sending a single email or a quick API call, django-q or even a simple threading.Thread can cover it without adding a new moving part to the deployment. For something genuinely long-running or that needs proper retry semantics, scheduled periodic tasks, or distributed workers across machines, Celery clearly earns its place. Where's the actual line for you? Is it request duration, is it "do I need retries," is it team size (nobody wants to debug a Celery worker at 2am solo), or something else entirely? Also curious if anyone's regretted adding Celery early and had to live with the operational overhead for a workload that didn't really need it.


r/django 20h ago

I got tired of rebuilding Django auth for every SaaS, so I built it once properly and open-sourced it

0 Upvotes

Every SaaS project I start ends up needing the same authentication layer. Email verification codes, password resets, JWT with the refresh token in an httpOnly cookie, Google OAuth, throttles. And every time I re-derive the same decisions, usually badly the first time.

I want to start building more SaaS's, and since Django is the backend framework I genuinely love working with the most, I wanted something that lets me move fast without rebuilding authentication from scratch every time.

So I wrote it once properly and open-sourced it. DRF, 18 endpoints, 407 tests so far, most of them covering failure paths: expired codes, replayed codes, forged OAuth state, suspension mid-session. Celery handles email asynchronously so a signup never waits on the email provider etc.

MIT. Billing (stripe) and admin app next.

Repo: https://github.com/fulanii/rebar-backend

Tell me where the security reasoning is wrong.


r/django 1d ago

Mobile client -> Django [django-tenants] client minted ids batch partial failure

0 Upvotes

Mobile clients create records offline that reference each other, a job gets a ticket, the ticket gets line items, the line items reference a piece of inventory/equipment (chain of custody logic happens) that was also just created on the device. All IDs are minted client-side (leaning UUIDv7 for index locality) so the graph is internally consistent before it's ever seen a server.

Ive been thinking about where things could go wrong. i.e. partial failure on one that has children? The device pushes a batch of 40 mutations. Number 12 is rejected [stale reference, validation, a conflict, whatever]. Numbers 13-40 include children of 12.

Ideas:

  • all-or-nothing per batch (simple, but one bad row blocks a whole shift)
  • dependency-ordered application, skip the subtree under any rejection, surface it to the user
  • accept everything and let the server hold orphans in a quarantine state

What do people actually do? Does anyone regret minting IDs on the client, or is that just table stakes for offlinefirst now?


r/django 1d ago

Article Refactoring a monolithic Telegram bot, fixing N+1 queries, and building a self-hosted link shortener for my Django CRM

7 Upvotes

Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7 - production CRM for truck service center, Django + DRF.

This one cover v2.10 and v2.11. Splitting 900-line bot file into modules, killing an N+1 that was hiding in plain sight, adding transmission-aware maintenance, and tiny shortlinks app that uses F() expressions the way I should have been use them all along.

The 900-line bot file

Do you remember my Telegram bot from Part 1? It started as one runbot.py file. By v2.9 it was 900+ lines - handlers for every role, inline keyboards, photo uploads, mileage reporting, Nova Poshta tracking, maintenance checks. Every time I needed to fix something, I had to scroll through the entire file looking for the right handler.

I split it into modules:

bot/handlers/
    __init__.py
    admin.py       # admin-only handlers
    callbacks.py   # inline keyboard callbacks  
    main.py        # start, contact, my_cars
    photos.py      # repair photo uploads
    utils.py       # shared helpers

Each module imports from shared bot/queries.py that wraps all the Django ORM calls (through sync_to_async). The main.py handler is now 80 lines instead of 900. I can find any handler in second.

The tricky part was clear_awaiting_states - a utility that resets stale conversation states before setting new ones. Without it, if user started one flow (like mileage reporting) and then tapped a different button, the bot would get confused about what it was waiting for. Every handler calls it at the start:

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user     = update.message.from_user
    bot_user = await get_or_create_bot_user(user)
    is_linked, is_admin, _ = await check_if_user_is_linked(user.id)

Nothing groundbreaking architecturally, but it turn "I dread touching the bot code" into "I can add new handler in 10 minutes." Sometimes the best refactoring is just splitting a big file.

The N+1 hiding in update_total_cost

Every service order has a total cost that gets recalculated when works or parts change. The original version looped through all ServiceWork and UsedPart records one by one, summing prices in Python. Classic N+1 - for an order with 15 work items and 30 parts, that was 45+ queries.

The fix was replacing the loop with three aggregated queries:

def update_total_cost(self):
    works_cost = self.works.aggregate(
        total=Sum(
            ExpressionWrapper(
                F('price_at_moment') * F('hours_spent'),
                output_field=DecimalField()
            )
        )
    )['total'] or 0

    work_parts_cost = UsedPart.objects.filter(
        service_work__service_order=self
    ).aggregate(
        total=Sum(F('quantity') * F('unit_price'))
    )['total'] or 0

    direct_parts_cost = UsedPart.objects.filter(
        service_order=self, service_work__isnull=True
    ).aggregate(
        total=Sum(F('quantity') * F('unit_price'))
    )['total'] or 0

    self.total_cost = works_cost + work_parts_cost + direct_parts_cost
    self.save(update_fields=['total_cost'])

Three queries instead of 45+. And notice the F() expressions - F('price_at_moment') * F('hours_spent') does the multiplication at SQL level, not in Python. For those who have been following the series: yes, this is the proper use of F() that I have been promising since Part 5. Took me a few versions but we got here.

The direct_parts_cost vs work_parts_cost split is because parts can be attached either to specific work item (replacing filter during oil change) or directly to order (miscellaneous parts). Two different querysets, same aggregation pattern.

Transmission-aware maintenance

Trucks have different transmission types - manual, automatic, robotic. Each type needs different maintenance: automatic transmissions need ATF fluid changes, manual ones don't. Before this, maintenance kits treated all trucks the same.

TRANSMISSION_CHOICES = [
    ('manual', 'Manual'),
    ('automatic', 'Automatic'),
    ('robotic', 'Robotic'),
]

The maintenance countdown logic now checks which interval fields are actually filled. If truck has automatic transmission and the auto_gearbox_interval is set, it shows up in maintenance schedule. If it is manual and that field is empty, it is skipped. No hardcoded if-else chains - just checking whether the field has a value.

Also added engine hours tracking for heavy-duty models. Some trucks (like construction site vehicles) track maintenance by engine hours instead of mileage. New TrackingMode choice on the maintenance intervals:

class TrackingMode(models.TextChoices):
    MILEAGE = 'mileage', 'By mileage'
    ENGINE_HOURS = 'engine_hours', 'By engine hours'

Order form now has an optional engine_hours field. When tracking mode is set to engine hours, maintenance countdown uses that instead of mileage. Small change in the model, but it means the system can handle trucks that barely drive but run engines all day.

Maintenance templates (apply_to_truck)

Before this, every time a new truck was added, someone had to manually create a maintenance kit - fill in oil types, filter brands, intervals, everything from scratch. For a service center that adds 3-4 trucks a month, this gets old fast.

I built template kits tied to base model + euro standard + transmission type. When new truck comes in, you pick the matching template and run apply_to_truck:

Template says "This model with EURO5 and automatic transmission needs 30L of 10W-40 every 20,000 km, ATF change every 60,000 km, this specific oil filter, this air filter..." One click and the truck has its full maintenance schedule. The mechanic can still tweak individual values afterward - template is a starting point, not a straitjacket.

Self-hosted shortlinks with F()

The service center has QR codes on business cards, stickers in the waiting room, printed materials. Each QR needs to point somewhere - Google Maps for reviews, Telegram bot link, website. The problem: if the Telegram bot link changes, every printed QR code is dead.

Solution - a tiny shortlinks app:

class ShortLink(models.Model):
    slug = models.SlugField(unique=True, max_length=64)
    target_url = models.URLField(max_length=2048)
    label = models.CharField(max_length=200, blank=True)
    is_active = models.BooleanField(default=True)
    hits = models.PositiveIntegerField(default=0, editable=False)

QR codes point to yourdomain.com/go/bot, yourdomain.com/go/maps, etc. The redirect view is 10 lines:

class ShortLinkRedirectView(View):
    def get(self, request, slug):
        try:
            link = ShortLink.objects.only(
                'id', 'target_url', 'is_active'
            ).get(slug=slug)
        except ShortLink.DoesNotExist:
            raise Http404('Short link not found')
        if not link.is_active:
            raise Http404('Short link is disabled')
        ShortLink.objects.filter(pk=link.pk).update(
            hits=F('hits') + 1
        )
        return HttpResponseRedirect(link.target_url)

Notice hits=F('hits') + 1 - the counter increment happens at the database level, no race condition even under concurrent requests. This is the same F() pattern that was missing from my stock deduction code back in Part 5. Funny how simplest feature in a project has the cleanest implementation.

Now when the Telegram bot link changes, someone just updates the target URL in admin. Every QR code keeps working. And the hit counter tells you which QR codes actually get scanned - the waiting room sticker gets 10x more hits than the business card one, which is useful to know.

Smaller things

Continue-order action. Sometimes a truck comes back with the same problem a week later. Instead of creating a new order and losing context, there is now a continue-order endpoint that reopens a DONE/CLOSED order back to IN_PROGRESS. Simple status transition, but it keeps all historical work items and notes in one place.

Bot: unknown plate tracking. When someone searches for a license plate in the Telegram bot and it is not found, the search gets logged. After a month I had a list of plates that clients were asking about but were not in the system - basically a lead generation tool I did not plan for.

Stale state cleanup in bot. Added clear_awaiting_states() that runs before every handler. Clears any leftover input-awaiting flags from interrupted flows. Without this, the bot would occasionally respond to a text message as if it was still waiting for a mileage number from 3 hours ago.

What I learned

Split big files before they become scary. 900 lines is not a lot of code, but it is enough to make you avoid the file. The refactoring took maybe 2 hours and immediately made the bot maintainable again. If you dread opening file, that is the signal.

F() expressions are not just for updates. Using them in aggregate() with ExpressionWrapper lets the database do math that would otherwise be a Python loop. The performance difference on 50+ items per order was noticeable.

The simplest features can have the cleanest code. ShortLink is maybe 30 lines of model + view. No signals, no Celery, no complex business logic. Just a little redirect with a counter. And yet it uses F() correctly while my inventory system (which is 10x more complex) did not for months.

What is next

Still have i18n (UK/EN), the full React frontend with PWA, and backup/restore API to cover. If there is interest I will keep going.

Also I take on freelance Django projects when something interesting comes along. If you are building something in this space, feel free to DM, I will help you with a great pleasure.

Previous posts: Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7 GitHub (demo repo): github.com/VNmagistr/truckmaster_demo - branches demo/v2.10 and demo/v2.11

To be continued... (I hope, as usually)


r/django 2d ago

CI pipeline

2 Upvotes

I started learning CI/CD using github actions after containerising my application and I have created CI pipeline for django app that runs test, builds and pushes image to github container registry.
I am sharing my yaml file for CI pipeline. Please do share your thoughts and where can i improve.

name: Test Pipeline 
on: 
  push:
jobs:
  test-backend:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:14
        ports:
          - 5432:5432
        env: 
          POSTGRES_USER: test_user
          POSTGRES_DB: erp
          POSTGRES_PASSWORD: 123456

    steps:
      - name: Checkout repo
        uses: actions/checkout@v4

      - name: setup python
        uses: actions/setup-python@v5
        with: 
          python-version: "3.13.5"

      - name: install dependencies
        run: pip install -r Backend/requirement.txt

      - name: run tests
        env: 
          DATABASE_URL: postgresql://test_user:123456@localhost:5432/erp
          DEBUG: 'True'
          ALLOWED_HOST: '*'
        run: |
          cd Backend 
          python manage.py test

  build-and-push-image:
    needs: test-backend
    permissions:
      contents: read
      packages: write
    runs-on: ubuntu-latest
    steps:
      - name: login to ghcr
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}


      - name: checkout repo
        uses: actions/checkout@v4


      - name: build image
        run: docker build -t ghcr.io/namespace/erp:${{ github.sha }} ./Backend


      - name: push image
        run: docker push ghcr.io/namespace/erp:${{ github.sha }}

r/django 2d ago

Tutorial Django 6.1's tip DB_CASCADE Let the Database Do the Deleting, Not python

Post image
89 Upvotes

Imagine deleting a folder with 10,000 files. Would you rather Python open each file one by one, check it, then delete it — or would you rather tell the operating system "delete everything inside" and let it happen instantly at the filesystem level? That's exactly the upgrade Django 6.1 brings to on_delete. The old CASCADE loads every related row into Python memory just to delete it row-by-row. The new DB_CASCADE tells the database itself to cascade the delete natively — no Python loop, no memory spike, just one SQL statement.

The trade-off: since Django never touches the related rows in Python, your pre_delete/post_delete signal handlers won't run. If your app depends on those signals (e.g. cleaning up files, sending notifications), stick with the classic CASCADE.


r/django 2d ago

Hi guys , I have created a django workflow package

0 Upvotes

So , I have developed a django workflow kit package recently. Why I created this was cause I wanted everything in one like a kit or toolbox which contains every tool needed. This package supports

- Workflow versioning

- Auditing

- Notification and triggers

- Can add attachments

- Supports parallel worfkows

- In built workflow Dashboard

- Few REST APIs exposing tasks etc

- Supports User level and group level permissions

- Includes some stats like turnover , delay point , sla etc

- Real-time states can be defined just like JIRA worflow style

I may have missed few things here and there.

Check this out. If you have any concern , feedback or constructive criticism please let me know.

Pypi :

django-workflow-kit

This is relatively new project. Was started 3 weeks ago. I have deleted my previous post as it was AI generated content and few user raised concerns as they should.

Thanks


r/django 3d ago

Article Coding a database proxy for fun

Thumbnail packagemain.tech
0 Upvotes

r/django 3d ago

Best Django roadmap from basics to advanced?

17 Upvotes

Hey everyone, I'm a 3rd year Computer Engineering student. I already know C/C++ and Python well, and I want to learn Django from scratch to an advanced/job-ready level.

Could you share a step-by-step roadmap or resources (courses, docs, projects) that go:

  • Basics (setup, models, views, templates, ORM)
  • Intermediate (auth, forms, REST APIs with DRF, admin customization)
  • Advanced (deployment, testing, caching, Celery, scaling, security)

Also, any project ideas at each stage would be super helpful. Thanks in advance!


r/django 3d ago

ClaraX: Accelerating Django and DRF Serialization with Rust

12 Upvotes

Django REST Framework is excellent for building APIs quickly, but in larger applications serialization and validation can become a noticeable CPU bottleneck.

I built ClaraX to explore a simple idea: keep the Django application exactly where it is, but move selected expensive serialization and validation paths into Rust.

ClaraX does not try to replace Django or DRF. Existing models, views, URLs and serializers can remain in place.

For example, an existing DRF serializer can opt in with a mixin:

from django_clarax.serializers import RustSerializerMixin

class ApplicationSerializer(RustSerializerMixin, serializers.ModelSerializer):

class Meta:

model = Application

fields = "__all__"

The goal is to make Rust an implementation detail rather than requiring a team to rewrite its application or learn a new framework.

ClaraX also includes a diagnostic command:

python manage.py clarax_doctor

It inspects serializers and helps identify which ones may benefit from acceleration.

That distinction matters because Rust is not automatically the answer to every performance problem. If an endpoint is database-bound, query optimization should come first. Small responses may not justify crossing the Python/Rust boundary, and serializers dominated by Python-computed fields may still spend most of their time in Python.

For workloads where serialization or validation really is the bottleneck, ClaraX provides a Rust fast path while keeping the normal Django development experience.

Installation is straightforward:

pip install clarax-django

For non-Django Python projects, the lower-level package is also available:

pip install clarax-core

Pre-built wheels are provided so normal Python users do not need to install Rust or Cargo.

The current release is ClaraX v1.0.1.

I would especially appreciate feedback from Django and Python developers about the API boundary: where this approach feels useful, where it creates unnecessary complexity, and which real-world workloads would be most valuable to test next.

Project:

https://github.com/abdulwahed-sweden/clarax


r/django 4d ago

Django Developers Survey 2026 results

Thumbnail djangoproject.com
35 Upvotes

r/django 4d ago

Django-guardian 3.4.0 is Released!

62 Upvotes

Hi everyone! I’m a maintainer of django-guardian, and I'm excited to announce that version 3.4.0 is officially out!

For those who aren't familiar, django-guardian provides per-object permissions for Django, extending the default authorization backend to allow assigning permissions to specific user or group instances.

🚀 What's New in 3.4.0

  • Django & Python Compatibility Updates:
    • Added official support for Django 6.0 and Django 5.2 (LTS).
    • Added support for Python 3.14.
    • Dropped support for end-of-life Python 3.9 (Minimum supported Python version is now 3.10+).
  • Performance & Query Optimization:
    • Optimized prefetch_perms() to eliminate redundant database queries when prefetching object permissions.
    • Reduced DB overhead during assign_perm operations when working with generic permissions.
    • Refactored shortcut workflows for more efficient internal execution paths.
  • Shortcut & Bulk Operations:
    • Ensured shortcuts.assign_perm is idempotent when executing bulk assignments.
    • Standardized bulk permission removal behavior to align with bulk assignment workflows.
    • Added support for handling non-standard primary keys (pks) on target models.
  • Ecosystem & Integration:
    • Added out-of-the-box support for django-unfold via a dedicated contrib package.
    • Updated contribution guidelines with modernized uv workflows, test/lint tooling, and developer guidelines.

📦 Installation & Upgrade

To upgrade using pip:

Bash

pip install --upgrade django-guardian

Or using uv:

Bash

uv add django-guardian@latest

🔗 Links & Resources

Huge thanks to all the contributors, issue reporters, and testers who helped shape this release!

Feel free to open an issue or start a discussion on GitHub if you encounter any bugs or have feedback!


r/django 4d ago

Complete beginner in Django, how do I learn it?

0 Upvotes

Heyo, I started my internship as a software developer with previous knowledge in Java, but now forced to do Python. I do like Python better, but all I have done so far is console related. Like the typical python slotmachine you code from an exercise you find online. My Supervisor now wants me to get into Django but I literally have no idea how to start whatsoever. Most of the tutorials I have (tried) to watch just sound like gibberish to me. It also kinda turns me off when I code in VScode but someone uses something different or this dreaded mac terminal.

I'm willing to learn but with django it feels like I'm in such deep waters that I cannot swim myself.

If anyone could give me advice on how to learn django with some basic understanding of python would literally save me.


r/django 4d ago

Models/ORM django-fk-optimize

4 Upvotes

As you know, django has two database functions for N+1 resolutions,namely select_related and prefetch_related . These functions are useful when you model has fk’s that can cause N+1 issues, and tools such as django-auto-prefetch help you to automatically apply them to your queries to provide you the most optimal query.

However sometimes you need to actually know the shape of the data you are working with to decide which one of these functions you should use.
For example if you have many2one relationships, prefetch may win over select related if the relationship looks like owners of posts, where one owner porbably has many posts as opposed to a many2one relationship that had near identical counts.
So sometimes the function you need to use depends on your data shape, and thats why I am developing this tool called django-fk-optimize that is a management command that lets devs test their models and tables in prod enviroments to decide which function is the best for which field of a model.

Here is the link to the repo: github

I would love to hear every bit of criticism , thanks in advance!


r/django 6d ago

Channels ChanX/Channels now support WebSocket multiplexing (again)

3 Upvotes

Hi all.

One feature that was removed from Django Channels around the v2 era was WebSocket multiplexing. There have been issues and PRs discussing bringing it back, but it hasn’t been resolved for quite a while.

So, ChanX now officially supports WebSocket multiplexing through a feature called Topics.

The basic idea is to define a topic with its own WebSocket handlers and channel event handlers:

Then, you can easily mount multiple topics onto an existing WebSocket consumer:

This allows multiple independent WebSocket features/topics to share a single WebSocket connection, instead of requiring a separate connection for each feature.

The design is inspired by Phoenix Channels topics. The goal is to make it easier to compose and reuse WebSocket functionality while potentially reducing the number of connections your application needs.

If this is your first time hearing about ChanX, it’s a batteries-included WebSocket toolkit for Django Channels, FastAPI, and other ASGI applications. It provides things like:

  • Type-safe WebSocket message handling
  • Automatic message routing and validation
  • AsyncAPI schema generation
  • Authentication
  • Channel-layer integration
  • Testing utilities

If you’re working with Django Channels, or you’re starting a new WebSocket application with FastAPI, I’d love to hear what you think.

Feedback, ideas, issues, and PRs are very welcome!

Links:


r/django 6d ago

django-binary-builder: package a Django project as a Windows Setup.exe with one command

38 Upvotes

Hi everyone,

I’ve been working on django-binary-builder, a Python package that turns a Django project into an installable Windows desktop application.

The project is available here:

GitHub: https://github.com/swarfte/django-binary-builder

The basic workflow is:

pip install django-binary-builder

Add the app to INSTALLED_APPS:

INSTALLED_APPS = [ 
    # Your apps... 
    "django_binary_builder", 
] 

Then build the Windows application:

python manage.py binary windows

The result is a standard per-user Windows installer:

release/windows/<executable-name>-<version>-Setup.exe

Here is a real build from one of my Django projects:

The generated application includes:

  • A portable CPython runtime
  • The Django project and its pip dependencies
  • Waitress serving Django on a loopback port
  • A native desktop window using pywebview
  • A default-browser fallback if pywebview is unavailable
  • Automatic migrations at startup
  • Static and media file handling
  • Per-user SQLite storage
  • A desktop shortcut and Start menu entry
  • A Windows installer built with Inno Setup

The Django project itself is not frozen. The package copies a complete Python runtime and installs the project’s dependencies into it. This deliberately produces a larger installer, but it improves compatibility with ordinary Python packages, including many packages with native extensions.

A minimal optional configuration looks like this:

DJANGO_BINARY_BUILDER = { 
    "NAME": "Example Project", 
    "VERSION": "0.1.1", 
    "PUBLISHER": "Example Company", 
    "EXECUTABLE_NAME": "example-project", 
    "ICON": BASE_DIR / "assets" / "icon.ico", 
} 

Current limitations:

  • Windows 10 and 11 only
  • WSGI only
  • No Django Channels or WebSockets
  • No Celery worker or beat
  • No automatic updater
  • No code signing
  • Large bundle size because the complete Python runtime is included

I’d especially appreciate feedback on:

  1. The installation and build experience
  2. Projects or dependencies that fail to package
  3. Runtime behavior on different Windows systems
  4. Features that would make this useful for real deployments

Thanks for taking a look.


r/django 7d ago

Article Moving from signals to a service layer, adding RBAC, and importing 500 clients from Excel into my Django CRM

18 Upvotes

Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 - production CRM for truck-service center, Django + DRF.

This covers v2.7 and v2.9. The big theme here is cleaning up architecture decisions I made early on that started to hurt. Signals for stock management, no proper role-based access, manual client onboarding. All fixed now.

Why I killed my signals (StockService refactor)

In earlier versions, stock deductions happened through Django signals. When a UsedPart was created, post_save signal would fire and reduce warehouse stock. When deleted - post_delete would restore it. Sounds clean in theory. But..

In practice - it was nightmare. The signals were invisible - new developer (me, three months later) would look at view code and have no idea that saving a UsedPart triggers stock changes. Debugging was painful because the traceback starts in the signal handler, not where you actually called .save(). And testing was awful - every test that touches UsedPart was also triggering stock logic, whether I want it or not.

So I replaced everything with a StockService class:

class StockService:
    u/staticmethod
    def deduct(used_part):
        warehouse = _get_warehouse(used_part)
        if not warehouse:
            return
        stock_item, _ = StockItem.objects.get_or_create(
            warehouse=warehouse,
            product=used_part.part,
            defaults={'quantity': 0},
        )
        stock_item.quantity -= used_part.quantity
        stock_item.save()

        service_order = _get_service_order(used_part)
        StockMovement.objects.create(
            movement_type='out',
            product=used_part.part,
            quantity=used_part.quantity,
            warehouse_from=warehouse,
            service_order=service_order,
        )

    u/staticmethod
    def restore(used_part):
        # ... opposite of deduct ...

    u/staticmethod
    def adjust(used_part, old_quantity):
        delta = old_quantity - used_part.quantity
        if delta == 0:
            return
        # ... adjust stock by delta ...

Three methods: deduct, restore, adjust. Called explicitly from views and serializers. No magic, no hidden side effects. When I read view code now, I can see exactly where stock changes happen because there is line that says StockService.deduct(used_part).

The adjust method was the thing signals could never handle cleanly. When mechanic changes the quantity on an existing UsedPart (used 3 filters instead of 2), you need to calculate the delta and adjust. With signals you would need to stash old value in pre_save, compare in post_save... same mess I had with appointment status tracking in Part 3. Service layer just takes old_quantity as parameter.

Importing 500 clients from an Excel spreadsheet

The service center had been running for years before TruckMaster. All their client data lived in one massive Excel file - names, phone numbers, VIN's, license plates, truck models. About 500 rows. Entering them by hand through the admin panel was not an option.

I wrote a management command:

python manage.py import_clients_xlsx --file /path/to/clients.xlsx
python manage.py import_clients_xlsx --file /path/to/clients.xlsx --dry-run

The --dry-run flag was the most important feature. It run the whole import logic but does not write to the database. Just prints what would happen: "would create client X", "would update truck Y", "phone +380... already taken by client Z". The Owner ran dry-run first, fix duplicates in the Excel, then ran the real import. Zero surprises.

Tricky part was deduplication. The Excel had inconsistent naming - same client could be "Тра***", "ТРА***". I normalized names with ' '.join(str(s).strip().split()).lower() and matched on that. Not bulletproof but caught 90% of case.

Another thing - ownership tracking during import. If a license plate already exists in system under a different client, the import creates an OwnershipHistory record (same as the Truck.save() logic from Part 1) before reassigning. So historical service records stay with old owner.

Redis debounce for ALPR

Remember the ALPR system from Part 3? Security camera sends a plate recognition event to Django every time it sees a plate. Problem: camera sometimes sends the same plate 10 times in 30 seconds (truck passing slowly, multiple frames). Without debounce, staff Telegram chat would get spammed with duplicate "VEHICLE ARRIVED" notifications.

The fix was simple - Redis cache with a 5-minute TTL:

ALPR_DEBOUNCE_TTL = 300

debounce_key = f'alpr:debounce:{plate}'
if cache.get(debounce_key):
    return Response({'status': 'debounced', 'license_plate': plate})
cache.set(debounce_key, True, ALPR_DEBOUNCE_TTL)

First time a plate is seen - process it normally and set the cache key. Next time the same plate shows up within 5 minutes - return early with debounced status. Redis TTL handles expiry automatically, no cleanup need.

I should have built this from day one. The camera was sending around 50 duplicate events per day and I only noticed because the Telegram notification log was full of identical messages one second apart.

Role-based access control

Up to this point, authentication was JWT-based (Part 1) but authorization was basically "logged in = can do everything." The owner, the mechanic, and the storekeeper all had the same API access. Not ideal.

I added role-based permission classes:

class IsAdminRole(BasePermission):
    def has_permission(self, request, view):
        return (request.user.is_superuser
                or _role(request.user) == 'admin')

class CanManageStock(BasePermission):
    def has_permission(self, request, view):
        return (request.user.is_superuser
                or _role(request.user) in (
                    'admin', 'manager', 'storekeeper'
                ))

class CanAccessInvoices(BasePermission):
    def has_permission(self, request, view):
        return (request.user.is_superuser
                or _role(request.user) in (
                    'admin', 'manager', 'accountant'
                ))

Roles are stored on UserProfile and checked via a simple _role() helper. Nothing fancy - no django-guardian, no object-level permissions. Just "this role can access this viewset." For team of 5 people this is more than enough.

The important thing was that I could add these to existing viewsets without changing any view logic - just add permission_classes = [IsAuthenticated, CanManageStock] and it works. DRF's permission system is really well designed for this.

Also reduced JWT access token lifetime from 12 hours to 15 minutes. 12 hours was lazy and insecure. If someone's token leaks, 15 minutes limits damage.

Smaller things

Bulk repair photo upload. Before, mechanics uploaded photos one by one. Now there is a bulk_upload endpoint that accepts multiple files, saves them all, and sends one Telegram notification instead of ten. Also added MAX_REPAIR_PHOTOS_PER_ORDER constant - 20 photos per order, because without limit someone will upload their entire camera roll.

Barcode lookup. Added a barcode field to Product and a query parameter on the inventory API. Scan a barcode with a phone, hit the API, get the product. Took maybe 30 minutes to implement but the storekeeper acts like I gave him a superpower.

Bot maintenance history. Truck owners can now check maintenance history for their vehicles directly in Telegram. Shows last 3 service orders per truck with dates and work descriptions. Moved it under a "My vehicles" submenu to keep the bot keyboard clean.

What I learned

Signals are for cross-cutting concerns, not business logic. Audit logging, cache invalidation, sending notifications - signals are great for these. Stock management, payment processing, status transitions - these belong in explicit service calls. The moment you catch yourself writing pre_save + post_save combos to track field changes, you have outgrown signals.

Always add --dry-run to import commands. The cost of implementing it is maybe 20 minutes. The cost of a botched import that creates 200 duplicate clients is a weekend of cleanup and an angry owner.

Redis debounce is a pattern you will use everywhere. ALPR events, webhook handlers, rate limiting, notification dedup - same pattern, different keys and TTLs. Once you build it for one thing, you start seeing opportunities everywhere.

What is next

More versions to cover - i18n (UK/EN), maintenance templates, QR/shortlinks, and eventually the full React frontend with PWA. If there is interest I will keep going.

Also I take on freelance Django projects when something interesting comes along. If you are building something in this space, feel free to dm, I'll help you with a great pleasure

Previous posts: Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 GitHub (demo repo): github.com/VNmagistr/truckmaster_demo — branches demo/v2.7 and demo/v2.9

To be continued... (I hope, as usually)


r/django 7d ago

PyCharm & Django Fall Fundraiser

Thumbnail djangoproject.com
24 Upvotes