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()
password = PasswordField()
is_admin = 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
admin_users = User.objects.filter(is_admin=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
Fields
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 def __str__(self):
67 """
68 Use value when cast to str, so that Choices set as model instance
69 attributes are rendered as expected in templates and similar contexts.
70 """
71 return str(self.value)
72
73 # A similar format was proposed for Python 3.10.
74 def __repr__(self):
75 return f"{self.__class__.__qualname__}.{self._name_}"
76
77
78class IntegerChoices(int, Choices):
79 """Class for creating enumerated integer choices."""
80
81 pass
82
83
84class TextChoices(str, Choices):
85 """Class for creating enumerated string choices."""
86
87 def _generate_next_value_(name, start, count, last_values):
88 return name