Kurt Peek
Kurt Peek

Reputation: 57381

How to authenticate a user in a unit test for the Django admin page?

I'm trying to adapt the answer from Testing custom admin actions in django, but I'm running into some unexpected behavior. I've created a simplified Django app, myapp, which has a Worker and Invoice model:

class Worker(models.Model):
    name = models.CharField(max_length=255)


class Invoice(models.Model):
    UNPAID = 0
    PAID = 1

    worker = models.ForeignKey(
        'Worker', on_delete=models.CASCADE)
    amount = models.DecimalField(
        max_digits=10, decimal_places=2)
    amount_paid = models.DecimalField(
        max_digits=10, decimal_places=2, default=Decimal('0.00'))
    status = models.IntegerField(
        choices=[(UNPAID, 'Unpaid'), (PAID, 'Paid')], default=0)

I've create a custom admin action called mark_as_paid which changes the status of the selected invoices to Invoice.PAID in admin.py:

from django.contrib import admin
from django.db.models import F

from .models import Invoice


@admin.register(Invoice)
class InvoiceAdmin(admin.ModelAdmin):
    list_display = ('worker', 'status', 'amount', 'amount_paid')
    actions = ['mark_as_paid']

    def mark_as_paid(self, request, queryset):
        queryset.update(amount_paid=F('amount'))

I'm trying to test this like so:

from decimal import Decimal

from django.contrib import admin
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse

from myapp.models import Invoice, Worker


class InvoiceAdminTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username='foobar',
            email='[email protected]',
            password='barbaz',
            is_superuser=True)
        self.client.force_login(user=self.user)

    def test_mark_as_paid(self):
        worker = Worker.objects.create(name="John Doe")
        invoice = Invoice.objects.create(
            worker=worker, amount=Decimal('100.00'))

        response = self.client.post(
            reverse('admin:myapp_invoice_changelist'),
            data={
                'action': 'mark_as_paid',
                admin.ACTION_CHECKBOX_NAME: [invoice.id]})

        import ipdb; ipdb.set_trace()

        invoice.refresh_from_db()
        self.assertEqual(invoice.amount_paid, Decimal('100.00'))

I've set a debugger trace in the test, but ultimately I would expect it to pass. Currently, however, it is failing because invoice.amount_paid is still Decimal('0.00').

Strangely, the response has a status code of 302 (not 200) and empty content:

ipdb> response.status_code                                                               
302
ipdb> response.content                                                                   
b''

I've also set a trace in the mark_as_paid() method and it's not getting hit, so I suspect there is something wrong about the way I'm mocking the authentication of the user, since if I create a superuser with python manage.py createsuperuser and test this manually, everything works as expected.

Any idea what is wrong with this approach?

Upvotes: 0

Views: 1412

Answers (1)

Kurt Peek
Kurt Peek

Reputation: 57381

Looking at the example at Testing custom admin actions in django more closely, it turns out I need to use the create_superuser() method instead of create_user() with is_superuser=True. The test passes with the following modified setUp() method:

class InvoiceAdminTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_superuser(
            username='foobar',
            email='[email protected]',
            password='barbaz')
        self.client.force_login(user=self.user)

Upvotes: 4

Related Questions