In Django, how would you create a Profile row automatically whenever a new user signs up, using a post_save receiver?
answer
- signal fired at the end of save()
- filter by the user model as sender
- the created flag
- skip fixture loads via raw
basics
~10 sConnect a receiver to post_save with sender=settings.AUTH_USER_MODEL and create the Profile only when the created argument is True, so later saves of the same user do not insert a second profile.
solid answer
~30 sI write a function decorated with `@receiver(post_save, sender=settings.AUTH_USER_MODEL)` that takes `sender`, `instance`, `created` and `**kwargs`. `post_save` fires at the end of every `Model.save()`, so I guard with `if created:` and then call `Profile.objects.create(user=instance)`. I also skip when `raw` is True, because `loaddata` sends the signal with `raw=True` and the fixture usually carries its own profiles. The receiver lives in the app's `signals.py` and is imported from `AppConfig.ready()`, otherwise it is never connected. Because `create_user()` calls `save()`, every sign-up path that goes through the model gets a profile, but a bulk insert does not.
code
python · 12 linesfrom django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Profile
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile(sender, instance, created, raw, **kwargs):
if raw or not created:
return
Profile.objects.create(user=instance)go deeper
Recall the receiver signature, sender, instance, created and **kwargs, and why the created check prevents a second profile on later saves.
Explain the lazy string sender, the raw flag during loaddata, and why the receiver only exists once ready() imports its module.
Name the paths that create users without save(), such as bulk_create and raw SQL, and how you detect and backfill users that ended up with no profile.
Weigh the signal against one explicit call in the sign-up service: the signal covers users created from the admin and third-party packages, the explicit call is easier to trace and test.
## What the post_save signal is Django's **signal dispatcher** lets code register a function, called a **receiver**, that Django calls when something happens. The model system defines several signals in `django.db.models.signals`; `post_save` is sent at the very end of `Model.save()`, after the `INSERT` or `UPDATE` query has run. The receiver is called **synchronously**, in the same thread as the code that called `save()`, before `save()` returns. Every receiver is called with keyword arguments: - `sender` — the model class being saved. - `instance` — the actual object that was saved. - `created` — `True` if a new row was inserted, `False` if an existing row was updated. - `raw` — `True` when the instance is saved exactly as presented, which is what `loaddata` does with fixtures. - `using` — the database alias. - `update_fields` — the set passed to `save(update_fields=...)`, or `None`. A receiver must accept `**kwargs`; with `DEBUG` on, Django raises `ValueError` at connect time if it does not. ## The receiver for a sign-up profile ```python # accounts/signals.py from django.conf import settings from django.db.models.signals import post_save from django.dispatch import receiver from .models import Profile @receiver(post_save, sender=settings.AUTH_USER_MODEL) def create_profile(sender, instance, created, raw, **kwargs): if raw or not created: return Profile.objects.create(user=instance) ``` Three details carry the answer: 1. **`sender=settings.AUTH_USER_MODEL`** — model signals accept the sender as an `"app_label.ModelName"` string and resolve it lazily, which avoids importing a custom user model at module load and keeps the receiver correct for a swapped user model. 2. **`if created`** — `post_save` fires on *every* save, including a password change or a `last_login` update. Without the guard the second save tries to insert a second profile and fails on the one-to-one uniqueness constraint. 3. **`raw`** — when fixtures are loaded, the user and the profile both come from the fixture; creating another profile in the receiver collides with the fixture's own row. ## Connecting it A decorated function is only connected when its module is imported. The documented place is the app configuration: ```python # accounts/apps.py from django.apps import AppConfig class AccountsConfig(AppConfig): name = "accounts" def ready(self): from . import signals # noqa: F401 connects the receivers ``` If nothing imports `signals.py`, the code looks right and simply never runs, which is the most common "my signal does not fire" bug. ## Which sign-up paths trigger it | Path | Receiver runs? | |---|---| | `User.objects.create_user(...)` | Yes — it calls `save()` | | A registration `ModelForm.save()` | Yes | | The admin "Add user" page | Yes | | `createsuperuser` | Yes, it goes through the manager and `save()` | | `User.objects.bulk_create([...])` | **No** — no `pre_save`/`post_save` | | `loaddata` of a fixture | Yes, with `raw=True` (skipped by the guard) | ## When a signal is the wrong tool for this The receiver is a classic interview answer, but the Django docs themselves warn that signals make code harder to follow and suggest a manager method or an overridden method first. If sign-up happens in exactly one service function in your own code, calling `Profile.objects.create(user=user)` right there is easier to read and to test. A signal earns its place when users are created from places you do not control — the admin, a third-party auth package, a management command — and every one of them should end up with a profile. A defensive alternative some teams add is a lazy `get_or_create` of the profile on first access, which also repairs users created by a bulk path. ## Traps to mention - Accessing `instance.profile` inside the same receiver before creating it raises `RelatedObjectDoesNotExist`. - The receiver runs inside the caller's transaction if one is open; an exception in it propagates out of `save()`. - Calling `instance.save()` inside its own `post_save` receiver sends `post_save` again; without a guard that stops the second pass, the receiver recurses until Python gives up.
- Why pass sender=settings.AUTH_USER_MODEL rather than importing User?Model signals accept an `"app_label.ModelName"` string and resolve it lazily once the app registry is ready. That keeps the receiver working for a custom user model, avoids importing `django.contrib.auth.models.User` when the project swapped it, and sidesteps circular imports between the accounts app and the user model's app.
- Some users in production have no profile. How can that happen with this receiver in place?Anything that inserts users without `Model.save()` skips `post_save`: `bulk_create`, raw SQL, a data migration writing rows directly. A receiver that was not connected because `signals.py` was never imported has the same effect. Fix the data with a one-off backfill and make profile access tolerant, for example with `Profile.objects.get_or_create(user=user)`.
saying these in an interview costs you the question
- Creates the profile on every post_save without checking created
- Puts the receiver in signals.py but never imports it anywhere
- Believes bulk_create of users also triggers post_save
- Thinks post_save runs later in a background worker
- Imports django.contrib.auth.models.User even though the project uses a custom user model