How do I prevent insertion of an invalid Model object?

I’m working through Harry Percival’s book Test-Driven Development with Python and have run into a problem with model validation.

Here is my model:

from django.db import models

class List(models.Model):
    pass


class Item(models.Model):
    text = models.TextField(default='')
    list = models.ForeignKey(List, default=None)

The following code correctly prevents adding a blank Item to an empty List:

def new_list(request):
    list_ = List.objects.create()
    item = Item.objects.create(text=request.POST['item_text'], list=list_)
    try:
        item.full_clean()
        item.save()
    except ValidationError:
        list_.delete()
        error = "You can't have an empty list item"
        return render(request, 'home.html', {"error": error})
    return redirect(f'/lists/{list_.id}/')

The following code is supposed to prevent addition of a blank Item to an existing List:

def view_list(request, list_id):
    list_ = List.objects.get(id=list_id)
    error = None

    if request.method == 'POST':
        try:
            item = Item.objects.create(text=request.POST['item_text'], list=list_)
            item.full_clean()
            item.save()
            return redirect(f'/lists/{list_.id}/')
        except ValidationError:
            error = "You can't have an empty list item"
        
    return render(request, 'list.html', {'list': list_, 'error': error})

It does throw a ValidationError, but it still inserts the invalid (blank) Item into the database.

Can someone please tell me what I’m doing wrong? I’m using Django 1.11, Sqlite3 and Python 3.6.9 on Ubuntu 18.04. The above code is identical to the book, and essentially identical to the GitHub repo (he later factors out the URLs.)

Item.objects.create() does create the object immediately. You do not need that item.save().
if this method is part of API view, you should use serializer for validation.
If you do not intend to use DRF, then you should add your own validation before you create the object.
I am not sure what you want to validate

@devspectre I’m trying to validate that Item.text is not an empty string before I insert it into the database. At this point in the book the value of Item.text is set from user input on a web form. If I’ve understood the docs, blank=False is a default constraint on Model.TextField, and this should be (and is) caught by Item.full_clean(). The problem is that despite failing validation the blank Item is still being inserted into the database.

class Item(models.Model):
    text = models.TextField(default='')
    list = models.ForeignKey(List, default=None)

This indicates that the field can be empty string, you should get rid of that default='' if you want that default constraint work

@devspectre That made no difference. I’m still inserting blank Items.

The issue here is that you’re using the create shortcut method before you’re running your validation. This means that item has already been saved when you run full_clean.

You can create item directly without saving it:
item = Item(text=request.POST['item_text'], list=list_)

@KenWhitesell That would be it. Apparently I’ve contracted a mild case of illiteracy. Thank you.