Skip to content

Commit e0ad13a

Browse files
committed
Documentation, 0.3.1
1 parent 10f43e0 commit e0ad13a

8 files changed

+278
-7
lines changed

Makefile docs/Makefile

File renamed without changes.

conf.py docs/conf.py

+11
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@
7676
# a list of builtin themes.
7777
#
7878
html_theme = 'alabaster'
79+
# html_theme = 'sphinx_rtd_theme'
80+
81+
html_sidebars = {
82+
'**': [
83+
'about.html',
84+
'localtoc.html',
85+
'navigation.html',
86+
'relations.html',
87+
'searchbox.html',
88+
]
89+
}
7990

8091
# Theme options are theme-specific and customize the look and feel of a theme
8192
# further. For a list of options available for each theme, see the

index.rst docs/index.rst

+9
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,12 @@ Also
148148
Huge thanks to https://github.com/kayak/pypika for making this possible.
149149

150150
If you want to contribute check out issues, or just straightforwardly create PR
151+
152+
153+
Table Of Contents
154+
=================
155+
156+
.. toctree::
157+
158+
models_and_fields
159+
query

docs/models_and_fields.rst

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
=================
2+
Models and fields
3+
=================
4+
To get working with models, first you should import them
5+
from tortoise.models import Model
6+
7+
With that you can start describing your own models like that
8+
9+
.. code-block:: python
10+
11+
class Tournament(Model):
12+
id = fields.IntField(pk=True)
13+
name = fields.StringField()
14+
created = fields.DatetimeField(auto_now_add=True)
15+
16+
def __str__(self):
17+
return self.name
18+
19+
20+
class Event(Model):
21+
id = fields.IntField(pk=True)
22+
name = fields.StringField()
23+
tournament = fields.ForeignKeyField('models.Tournament', related_name='events')
24+
participants = fields.ManyToManyField('models.Team', related_name='events', through='event_team')
25+
modified = fields.DatetimeField(auto_now=True)
26+
prize = fields.DecimalField(max_digits=10, decimal_places=2, null=True)
27+
28+
def __str__(self):
29+
return self.name
30+
31+
32+
class Team(Model):
33+
id = fields.IntField(pk=True)
34+
name = fields.StringField()
35+
36+
def __str__(self):
37+
return self.name
38+
39+
Let see in details what we accomplished here:
40+
41+
.. code-block:: python
42+
43+
class Tournament(Model):
44+
45+
Every model should be derived from base model. You also can derive from your own model subclasses and you can make abstract models like this
46+
47+
.. code-block:: python
48+
49+
class AbstractTournament(Model):
50+
id = fields.IntField(pk=True)
51+
name = fields.StringField()
52+
created = fields.DatetimeField(auto_now_add=True)
53+
54+
class Meta:
55+
abstract = True
56+
57+
def __str__(self):
58+
return self.name
59+
60+
This models won't be created in schema generation and won't create relations to other models.
61+
62+
Further, let's take a look at created fields for models
63+
64+
.. code-block:: python
65+
66+
id = fields.IntField(pk=True)
67+
68+
This code defines integer primary key for table. Sadly, currently only simple integer pk is supported.
69+
70+
.. code-block:: python
71+
72+
tournament = fields.ForeignKeyField('models.Tournament', related_name='events')
73+
participants = fields.ManyToManyField('models.Team', related_name='events', through='event_team')
74+
modified = fields.DatetimeField(auto_now=True)
75+
prize = fields.DecimalField(max_digits=10, decimal_places=2, null=True)
76+
77+
In event model we got some more fields, that could be interesting for us.
78+
``fields.ForeignKeyField('models.Tournament', related_name='events')`` - here we create foreign key reference to tournament. We create it by referring to model by it's literal, consisting of app name and model name. `models` is default app name, but you can change it in `class Meta` with `app = 'other'`.
79+
``related_name`` is keyword argument, that defines field for related query on referenced models, so with that you could fetch all tournaments's events with like this:
80+
81+
.. code-block:: python
82+
83+
await tournament.events.all()
84+
85+
or like this:
86+
87+
.. code-block:: python
88+
89+
await tournament.fetch_related('events')
90+
91+
92+
Next field is ``fields.ManyToManyField('models.Team', related_name='events', through='event_team')``. It describes many to many relation to model Team.
93+
Here we have additional kwarg ``through`` that defines name of intermediate table.
94+
95+
Further we have field ``fields.DatetimeField(auto_now=True)``. Options ``auto_now`` and ``auto_now_add`` work like Django's options.
96+
97+
Init app
98+
========
99+
100+
After you defined all your models, tortoise needs you to init them, in order to create backward relations between models and match your db client with appropriate models.
101+
102+
You can do it like this:
103+
104+
.. code-block:: python
105+
106+
from tortoise.backends.asyncpg.client import AsyncpgDBClient
107+
from tortoise import Tortoise
108+
from app import models # without importing models Tortoise can't find and init them
109+
110+
111+
async def init():
112+
db = AsyncpgDBClient(
113+
host='localhost',
114+
port=5432,
115+
user='postgres',
116+
password='qwerty123',
117+
database='events',
118+
)
119+
120+
await db.create_connection()
121+
Tortoise.init(db)
122+
123+
await generate_schema(client)
124+
125+
Here we create connection to database with default asyncpg client and then we init models. Be sure that you have your models imported in the app. Usually that's the case, because you use your models across you app, but if you have only local imports of it, tortoise won't be able to find them and init them with connection to db.
126+
``generate_schema`` generates schema on empty database, you shouldn't run it on every app init, run it just once, maybe out of your main code.
127+
128+
129+
Fields
130+
======
131+
132+
Here is list of fields available at the moment with custom options of this fields:
133+
134+
- IntField (``pk``)
135+
- SmallIntField
136+
- StringField
137+
- BooleanField
138+
- DecimalField (``max_digits``, ``decimal_places``)
139+
- DatetimeField (``auto_now``, ``auto_now_add``)
140+
- DateField
141+
- ForeignKeyField (model_name, related_name, on_delete)
142+
- ManyToManyField (``model_name``, ``related_name``, ``through``, ``backward_key``, ``forward_key``)
143+
144+
Also all fields fields have this options:
145+
- ``source_field`` - field name in schema, can be different from field name
146+
- ``null`` - is field nullable
147+
- ``default`` - default value for field
148+
- ``generated`` - flag that says that this field is read only and value should be generated in db. Normally, should be used only if you working on already created schema, not generated by tortoise.
149+

docs/query.rst

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
=========
2+
Query API
3+
=========
4+
5+
Be sure to check `examples <https://github.com/Zeliboba5/tortoise-orm/tree/master/examples>`_ for better understanding
6+
7+
You start your query from your model instance:
8+
9+
.. code-block:: python
10+
11+
Event.filter(id=1)
12+
13+
There are several method on model itself to start query:
14+
15+
- ``first(*args, **kwargs)`` - create QuerySet with given filters
16+
- ``all()`` - create QuerySet without filters
17+
- ``first()`` - create QuerySet limited to one object and returning instance instead of list
18+
19+
This method returns ``QuerySet`` object, that allows further filtering and some more complex operations
20+
21+
Also model class have this methods to create object:
22+
23+
- ``create(**kwargs)`` - creates object with given kwargs
24+
- ``get_or_create(defaults, **kwargs)`` - gets object for given kwargs, if not found create it with additional kwargs from defaults dict
25+
26+
Also instance of model itself has these methods:
27+
28+
- ``save()`` - update instance, or insert it, if it was never saved before
29+
- ``delete()`` - delete instance from db
30+
- ``fetch_related(*args)`` - fetches objects related to instance. It can fetch fk relation, backward fk realtion and m2m relation. It also can fetch variable depth of related objects like this: ``await team.fetch_related('events__tournament')`` - this will fetch all events for team, and for each of this events their tournament will be prefetched too. After fetching objects they should be available normally like this: ``team.events[0].tournament.name``
31+
32+
Another approach to work with related objects on instance is to query them explicitly in ``async for``:
33+
34+
.. code-block:: python
35+
36+
async for team in event.participants:
37+
print(team.name)
38+
39+
You also can filter related objects like this:
40+
41+
await team.events.filter(name='First')
42+
43+
which will return you a QuerySet object with predefined filter
44+
45+
QuerySet
46+
========
47+
48+
After you obtained queryset from object you can do following operations with it:
49+
50+
- ``filter(*args, **kwargs)`` - filters QuerySet by given kwargs. You can filter by related objects like this: ``Team.filter(events__tournament__name='Test')``. You can also pass ``Q`` objects to ``filters`` as args.
51+
- ``order_by(*args)`` - accept args to filter by in format like ``.order_by('name', '-tournament__name')``. Supports ordering by related models too.
52+
- ``limit(limit)`` - limits QuerySet by given int
53+
- ``offset(offset)`` - offset for QuerySet
54+
- ``distinct()`` - make QuerySet distinct
55+
- ``values_list(*args, flat=False)`` - make QuerySet returns list of tuples for given args instead of objects. If ``flat`` is ``True`` and only one arg is passed can return flat list
56+
- ``values(*args)`` - make QuerySet return dicts instead of objects
57+
- ``delete()`` - deletes all objects in QuerySet
58+
- ``update(**kwargs)`` - updates all objects in QuerySet with given kwargs
59+
- ``count()`` - returns count of objects in queryset instead of objects
60+
- ``first()`` - limit queryset to one object and return one object instead of list
61+
- ``prefetch_related(*args)`` - like ``.fetch_related()`` on instance, but works on all objects in QuerySet.
62+
- ``using_db(client)`` - executes query in other db client. Useful for transactions workaround.
63+
64+
After making your QuerySet you just have to ``await`` it to get the result
65+
66+
Check `examples <https://github.com/Zeliboba5/tortoise-orm/tree/master/examples>`_ to see it all in work
67+
68+
Many to Many
69+
============
70+
71+
Tortoise provides api for working with M2M relations
72+
- ``add(*args)`` - adds instances to relation
73+
- ``remove(*args)`` - removes instances from relation
74+
- ``clear()`` - removes all objects from relation
75+
76+
You can use them like this:
77+
78+
.. code-block:: python
79+
80+
await event.participants.add(participant_1, participant_2)
81+
82+
83+
84+
Q objects
85+
=========
86+
87+
When you need to make ``OR`` query or something a little more challenging you could use Q objects for it. It is as easy as this:
88+
89+
.. code-block:: python
90+
91+
found_events = await Event.filter(
92+
Q(id__in=[event_first.id, event_second.id]) | Q(name='3')
93+
)
94+

tortoise/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,4 @@ def init(cls, global_client=None, db_routing=None):
127127
cls._inited = True
128128

129129

130-
__version__ = "0.3.0"
130+
__version__ = "0.3.1"

tortoise/fields.py

+8
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,19 @@ def __init__(self, model, instance, m2m_field, is_new):
187187
async def add(self, *instances, using_db=None):
188188
db = using_db if using_db else self._db
189189
through_table = Table(self.field.through)
190+
select_query = db.query_class.from_(through_table).where(
191+
getattr(through_table, self.field.backward_key) == self.instance.id
192+
)
190193
query = db.query_class.into(through_table).columns(
191194
getattr(through_table, self.field.forward_key),
192195
getattr(through_table, self.field.backward_key),
193196
)
194197
for instance_to_add in instances:
198+
already_existing = await db.execute_query(str(select_query.where(
199+
getattr(through_table, self.field.forward_key) == instance_to_add.id
200+
).limit(1)))
201+
if already_existing:
202+
continue
195203
query = query.insert(instance_to_add.id, self.instance.id)
196204
await db.execute_query(str(query))
197205

tortoise/models.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,6 @@ async def save(self, *args, **kwargs):
266266
else:
267267
await self._update_instance(*args, **kwargs)
268268

269-
@classmethod
270-
async def create(cls, **kwargs):
271-
instance = cls(**kwargs)
272-
await instance.save(kwargs.get('using_db'))
273-
return instance
274-
275269
async def delete(self, using_db=None):
276270
db = using_db if using_db else self._meta.db
277271
if not self.id:
@@ -316,6 +310,12 @@ async def get_or_create(cls, using_db=None, defaults=None, **kwargs):
316310
return instance, False
317311
return await cls(**defaults, **kwargs).save(using_db=using_db), True
318312

313+
@classmethod
314+
async def create(cls, **kwargs):
315+
instance = cls(**kwargs)
316+
await instance.save(kwargs.get('using_db'))
317+
return instance
318+
319319
@classmethod
320320
def first(cls):
321321
return QuerySet(cls).first()

0 commit comments

Comments
 (0)