A package containing a set of structural field types for Django.
This package is inspired by Wagtail's StreamField, but with the following changes:
- Allows any Django field type to be used
- Provides a separate "enum" type
- structs/enums/lists can be added directly to models
I hope to support all of features of Django's fields, including adding the necessary database constraints and supporting all lookups.
pip install django-structured-fields
This field type combines a group of fields together. A StructField can either be defined within your model or as a subclass that can be reused. For example:
from django.db import models
from structured_fields.fields import StructField
class ImageWithCaptionField(StructField):
image = models.ImageField()
caption = models.TextField()
class MyModel(models.Model):
image_with_caption = ImageWithCaptionField()
# here's how you could define this inline
inline_image_with_caption = StructField([
('image', models.ImageField()),
('caption', models.TextField()),
])
Internally, this field type creates a JSON field with a single dict containing the sub-field values. For example:
{
"image": "/images/my-image.jpg",
"caption": "This is my image",
}
If you want a field type that converts to multiple fields when applied to a model, you can swap this out for django-composite-field
. This field type will also work when nested within an EnumField
/ListField
too.
This field type allows the user to choose from one or more field types. Validation is only performed on the type that was chosen. Like StructField, this could also be defined inline or as a subclass
from django.db import models
from structured_fields.fields import EnumField
class LinkField(EnumField)
"""
Links either to an internal page or an external URL
"""
page = models.ForeignKey("wagtailcore.Page")
url = models.URLField()
class MyModel(models.Model):
link = LinkField()
# here's how you could define this inline
inline_link = EnumField([
('page', models.ForeignKey("wagtailcore.Page")),
('url', models.URLField()),
])
When used directly on a model, this will add each field directly on to the model prefixed with the field name and with an additional "type" field added. For example, the MyModel.link
field would generate the following model structure:
class MyModel(models.Model):
link_type = models.CharField(max_length=255, choices=["page", "url"])
link_page = models.ForeignKey("wagtailcore.Page", null=True)
link_url = models.URLField(null=True)
# If possible, we should automatically add constraints too
class Meta:
constraints = [
CheckConstraint(
Q(link_type="page", link_page__isnull=False)
| Q(link_type="url", link_url__isnull=False)
)
]
When used within a ListField
, this will use the following JSON representation:
{
"type": "page",
"value": 1,
}
{
"type": "url",
"value": "https://www.github.com/",
}
This field type allows you to have zero or more values of another field. It is similar to the ArrayField
type in django.contrib.postgres
, except the underlying data type will always be a JSONField
. Like the field types before, this could also be defined inline or as a sub-class:
from django.db import models
from structured_fields.fields import EnumField, ListField,
class LinksField(ListField)
item = EnumField([
('page', models.ForeignKey("wagtailcore.Page")),
('url', models.URLField()),
])
class MyModel(models.Model):
links = LinksField()
# here's how you could define this inline
inline_links = ListField(
item=EnumField([
('page', models.ForeignKey("wagtailcore.Page")),
('url', models.URLField()),
])
)
ListField
is a sub-class of Django's JSONField
, so when used directly on a model, a JSON field will be added.
The JSON representation is a list of dictionaries, each dictionary contains an id
(UUID) and a value
key.
When nested in another JSON field, this same representation is used.
Internally, this field type is equivalent to ListField(item=EnumField(...))
, however it provides a UI similar to Wagtail's StreamField and some extra helper methods.
Here's an example showing StreamField
but also how you can combine the different field types.
from django.db import models
from structured_fields.fields import StructField, StreamField
class ImageWithCaptionField(StructField):
image = models.ImageField()
caption = models.TextField()
class LinkField(EnumField):
page = models.ForeignKey("wagtailcore.Page")
url = models.URLField()
class BodyField(StreamField)
paragraph = models.TextField()
image = ImageWithCaptionField()
link = LinkField()
class MyPage(models.Model):
body = BodyField()
# here's how you could define this inline
inline_body = StreamField([
("paragraph", models.TextField()),
("image", ImageWithCaptionField()),
("link", LinkField()),
])