{"id":20529171,"url":"https://github.com/rcrowther/django-streamfield-w","last_synced_at":"2025-04-14T04:53:08.653Z","repository":{"id":57422182,"uuid":"289764630","full_name":"rcrowther/django-streamfield-w","owner":"rcrowther","description":"A StreamField for Django. Ported from Wagtail, with new DjangoAdmin-like API.","archived":false,"fork":false,"pushed_at":"2022-01-31T00:53:27.000Z","size":175,"stargazers_count":27,"open_issues_count":1,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-11T21:40:41.629Z","etag":null,"topics":["app","django","field","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rcrowther.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-23T20:52:36.000Z","updated_at":"2022-06-01T10:20:47.000Z","dependencies_parsed_at":"2022-09-06T13:40:55.113Z","dependency_job_id":null,"html_url":"https://github.com/rcrowther/django-streamfield-w","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrowther%2Fdjango-streamfield-w","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrowther%2Fdjango-streamfield-w/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrowther%2Fdjango-streamfield-w/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrowther%2Fdjango-streamfield-w/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rcrowther","download_url":"https://codeload.github.com/rcrowther/django-streamfield-w/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248824693,"owners_count":21167343,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["app","django","field","python3"],"created_at":"2024-11-15T23:29:35.878Z","updated_at":"2025-04-14T04:53:08.623Z","avatar_url":"https://github.com/rcrowther.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# StreamField\nA Streamfield for Django. Saves data as JSON to the referenced field. The admin display is designed to operate unobtrusively and be styled as the stock Django Admin.\n\nThe distribution is called 'django-streamfield-w', but internally the module is called 'streamfield'.\n\n## Pros\n- It's a StreamField\n- Written data should be Wagtail-compatible (mostly, no guarantee)\n\n## Cons\n- No file or image handling (unless you implement from another app)\n- Small but scenery-chewing admin\n\nAnd an admission: anything that works here has needed code reduction, code expansion, and/or hacks. There are still many possibilities for work within the programmer API. So this codebase will not be solid anytime soon, and lacks tests on code, app, and distribution. Best I can say is that I have outlined the functionality.\n\n## Examples\nWant to give a user the ability to add blocks of text, interspaced with blockquotes, URL links and dates?  Declare this field,\n\n    body = StreamField(\n        block_types = [\n            ('text', blocks.TextBlock),\n            ('blockquote', blocks.BlockQuoteBlock),\n            ('anchor', blocks.AnchorBlock),\n            ('date', blocks.DateBlock),\n        ],\n        verbose_name=\"Content\"\n    )\n\nThe form looks like this,\n\n![What a user wants to see, not a coder](screenshots/content_form.png)\n\nwhich renders as,\n\n![Where's the CSS guy?](screenshots/content_render.png)\n\nWant a list of web-links in the footer of your articles on coding? Declare this field,\n\n    weblinks = DefinitionListField(\n        definition_block = blocks.RawAnchorBlock,\n        verbose_name=\"Web links\"    \n    ) \n\nThe form looks like this,\n\n![No markup in sight](screenshots/weblinks_form.png)\n\nwhich renders as,\n\n![No probleem](screenshots/weblinks_render.png)\n\n\n## Alternatives\n[Steamfield](https://github.com/raagin/django-streamfield/blob/master/README.md) for Django.\n\nThis app didn't work for me. However, the UI, which uses Django's popup mechanism, is interesting, as is the code. There are substantial and notable differences between this app and the code provided here.\n\n\n## If you have done this before\n- [Install](#install)\n- [Add fields](#declaring-fields) to models that will use them\n- (as you would usually) Migrate the models\n\nThat's all.\n\n\n## Install\nNo dependencies. PyPi,\n\n    pip install django-streamfield-w\n\nOr download the app code to Django.\n\nDeclare in Django settings,\n\n        INSTALLED_APPS = [\n            ...\n            'image.apps.StreamfieldConfig',\n            ...\n        ]\n\n## Overall\nA streamfield is [declared as a model field](#declaring-fields). That field is, unlike most model fields, a growable value of elements which may be of different types. The data is stored as JSON.\n\n'Blocks' is the name for the types of items that can be inserted in a streamfield. Blocks to be made available must be declared in the field.\n\nUnderneath, blocks know how to render themselves as a form item. So the supplied Javascrript will allow a user to extend the value held by the field  to any number of sub-values.\n\nWhen rendered, all the subvalues will be [templated to the screen](#display)\n\n\n## Declaring fields\nLike this, in 'models.py',\n\n    class Page(models.Model):\n        ...\n        stream = StreamField(\n            block_types = [\n                ('heading', blocks.CharBlock()),    \n                ('text', blocks.TextBlock()),    \n            ],\n            verbose_name=\"Page content\"\n            )\n\nBlock declarations can be classes, or instances with [optional parameters](#block-attributes).\n\nLike the model classes in Django Admin, if you set 'block_types' empty it will not default to 'all blocks'. Instead, the field will issue a warning that no blocks will be shown in the field. This is a small security measure.\n \nIf you want to declare many similar fields, 'block_types' can be declared in class,\n\n    class MyStreamField(\n            '''\n            A StreamField that allows subtitles, images and text, nothing else\n            '''\n            block_types = [\n                ('subtitle', blocks.CharBlock()),    \n                ('image', blocks.ImageBlock()),                    \n                ('text', blocks.TextBlock()),    \n            ],\n            verbose_name=\"Page content\"\n            )\n\n#### ListField\nYou may not want a full StreamField. If you want a list, a StreamField is not what you want to say to a user (a ListBlock inside a Strreamfield means the list is replicable, not it's elements). Use a ListField,\n\n    class Page(models.Model):\n        ...\n        stream = ListField(\n            block_type = blocks.CharBlock(max_length=255),\n            verbose_name=\"Page list\"\n            )\n\n\n#### Model-field attributes\nAre the same as any model field, 'blank', 'help_text', 'verbose_name', 'validators' etc. Consider that the value on this field is a lump of JSON that is recursively evaluated, and the forms are an embedded heap. Setting 'primary_key' on a StreamField is strange, and 'null' should be avoided.\n\n#### Side note - get_searchable_content()\nStreamfield has a method get_searchable_content(). This is inherited from Wagtail, which has builtin search engine API. While there is nothing in Django that uses the method, it is maybe useful, so I left it in.\n\n\n## Blocks\n'Blocks' is the name for the items that can be inserted in a streamfield.\n\nBlocks are tricky concept containing elements of Django model-fields, form-fields, and widgets. But if you are implementing, you only need to know one attribute,\n\n    css_classes\n        list of strings to be used as classnames in HTML rendering. The value is passed to the context. \n\nThis attribute is a limited replacement for the widget attribute 'attrs'. CSS classes are only printed when the template enables this. Most of the default templates for blocks enable printing, except where there is no HTML at all.\n\n\n### Field blocks\nThere blocks represent most of Djangos's form fields. To get you going, here is a declaration,\n\n    class Page(models.Model):\n\n        class Creepy(models.TextChoices):\n            SPIDER = 'SP','Spider'\n            ANT = 'AT','Ant'\n            PYTHON = 'PY','Python'\n            BAT = 'BT','Bat'\n            CRICKET = 'CR','Cricket'\n            MOTH = 'MO','Moth'\n\n        stream = StreamField(\n           block_types = [\n                ('chars', blocks.CharBlock(\n                    required=False,\n                    help_text=\"A block inputting a short length of chars\",\n                    max_length=5,\n                    ),\n                ),\n                ('subtitle', blocks.HeaderBlock()),\n                ('subsubtitle', blocks.HeaderBlock(level=4)),\n                ('quote', blocks.QuoteBlock(\n                    required=False,\n                    ),\n                ), \n                ('url', blocks.URLBlock),\n                ('relurl', blocks.RelURLBlock),\n                ('email', blocks.EmailBlock(css_classes=['email'])),\n                ('regex', blocks.RegexBlock(regex='\\w+')),\n                ('text', blocks.TextBlock()),    \n                ('blockquote', blocks.BlockQuoteBlock()),\n                ('html', blocks.RawHTMLBlock()),\n                ('bool', blocks.BooleanBlock()),\n                ('choice', blocks.ChoiceBlock(choices=Creepy.choices)),\n                ('choices', blocks.MultipleChoiceBlock(choices=Creepy.choices)),\n                ('integer', blocks.IntegerBlock()),\n                ('decimal', blocks.DecimalBlock()),\n                ('float', blocks.FloatBlock()),\n                ('date', blocks.DateBlock()),\n                ('time', blocks.TimeBlock()),\n                ('datetime', blocks.DateTimeBlock(css_classes=['datetime'])),\n                ('rawanchor', blocks.RawAnchorBlock()),\n                ('anchor', blocks.AnchorBlock()),\n            ],\n            verbose_name=\"Page blocks\"\n            )\n            ...\n\nAs you can see from the declarations, blocks can be declared as classes (with default parameters), or instances (with optional parameter details).\n\nYou may note the lack of a FileBlock/FileUploadBlock. This would give embedded images, so is a serious loss. However, it difficult to make an upload block, as Django spreads the functionality across model processing, and there is no model instance or field to work with (StreamField data is a lump of JSON). If your images/galleries etc. are tracked in the database, make a custom ModelChoceBlock. That's the Wagtail solution.\n\nNotes on some of the blocks,\n\n    ChoiceBlock and MultipleChoiceBlock \n        To make them work, give them choices. The blocks will handle the types and their storage. These blocks cut back on Wagtail/Django provision, they need static declarations, will not accept callables, etc. \n\n    RelURLBlock \n        Accepts relative URLs and fragments, unlike Django's UELField (the field is also a bit stricter about what gets in)\n\n    ModelChoiceBlockBase \n        A base to build on. See below.\n\n    RawAnchorBlock\n        An HTML anchor where the text is a repetition of the href attribute.\n\n    AnchorBlock\n        A dual input block built with StructBlock that generates... an HTML anchor.\n\nYou may note that the value applied to the templates is the value from the database. This may not be always what you want, especially with enumerables. You can break up the stream and address the properties individually, or construct template tags etc.\n\n#### Block attributes\nThe field Blocks (not the collection blocks) wrap Django form fields Some standard attributes from the form fields are exposed,\n\n    required\n        Works, but in a slightly different way to a form field. If True (the default) the block inpput can not be empty, if the block is summoned by the user.. \n    widget\n        Set a different widget \n            #initial  \n    help_text\n        Set a help text on the block  \n    validators\n        Add validators to the block\n    localize\n        Localise block displays\n\nand this widget-like attribute, \n\n     placeholder\n         set the placeholder on an input\n\nUnlike Wagtail, but like Django, all blocks are required unless declared otherwise.\n\nOther form-field parameters such as 'label_suffix', 'initial', 'error_messages', 'disabled', are either fixed or not useful for blocks.\n\nSome blocks have extra attributes. Usually these are similar to form field attributes, but you need to go to source to check.\n\n\n#### ModelChoiceBlocks\nThe ModelChoiceBlocks are ModelChoiceBlock and ModelMultipleChoiceBlock, both building from ModelChoiceBlockBase (hope you are okay with that). They offer the possibility of building selections from any Django model, which opens many possibilities.\n\nThe fields accept the common parameters, and these,\n\n        target_model\n            The model to do queries on\n        target_filter\n            A query to limit offered options. The query is organised as a dict e.g. {'headline__contains':'Lennon'} \n        to_field_name\n            The field to use to supply label names.\n        empty_label\n            An alternative to the default empty label (which is '--------')\n\nThe querybuilding is unlike the usual Django or Wagtail provision. \n\n\n\n#### Widgets\nThe widgets used as default are a strange collection. To give you some idea,\n\n    Charboxes (URL, Email, Regex)\n         Are Django Admin (which are a light restyle for size)\n    Textareas (inc. RawHTMLArea and Blockquote)\n        Home-brew auto-resize (an idea from Wagtail, re-implemented here)\n    Time/Date inputs\n        Home-brew TimeDate handler\n\nA word about the temporal widgets: the Javascript may crash on asserting a locale. Either use stock Django widgets,\n\n    from django.forms.widgets import DateTime\n\n            block_types = [\n                ...\n                ('subtitle', blocks.DateBlock(widget=DateTime)),\n            ]\n\nor explicitly state formats (see below).\n\nLike other Django widgets, if you like AutoHeightTextWidget or the temporal widgets you can use them elsewhere.\n\n\n##### The temporal widgets\nDjango's temporal input form-handling is a tiring mass of code. Formats do not fall through model-fields, to form-fields, to widgets. There are catch-sensible-input defaults in the decode process. Decode and encode happen in different places in the codebase, handled by different code resources. And declared formats can be subverted by locale handling.\n\nIf you want to do interesting things with temporal inputs, you will need to set the form field so the format will be accepted. Then you also need to set the format on the widget. Nothing to do with this app or Wagtail, it's a Django thang,\n\n    class SpaceyDateBlock(blocks.DateBlock):\n        widget = MiniDateWidget(format='%d / %m / %Y')\n\nThen set on the field,\n\n            block_types = [\n                ...\n                ('subtitle', SpaceyDateBlock(input_formats=['%d / %m / %Y'])),\n            ]\n\n\n\n### Collection blocks\nThese blocks represent collections of other blocks,\n\n    ListBlock\n        An unfixed collection of similar blocks\n\n    StreamBlock\n        An unfixed collection of disimilar blocks (block choice offered to user)\n\n    StructBlock\n        A fixed collection of disimilar blocks\n\n#### ListBlock\nAn unlinited list of a single block type,\n\n    class Page(models.Model):\n        stream = StreamField(\n            block_types = [\n                ('list', blocks.ListBlock( blocks.CharBlock )),\n            ],\n            verbose_name=\"Page blocks\"\n        )\n        ...\n\nNow that is interesting. To represent a list using database records needs a specialised table with foreign key links. Though in many cases you may want the modelfield wrap of this block, a [ListField](#ListField)\n\n\n##### DefinitionListField \nIn line with basic HTML provision, there is also a specialist DefinitionListField.\n\nIt accepts common attributes plus,\n\n    term_block_type\n    definition_block_type\n\n#### StructBlock\nA fixed collection of different block types. It can be declared in-place,\n\n    class Page(models.Model):\n        stream = StreamField(\n            block_types = [\n                ('link', blocks.StructBlock([\n                    ('url', blocks.URLBlock()),\n                    ('title', blocks.CharBlock()),\n                    ]),\n                ),\n            ],\n            verbose_name=\"Page blocks\"\n        )\n        ...\n\nThis will do as you hope, produce a one-click field of several blocks at once. However, inline StructBlocks are confusing to write and read, and the agility of StructBlocks is maybe better demonstrated by subclassing and applying declarations,\n\n     class Licence(models.TextChoices):\n            NO_RIGHTS = 'CCO', 'Creative Commons (\"No Rights Reserved\")' \n            CREDIT = 'CC_BY', 'Creative Commons (credit)'\n            CREDIT_NC = 'CC_BY-NC-ND', 'Creative Commons (credit, non-commercial, no adaption)'\n            NON_EXCLUSIVE = 'NE','Non-exclusive Rights available'\n            EXCLUSIVE = 'EX','Exclusive rights available'\n\n\n    class QuoteBlock(blocks.StructBlock):\n        quote = blocks.BlockQuoteBlock\n        author = blocks.CharBlock()\n        date = blocks.DateBlock()\n        licence = blocks.ChoiceBlock(choices=Licence.choices)\n\n\n    class Page(models.Model):\n        stream = StreamField(\n            block_types = [\n                ('text', blocks.TextBlock()),\n                ('quote', QuoteBlock()),\n            ],\n            verbose_name=\"Page block\"\n        )\n\nThis is a big StreamField win. A user can write text, then insert this QuoteBlock anywhere they like, Which database solutions can not do. And RichText i.e. text markup, can not structure the input.\n\n\n#### StreamBlock\nStreamBlock is an unlimited list of different block types. It is the base block of a Streamfield. It can also be embedded in a StreamField. \n\nAttributes are the common set plus,\n\n    max_num\n    min_num\n\nThe reason you would embed a StreamBlock is to establish a different context for the user to work in, for example, creating an index. Think before you do this. It will be difficult coding, and hard for a user to understand. Also, it will encourage blobs of data in the database, which is the argument against this kind of Streamfield. Can the context be regarded as a fixed entity? In which case, maybe it should be on a separate database field or table. That said, it can be done.\n\n\n\n### Custom blocks\nNot as difficult as promised. If you already have a form field you wish to use, you can wrap it, see 'form_field.py' for details. There is usually a form field that will handle data in the way you want. Most of the time, you will want a different widget or to change rendering. Well, 'widget' has been broken out as an attribute, and can be passed as an instance or class. For a one-off change, in 'models.py',\n\n    body = StreamField(\n        block_types = [\n            ('float', blocks.FloatBlock(widget=MySuperFloatWidget(point_limit=3))),\n        ]\n\nOr if you plan to use the new widget field a few times,\n\n    class GroovyFloatBlock(FloatBlock):\n        widget = MySuperFloatWidget\n\nTo change rendering, this is the code from QuoteBlock,\n\n    class QuoteBlock(CharBlock):\n\n        def render_basic(self, value, context=None):\n            if value:\n                return format_html('\u003cquote\u003e{0}\u003c/quote\u003e', value)\n            else:\n                return '\n\nTo initialise Javascript, there is a widget mixin called 'WithScript'. See the next section.\n\n\n#### Issues with custom blocks\nStreamfield adds form elements dynamically to the form. It will pick up and add Media resources attached to a widget. If you have a plain widget, this is all you need. But if you want a fancy widget, you need to think about the Javascript.\n\nJavascript that supports widgets used in streamfields is tricky. Javascript designed to run on page load will not trigger. And the script may not have a non-page-load initialisation. Finally, there may be issues with JS dependency resolution. Django provides some help, but you need to know about it. \n\nThe problems start here. Many, many Javascripts auto-run on page initialisation. You can hope that this is disabled or ineffective. But if not, or the script produces side effects in the form, you will need to modify the script, much as you may dislike the idea (I would).\n\nThen you must hope the author built the script with a hook for initialisation. Again, if such a breakout of functionality is missing. you will need to modify.\n\nThen you need to initialise. First, ignore the method in blocks called jsinitialiser(). This may seem promising, but it is for setup of block code on page load. The Wagtail solution is to add a script to the block template (the template is written into the page). This will run whenever a block is loaded into the main form. In a typically cute piece of code, Wagtail have a mixin to do this, called WidgetWithScript. WidgetWithScript is ported to this app as 'streamfield.widgets.WithScript'. All the field block widgets use it.\n\nIf initialisation is not targeted and precise, there can be unwanted effects. For example, outside of Streamfields, a few people have wished to reuse Django Admin widgets. Well, if the widget initialisation runs on page load, then is initialised dynamically later, forms can display with, for example, multiple clocks and calenders attached to temporal inputs. And there is no way to namespace this behaviour away.\n\nFinally, you need to consider Javascript imports. Django does a somewhat unpubicised dependency resolution on JS resources. Mostly this is great, removing duplicates and ensuring, for example, that JQuery scripts run after JQuery is loaded. But, in such a case, you must declare a JQuery dependency in the widget, or the resource may be sorted before the JQuery library. And don't try a simple rename on JS resources, in the hope this will namespace the code. The dependency resolution will not be able to see the code rename, so will write the code in twice, and then you are in trouble. \n\nAll in all, to add Javascripted widgets to a streamfield block,\n- Preferably find Javascript that does nothing on pageload, or disable it. Especially avoid Javascript with general searches. You need a function ''do this to element with 'id' XXX'\n- Add the widget to a new FieldBlock, using the broken-out definitions above\n- Use streamfield.widgets.WithWidget to initialise\n- On the Media class of the widget, declare dependencies for the Javascript, including external dependencies like JQuery\n- Besides dependencies, beware how the Javascript loads into namespaces. If it is loaded on ''window', will it clash/ produce side-effects?\n\nThis  app has default widgets that seemed to suit the overall theme. but the above points were also a consideration.\n\n\n## Display\nMostly, you only need to print the value,\n\n    {{ page.content }}\n\nThis will template each block used with the default templating.\n\n\n### The template tag\nThere is o template tag which allows you to control/supplement the supplied context.\n\n    {% load streamfield_tags %}\n\n                ...\n                \u003carticle\u003e\n                ...\n                {% streamfield page.content with class=\"my-block-name\" %} \n                ...\n                \u003c/article\u003e\n\n#### More on rendering\nA value can be iterated, then the tag called (in recursive fashion) on elements,\n\n    {% for block in page.content %}\n        \u003cp\u003e{% streamfield block %}\u003c/p\u003e\n    {% endfor %}\n\nAnd the elements can be controlled by their 'block_type' and 'value' directly,\n\n    {% for block in page.content %}\n        {% if block.block_type == 'heading' %}\n            \u003ch1\u003e{{ block.value }}\u003c/h1\u003e\n        {% else %}\n            \u003csection class=\"block-{{ block.block_type }}\"\u003e\n                {% streamfield block %}\n            \u003c/section\u003e\n        {% endif %}\n    {% endfor %}\n\nI would not be keen on programming which individually targets elements, because what happens if you later introduce collections into the field? But the possibility is there.\n\n\n## Templating and HTML\n### Overview\nIt's difficult to produce generic HTML for StreamFields, because the potential uses are many. If you approach the app with specific needs, you should be expecting to override or produce your own blocks to adjust rendering.\n\nThe default rendering is set to be slim and somewhat aimed at supporting HTML typography. For example, text fields render in paragraph tags, numbers have no markup at all.\n\n## Migration\n\n## Notes\n### What is a streamfield?\nWhen people talk about text, they usually mean more than that. They usually mean a descending block that includes text, images, maybe titles, and various other structures such as lists and indexes. The 'content'.\n\nHow to represent this in a structured way has always been an issue, especially for databases. Some very, very structured text can be broken into database fields. Think of a product review, which will have a title, photograph, review and conclusion. Indeed, reviews are often used as an example.\n\nBut much text is not rigidly structured. An article for a newspaper may have images inserted, depending on what the staff managed to photograph. A book may have occasional illustrations. An author of technical books may wish to insert tables or checklists. The rigid structures of a database can not model these cases.\n\nThe answer would seem to be obvious, and has been used for years. Various text markup languages, from LaTex to HTML are designed to define structures which can be organised in a freeform way. Why not store these as a blob of text in a 'content' or 'body' database field?\n\nUnfortunately, this fails both the end-user and the computer. The end user knows the structures they want. They know they want a blockquote, even if they don't know the name for it. But they have no time or life to learn markup. So they try to reproduce what they see using indents, or worse, tabs. Coders try to get round this using complex WYSIWYG editors but, marvels of coding though they are, the structure is still not reaching the user. It's known fact that users hate those editors. They never \"do what I want\".\n\nAs for computers, the data is stored in an unstructured blob in a hard-to-parse text format. And if the user has faked text structure, there is no record of their intention. And introducing the possibility of markup to end-users opens up a large security hole.\n\nA streamfield stores the structures of the text not as text markup, but in a computer format. There are at least two ways of storing the structures. The first is to keep all structures in dedicated tables, for example a 'blockquote' table, and keep track of the order a user asked for them to be organised. The other way is to write down a blob of data to a field, but in a format computers can get to grips with, like JSON.\n\nEither way, a computer format benefits both user and computer. The user gets a stream of forms that \"do what they want\". If they don't find a form, at least that is clear. And the computer gets the kind of data they like, structured lists of encoded data.\n\nThe main difficulty is that the structures are a massive challenge to web-coders. They need to be parsed to produce a screen representation. And making forms for all the structures needed is a trial of endurance. Not to mention that dynamically creating web-forms is a Javascript/HTML battleground. But people are trying. \n\n\n## The difference between Django-streamfield and Wagtail CMS implementations\nDjango-streamfield stores block data in dedicated database tables. This is good for reusing existing components. It can be good for maintenance because it single-sources maintenance operations such as migration, data dumps, and transfer/salvage.\n\nWagtail CMS stores block data as JSON, in text fields. This represents the structure intended by the coder. JSON is a robust, maintainable and human-readable format. But this approach creates much work on UI widgets for modification and display of data.\n\nSo one represents a clean, easily leveraged solution. The other represents what may become an industry-standard solution, at a cost of code size and complexity. \n\n\n\n### Why not use Wagtail?\nMaybe you should. But Wagtail introduces code you may not want or find limiting, such as preset Page types, modified middleware and admin, new configuration methods, stacks of resulting preset URLs, and plenty more.\n\n\n### Why didn't you include the RichText code?\nWagtail has a well-worked Richtext implementation. But, in short, this app has some integrity to defend. It's a Streamfield implementation, not a \"useful stuff nicked from Wagtail\" app.\n\n\n### History/Credits\nThis was originally intended to be a port of Wagtail's StreamField. However, that code is closely tied to Wagtail's custom Admin, so was abandoned. I looked at [Steamfield](https://github.com/raagin/django-streamfield/blob/master/README.md), but that code is tied to it's data representation. I reverted to porting the Wagtail code. Because [this](https://pypi.org/project/wagtail-react-streamfield/) exists, this code sooner or later becomes a maintained branch.\n\nThe base Javascript and field handling remains as in Wagtail. There are tricks to make this work in Django, and most Wagtail-specific functionality is removed. The programmer API is reworked and extended, and not like Wagtail.\n\nThroughout, I was trying not to learn anything about StreamField implementations. I only partially succeeded.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcrowther%2Fdjango-streamfield-w","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frcrowther%2Fdjango-streamfield-w","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcrowther%2Fdjango-streamfield-w/lists"}