7

I would like to follow this guideline and avoid a nullable ForeignKey.

I have a ForeignKey to the django User model.

If I try to store request.user in an instance of this model, I get this error:

ValueError: Cannot assign "<SimpleLazyObject: <django.contrib.auth.models.AnonymousUser>>":
 "MyModel.user" must be a "User" instance.

I think it is feasible to avoid the non-conditionless data schema (nullable foreign key).

How could I solve this?

guettli
  • 25,042
  • 81
  • 346
  • 663

3 Answers3

9

Those guidelines suggest you create an instance of user that reflects an anonymous user. Otherwise known as a sentinel value. You'd have to keep track of it via some unique key, likely the username. Then make sure it exists and nobody else has actually created a user with that key otherwise you run into other problems.

However, because of those outlined issues above I disagree with those guidelines. If your data model allows for optional relationships, then you absolutely should use NULL values.

Regarding the comment:

If there is no NULL in your data, then there will be no NullPointerException in your source code while processing the data :-)

Simply because there are no NULL fields, doesn't mean those conditions don't exist. You still are handling these edge cases, but changing the names and some of the syntax. You're still vulnerable to bugs because you still have as many conditions (and potentially more given that you have to now make sure your sentinel value is unique).

schillingt
  • 13,493
  • 2
  • 32
  • 34
2

Hey it's my first attempt at answering a question! I'm a newbie, but I had a similar error recently. I suppose this is a naive version Nigel222's answer which calls for doing this with a migration, but maybe there is something of value here nonetheless for another newbie who needs a simpler solution. I was influenced by an answer to this post by Ayman Al-Absi that suggests that you may need to reference this user by it's auto-generated primary key.

By default, request.user is AnonymousUser when not authenticated. It seems from the error message, AnonymousUser can't be used as value for your foreign key in the User table. Proposed solution:

from django.contrib.auth.models import User
# Start by creating a user in your User table called something like anon in some kind of initialization method:
tempUser= User.objects.create_user(username="anon", email="none", first_name="none", last_name="none")
tempUser.save()
#when the user is unauthenticated, before calling a method that takes request as a parameter do:
if request.user.is_anonymous:
  anonUser = User.objects.get(username='anon')
  request.user=User.objects.get(id=anonUser.id)

Another comment for the newbie. I made my own table called User in models.py. This became confusing. I had to import it with an alias: from .models import User as my_user_table It would have been better just to call it my_user_table to begin with.

Nate
  • 21
  • 2
0

Create a special instance of User for this purpose. It's The best place to do so is in a data migration for the model which will rely on being able to create a ForeignKey to this special User object. When you deploy your app and run makemigrations and migrate, it will create the special user objects before there are any actual users in the DB.

There's a lot of detail on creating data migrations here

Here's an example of making sure that some Group objects will exist as of this migration for any future deployment.

# Generated by Django 2.2.8 on 2020-03-05 09:53

from django.db import migrations

def apply_migration(apps, schema_editor):

    Group = apps.get_model("auth", "Group")
    Group.objects.bulk_create(
        [Group(name="orderadmin"), 
         Group(name="production"),
         Group(name="shipping")]
    )

def revert_migration(apps, schema_editor):
    Group = apps.get_model("auth", "Group")
    Group.objects.filter(name__in=["orderadmin", "production", "shipping"]).delete()


class Migration(migrations.Migration):

    dependencies = [
        ('jobs', '0034_auto_20200303_1810'),
    ]

    operations = [
        migrations.RunPython(apply_migration, revert_migration)
    ]
nigel222
  • 7,582
  • 1
  • 14
  • 22