0

I want my User objects in Django to be linked to a Client object (which I create). To do so I extend the User model with a one-to-one link with a Profile class (which I create) class Profile that links User & Client. I followed the instructions from https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html So far so good.

However when I create a User object I dont have access by default to I want my User objects

My models.py:

from django.contrib.auth.models import User

[...]

class Client(models.Model):
    name = models.CharField(max_length=255)
    address = models.CharField(max_length=255)
    [...]

class Profile(models.Model):
    need_setup = Client.objects.get(name='NEED TO ADD CLIENT IN ADMIN > PROFILES')
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    client = models.ForeignKey(Client, on_delete=models.DO_NOTHING)

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

The problem comes when I make my migrations / runserver (my app is called 'dashboard'): django.db.utils.ProgrammingError: relation "dashboard_client" does not exist LINE 1: ...nque_id", "dashboard_client"."date_creation" FROM "dashboard... because of the need_setup = Client.objects.get(name='NEED TO ADD CLIENT IN ADMIN > PROFILES') (no problems if I comment it out).

I can work around the problem by manually creating a client in the db called 'NEED TO ADD CLIENT IN ADMIN > PROFILES' and then run my migrations and deploy but I'm looking for a better way of doing this, especially when I deploy from scratch in prod.

I tried to override the def ready(self) function in apps.py, which is a function that runs when the apps boots. Unfortunately it needs to have the correct models.py before running.

Your thoughts and ideas are appreciated!

4
  • 2
    So, your problem here is that you are trying to get an instance of a Client in your Profile model. At the outset, you don't actually have any client instances, so your call to Client.objects.get() is rightfully returning an error. This is why this problem is fixed when you manually add a client to the DB that matches that name. I'm unsure what your goal is with the need_setup field, but as it is right now, it's not actually a field, it's a queryset, which should not be in a Model. If you could clear up what need_setup is supposed to be, we can probably figure out how to fix it Commented Jan 5, 2021 at 4:13
  • 2
    I agree with @rchurch4. It seems like you want to know which Profile objects have been newly created with a user from the post-save signal create_user_profile. In that case, you can simply set need_setup as a BooleanField with a default value of True. Then, override the save method to check if the object has a client linked. If client is still null, keep need_setup as True, otherwise set to False. Commented Jan 5, 2021 at 4:27
  • yep, exactly what I was going for ^ Commented Jan 5, 2021 at 4:29
  • you both are legends thank you very much. I did implement the idea from @Scratch'N'Purr - can you post it as an answer so that I can mark it as answered? Cheers guys Commented Jan 5, 2021 at 16:47

1 Answer 1

1

Based on my understanding of your case, you want to:

  1. Trigger a creation of a Profile object after a User is created and have the Profile object linked to the user (already done with your post-save signal).
  2. Determine whether the profile object needs to be configured with a client (the problem you want to resolve)

In this case, you set your need_setup field as a BooleanField with an attribute of default=True. This means that any Profile objects that are newly created will by default need to be set up. For security measure, you can add the attribute editable=False so that the field can't be modified externally.

class Profile(models.Model):
    need_setup = models.BooleanField(default=True, editable=False)
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    client = models.ForeignKey(Client, on_delete=models.DO_NOTHING)

Next, you override the model's save method to toggle the boolean field based on whether client is populated or not.

class Profile(models.Model):
    need_setup = models.BooleanField(default=True, editable=False)
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    client = models.ForeignKey(Client, on_delete=models.DO_NOTHING)

    def save(self, *args, **kwargs):
        if self.client is None:
            self.need_setup = True
        else:
            self.need_setup = False

        super().save(*args, **kwargs)

An alternative solution is using python's @property. With this solution, you basically set need_setup as a property instead of a field. This has the advantage of not having to create a field on the database level. The downside of this solution is that you can't use it in django querysets, e.g. Profile.objects.filter(need_setup=True), because django querysets use database fields.

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    client = models.ForeignKey(Client, on_delete=models.DO_NOTHING)

    @property
    def need_setup(self):
        return not bool(self.client)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.