from django.contrib.auth.models import ( AbstractBaseUser, BaseUserManager, PermissionsMixin, ) from django.db import models class EmailUserManager(BaseUserManager): """Custom user manager using email as the unique identifier.""" def create_user(self, email, password=None, **extra_fields): if not email: raise ValueError("Email is required") email = self.normalize_email(email) user = self.model(email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, password=None, **extra_fields): extra_fields.setdefault("is_staff", True) extra_fields.setdefault("is_superuser", True) return self.create_user(email, password, **extra_fields) class EmailUser(AbstractBaseUser, PermissionsMixin): """Minimal user model with email as USERNAME_FIELD. Matches the calling convention used in mizan's test suite: User.objects.create_user(email="...", password="...", is_staff=True) """ email = models.EmailField(unique=True) is_staff = models.BooleanField(default=False) is_active = models.BooleanField(default=True) objects = EmailUserManager() USERNAME_FIELD = "email" REQUIRED_FIELDS = [] class Meta: app_label = "tests" # ─── Shape test models ────────────────────────────────────────────────────── import uuid class TimestampMixin(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: abstract = True class Publisher(TimestampMixin): name = models.CharField(max_length=200) country = models.CharField(max_length=100, default="") class Meta: app_label = "tests" class Author(TimestampMixin): name = models.CharField(max_length=200) bio = models.TextField(default="") publisher = models.ForeignKey( Publisher, on_delete=models.CASCADE, related_name="authors" ) mentor = models.ForeignKey( "self", on_delete=models.SET_NULL, null=True, blank=True, related_name="mentees" ) class Meta: app_label = "tests" class Tag(models.Model): slug = models.SlugField(primary_key=True, max_length=100) label = models.CharField(max_length=100) class Meta: app_label = "tests" class Book(TimestampMixin): title = models.CharField(max_length=300) isbn = models.CharField(max_length=13, unique=True) page_count = models.IntegerField(default=0) is_published = models.BooleanField(default=False) author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books") editor = models.ForeignKey( Author, on_delete=models.SET_NULL, null=True, blank=True, related_name="edited_books", ) tags = models.ManyToManyField(Tag, blank=True, related_name="books") class Meta: app_label = "tests" class Chapter(TimestampMixin): book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name="chapters") number = models.IntegerField() title = models.CharField(max_length=300) word_count = models.IntegerField(default=0) class Meta: app_label = "tests" ordering = ["number"] unique_together = [("book", "number")] class Section(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) chapter = models.ForeignKey( Chapter, on_delete=models.CASCADE, related_name="sections" ) heading = models.CharField(max_length=300) body = models.TextField(default="") position = models.IntegerField(default=0) class Meta: app_label = "tests" ordering = ["position"] class Category(models.Model): name = models.CharField(max_length=200) parent = models.ForeignKey( "self", on_delete=models.CASCADE, null=True, blank=True, related_name="children" ) class Meta: app_label = "tests"