Skip to content

Commit 60498a8

Browse files
committed
Split documentation into multiple pages
1 parent 708763e commit 60498a8

File tree

4 files changed

+179
-181
lines changed

4 files changed

+179
-181
lines changed

docs/features.md

Lines changed: 0 additions & 181 deletions
This file was deleted.

docs/hstorefield.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# HStoreField
2+
`psqlextra.fields.HStoreField` is based on Django's [HStoreField](https://docs.djangoproject.com/en/1.10/ref/contrib/postgres/fields/#hstorefield) and therefore supports everything Django does natively, plus more.
3+
4+
## Unique constraint
5+
The `uniqueness` constraint can be added on one or more `hstore` keys, similar to how a `UNIQUE` constraint can be added to a column. Setting this option causes unique indexes to be created on the specified keys.
6+
7+
You can specify a `list` of strings to specify the keys that must be marked as unique:
8+
9+
from psqlextra.fields import HStoreField
10+
from psqlextra.models import PostgresModel
11+
12+
class MyModel(PostgresModel):
13+
myfield = HStoreField(uniqueness=['key1']
14+
15+
MyModel.objects.create(myfield={'key1': 'value1'})
16+
MyModel.objects.create(myfield={'key1': 'value1'})
17+
18+
The second `create` call will fail with a [IntegrityError](https://docs.djangoproject.com/en/1.10/ref/exceptions/#django.db.IntegrityError) because there's already a row with `key1=value1`.
19+
20+
Uniqueness can also be enforced "together", similar to Django's [unique_together](https://docs.djangoproject.com/en/1.10/ref/models/options/#unique-together) by specifying a tuple of fields rather than a single string:
21+
22+
myfield = HStoreField(uniqueness=[('key1', 'key2'), 'key3])
23+
24+
In the example above, `key1` and `key2` must unique **together**, and `key3` must unique on its own. By default, none of the keys are marked as "unique".
25+
26+
## Not null constraint
27+
The `required` option can be added to ensure that the specified `hstore` keys are set for every row. This is similar to a `NOT NULL` constraint on a column. You can specify a list of `hstore` keys that are required:
28+
29+
from psqlextra.fields import HStoreField
30+
from psqlextra.models import PostgresModel
31+
32+
class MyModel(PostgresModel):
33+
myfield = HStoreField(required=['key1'])
34+
35+
mymodel.objects.create(myfield={'key1': none})
36+
MyModel.objects.create(myfield={'key2': 'value1'})
37+
38+
Both calls to `create` would fail in the example above since they do not provide a non-null value for `key1`. By default, none of the keys are required.

docs/manager.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Upsert
2+
An "upsert" is an operation where a piece of data is inserted/created if it doesn't exist yet and updated (overwritten) when it already exists. Django has long provided this functionality through [`update_or_create`](https://docs.djangoproject.com/en/1.10/ref/models/querysets/#update-or-create). It does this by first checking whether the record exists and creating it not.
3+
4+
The major problem with this approach is possibility of race conditions. In between the `SELECT` and `INSERT`, another process could perform the `INSERT`. The last `INSERT` would most likely fail because it would be duplicating a `UNIQUE` constraint.
5+
6+
In order to combat this, PostgreSQL added native upserts. Also known as [`ON CONFLICT DO ...`](https://www.postgresql.org/docs/9.5/static/sql-insert.html#SQL-ON-CONFLICT). This allows a user to specify what to do when a conflict occurs.
7+
8+
9+
## upsert
10+
The `upsert` method attempts to insert a row with the specified data or updates (and overwrites) the duplicate row, and then returns the primary key of the row that was created/updated.
11+
12+
Upserts work by catching conflicts. PostgreSQL requires to know which conflicts to react to. You have to specify the name of the column which's constraint you want to react to. This is specified in the `conflict_target` field. If the constraint you're trying to react to consists of multiple columns, specify multiple columns.
13+
14+
from django.db import models
15+
from psqlextra.models import PostgresModel
16+
17+
class MyModel(PostgresModel):
18+
myfield = models.CharField(max_length=255, unique=True)
19+
20+
id1 = MyModel.objects.upsert(
21+
conflict_target=['myfield'],
22+
fields=dict(
23+
myfield='beer'
24+
)
25+
)
26+
27+
id2 = MyModel.objects.upsert(
28+
conflict_target=['myfield'],
29+
fields=dict(
30+
myfield='beer'
31+
)
32+
)
33+
34+
assert id1 == id2
35+
36+
Note that a single call to `upsert` results in a single `INSERT INTO ... ON CONFLICT DO UPDATE ...`. This fixes the problem outlined earlier about another process doing the `INSERT` in the mean time.
37+
38+
## upsert_and_get
39+
`upsert_and_get` does the same thing as `upsert`, but returns a model instance rather than the primary key of the row that was created/updated. This also happens in a single query using `RETURNING` clause on the `INSERT INTO` statement:
40+
41+
from django.db import models
42+
from psqlextra.models import PostgresModel
43+
44+
class MyModel(PostgresModel):
45+
myfield = models.CharField(max_length=255, unique=True)
46+
47+
obj1 = MyModel.objects.upsert_and_get(myfield='beer')
48+
obj2 = MyModel.objects.upsert_and_get(myfield='beer')
49+
50+
obj1 = MyModel.objects.upsert_and_get(
51+
conflict_target=['myfield'],
52+
fields=dict(
53+
myfield='beer'
54+
)
55+
)
56+
57+
obj2 = MyModel.objects.upsert_and_get(
58+
conflict_target=['myfield'],
59+
fields=dict(
60+
myfield='beer'
61+
)
62+
)
63+
64+
assert obj1.id == obj2.id

docs/signals.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Signals
2+
Django has long supported "signals", a feature that can be really useful. It allows you to get notified in a callback when a certain event occurs. One of the most common use cases is connecting to model signals. These signals get triggered when a model gets saved, or deleted.
3+
4+
Django's built-in signals have one major flaw:
5+
6+
* The `QuerySet.update(..)` method does **not** emit **any** signals.
7+
8+
Because of this limitation, Django's signals cannot be reliably used to be signaled on model changes. `django-postges-extra` adds three new signals which are much more primitive, but work reliably across the board.
9+
10+
The signals defined by this library are completely valid, standard Django signals. Therefore, their documentation also applies: [https://docs.djangoproject.com/en/1.10/topics/signals/](https://docs.djangoproject.com/en/1.10/topics/signals/).
11+
12+
Each of the signals send upon model modification send one parameter containing the value of the primary key of the row that was affected. Therefore the signal's signature looks like this:
13+
14+
def my_receiver(sender, pk: int):
15+
# pk is the primary key, a keyword argument
16+
17+
## psqlextra.signals.create
18+
Send **after** a new model instance was created.
19+
20+
from django.db import models
21+
from psqlextra.models import PostgresModel
22+
from psqlextra import signals
23+
24+
class MyModel(PostgresModel):
25+
myfield = models.CharField(max_length=255, unique=True)
26+
27+
def on_create(sender, **kwargs):
28+
print('model created with pk %d' % kwargs['pk'])
29+
30+
signals.create.connect(MyModel, on_create, weak=False)
31+
32+
# this will trigger the signal
33+
instance = MyModel(myfield='cookies')
34+
instance.save()
35+
36+
# but so will this
37+
MyModel.objects.create(myfield='cheers')
38+
39+
## psqlextra.signals.update
40+
Send **after** a new model instance was updated.
41+
42+
from django.db import models
43+
from psqlextra.models import PostgresModel
44+
from psqlextra import signals
45+
46+
class MyModel(PostgresModel):
47+
myfield = models.CharField(max_length=255, unique=True)
48+
49+
def on_update(sender, **kwargs):
50+
print('model updated with pk %d' % kwargs['pk'])
51+
52+
signals.update.connect(MyModel, on_update, weak=False)
53+
54+
# for every row that is affected, the signal will be send
55+
MyModel.objects.filter(myfield='cookies').update(myfield='cheers')
56+
57+
## psqlextra.signals.delete
58+
Send **before** a new model instance is deleted.
59+
60+
from django.db import models
61+
from psqlextra.models import PostgresModel
62+
from psqlextra import signals
63+
64+
class MyModel(PostgresModel):
65+
myfield = models.CharField(max_length=255, unique=True)
66+
67+
def on_delete(sender, **kwargs):
68+
print('model deleted with pk %d' % kwargs['pk'])
69+
70+
signals.delete.connect(MyModel, on_update, weak=False)
71+
72+
# for every row that is affected, the signal will be send
73+
MyModel.objects.filter(myfield='cookies').delete()
74+
75+
# in this case, a single row is deleted, the signal will be send
76+
# for this particular row
77+
MyModel.objects.get(id=1).delete()

0 commit comments

Comments
 (0)