pinax / django-user-accounts

User accounts for Django
MIT License
1.22k stars 356 forks source link

django.contrib.auth.models.User.account.RelatedObjectDoesNotExist: User has no account #345

Closed sinister-ghost closed 3 months ago

sinister-ghost commented 3 years ago

when i want to go to user settings : "django.contrib.auth.models.User.account.RelatedObjectDoesNotExist: User has no account"when i want to go to user settings But everything else works. Registration, login, everything except an attempt to enter the profile settings.

jonathan-s commented 3 years ago

Could you detail the exact replication steps for when this happens?

toraritte commented 1 year ago

Can't speak for @sinister-ghost, but this happened to us when upgrading an old Django app (Django 2.2.17, django-user-accounts 3.0.4, PostgreSQL 10.4) running in production to Django 4.2 and django-user-accounts 3.2.0. These are the steps in our case (although it is hard to give specifics given how outdated everything was...):

  1. Upgrade requirements.txt
  2. pg_dump app_db > app_db.sql on the production server
  3. psql -f app_db.sql on dev server
  4. python lynx/manage.py migrate
  5. psql --command="analyze"

The codebase was not touched during all this. (edit: Kind of; see note at the bottom.) Serving the updated app worked, except for the error in the issue description when trying to log in with the right credentials.

For the record, the fix (in our case) was to simply insert rows to the account_account table linking to each user in the auth_user table.

SELECT id FROM auth_user WHERE username = 'my_username';  --> 27
INSERT INTO account_account(id, timezone, language, user_id) 
  VALUES (107,'','en', 27);

(Yeah, I'm neither a Python/Django nor a SQL whiz.)

The interesting thing is that in the outdated app, the logins worked, even though almost no user had a corresponding account_account entry. The django-user-accouns migrations haven't changed since 2017(ish), so I'm almost sure that this issue has been introduced in Django 4. (Although, I couldn't make a full comparison between Django 2.2.x contrib/auth/migrations and Django 4.2.x contrib/auth/migrations yet.)


edit: Forgot about this warning tsunami after upgrading from Django 2.2 to 4.2 where all the models were mentioned:

account.Account: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'.
       HINT: Configure the DEFAULT_AUTO_FIELD setting or the AppConfig.default_auto_field attribute to point to a subclass of AutoField, e.g. 'django.db.models.BigAutoField'.

The global solution (i.e., adding DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' to settings.py) from this SO thread worked like a charm. Not sure if this matters.

uhurusurfa commented 1 year ago

The error you saw (W042) seems to indicate seems to indicate that it thinks there was no primary key defined for the account_account model. Can you share statement in the SQL dump file that creates the account_account table?

NOTE: If the "id" field was not defined as a primary key (even though it existed as a column on the table) then the missing records are expected after the import.

Spawin commented 3 months ago

Hi; I encounter this same error. It is caused by the absence of the account attribute at a user level. This is possible in my case because I added django-user-accounts to an existing project with users who do not have an associated account.

# account.models.py
    @classmethod
    def for_request(cls, request):
        user = getattr(request, "user", None)
        if user and user.is_authenticated:
            account = user.account # <-- Error because a OneToOneField actually is a ForeignKey field with a unique=True constraint 
            # and a OneToOneField field does not mean that the referenced model always has account object(in your case). 
            # This is why in some case if user.account for some user not exists then it raises an RelatedObjectDoesNotExist.
            if account:
                return account
        return AnonymousAccount(request)

This can be solved with:

# account.models.py
    @classmethod
    def for_request(cls, request):
        user = getattr(request, "user", None)
        if user and user.is_authenticated:
            account = getattr(user, "account", None)
            if account:
                return account
        return AnonymousAccount(request)

I can make a PR to propose this fix if this approach is accepted.

uhurusurfa commented 3 months ago

Looks good - Go ahead and raise a PR.