python Django fill in auto-created Profile model with User registration form

1.1k Views Asked by At

I am creating an art marketplace with django. I have the registration and the auto-create signal set up for the profile. There are three types of Users who will register on this app: Artists, Patrons, and Brokers (I call these user_status.) I would like the User to choose one of these three fields on the registration form. I have gotten to the point where the reg form has the choicefield and the choices and the user can select one and the form saves to create the User and Profile, but the profile does not have a selected 'user_status' when I go in to admin.

I am aware it is heavily discouraged to bundle up profile modelfields at registration, but I'd like for just this one field to be selectable since it will have a major effect on how the site will look for the user. I have read (here) that there would be a lot of annoying customization involved to properly fill profile fields at registration. Is this true?

I think if there is a solution it'll be on Signals.py but am not sure.

In signals.py when I type in:

user_status='Patron',

Can I replace the 'Patron' with something like instance.get('user_status')? I know there are something called 'defaults' to use here as well but I am unfamiliar.

user/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_profile(sender, instance, created, **kwargs):
    if created:

        # This functions but cannot get it to grab the form chosen field from the user.
        # just default creates status as patron for now :(
        Profile.objects.update_or_create(
            user=instance,
            user_status='Patron',
        )


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

user/forms.py

from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm

from users.models import Profile

STATUS_CHOICES = (
    ('Artist', 'Artist'),
    ('Patron', 'Patron'),
    ('Broker', 'Broker')
)


class UserRegisterForm(UserCreationForm):
    email = forms.EmailField()
    user_status = forms.ChoiceField(choices=STATUS_CHOICES)

    class Meta:
        model = User
        fields = ['username', 'email', 'user_status', 'password1', 'password2']

users/views.py

from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import UserRegisterForm


def register(request):
    if request.method == 'POST':
        form = UserRegisterForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data.get('username')
            messages.success(request, f'Hello {username}! Your account has been created. You are now ready to log in.')
            return redirect('login')
    else:
        form = UserRegisterForm()

    return render(request, 'users/register.html', {'form': form})


@login_required
def profile(request):
    return render(request, 'users/profile.html')

user/models.py

class Profile(models.Model):
    STATUS_CHOICES = (
        ('Artist', 'Artist'),
        ('Patron', 'Patron'),
        ('Broker', 'Broker')
    )
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
    user_slug = models.SlugField(blank=True)
    image = models.ImageField(default='default.jpg', upload_to='profile_pics')
    user_status = models.CharField(max_length=100, choices=STATUS_CHOICES)

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

Edit reflecting 1st recommendation:

Views.py

def register(request):
    if request.method == 'POST':

        # create profile object but don't save it to db
        profile = ProfileSetupForm.save(request.POST)
        profile = profile_setup_form.save(commit=False)

        user_form = UserRegisterForm(request.POST)

        if user_form.is_valid():
            # create user object
            user = user_form.save(commit=False)
            # set profile attribute of user object
            user.profile = profile
            # save user - calls post_save
            user.save()

            username = user.username
            messages.success(request, f'Hello {username}! Your account has been created. You are now ready to log in.')
            return redirect('login')
    else:
        user_form = UserRegisterForm()
        profile_setup_form = ProfileSetupForm()

    return render(
        request,
        'users/register.html',
        {
            'user_form': user_form,
            'profile_setup_form': profile_setup_form
        }
    )

signals.py

@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.update_or_create(
            user=instance,
            user_status=instance.profile.user_status,
        )

register.html

{% extends "blog/base.html" %}
{% load crispy_forms_tags %}

{% block content %}
    <div class="content-section">
        <form method="POST">
            {% csrf_token %}
            <fieldset class="form-group">
                <legend class="border-bottom mb-4">Join Today</legend>
                {{ user_form|crispy }}
                {{ profile_setup_form|crispy }}
            </fieldset>
            <div class="form-group">
                <button class="btn btn-outline-info" type="submit">Sign Up</button>
            </div>
        </form>
        <div class="border-top pt-3">
            <small class="muted">
                Already have an account? <a class="ml-2" href="{% url 'login' %}">Sign In</a>
            </small>
        </div>
    </div>
{% endblock content %}

Error Code

Traceback (most recent call last):
  File "/Users/Tom/Desktop/art_project/art_env/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/Users/Tom/Desktop/art_project/art_env/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/Tom/Desktop/art_project/users/views.py", line 12, in register
    profile = ProfileSetupForm.save(user_status, request.POST)
  File "/Users/Tom/Desktop/art_project/art_env/lib/python3.8/site-packages/django/forms/models.py", line 451, in save
    if self.errors:

Exception Type: AttributeError at /register/
Exception Value: 'DeferredAttribute' object has no attribute 'errors'

Here is the error code if i take that 'user_status' out so it's your suggested code:

Traceback (most recent call last):
  File "/Users/Tom/Desktop/art_project/art_env/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/Users/Tom/Desktop/art_project/art_env/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/Tom/Desktop/art_project/users/views.py", line 12, in register
    profile = ProfileSetupForm.save(request.POST)
  File "/Users/Tom/Desktop/art_project/art_env/lib/python3.8/site-packages/django/forms/models.py", line 451, in save
    if self.errors:

Exception Type: AttributeError at /register/
Exception Value: 'QueryDict' object has no attribute 'errors'

Adjusted forms.py file

from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm

from users.models import Profile

STATUS_CHOICES = (
    ('Artist', 'Artist'),
    ('Patron', 'Patron'),
    ('Broker', 'Broker')
)


class UserRegisterForm(UserCreationForm):
    email = forms.EmailField()
    # user_status = forms.ChoiceField(choices=STATUS_CHOICES)

    class Meta:
        model = User
        fields = ['username', 'email', 'password1', 'password2']


class ProfileSetupForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['user_status']

Current views.py file with adjustments from comments

from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import UserRegisterForm, ProfileSetupForm


def register(request):
    if request.method == 'POST':

        # create profile object but don't save it to db
        profile_setup_form = ProfileSetupForm.save(request.POST)
        profile = profile_setup_form.save(commit=False)

        user_form = UserRegisterForm(request.POST)

        if user_form.is_valid():
            # create user object
            user = user_form.save(commit=False)
            # set profile attribute of user object
            user.profile = profile
            # save user - calls post_save
            user.save()

            username = user.username
            messages.success(request, f'Hello {username}! Your account has been created. You are now ready to log in.')
            return redirect('login')
    else:
        user_form = UserRegisterForm()
        profile_setup_form = ProfileSetupForm()

    return render(
        request,
        'users/register.html',
        {
            'user_form': user_form,
            'profile_setup_form': profile_setup_form
        }
    )
1

There are 1 best solutions below

8
Danoram On

I would include another form on your register page although this is only a suggestion as a solution.

forms.py

from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm

from user_status.models import Profile

STATUS_CHOICES = (
    ('Artist', 'Artist'),
    ('Patron', 'Patron'),
    ('Broker', 'Broker')
)


class UserRegisterForm(UserCreationForm):
    email = forms.EmailField()
    # user_status = forms.ChoiceField(choices=STATUS_CHOICES)

    class Meta:
        model = User
        fields = ['username', 'email', 'password1', 'password2']


class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['user_status']

views.py

from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import UserRegisterForm, ProfileForm


def register(request):
    if request.method == 'POST':

        # create profile object but don't save it to db
        profile_form = ProfileForm(request.POST)
        profile = profile_form.save(commit=False)

        user_form = UserRegisterForm(request.POST)

        if user_form.is_valid():
            # create user object
            user = user_form.save(commit=False)
            # set profile attribute of user object
            user.profile = profile
            # save user - calls post_save
            user.save()
            
            username = user.username
            messages.success(request, f'Hello {username}! Your account has been created. You are now ready to log in.')
            return redirect('login')
    else:
        user_form = UserRegisterForm()
        profile_form = ProfileForm()


    return render(
        request, 
        'users/register.html', 
        {
            'user_form': user_form,
            'profile_form': profile_form
        }
    )


@login_required
def profile(request):
    return render(request, 'users/profile.html')

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_profile(sender, instance, created, **kwargs):
    if created:

        # can now set user_status attribute
        Profile.objects.update_or_create(
            user=instance,
            user_status=instance.profile.user_status,
        )


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

I don't know what your register.html page looks like but you'd show these forms like in the following way as an example.

register.html

<h1>Registration Page</h1>

<form action="/register" method="POST">
    {% csrf_token %}

    {{ user_form.as_p }}

    {{ profile_form.as_p }}

    <input type="submit" value="Register"></input>
</form>

You could include more fields in the profile_form such as the user's image if you wanted to let them include that during registration too.