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

 1from functools import partial
 2
 3from plain.models.utils import make_model_tuple
 4from plain.signals.dispatch import Signal
 5
 6class_prepared = Signal()
 7
 8
 9class ModelSignal(Signal):
10    """
11    Signal subclass that allows the sender to be lazily specified as a string
12    of the `package_label.ModelName` form.
13    """
14
15    def _lazy_method(self, method, packages, receiver, sender, **kwargs):
16        from plain.models.options import Options
17
18        # This partial takes a single optional argument named "sender".
19        partial_method = partial(method, receiver, **kwargs)
20        if isinstance(sender, str):
21            packages = packages or Options.default_packages
22            packages.lazy_model_operation(partial_method, make_model_tuple(sender))
23        else:
24            return partial_method(sender)
25
26    def connect(
27        self, receiver, sender=None, weak=True, dispatch_uid=None, packages=None
28    ):
29        self._lazy_method(
30            super().connect,
31            packages,
32            receiver,
33            sender,
34            weak=weak,
35            dispatch_uid=dispatch_uid,
36        )
37
38    def disconnect(self, receiver=None, sender=None, dispatch_uid=None, packages=None):
39        return self._lazy_method(
40            super().disconnect, packages, receiver, sender, dispatch_uid=dispatch_uid
41        )
42
43
44pre_init = ModelSignal(use_caching=True)
45post_init = ModelSignal(use_caching=True)
46
47pre_save = ModelSignal(use_caching=True)
48post_save = ModelSignal(use_caching=True)
49
50pre_delete = ModelSignal(use_caching=True)
51post_delete = ModelSignal(use_caching=True)
52
53m2m_changed = ModelSignal(use_caching=True)