Fields inherited from Django's `AbstractBaseUser` / `PermissionsMixin` / `AbstractUser` are missing

Open
#267 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
62/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
django, postgresql, python
Domain
backend, databases

Research direction

Start in cli/orm_lens/parser.py: _abstract_bases_fields() near line 833 skips AbstractBaseUser because by_name.get(tail, []) finds nothing, and _attach_inherited_fields() near line 874 fills inherited_fields from that. Add a table of Django's own abstract bases (AbstractBaseUser, PermissionsMixin, AbstractUser) used only when no workspace class has that name. Done when the repro's drift reports nothing, describe lists the inherited fields, and the new tests plus the golden snapshot update pass with pytest, ruff and mypy.

Written by the indexing model from the issue text.

Description

bug good first issue python

Source: taiga-back — taiga/users/models.py:117; pretix — base.User; label-studio — users.User; weblate — weblate/auth/models.py:942

What happens

A custom user model usually inherits its auth fields from Django:

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)

password and last_login come from AbstractBaseUser, is_superuser, groups and user_permissions from PermissionsMixin. orm-lens only merges fields from abstract bases it can read in the workspace, and those two live in Django itself, so the fields are dropped. The same goes for AbstractUser (username, first_name, email, is_staff, date_joined, ...).

On all four projects above drift reports "migrated but no longer declared: groups, is_superuser, last_login, password, user_permissions", and the M2M edges to auth.Group / auth.Permission are missing from er and cascade.

Repro

accounts/models.py

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models


class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)

    USERNAME_FIELD = "email"

accounts/migrations/0001_initial.py (what makemigrations writes for it)

from django.db import migrations, models


class Migration(migrations.Migration):
    initial = True
    dependencies = [("auth", "0012_alter_user_first_name_max_length")]
    operations = [
        migrations.CreateModel(
            name="User",
            fields=[
                ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
                ("password", models.CharField(max_length=128, verbose_name="password")),
                ("last_login", models.DateTimeField(blank=True, null=True, verbose_name="last login")),
                ("is_superuser", models.BooleanField(default=False)),
                ("email", models.EmailField(max_length=254, unique=True)),
                ("groups", models.ManyToManyField(blank=True, related_name="user_set", related_query_name="user", to="auth.group")),
                ("user_permissions", models.ManyToManyField(blank=True, related_name="user_set", related_query_name="user", to="auth.permission")),
            ],
        ),
    ]
$ orm-lens drift
 ~ accounts.user:
     migrated but no longer declared: groups, is_superuser, last_login, password, user_permissions

summary: 1 model(s) drifted, 0 blocking, 1 migration(s) replayed

$ orm-lens describe accounts.User
| Field | Type | Args |
| --- | --- | --- |
| `email` | EmailField | `unique=True` |

With class User(AbstractUser) plus one own field, describe likewise shows only that field.

Expected: no drift; describe lists the inherited fields too, and groups / user_permissions are M2M relations to auth.Group / auth.Permission.

Where

cli/orm_lens/parser.py

  • line 833, _abstract_bases_fields(): by_name.get(tail, []) finds nothing for AbstractBaseUser, so the base is skipped;
  • line 874, _attach_inherited_fields(): fills inherited_fields from that.
Suggested fix

Add a small table of Django's own abstract bases and their fields (as ParsedField, with inherited_from set):

  • AbstractBaseUser: password (CharField, max_length=128), last_login (DateTimeField, blank=True, null=True);
  • PermissionsMixin: is_superuser (BooleanField), groups (ManyToManyField → auth.Group), user_permissions (ManyToManyField → auth.Permission);
  • AbstractUser: both of the above plus username, first_name, last_name, email, is_staff, is_active, date_joined.

In _abstract_bases_fields(), when by_name has no class for the base name and the name is in the table, use the table's fields. A class with the same name in the workspace still wins, so a project that defines its own PermissionsMixin isn't affected. Note that is_active on AbstractBaseUser is a plain class attribute, not a field.

How to test
  • A new test with the repro: drift reports nothing, describe lists password, last_login, is_superuser, groups, user_permissions, email.
  • class User(AbstractUser) gets the AbstractUser fields; an own email shadows the inherited one.
  • A workspace class named PermissionsMixin (abstract, with its own fields) is used instead of the table.
  • Run the golden snapshots: zulip's UserProfile uses AbstractBaseUser and PermissionsMixin, so it will gain fields. Update the snapshot and list the changes in the PR.
  • cd cli && python -m pytest -q, ruff and mypy pass.

Difficulty: good first issue, a lookup table and one branch.

Dominant language
Python
Stars
84
Forks
13
PR merge metrics
No merged PRs in 30d

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from FROWNINGdev/django-orm-lens

All issues in FROWNINGdev/django-orm-lens

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.