Plain is headed towards 1.0! Subscribe for development updates →

plain.models

Model your data and store it in a database.

# app/users/models.py
from plain import models
from plain.passwords.models import PasswordField


class User(models.Model):
    email = models.EmailField(unique=True)
    password = PasswordField()
    is_staff = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.email

Create, update, and delete instances of your models:

from .models import User


# Create a new user
user = User.objects.create(
    email="[email protected]",
    password="password",
)

# Update a user
user.email = "[email protected]"
user.save()

# Delete a user
user.delete()

# Query for users
staff_users = User.objects.filter(is_staff=True)

Installation

# app/settings.py
INSTALLED_PACKAGES = [
    ...
    "plain.models",
]

To connect to a database, you can provide a DATABASE_URL environment variable.

DATABASE_URL=postgresql://user:password@localhost:5432/dbname

Or you can manually define the DATABASES setting.

# app/settings.py
DATABASES = {
    "default": {
        "ENGINE": "plain.models.backends.postgresql",
        "NAME": "dbname",
        "USER": "user",
        "PASSWORD": "password",
        "HOST": "localhost",
        "PORT": "5432",
    }
}

Multiple backends are supported, including Postgres, MySQL, and SQLite.

Querying

Migrations

Migration docs

Fields

Field docs

Validation

Indexes and constraints

Managers

Forms

 1import enum
 2from types import DynamicClassAttribute
 3
 4from plain.utils.functional import Promise
 5
 6__all__ = ["Choices", "IntegerChoices", "TextChoices"]
 7
 8
 9class ChoicesMeta(enum.EnumMeta):
10    """A metaclass for creating a enum choices."""
11
12    def __new__(metacls, classname, bases, classdict, **kwds):
13        labels = []
14        for key in classdict._member_names:
15            value = classdict[key]
16            if (
17                isinstance(value, list | tuple)
18                and len(value) > 1
19                and isinstance(value[-1], Promise | str)
20            ):
21                *value, label = value
22                value = tuple(value)
23            else:
24                label = key.replace("_", " ").title()
25            labels.append(label)
26            # Use dict.__setitem__() to suppress defenses against double
27            # assignment in enum's classdict.
28            dict.__setitem__(classdict, key, value)
29        cls = super().__new__(metacls, classname, bases, classdict, **kwds)
30        for member, label in zip(cls.__members__.values(), labels):
31            member._label_ = label
32        return enum.unique(cls)
33
34    def __contains__(cls, member):
35        if not isinstance(member, enum.Enum):
36            # Allow non-enums to match against member values.
37            return any(x.value == member for x in cls)
38        return super().__contains__(member)
39
40    @property
41    def names(cls):
42        empty = ["__empty__"] if hasattr(cls, "__empty__") else []
43        return empty + [member.name for member in cls]
44
45    @property
46    def choices(cls):
47        empty = [(None, cls.__empty__)] if hasattr(cls, "__empty__") else []
48        return empty + [(member.value, member.label) for member in cls]
49
50    @property
51    def labels(cls):
52        return [label for _, label in cls.choices]
53
54    @property
55    def values(cls):
56        return [value for value, _ in cls.choices]
57
58
59class Choices(enum.Enum, metaclass=ChoicesMeta):
60    """Class for creating enumerated choices."""
61
62    @DynamicClassAttribute
63    def label(self):
64        return self._label_
65
66    @property
67    def do_not_call_in_templates(self):
68        return True
69
70    def __str__(self):
71        """
72        Use value when cast to str, so that Choices set as model instance
73        attributes are rendered as expected in templates and similar contexts.
74        """
75        return str(self.value)
76
77    # A similar format was proposed for Python 3.10.
78    def __repr__(self):
79        return f"{self.__class__.__qualname__}.{self._name_}"
80
81
82class IntegerChoices(int, Choices):
83    """Class for creating enumerated integer choices."""
84
85    pass
86
87
88class TextChoices(str, Choices):
89    """Class for creating enumerated string choices."""
90
91    def _generate_next_value_(name, start, count, last_values):
92        return name