Package: django-streamfield
Django StreamFieldThis is a simple realisation of StreamField's idea of Wagtail CMS for plain Django admin or with Grappelli skin.
Stable version: 2.1.2
Django <= 4.2.1Major changes (1.4.5 > 2)HighlightsYou can build your page with different kind of blocks.
Sort them and sort the lists inside the blocks.The blocks here are regular instances of Django models.For editing content inside the blocks, it use native popup mechanism in Django admin interface.
This allow you to use other field's widgets inside the blocks as is.
For example, if you want to use in your blocks FileBrowseField
from django-filebrowser, it will perfectly working
without any additional settings.Module also working withGrappelli Interface(Optional)ContentsInstallationHow to useAdminCustom admin class for block's modelsCustom templates for render block models in adminOverride how to render block's fields in adminOverride list of blocks for your StreamField in admin.pyBlock optionsSpecial casesComplex BlocksBlocks without data in database. Only templatesAdd extra context to blocksGet field data as listCache for reduce the number of database requestsCreate a copySettingsMigrationsInstallationRequirements:django>=3.1pip install django-streamfieldHow to useCreate streamblocks app with your modelsAdd streamfield and streamblocks to INSTALLED_APPSAdd streamfield.urlsCreate templates for streamblocksAdd StreamField to your modelUse it in templates1. Create new app calledstreamblocksand put there some models...that you want to use as blocks in your StreamField.Add them to the listSTREAMBLOCKS_MODELS.
For example:# streamblocks/models.py# one objectclassRichText(models.Model):text=models.TextField(blank=True,null=True)def__str__(self):# This text will be added to block title name.# For better navigation when block is collapsed.returnself.text[:30]classMeta:# This will use as name of block in admin# See also STREAMFIELD_BLOCK_TITLE in settingsverbose_name="Text"# list of objectsclassImageWithText(models.Model):image=models.ImageField(upload_to="folder/")text=models.TextField(null=True,blank=True)# StreamField option for list of objectsas_list=Truedef__str__(self):# This text will be added to block title name. For better navigation when block is collapsed.returnself.text[:30]classMeta:verbose_name="Image with text"verbose_name_plural="Images with text"# Register blocks for StreamField as list of modelsSTREAMBLOCKS_MODELS=[RichText,ImageWithText]Important!: Don't use 'as_list', 'options', 'extra_options' as models field names, because they are used by streamfield.2. Add apps to settings.py and make migrationsAdd to INSTALLED_APPSINSTALLED_APPS=[...'streamblocks','streamfield',...Runpython manage.py makemigrationsandpython manage.py migrate3. Add streamfield.urls to main urls.pyurlpatterns+=[path('streamfield/',include('streamfield.urls'))]4. Create templates for each block model, named as lowercase names of the models:streamblocks/templates/streamblocks/richtext.htmlstreamblocks/templates/streamblocks/imagewithtext.htmlAnd useblock_contentas context.Note: block_content will be single object
if no 'as_list' property in your model,
and will be a list of objects if there is.<!--richtext.html--><divclass="rich-text-block">{{ block_content.text|safe }}</div><!--imagewithtext.html--><ulclass="image-with-text-block">{% for block in block_content %}<li><imgsrc="{{ block.image.url }}"alt=""><p>{{ block.text }}</p></li>{% endfor %}</ul>Note: You may use alsoblock_templateoption. For specify a block template file.classRichText(models.Model):...block_template="streamblocks/richtext.html"...Note: If you need unique string in block template, useblock_modelandblock_unique_idFull list of variables in template context:block_model(lowercase of modelname - "richtext")block_unique_id(unique string)block_content(block data from db)as_list(boolean)options(block options)Note: For unique idetifier inside the lists you may use a combination ofblock_unique_idandblock.idof subblock.5. Add StreamField to your model in your applicationAnd add the models that you want to use in this stream as model_list# models.pyfromstreamfield.fieldsimportStreamFieldfromstreamblocks.modelsimportRichText,ImageWithTextclassPage(models.Model):stream=StreamField(model_list=[RichText,ImageWithText],verbose_name="Page blocks")You can set size of popup windowAddpopup_sizeattribute to StreamField...stream=StreamField(model_list=[...],popup_size=(1000,500)# default value. Width: 1000px, Height: 500px)...6. Use it in templateIf you have yourpagein context,
you can get content by field's cached property page.stream.render...<divclass="content">{{ page.stream.render }}</div>...Or, if you need extra context in blocks, you may use template tag:{% load streamfield_tags %}
...<divclass="content">{% stream_render page.stream request=request %}</div>...Third way it's to use list.See bellowAdminCustom admin class for block's modelsModels will automaticaly register in admin.
If you want provide custom admin class,
first unregister models and register again, usingStreamBlocksAdminclass.# streamblocks/admin.pyfromdjango.contribimportadminfromstreamfield.adminimportStreamBlocksAdminfromstreamblocks.modelsimportRichTextadmin.site.unregister(RichText)@admin.register(RichText)classRichTextBlockAdmin(StreamBlocksAdmin,admin.ModelAdmin):passCustom templates for render block models in adminIf you need to customize admin templates for block models wich you are using, you need to put templates named as
described in section 3 (above). but put it inside "admin" folder.For example for RichText block it will be:streamblocks/templates/streamblocks/admin/richtext.htmlAs context use "form" and/or "object" (Not working for abstract blocks):{{ form.text.value }}
{{ object }}The default admin template is:streamfield/admin/change_form_render_template.htmlYou can extend it{% extends "streamfield/admin/change_form_render_template.html" %}
{% block streamblock_form %}
{{ block.super }}
Original object is: {{ object }}
{% endblock streamblock_form %}You may also specify custom template as option:classRichText(models.Model):...custom_admin_template="streamblocks/admin/richtext.html"...Override how to render block's fields in adminCreate custom template for field with name as generated bydjango.utils.text.camel_case_to_spacesfrom field widget name, and put it inside.../streamblocks/admin/fields/folder.For example for TextField widget (Textarea) of RichText block, it will be:streamblocks/templates/streamblocks/admin/fields/textarea.htmlAndMyCustomWidget:streamblocks/templates/streamblocks/admin/fields/my_custom_widget.htmlAs context use "field":{{ field.value|default:""|safe }}Override list of blocks for your StreamField in admin.pyTypicaly you set the blocks in your models asmodel_listattribute of StreamField field.
But if you want to change blocks, for example depending on object, you can do it in admin site
of your model. Suppose you want to use onlyRichTexton page with id=1.# admin.pyfromstreamfield.fieldsimportStreamFieldWidgetfromstreamblocks.modelsimportRichTextfrom.modelsimportPage@admin.register(Page)classPageAdmin(models.Admin):defget_form(self,request,obj=None,**kwargs):form=super().get_form(request,obj,**kwargs)ifobjandobj.id==1:form.base_fields['stream'].widget=StreamFieldWidget(attrs={'model_list':[RichText]})returnformBe careful with already existing blocks in db. If you remove them from admin, it produce error.Block optionsYou may useoptionsproperty in your streamblocks models to add some additional options to your block.
This is useful withas_listproperty when you need to add some options to whole block not separatly to each object of this list.For example:# streamblocks/models.py# list of objects as sliderclassSlide(models.Model):image=models.ImageField(upload_to="folder/")text=models.TextField(null=True,blank=True)# StreamField option for list of objectsas_list=Trueoptions={'autoplay':{'label':'Autoplay slider','type':'checkbox','default':False},'width':{'label':'Slider size','type':'select','default':'wide','options':[{'value':'wide','name':'Wide slider'},{'value':'narrow','name':'Narrow slider'},]},'class_name':{'label':'Class Name','type':'text','default':''}}classMeta:verbose_name="Slide"verbose_name_plural="Slider"In block template you can use this options asoptions.autoplayIn page admin you will see it on the bottom of this block.Note: Now only "checkbox", "text" and "select" type is working.
You may apply options for all blocks withSTREAMFIELD_BLOCK_OPTIONS(SeeSettings)If you want to add block options to options, which was set in django settings, you may useextra_options.classSlide(models.Model):...extra_options={"autoplay":{"label":"Autoplay","type":"checkbox","default":False}}...If you want to switch off options, which set in django settings, for current block. Setoptions={}Special casesComplex BlocksYou may use StreamField as part of blocks and create with that way complex structure
and use{{ block_content.<field_name>.render }}Blocks without data in database. Only templates.You may use it for widgets or separators or for whatever you want...
Just make the block modelabstract.classEmptyBlock(models.Model):classMeta:abstract=Trueverbose_name='Empty space'and usestreamblocks/templates/streamblocks/emptyblock.htmlfor your content.
For adminstreamblocks/templates/streamblocks/admin/emptyblock.htmlNote: Don't forget to register a block in STREAMBLOCKS_MODELSAdd extra context to blocksSupose, you need to add some data to blocks from global context.
Instead of using render property in template{{ page.stream.render }},
you need to use template tagstream_renderfromstreamfield_tagswith keywords arguments.For example, if you have in page templaterequestandpageobjects and want to use it in blocks:{% load streamfield_tags %}
...<divclass="content">{% stream_render page.stream request=request page=page %}</div>...Get field data as listIf you have special case, you can get data as list.# views.pystream_list=page.stream.as_list()# You will get list of dictionaries# print(stream_list)[{'data':{'block_model':'.....','block_unique_id':'....','block_content':[...],'as_list':True,'options':{}},'template':'....'},...]<!-- template.html -->{% for b in page.stream.as_list %}
    {% include b.template with block_content=b.data.block_content %}
{% endfor %}Cache for reduce the number of database requestsThere is two ways of caching:Simple cache view with django cacheCreate additional field, for example: 'stream_rendered'
and render to this field html in save methoddefsave(self,*args,**kwargs):self.stream_rendered=self.stream.rendersuper().save(*args,**kwargs)...and use this field in your htmlCreate a copyStreamObject have a method 'copy', which create a copies of all the instances that used in this field.For example, if you have object 'page' with the field 'stream', and you need a copy of this object. You can do this:page.pk=Nonepage.stream=page.stream.copy()page.save()Note: If you will not usepage.stream.copy()instances will be the same as in the original objectSettings# settings.pySTREAMFIELD_SHOW_ADMIN_HELP_TEXTIf you want to show "Help" link in admin.Set:STREAMFIELD_SHOW_ADMIN_HELP_TEXT=TrueSTREAMFIELD_ADMIN_HELP_TEXTYou can setup custom help text in settingsSTREAMFIELD_ADMIN_HELP_TEXT='<p>Text</p>'STREAMFIELD_DELETE_BLOCKS_FROM_DBIf you want to keep streamblock's instances in db, when you removing it from StreamField. Set:STREAMFIELD_DELETE_BLOCKS_FROM_DB=FalseIt was default behavior in previous releases.Note: If you delete entire object which contain StreamField, streamblock's instances will not be deleted. You should care about it by yourself.STREAMFIELD_BLOCK_TITLE (> 2.0.1)The default block name uses the verbose_name from the model. Plus the name for each object is taken from__str__method. For "as_list" blocks, from the first block. You can use STREAMFIELD_BLOCK_TITLE to change it to another method or property. If you want disable this, set to False. If some blocks will not have setuped method, they will be ignored.STREAMFIELD_BLOCK_OPTIONSYou may useSTREAMFIELD_BLOCK_OPTIONSin settings.py to add some options to all blocks.For example:STREAMFIELD_BLOCK_OPTIONS={"margins":{"label":"Margins","type":"checkbox","default":True}}In block template use{{ options.margins }}Note: Now only "checkbox", "text",  and "select" type is working.MigrationsIf you add new options to Block with already existed data, you need to migrate options for adding default values to stored json.Create empty migration and usemigrate_stream_optionsfunction fromstreamfield.base.At the moment this only works with unique streamblocks class names.Example:# migrationfromdjango.dbimportmigrationsfromstreamfield.baseimportmigrate_stream_optionsdefmigrate_options(apps,schema_editor):Page=apps.get_model("main","Page")forpageinPage.objects.all():page.stream=migrate_stream_options(page.stream)page.save()classMigration(migrations.Migration):dependencies=['...']operations=[migrations.RunPython(migrate_options),]