Long-overdue update: I opened #35671 and added a documentation note that empty strings on string-based fields don’t violate null=False.
Also FWIW, to completely disallow empty strings from the database, you can use a CheckConstraint like the following in your model’s Meta class.
from django.db.models import Q, CheckConstraint
constraints = [
CheckConstraint(condition=~Q(field=""), name="field_non_empty"),
# ...
]