Update profile with allauth

Good day again django enthusiasts.

@KenWhitesell my apologies for putting this in the wrong forum.

I’m trying to extend my website’s profile functionality by adding some additional forms to the profile. I’ve been using the book Django for Professionals by william vincent which uses user creation sing allauth. The book however does not say anything regarding adding additional profile fields. CoreyMS published a nice tutorial. I’m trying to configure both methods to be able to give a registered user the option to fill in some additional profile information.

So far this is what I’ve come up with. I’m now getting errors that there is something wrong with my p_form. I’ve retraced all routes to that object, it seems to me as it’s all connected as it should. I think my problem arises form the fact that I’m trying to create Profile without correctly referencing to allauth’s custom profile.

any help is greatly appreciated.

Thank you in advance.

This is my code:

models.py

from django.db import models
from django.contrib.auth.models import AbstractUser, User
from django_countries.fields import CountryField
from django.conf import settings

# Create your models here.
class CustomUser(AbstractUser):
    pass

class Profile(models.Model):
    #user = models.OneToOneField(User, on_delete=models.CASCADE)
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    bio = models.TextField(max_length=500, blank=True)
    birth_date = models.DateField(null=True, blank=True)
    country = CountryField()
    city = models.CharField(max_length=50, blank=True)
    group = models.CharField(max_length=50, blank=True)
    image = models.ImageField(upload_to='profile_pics', default='default.jpg')

    def __str__(self):
        return f'{self.user.username} Profile'
forms.py

# accounts/forms.py

from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import Profile
from django import forms

class CustomUserCreationForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        model = get_user_model()
        fields = ('username', 'email')

class CustomUserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = get_user_model()
        fields = ('username', 'email')

class ProfileUpdateForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ('bio', 'birth_date', 'country', 'city', 'group', 'image')
views.py

from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from .forms import CustomUserChangeForm, ProfileUpdateForm

@login_required
def ProfileUpdateView(request):
    u_form = CustomUserChangeForm(instance=request.user)
    p_form = ProfileUpdateForm(instance=request.user.profile)

    context = {
        'u_form': u_form,
        'p_form': p_form
    }

    return render(request, 'account/update_profile.html', context)
urls.py

# accounts/urls.py

from django.urls import path

from .views import ProfileUpdateView

urlpatterns = [
    path("update_profile/", ProfileUpdateView, name="update_profile"),
    ]
signals.py

from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver

from .models import Profile

@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()
apps.py

from django.apps import AppConfig

class AccountsConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'accounts'

    def ready(self):
        import accounts.signals

and finally my html template:

<!-- templates/account/signup.html -->
{% extends "_base.html" %}
{% load static %}
{% load crispy_forms_tags %}

{% block title %}Update profile{% endblock title %}

{% block content %}

			<!-- Form -->
                        <form method="post" enctype="multipart/form-data">
                            {% csrf_token %}
                            {{ u_form|crispy }}
                            {{ p_form|crispy }}
                            <button class="btn btn-primary mb-2" type="submit">Update account</button>
                        </form>
					
{% endblock content %}

The error is as follows:

RelatedObjectDoesNotExist at /accounts/update_profile/
CustomUser has no profile.
Request Method:	GET
Request URL:	http://localhost:8000/accounts/update_profile/
Django Version:	4.2.4
Exception Type:	RelatedObjectDoesNotExist
Exception Value:	
CustomUser has no profile.
Exception Location:	/usr/local/lib/python3.10/site-packages/django/db/models/fields/related_descriptors.py, line 492, in __get__
Raised during:	accounts.views.ProfileUpdateView
Python Executable:	/usr/local/bin/python
Python Version:	3.10.4
Python Path:	
['/websiteV2',
 '/usr/local/lib/python310.zip',
 '/usr/local/lib/python3.10',
 '/usr/local/lib/python3.10/lib-dynload',
 '/usr/local/lib/python3.10/site-packages']
Server time:	Tue, 29 Aug 2023 21:05:25 +0000
Traceback Switch to copy-and-paste view
/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py, line 55, in inner
                response = get_response(request) …
Local vars
Variable	Value
exc	
RelatedObjectDoesNotExist('CustomUser has no profile.')
get_response	
<bound method BaseHandler._get_response of <django.core.handlers.wsgi.WSGIHandler object at 0x7fd421b43070>>
request	
<WSGIRequest: GET '/accounts/update_profile/'>
/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py, line 197, in _get_response
                response = wrapped_callback(request, *callback_args, **callback_kwargs) …
Local vars
Variable	Value
callback	
<function ProfileUpdateView at 0x7fd41efe3250>
callback_args	
()
callback_kwargs	
{}
middleware_method	
<bound method CsrfViewMiddleware.process_view of <CsrfViewMiddleware get_response=convert_exception_to_response.<locals>.inner>>
request	
<WSGIRequest: GET '/accounts/update_profile/'>
response	
None
self	
<django.core.handlers.wsgi.WSGIHandler object at 0x7fd421b43070>
wrapped_callback	
<function ProfileUpdateView at 0x7fd41efe3250>
/usr/local/lib/python3.10/site-packages/django/contrib/auth/decorators.py, line 23, in _wrapper_view
                return view_func(request, *args, **kwargs) …
Local vars
Variable	Value
args	
()
kwargs	
{}
login_url	
None
redirect_field_name	
'next'
request	
<WSGIRequest: GET '/accounts/update_profile/'>
test_func	
<function login_required.<locals>.<lambda> at 0x7fd41efe3130>
view_func	
<function ProfileUpdateView at 0x7fd41efe30a0>
/websiteV2/accounts/views.py, line 17, in ProfileUpdateView
    p_form = ProfileUpdateForm(instance=request.user.profile) …
Local vars
Variable	Value
request	
<WSGIRequest: GET '/accounts/update_profile/'>
u_form	
<CustomUserChangeForm bound=False, valid=Unknown, fields=(username;email;password)>

Regarding this specific error, the first thing I’d check would be to verify that the user making the request does have a related profile object.

Your signals are configured to be triggered by a save to User, but you’re not using User, you’re using CustomUser, which leads me to believe the profile model is not being created.

You could change your view to do a get_or_create on the profile in the update view - save you from having to wire things up elsewhere unless you always want the profile model to exist, in which case you could create it in the model’s save method.

(Personally, I’d get rid of the signals. When you have control over both the sender and receiver of the signal, the signal doesn’t really buy you anything - but that’s just my opinion.)

As a side note, it looks like your ProfileUpdateView only handles the GET but not the POST - which you may have simplified for the purposes of your message here, which is fine. (Also, no worries on the forum groupings - it’s a judgement call and I’ll sometimes even reverse myself after following a thread.)