2

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 Answers1

0

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.

Danoram
  • 8,132
  • 12
  • 51
  • 71
  • I added your recommendations and am getting error code (added as edit above). Tried adding 'user_status' (per recomendation on a dif question) as a positional argument. also tried adding self. Neither solved. Might I need to add **kwargs? Also, I should mention I'm using crispy forms. Code added as an edit above. – Thomas Byers Feb 08 '21 at 15:46
  • The stacktrace you provided shows the error is in the line `profile = ProfileSetupForm.save(user_status, request.POST)` which isn't in any code I provided. Can you provide the error you got before making this 'user_status` change, which you say my code gives? – Danoram Feb 08 '21 at 16:08
  • Oh yeah, sorry. I adjusted and pasted in the error from your original suggested code. – Thomas Byers Feb 08 '21 at 16:35
  • Can you include your forms.py file please. Also change `profile = ProfileSetupForm.save(request.POST)` to `profile_setup_form = ProfileSetupForm.save(request.POST)` – Danoram Feb 08 '21 at 16:59
  • The code containing ProfileSetupForm I should say – Danoram Feb 08 '21 at 17:48
  • adjusted the views.py code and included that and current forms.py above – Thomas Byers Feb 08 '21 at 23:51
  • This line `profile = ProfileSetupForm.save(request.POST)` needs to be changed to `profile = ProfileSetupForm(request.POST)` – Danoram Feb 09 '21 at 07:21
  • Hi, thanks for your help so far. The line that you are suggesting I change above actually appears like this in my code : `profile_setup_form = ProfileSetupForm.save(request.POST) profile = profile_setup_form.save(commit=False)` I assume you want me to remove .save on the top one? I get this error when I take the .save out `TypeError at /register/ save() got an unexpected keyword argument 'force_insert'` Also, in PyCharm, on the second line mentioned here (the one that starts with profile) it says the 'profile' is outer scope FYI. Not sure if that's a factor. – Thomas Byers Feb 09 '21 at 15:58