Skip to content

Commit 7426859

Browse files
committed
stub out docs
1 parent ff4d5c1 commit 7426859

File tree

5 files changed

+314
-0
lines changed

5 files changed

+314
-0
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ repos:
4444
hooks:
4545
- id: rstcheck
4646
additional_dependencies: [sphinx]
47+
args: ["--ignore-directives=fieldlookup", "--ignore-roles=lookup"]
4748

4849
# We use the Python version instead of the original version which seems to require Docker
4950
# https://github.com/koalaman/shellcheck-precommit

docs/source/_ext/djangodocs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def setup(app):
2+
app.add_crossref_type(
3+
directivename="fieldlookup",
4+
rolename="lookup",
5+
indextemplate="pair: %s; field lookup type",
6+
)

docs/source/conf.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@
77
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
88
from __future__ import annotations
99

10+
import sys
1011
from importlib.metadata import version as _version
12+
from pathlib import Path
13+
14+
# If extensions (or modules to document with autodoc) are in another directory,
15+
# add these directories to sys.path here. If the directory is relative to the
16+
# documentation root, use os.path.abspath to make it absolute, like shown here.
17+
sys.path.append(str((Path(__file__).parent / "_ext").resolve()))
1118

1219
project = "django_mongodb"
1320
copyright = "2024, The MongoDB Python Team"
@@ -22,6 +29,7 @@
2229
add_module_names = False
2330

2431
extensions = [
32+
"djangodocs",
2533
"sphinx.ext.intersphinx",
2634
]
2735

docs/source/fields.rst

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,172 @@ Model field reference
55

66
Some MongoDB-specific fields are available in ``django_mongodb.fields``.
77

8+
``ArrayField``
9+
--------------
10+
11+
.. class:: ArrayField(base_field, size=None, **options)
12+
13+
A field for storing lists of data. Most field types can be used, and you
14+
pass another field instance as the :attr:`base_field
15+
<ArrayField.base_field>`. You may also specify a :attr:`size
16+
<ArrayField.size>`. ``ArrayField`` can be nested to store multi-dimensional
17+
arrays.
18+
19+
If you give the field a :attr:`~django.db.models.Field.default`, ensure
20+
it's a callable such as ``list`` (for an empty default) or a callable that
21+
returns a list (such as a function). Incorrectly using ``default=[]``
22+
creates a mutable default that is shared between all instances of
23+
``ArrayField``.
24+
25+
.. attribute:: base_field
26+
27+
This is a required argument.
28+
29+
Specifies the underlying data type and behavior for the array. It
30+
should be an instance of a subclass of
31+
:class:`~django.db.models.Field`. For example, it could be an
32+
:class:`~django.db.models.IntegerField` or a
33+
:class:`~django.db.models.CharField`. Most field types are permitted,
34+
with the exception of those handling relational data
35+
(:class:`~django.db.models.ForeignKey`,
36+
:class:`~django.db.models.OneToOneField` and
37+
:class:`~django.db.models.ManyToManyField`) and file fields (
38+
:class:`~django.db.models.FileField` and
39+
:class:`~django.db.models.ImageField`).
40+
41+
It is possible to nest array fields - you can specify an instance of
42+
``ArrayField`` as the ``base_field``. For example::
43+
44+
from django.db import models
45+
from django_mongodb.fields import ArrayField
46+
47+
48+
class ChessBoard(models.Model):
49+
board = ArrayField(
50+
ArrayField(
51+
models.CharField(max_length=10, blank=True),
52+
size=8,
53+
),
54+
size=8,
55+
)
56+
57+
Transformation of values between the database and the model, validation
58+
of data and configuration, and serialization are all delegated to the
59+
underlying base field.
60+
61+
.. attribute:: size
62+
63+
This is an optional argument.
64+
65+
If passed, the array will have a maximum size as specified, validated
66+
only by forms.
67+
68+
Querying ``ArrayField``
69+
~~~~~~~~~~~~~~~~~~~~~~~
70+
71+
There are a number of custom lookups and transforms for :class:`ArrayField`.
72+
We will use the following example model::
73+
74+
from django.db import models
75+
from django_mongodb.fields import ArrayField
76+
77+
78+
class Post(models.Model):
79+
name = models.CharField(max_length=200)
80+
tags = ArrayField(models.CharField(max_length=200), blank=True)
81+
82+
def __str__(self):
83+
return self.name
84+
85+
.. fieldlookup:: arrayfield.contains
86+
87+
``contains``
88+
^^^^^^^^^^^^
89+
90+
The :lookup:`contains` lookup is overridden on :class:`ArrayField`. The
91+
returned objects will be those where the values passed are a subset of the
92+
data. It uses the ``$setIntersection`` operator. For example:
93+
94+
.. code-block:: pycon
95+
96+
>>> Post.objects.create(name="First post", tags=["thoughts", "django"])
97+
>>> Post.objects.create(name="Second post", tags=["thoughts"])
98+
>>> Post.objects.create(name="Third post", tags=["tutorial", "django"])
99+
100+
>>> Post.objects.filter(tags__contains=["thoughts"])
101+
<QuerySet [<Post: First post>, <Post: Second post>]>
102+
103+
>>> Post.objects.filter(tags__contains=["django"])
104+
<QuerySet [<Post: First post>, <Post: Third post>]>
105+
106+
>>> Post.objects.filter(tags__contains=["django", "thoughts"])
107+
<QuerySet [<Post: First post>]>
108+
109+
.. fieldlookup:: arrayfield.len
110+
111+
``len``
112+
^^^^^^^
113+
114+
Returns the length of the array. The lookups available afterward are those
115+
available for :class:`~django.db.models.IntegerField`. For example:
116+
117+
.. code-block:: pycon
118+
119+
>>> Post.objects.create(name="First post", tags=["thoughts", "django"])
120+
>>> Post.objects.create(name="Second post", tags=["thoughts"])
121+
122+
>>> Post.objects.filter(tags__len=1)
123+
<QuerySet [<Post: Second post>]>
124+
125+
.. fieldlookup:: arrayfield.index
126+
127+
Index transforms
128+
^^^^^^^^^^^^^^^^
129+
130+
Index transforms index into the array. Any non-negative integer can be used.
131+
There are no errors if it exceeds the :attr:`size <ArrayField.size>` of the
132+
array. The lookups available after the transform are those from the
133+
:attr:`base_field <ArrayField.base_field>`. For example:
134+
135+
.. code-block:: pycon
136+
137+
>>> Post.objects.create(name="First post", tags=["thoughts", "django"])
138+
>>> Post.objects.create(name="Second post", tags=["thoughts"])
139+
140+
>>> Post.objects.filter(tags__0="thoughts")
141+
<QuerySet [<Post: First post>, <Post: Second post>]>
142+
143+
>>> Post.objects.filter(tags__1__iexact="Django")
144+
<QuerySet [<Post: First post>]>
145+
146+
>>> Post.objects.filter(tags__276="javascript")
147+
<QuerySet []>
148+
149+
These indexes use 0-based indexing.
150+
151+
.. fieldlookup:: arrayfield.slice
152+
153+
Slice transforms
154+
^^^^^^^^^^^^^^^^
155+
156+
Slice transforms take a slice of the array. Any two non-negative integers can
157+
be used, separated by a single underscore. The lookups available after the
158+
transform do not change. For example:
159+
160+
.. code-block:: pycon
161+
162+
>>> Post.objects.create(name="First post", tags=["thoughts", "django"])
163+
>>> Post.objects.create(name="Second post", tags=["thoughts"])
164+
>>> Post.objects.create(name="Third post", tags=["django", "python", "thoughts"])
165+
166+
>>> Post.objects.filter(tags__0_1=["thoughts"])
167+
<QuerySet [<Post: First post>, <Post: Second post>]>
168+
169+
>>> Post.objects.filter(tags__0_2__contains=["thoughts"])
170+
<QuerySet [<Post: First post>, <Post: Second post>]>
171+
172+
These indexes use 0-based indexing.
173+
8174
``ObjectIdField``
9175
-----------------
10176

docs/source/forms.rst

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,136 @@ Some MongoDB-specific fields are available in ``django_mongodb.forms``.
1111
.. class:: ObjectIdField
1212

1313
Stores an :class:`~bson.objectid.ObjectId`.
14+
15+
``SimpleArrayField``
16+
--------------------
17+
18+
.. class:: SimpleArrayField(base_field, delimiter=',', max_length=None, min_length=None)
19+
20+
A field which maps to an array. It is represented by an HTML ``<input>``.
21+
22+
.. attribute:: base_field
23+
24+
This is a required argument.
25+
26+
It specifies the underlying form field for the array. This is not used
27+
to render any HTML, but it is used to process the submitted data and
28+
validate it. For example:
29+
30+
.. code-block:: pycon
31+
32+
>>> from django import forms
33+
>>> from django_mongodb.forms import SimpleArrayField
34+
35+
>>> class NumberListForm(forms.Form):
36+
... numbers = SimpleArrayField(forms.IntegerField())
37+
...
38+
39+
>>> form = NumberListForm({"numbers": "1,2,3"})
40+
>>> form.is_valid()
41+
True
42+
>>> form.cleaned_data
43+
{'numbers': [1, 2, 3]}
44+
45+
>>> form = NumberListForm({"numbers": "1,2,a"})
46+
>>> form.is_valid()
47+
False
48+
49+
.. attribute:: delimiter
50+
51+
This is an optional argument which defaults to a comma: ``,``. This
52+
value is used to split the submitted data. It allows you to chain
53+
``SimpleArrayField`` for multidimensional data:
54+
55+
.. code-block:: pycon
56+
57+
>>> from django import forms
58+
>>> from django_mongodb.forms import SimpleArrayField
59+
60+
>>> class GridForm(forms.Form):
61+
... places = SimpleArrayField(SimpleArrayField(IntegerField()), delimiter="|")
62+
...
63+
64+
>>> form = GridForm({"places": "1,2|2,1|4,3"})
65+
>>> form.is_valid()
66+
True
67+
>>> form.cleaned_data
68+
{'places': [[1, 2], [2, 1], [4, 3]]}
69+
70+
.. note::
71+
72+
The field does not support escaping of the delimiter, so be careful
73+
in cases where the delimiter is a valid character in the underlying
74+
field. The delimiter does not need to be only one character.
75+
76+
.. attribute:: max_length
77+
78+
This is an optional argument which validates that the array does not
79+
exceed the stated length.
80+
81+
.. attribute:: min_length
82+
83+
This is an optional argument which validates that the array reaches at
84+
least the stated length.
85+
86+
.. admonition:: User friendly forms
87+
88+
``SimpleArrayField`` is not particularly user friendly in most cases,
89+
however it is a useful way to format data from a client-side widget for
90+
submission to the server.
91+
92+
``SplitArrayField``
93+
-------------------
94+
95+
.. class:: SplitArrayField(base_field, size, remove_trailing_nulls=False)
96+
97+
This field handles arrays by reproducing the underlying field a fixed
98+
number of times.
99+
100+
.. attribute:: base_field
101+
102+
This is a required argument. It specifies the form field to be
103+
repeated.
104+
105+
.. attribute:: size
106+
107+
This is the fixed number of times the underlying field will be used.
108+
109+
.. attribute:: remove_trailing_nulls
110+
111+
By default, this is set to ``False``. When ``False``, each value from
112+
the repeated fields is stored. When set to ``True``, any trailing
113+
values which are blank will be stripped from the result. If the
114+
underlying field has ``required=True``, but ``remove_trailing_nulls``
115+
is ``True``, then null values are only allowed at the end, and will be
116+
stripped.
117+
118+
Some examples::
119+
120+
SplitArrayField(IntegerField(required=True), size=3, remove_trailing_nulls=False)
121+
122+
["1", "2", "3"] # -> [1, 2, 3]
123+
["1", "2", ""] # -> ValidationError - third entry required.
124+
["1", "", "3"] # -> ValidationError - second entry required.
125+
["", "2", ""] # -> ValidationError - first and third entries required.
126+
127+
SplitArrayField(IntegerField(required=False), size=3, remove_trailing_nulls=False)
128+
129+
["1", "2", "3"] # -> [1, 2, 3]
130+
["1", "2", ""] # -> [1, 2, None]
131+
["1", "", "3"] # -> [1, None, 3]
132+
["", "2", ""] # -> [None, 2, None]
133+
134+
SplitArrayField(IntegerField(required=True), size=3, remove_trailing_nulls=True)
135+
136+
["1", "2", "3"] # -> [1, 2, 3]
137+
["1", "2", ""] # -> [1, 2]
138+
["1", "", "3"] # -> ValidationError - second entry required.
139+
["", "2", ""] # -> ValidationError - first entry required.
140+
141+
SplitArrayField(IntegerField(required=False), size=3, remove_trailing_nulls=True)
142+
143+
["1", "2", "3"] # -> [1, 2, 3]
144+
["1", "2", ""] # -> [1, 2]
145+
["1", "", "3"] # -> [1, None, 3]
146+
["", "2", ""] # -> [None, 2]

0 commit comments

Comments
 (0)