Set Fields¶
Legacy
These field classes are only maintained for legacy purposes. They aren’t recommended as comma separation is a fragile serialization format.
For new uses, you’re better off using Django 3.1’s JSONField
that works
with all database backends. On earlier versions of Django, you can use
django-jsonfield-backport.
Two fields that store sets of a base field in comma-separated strings -
cousins of Django’s CommaSeparatedIntegerField
.
There are two versions: SetCharField
, which is based on CharField
and
appropriate for storing sets with a small maximum size, and SetTextField
,
which is based on TextField
and therefore suitable for sets of (near)
unbounded size (the underlying LONGTEXT
MySQL datatype has a maximum length
of 232 - 1 bytes).
- SetCharField(base_field, size=None, **kwargs):
A field for storing sets of data, which all conform to the
base_field
.- django_mysql.models.base_field¶
The base type of the data that is stored in the set. Currently, must be
IntegerField
,CharField
, or any subclass thereof - except fromSetCharField
itself.
- django_mysql.models.size¶
Optionally set the maximum number of elements in the set. This is only checked on form validation, not on model save!
As
SetCharField
is a subclass ofCharField
, anyCharField
options can be set too. Most importantly you’ll need to setmax_length
to determine how many characters to reserve in the database.Example instantiation:
from django.db.models import IntegerField, Model from django_mysql.models import SetCharField class LotteryTicket(Model): numbers = SetCharField( base_field=IntegerField(), size=6, max_length=(6 * 3), # 6 two digit numbers plus commas )
In Python simply set the field’s value as a set:
>>> lt = LotteryTicket.objects.create(numbers={1, 2, 4, 8, 16, 32}) >>> lt.numbers {1, 2, 4, 8, 16, 32} >>> lt.numbers.remove(1) >>> lt.numbers.add(3) >>> lt.numbers {32, 3, 2, 4, 8, 16} >>> lt.save()
Validation on save()
When performing the set-to-string conversion for the database,
SetCharField
performs some validation, and will raiseValueError
if there is a problem, to avoid saving bad data. The following are invalid:If there is a comma in any member’s string representation
If the empty string is stored.
The default form field is
SimpleSetField
.
- SetTextField(base_field, size=None, **kwargs):
The same as
SetCharField
, but backed by aTextField
and therefore much less restricted in length. There is nomax_length
argument.Example instantiation:
from django.db.models import IntegerField, Model from django_mysql.models import SetTextField class Post(Model): tags = SetTextField( base_field=CharField(max_length=32), )
Querying Set Fields¶
Warning
These fields are not built-in datatypes, and the filters use one or more SQL functions to parse the underlying string representation. They may slow down on large tables if your queries are not selective on other columns.
contains¶
The contains
lookup is overridden on SetCharField
and SetTextField
to match where the set field contains the given element, using MySQL’s
FIND_IN_SET
(docs:
MariaDB /
MySQL).
For example:
>>> Post.objects.create(name="First post", tags={"thoughts", "django"})
>>> Post.objects.create(name="Second post", tags={"thoughts"})
>>> Post.objects.create(name="Third post", tags={"tutorial", "django"})
>>> Post.objects.filter(tags__contains="thoughts")
[<Post: First post>, <Post: Second post>]
>>> Post.objects.filter(tags__contains="django")
[<Post: First post>, <Post: Third post>]
>>> Post.objects.filter(Q(tags__contains="django") & Q(tags__contains="thoughts"))
[<Post: First post>]
Note
ValueError
will be raised if you try contains
with a set. It’s not
possible without using AND
in the query, so you should add the filters
for each item individually, as per the last example.
len¶
A transform that converts to the number of items in the set. For example:
>>> Post.objects.filter(tags__len=1)
[<Post: Second post>]
>>> Post.objects.filter(tags__len=2)
[<Post: First post>, <Post: Third post>]
>>> Post.objects.filter(tags__len__lt=2)
[<Post: Second post>]
SetF()
expressions¶
Similar to Django’s F
expression, this
allows you to perform an atomic add or remove on a set field at the database
level:
>>> from django_mysql.models import SetF
>>> Post.objects.filter(tags__contains="django").update(
... tags=SetF("tags").add("programming")
... )
2
>>> Post.objects.update(tags=SetF("tags").remove("thoughts"))
2
Or with attribute assignment to a model:
>>> post = Post.objects.earliest("id")
>>> post.tags = SetF("tags").add("python")
>>> post.save()
- class django_mysql.models.SetF(field_name)¶
You should instantiate this class with the name of the field to use, and then call one of its two methods with a value to be added/removed.
Note that unlike
F
, you cannot chain the methods - the SQL involved is a bit too complicated, and thus you can only perform a single addition or removal.- add(value)¶
Takes an expression and returns a new expression that will take the value of the original field and add the value to the set if it is not contained:
post.tags = SetF("tags").add("python") post.save()
- remove(value)¶
Takes an expression and returns a new expression that will remove the given item from the set field if it is present:
post.tags = SetF("tags").remove("python") post.save()
Warning
Both of the above methods use SQL expressions with user variables in their queries, all of which start with
@tmp_
. This shouldn’t affect you much, but if you use user variables in your queries, beware for any conflicts.