overclock
overclock

Reputation: 625

How do I make a signal run only if the user created is on a specific group in Django?

I have a model Client which uses a @receiver signal to update its fields whenever a User is created, so it creates a Client profile.

class Client(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    address = models.CharField(max_length=200, verbose_name="Morada")
    city = models.CharField(max_length=200, verbose_name="Cidade")
    postal = models.CharField(max_length=8, validators=[RegexValidator(r'^\d{4}(-\d{3})?$')], verbose_name="Código Postal")
    nif = models.CharField(max_length=9, verbose_name="NIF", validators=[RegexValidator(r'^\d{1,10}$')], unique=True, null=True)
    mobile = models.CharField(max_length=9, verbose_name="Telemóvel", validators=[RegexValidator(r'^\d{1,10}$')])

    def __str__(self):
        return "%s %s" % (self.user.first_name, self.user.last_name)

    class Meta:
        verbose_name_plural = "Clientes"

    @receiver(post_save, sender=User)
    def update_user_profile(sender, instance, created, **kwargs):
        if created:
            Clients.objects.create(user=instance)
            instance.clients.save()

Is there a way to only run this if the user created belongs to the Clients group? Because if a user is created in the Employees group, I don't want to create a profile.

This is the view that creates the Client in the Clients group:

@login_required(login_url='./accounts/login/')
def signup(request):
    if request.method == 'POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            user = form.save()  # this creates the user with first_name, email and last_name as well!
            group = Group.objects.get(name='Clients')
            user.groups.add(group)
            user.refresh_from_db()  # load the profile instance created by the signal
            user.clients.address = form.cleaned_data.get('address')
            user.clients.city = form.cleaned_data.get('city')
            user.clients.postal = form.cleaned_data.get('postal')
            user.clients.nif = form.cleaned_data.get('nif')
            user.clients.mobile = form.cleaned_data.get('mobile')
            return redirect('clients')
    else:
        form = SignUpForm()
    return render(request, 'backend/new_client.html', {'form': form})

Upvotes: 0

Views: 65

Answers (1)

bruno desthuilliers
bruno desthuilliers

Reputation: 77912

Doing it in the view (without signal):

@login_required(login_url='./accounts/login/')
def signup(request):
    if request.method == 'POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            user = form.save()  
            group = Group.objects.get(name='Clients')
            user.groups.add(group)
            client = Client.objects.create(
                user=user,
                address=form.cleaned_data.get('address')
                city=form.cleaned_data.get('city')
                postal=form.cleaned_data.get('postal')
                nif=form.cleaned_data.get('nif')
                mobile=form.cleaned_data.get('mobile')
            )
            return redirect('clients')
    else:
        form = SignUpForm()
    return render(request, 'backend/new_client.html', {'form': form})

Then you can choose to move all the code under user = form.save() in the form's itself (I assume it's a custom ModelForm):

# forms.py

class SignUpForm(models.Form):

    # your existing code here

    def save(self):
       # NB if you're still using py2 you'll need
       # `user = super(SignUpForm, self).save()` instead
       user = super().save()
       group = Group.objects.get(name='Clients')
       user.groups.add(group)
       cleaned_data = self.cleaned_data
       client = Client.objects.create(
           user=user,
           address=cleaned_data.get('address')
           city=cleaned_data.get('city')
           postal=cleaned_data.get('postal')
           nif=cleaned_data.get('nif')
           mobile=cleaned_data.get('mobile')
       )
       return user

And your view becomes:

@login_required(login_url='./accounts/login/')
def signup(request):
    if request.method == 'POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            form.save()  
            return redirect('clients')
    else:
        form = SignUpForm()
    return render(request, 'backend/new_client.html', {'form': form})

Both options are correct and functionnaly equivalent, but the second one is IMHO more maintainable - first because a form is easier to test than a view (you don't need to create a request object), and also because it encapsulate the whole domain logic in the same place (the form) instead of scattering it between the form and the view. The only downside is that you loose the ability to pass the commit=False arg to form.save(), but since this form has obviously no other purpose you wouldn't use this feature anyway.

Upvotes: 1

Related Questions