Farzad
Farzad

Reputation: 57

Django - RelatedObjectDoesNotExist error after post_save creation signal

I have general user profile model which has several roles - Client, Translator and Editor. I'm using a one-to-one relation with the extended user model I've created in accounts app and I'm using Django post_save signal to automatically create a profile instance related to the user which works just fine. Now here is the problem: I need to have role-specific profile model like client profile, translator profile, etc. and again I'm trying to use Django post_save signal to create a {role} profile object related to the main UserProfile model and I'm getting the error below:

Traceback (most recent call last):
  File "D:\Django\tct\venv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "D:\Django\tct\venv\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\contrib\admin\options.py", line 614, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\utils\decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\views\decorators\cache.py", line 44, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\contrib\admin\sites.py", line 233, in inner
    return view(request, *args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\utils\decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\views\decorators\debug.py", line 89, in sensitive_post_parameters_wrapper
    return view(request, *args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\utils\decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\utils\decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\contrib\auth\admin.py", line 99, in add_view
    return self._add_view(request, form_url, extra_context)
  File "D:\Django\tct\venv\lib\site-packages\django\contrib\auth\admin.py", line 126, in _add_view
    return super().add_view(request, form_url, extra_context)
  File "D:\Django\tct\venv\lib\site-packages\django\contrib\admin\options.py", line 1653, in add_view
    return self.changeform_view(request, None, form_url, extra_context)
  File "D:\Django\tct\venv\lib\site-packages\django\utils\decorators.py", line 43, in _wrapper
    return bound_method(*args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\utils\decorators.py", line 130, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\contrib\admin\options.py", line 1534, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
  File "D:\Django\tct\venv\lib\site-packages\django\contrib\admin\options.py", line 1580, in _changeform_view
    self.save_model(request, new_object, form, not add)
  File "D:\Django\tct\venv\lib\site-packages\django\contrib\admin\options.py", line 1093, in save_model
    obj.save()
  File "D:\Django\tct\venv\lib\site-packages\django\contrib\auth\base_user.py", line 67, in save
    super().save(*args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\db\models\base.py", line 753, in save
    self.save_base(using=using, force_insert=force_insert,
  File "D:\Django\tct\venv\lib\site-packages\django\db\models\base.py", line 801, in save_base
    post_save.send(
  File "D:\Django\tct\venv\lib\site-packages\django\dispatch\dispatcher.py", line 177, in send
    return [
  File "D:\Django\tct\venv\lib\site-packages\django\dispatch\dispatcher.py", line 178, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
  File "D:\Django\tct\src\profiles\models.py", line 73, in create_user_profile
    UserProfile.objects.create(user=instance)
  File "D:\Django\tct\venv\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "D:\Django\tct\venv\lib\site-packages\django\db\models\query.py", line 447, in create
    obj.save(force_insert=True, using=self.db)
  File "D:\Django\tct\venv\lib\site-packages\django\db\models\base.py", line 753, in save
    self.save_base(using=using, force_insert=force_insert,
  File "D:\Django\tct\venv\lib\site-packages\django\db\models\base.py", line 801, in save_base
    post_save.send(
  File "D:\Django\tct\venv\lib\site-packages\django\dispatch\dispatcher.py", line 177, in send
    return [
  File "D:\Django\tct\venv\lib\site-packages\django\dispatch\dispatcher.py", line 178, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
  File "D:\Django\tct\src\clients\models.py", line 32, in save_client_profile
    instance.clientprofile.save()
  File "D:\Django\tct\venv\lib\site-packages\django\db\models\fields\related_descriptors.py", line 421, in __get__
    raise self.RelatedObjectDoesNotExist(

Exception Type: RelatedObjectDoesNotExist at /admin/accounts/user/add/
Exception Value: UserProfile has no clientprofile.

User Model:

class User(AbstractUser):
    id = models.UUIDField(
        _("User ID"), primary_key=True, default=uuid.uuid4, editable=False
    )

General Profile Model

class UserProfile(models.Model):
    # General Info
    CLIENT = 1
    TRANSLATOR = 2
    EDITOR = 3
    ROLE_CHOICES = (
        (CLIENT, _("Client")),
        (TRANSLATOR, _("Translator")),
        (EDITOR, _("Editor")),
    )
    MALE = 1
    FEMALE = 2
    GENDER_CHOICES = ((MALE, _("Male")), (FEMALE, _("Female")))
    user = models.OneToOneField(
        User, verbose_name=_("User"), on_delete=models.CASCADE, primary_key=True
    )
    role = models.PositiveSmallIntegerField(
        _("Role"), choices=ROLE_CHOICES, null=True, blank=True
    )
    bio = models.TextField(_("Bio"), null=True, blank=True)
    avatar = models.ImageField(
        _("Avatar"), upload_to="profiles/profile/avatar/", blank=True
    )
    gender = models.PositiveSmallIntegerField(
        _("Gender"), choices=GENDER_CHOICES, null=True, blank=True
    )

    # Contact Info
    phone = models.CharField(_("Phone"), max_length=20, null=True, blank=True)
    cell = models.CharField(_("Cell"), max_length=20, null=True, blank=True)
    id_number = models.CharField(_("ID Number"), max_length=20, null=True, blank=True)
    address = models.TextField(_("Address"), null=True, blank=True)
    postal_code = models.CharField(
        _("Postal Code"), max_length=20, null=True, blank=True
    )
    website_url = models.URLField(
        _("Website URL"), max_length=200, null=True, blank=True
    )
    instagram_username = models.CharField(
        _("Instagram Username"), max_length=200, null=True, blank=True
    )
    telegram_username = models.CharField(
        _("Telegram Username"), max_length=200, null=True, blank=True
    )
    twitter_username = models.CharField(
        _("Twitter Username"), max_length=200, null=True, blank=True
    )

    # Model Extra
    class Meta:
        verbose_name = _("User Profile")
        verbose_name_plural = _("User Profile")

    def __str__(self):
        return self.user.username

    def get_absolute_url(self):
        return reverse("profiles:user_profile_detail", args=[str(self.user.id)])


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


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

Client Profile Model

class ClientProfile(models.Model):
    profile = models.OneToOneField(
        UserProfile, verbose_name=_("User"), on_delete=models.CASCADE, primary_key=True
    )
    credit = models.IntegerField(_("Credit"), default=0, blank=True)
    debt = models.IntegerField(_("Debt"), default=0, blank=True)
    referral_code = ULIDField(default=default)
    referrals = models.ManyToManyField(
        User, verbose_name=_("Referrals"), related_name="client_profile"
    )
    income = models.IntegerField(_("Income"), default=0, blank=True)

@receiver(post_save, sender=UserProfile)
def create_client_profile(sender, instance, created, **kwargs):
    if created:
        if instance.role == 1:
            ClientProfile.objects.create(profile=instance)


@receiver(post_save, sender=UserProfile)
def save_client_profile(sender, instance, **kwargs):
    instance.clientprofile.save()

Custom sign up form with the role field

class CustomSignupForm(SignupForm):
    role = forms.ChoiceField(
        widget=forms.RadioSelect(), choices=UserProfile.ROLE_CHOICES, required=False, label=_("Role")
    )

    def save(self, request):

        # Ensure you call the parent class's save.
        # .save() returns a User object.
        user = super(CustomSignupForm, self).save(request)
        user.userprofile.role = self.cleaned_data.get("role")
        user.save()
        # You must return the original result.
        return user

Upvotes: 1

Views: 765

Answers (1)

birophilo
birophilo

Reputation: 962

It's running the save_client_profile and complaining that the UserProfile has no client_profile attached. The create_client_profile's role == 1 condition isn't met because the UserProfile's role is not set upon creation.

The save() method of SignUpForm currently sets the role in memory but doesn't commit it to the database, because user.save() only saves the User model, not its related UserProfile. You'll need to do user.userprofile.save() after user.userprofile.role = self.cleaned_data.get("role") for the role to persist.

Upvotes: 1

Related Questions