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
1from contextlib import ContextDecorator, contextmanager
2
3from plain.models.db import (
4 DEFAULT_DB_ALIAS,
5 DatabaseError,
6 Error,
7 ProgrammingError,
8 connections,
9)
10
11
12class TransactionManagementError(ProgrammingError):
13 """Transaction management is used improperly."""
14
15 pass
16
17
18def get_connection(using=None):
19 """
20 Get a database connection by name, or the default database connection
21 if no name is provided. This is a private API.
22 """
23 if using is None:
24 using = DEFAULT_DB_ALIAS
25 return connections[using]
26
27
28def get_autocommit(using=None):
29 """Get the autocommit status of the connection."""
30 return get_connection(using).get_autocommit()
31
32
33def set_autocommit(autocommit, using=None):
34 """Set the autocommit status of the connection."""
35 return get_connection(using).set_autocommit(autocommit)
36
37
38def commit(using=None):
39 """Commit a transaction."""
40 get_connection(using).commit()
41
42
43def rollback(using=None):
44 """Roll back a transaction."""
45 get_connection(using).rollback()
46
47
48def savepoint(using=None):
49 """
50 Create a savepoint (if supported and required by the backend) inside the
51 current transaction. Return an identifier for the savepoint that will be
52 used for the subsequent rollback or commit.
53 """
54 return get_connection(using).savepoint()
55
56
57def savepoint_rollback(sid, using=None):
58 """
59 Roll back the most recent savepoint (if one exists). Do nothing if
60 savepoints are not supported.
61 """
62 get_connection(using).savepoint_rollback(sid)
63
64
65def savepoint_commit(sid, using=None):
66 """
67 Commit the most recent savepoint (if one exists). Do nothing if
68 savepoints are not supported.
69 """
70 get_connection(using).savepoint_commit(sid)
71
72
73def clean_savepoints(using=None):
74 """
75 Reset the counter used to generate unique savepoint ids in this thread.
76 """
77 get_connection(using).clean_savepoints()
78
79
80def get_rollback(using=None):
81 """Get the "needs rollback" flag -- for *advanced use* only."""
82 return get_connection(using).get_rollback()
83
84
85def set_rollback(rollback, using=None):
86 """
87 Set or unset the "needs rollback" flag -- for *advanced use* only.
88
89 When `rollback` is `True`, trigger a rollback when exiting the innermost
90 enclosing atomic block that has `savepoint=True` (that's the default). Use
91 this to force a rollback without raising an exception.
92
93 When `rollback` is `False`, prevent such a rollback. Use this only after
94 rolling back to a known-good state! Otherwise, you break the atomic block
95 and data corruption may occur.
96 """
97 return get_connection(using).set_rollback(rollback)
98
99
100@contextmanager
101def mark_for_rollback_on_error(using=None):
102 """
103 Internal low-level utility to mark a transaction as "needs rollback" when
104 an exception is raised while not enforcing the enclosed block to be in a
105 transaction. This is needed by Model.save() and friends to avoid starting a
106 transaction when in autocommit mode and a single query is executed.
107
108 It's equivalent to:
109
110 connection = get_connection(using)
111 if connection.get_autocommit():
112 yield
113 else:
114 with transaction.atomic(using=using, savepoint=False):
115 yield
116
117 but it uses low-level utilities to avoid performance overhead.
118 """
119 try:
120 yield
121 except Exception as exc:
122 connection = get_connection(using)
123 if connection.in_atomic_block:
124 connection.needs_rollback = True
125 connection.rollback_exc = exc
126 raise
127
128
129def on_commit(func, using=None, robust=False):
130 """
131 Register `func` to be called when the current transaction is committed.
132 If the current transaction is rolled back, `func` will not be called.
133 """
134 get_connection(using).on_commit(func, robust)
135
136
137#################################
138# Decorators / context managers #
139#################################
140
141
142class Atomic(ContextDecorator):
143 """
144 Guarantee the atomic execution of a given block.
145
146 An instance can be used either as a decorator or as a context manager.
147
148 When it's used as a decorator, __call__ wraps the execution of the
149 decorated function in the instance itself, used as a context manager.
150
151 When it's used as a context manager, __enter__ creates a transaction or a
152 savepoint, depending on whether a transaction is already in progress, and
153 __exit__ commits the transaction or releases the savepoint on normal exit,
154 and rolls back the transaction or to the savepoint on exceptions.
155
156 It's possible to disable the creation of savepoints if the goal is to
157 ensure that some code runs within a transaction without creating overhead.
158
159 A stack of savepoints identifiers is maintained as an attribute of the
160 connection. None denotes the absence of a savepoint.
161
162 This allows reentrancy even if the same AtomicWrapper is reused. For
163 example, it's possible to define `oa = atomic('other')` and use `@oa` or
164 `with oa:` multiple times.
165
166 Since database connections are thread-local, this is thread-safe.
167
168 An atomic block can be tagged as durable. In this case, raise a
169 RuntimeError if it's nested within another atomic block. This guarantees
170 that database changes in a durable block are committed to the database when
171 the block exists without error.
172
173 This is a private API.
174 """
175
176 def __init__(self, using, savepoint, durable):
177 self.using = using
178 self.savepoint = savepoint
179 self.durable = durable
180 self._from_testcase = False
181
182 def __enter__(self):
183 connection = get_connection(self.using)
184
185 if (
186 self.durable
187 and connection.atomic_blocks
188 and not connection.atomic_blocks[-1]._from_testcase
189 ):
190 raise RuntimeError(
191 "A durable atomic block cannot be nested within another atomic block."
192 )
193 if not connection.in_atomic_block:
194 # Reset state when entering an outermost atomic block.
195 connection.commit_on_exit = True
196 connection.needs_rollback = False
197 if not connection.get_autocommit():
198 # Pretend we're already in an atomic block to bypass the code
199 # that disables autocommit to enter a transaction, and make a
200 # note to deal with this case in __exit__.
201 connection.in_atomic_block = True
202 connection.commit_on_exit = False
203
204 if connection.in_atomic_block:
205 # We're already in a transaction; create a savepoint, unless we
206 # were told not to or we're already waiting for a rollback. The
207 # second condition avoids creating useless savepoints and prevents
208 # overwriting needs_rollback until the rollback is performed.
209 if self.savepoint and not connection.needs_rollback:
210 sid = connection.savepoint()
211 connection.savepoint_ids.append(sid)
212 else:
213 connection.savepoint_ids.append(None)
214 else:
215 connection.set_autocommit(
216 False, force_begin_transaction_with_broken_autocommit=True
217 )
218 connection.in_atomic_block = True
219
220 if connection.in_atomic_block:
221 connection.atomic_blocks.append(self)
222
223 def __exit__(self, exc_type, exc_value, traceback):
224 connection = get_connection(self.using)
225
226 if connection.in_atomic_block:
227 connection.atomic_blocks.pop()
228
229 if connection.savepoint_ids:
230 sid = connection.savepoint_ids.pop()
231 else:
232 # Prematurely unset this flag to allow using commit or rollback.
233 connection.in_atomic_block = False
234
235 try:
236 if connection.closed_in_transaction:
237 # The database will perform a rollback by itself.
238 # Wait until we exit the outermost block.
239 pass
240
241 elif exc_type is None and not connection.needs_rollback:
242 if connection.in_atomic_block:
243 # Release savepoint if there is one
244 if sid is not None:
245 try:
246 connection.savepoint_commit(sid)
247 except DatabaseError:
248 try:
249 connection.savepoint_rollback(sid)
250 # The savepoint won't be reused. Release it to
251 # minimize overhead for the database server.
252 connection.savepoint_commit(sid)
253 except Error:
254 # If rolling back to a savepoint fails, mark for
255 # rollback at a higher level and avoid shadowing
256 # the original exception.
257 connection.needs_rollback = True
258 raise
259 else:
260 # Commit transaction
261 try:
262 connection.commit()
263 except DatabaseError:
264 try:
265 connection.rollback()
266 except Error:
267 # An error during rollback means that something
268 # went wrong with the connection. Drop it.
269 connection.close()
270 raise
271 else:
272 # This flag will be set to True again if there isn't a savepoint
273 # allowing to perform the rollback at this level.
274 connection.needs_rollback = False
275 if connection.in_atomic_block:
276 # Roll back to savepoint if there is one, mark for rollback
277 # otherwise.
278 if sid is None:
279 connection.needs_rollback = True
280 else:
281 try:
282 connection.savepoint_rollback(sid)
283 # The savepoint won't be reused. Release it to
284 # minimize overhead for the database server.
285 connection.savepoint_commit(sid)
286 except Error:
287 # If rolling back to a savepoint fails, mark for
288 # rollback at a higher level and avoid shadowing
289 # the original exception.
290 connection.needs_rollback = True
291 else:
292 # Roll back transaction
293 try:
294 connection.rollback()
295 except Error:
296 # An error during rollback means that something
297 # went wrong with the connection. Drop it.
298 connection.close()
299
300 finally:
301 # Outermost block exit when autocommit was enabled.
302 if not connection.in_atomic_block:
303 if connection.closed_in_transaction:
304 connection.connection = None
305 else:
306 connection.set_autocommit(True)
307 # Outermost block exit when autocommit was disabled.
308 elif not connection.savepoint_ids and not connection.commit_on_exit:
309 if connection.closed_in_transaction:
310 connection.connection = None
311 else:
312 connection.in_atomic_block = False
313
314
315def atomic(using=None, savepoint=True, durable=False):
316 # Bare decorator: @atomic -- although the first argument is called
317 # `using`, it's actually the function being decorated.
318 if callable(using):
319 return Atomic(DEFAULT_DB_ALIAS, savepoint, durable)(using)
320 # Decorator: @atomic(...) or context manager: with atomic(...): ...
321 else:
322 return Atomic(using, savepoint, durable)