Can't get my two forms in one view to save to database

The forms are a one-to-many via foreignkey in profile table. basically trying to update the profile of the current user. also looking to see how to prepopulate the data into the fields that already exist in the database.

models.py

class CustomUserAccount(AbstractUser):

    middle_name = models.CharField(_("Middle Name"), max_length=50, default="")
    is_employee = models.BooleanField(default=True, null=False)
    employee_id = models.IntegerField(_("Employee ID"), null=True)
    date_of_birth = models.DateField(
        _("Date of Birth"), auto_now=False, auto_now_add=False, null=True)
    gender = models.CharField(
        _("Gender"), max_length=8, choices=UserChoices.gender, null=True)

    USERNAME_FIELD = 'username'
    
    objects = models.Manager()

    def __str__(self):
        return f'{self.first_name} {self.last_name}'

    class Meta:
        db_table = "user_account"

class UserProfile(models.Model):

    phone_regex = RegexValidator(
        regex=r'^\+?1?\d{9,15}$', message="format as +999999999")

    user_avatar = models.ImageField(upload_to='avatars', blank=True)
    mobile_number = models.CharField(
        max_length=17, blank=True, validators=[phone_regex])
    user_id = models.OneToOneField(
        CustomUserAccount, on_delete=models.PROTECT, default="")
    address_id = models.ForeignKey("user.Address", verbose_name=_(
        "Address ID"), on_delete=models.CASCADE)

    class Meta:
        db_table = "user_profile"


class Address(models.Model):
    street_number = models.CharField(
        _("Street Number"), max_length=10, blank=True)
    street_name = models.CharField(_("Street Name"), max_length=50, blank=True)
    unit_apt = models.CharField(
        _("Unit/Apt Number"), max_length=10, null=True, blank=True)
    county = models.CharField(_("County"), max_length=50, blank=True)
    city = models.CharField(_("City"), max_length=50, blank=True)
    state = models.CharField(_("State"), max_length=50, blank=True)
    country = models.CharField(_("Country"), max_length=50, null=False)
    five_digit_zipcode = models.IntegerField(_("Zip Code"), blank=True)
    
    class Meta:
        db_table = 'address'
        verbose_name = 'Address'
        verbose_name_plural = 'Addresses'


forms.py

class UserProfileUpdateForm(forms.ModelForm):
    prefix = 'profile'
    
    class Meta:
        model = UserProfile
        exclude = ['user_id', 'address_id']
        
class AddressForm(forms.ModelForm):
    prefix = 'address'
    
    class Meta:
        model = Address
        fields = '__all__'


class UserAccountUpdateForm(forms.ModelForm):
    prefix = 'account'
    
    class Meta:
        model = CustomUserAccount
        fields = ['gender', 'employee_id', 'username', 'date_of_birth' ]

views.py

def UpdateProfileView(request):
    
    if request.method == 'POST':
        
        profile_form = UserProfileUpdateForm(instance=request.user, data=request.POST, prefix='profile')
        address_form = AddressForm(instance=request.user, data=request.POST, prefix='address')
        # context = {
        #     "profile_form" : profile_form,
        #     "address_form" : address_form
        #     }

        # print(profile_form)
        # print(type(profile_form))
        
        if all([profile_form.is_valid(), address_form.is_valid]):
            print(profile_form.cleaned_data)
            profile_form.save()
            address_form.save()
            messages.success(request, "Your profile has been updated")
            
        else: 
            messages.error(request, "There was an error updating your profile")
        
        return redirect(reverse('update-profile'))
    
    else:
        profile_form = UserProfileUpdateForm(instance=request.user, prefix='profile')
        address_form = AddressForm(instance=request.user, prefix='address')    
    context = {
        "profile_form" : profile_form,
        "address_form" : address_form
        }
    return render(request, "registration/update.html", context)

I know i have some general style errors with how i named functions and such.

Your definition is actually of a one-to-one relationship, not one-to-many:

Side note: The variable really should be named user and not user_id, because from the perspective of the ORM, that variable will contain a reference to a user. (The same logic holds true for the address_id field as well - that should be address.)

You’re looking to create an instance of a form for a UserProfile, not a CustomUserAccount. Therefore, the instance attribute needs to refer to a UserProfile, but you’re passing it a reference to request.user, which isn’t one.
The reference to the UserProfile would be request.user.userprofile.

The same logic applies to the AddressForm as well. The instance you supply needs to be the instance of Address being referenced by the Profile - request.user.userprofile.address_id.

Side note: This is one example of why you should not use the _id suffix on your definitions of foreign key fields. This is confusing in that someone familiar with Django and Django’s standards would think that you are actually referencing the pk of the address object, and not referencing the object itself.

This is just what I noticed off-hand. There may be other things wrong, but these are what’s immediately obvious.

Thank you for the info! I’m still very much learning but find this all so fascinating. I tried fixing the reference to request.user.userprofile even used ‘UserProfile’ but got this error …

Request Method: GET
Request URL: http://localhost:8000/accounts/profile/
Django Version: 5.0.7
Exception Type: RelatedObjectDoesNotExist
Exception Value: CustomUserAccount has no userprofile.

That’s a data issue - the message is exactly what is shown here.

You have a CustomUserAccount that doesn’t have a related UserProfile.

Hello @skeetno1 !

Side notes on using field default attribute as relates to your code:

For string-based fields, CharField and TextField, etc, the default used will be an empty string if you don’t specify any yourself, so there is no need to set default="".

But in this case:

user_id = models.OneToOneField(
        CustomUserAccount, on_delete=models.PROTECT, default="")

setting default to an empty string might lead to data integrity issues because OneToOneField is not a string-based field. If you want to allow it to be empty, use null=True. if you want to set a default of no value, use default=None, which is the default django uses if you don’t specify one.

1 Like