# paper-streamfield
Implementation of the Wagtail's StreamField block picker for paper-admin.
[](https://pypi.org/project/paper-streamfield/)
[](https://github.com/dldevinc/paper-streamfield)
[](https://pypi.org/project/paper-streamfield/)
## Compatibility
- `python` >= 3.9
- `django` >= 3.1
- `paper-admin` >= 6.0
## Installation
Install the latest release with pip:
```shell
pip install paper-streamfield
```
Add `streamfield` to your INSTALLED_APPS in django's `settings.py`:
```python
INSTALLED_APPS = (
# other apps
"streamfield",
)
```
Add `streamfield.urls` to your URLconf:
```python
urlpatterns = patterns('',
...
path("streamfields/", include("streamfield.urls")),
)
```
## How to use
1. Create some models that you want to use as blocks:
```python
# blocks/models.py
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.utils.text import Truncator
class HeadingBlock(models.Model):
text = models.TextField()
rank = models.PositiveSmallIntegerField(
default=1,
validators=[
MinValueValidator(1),
MaxValueValidator(6)
]
)
class Meta:
verbose_name = "Heading"
def __str__(self):
return Truncator(self.text).chars(128)
class TextBlock(models.Model):
text = models.TextField()
class Meta:
verbose_name = "Text"
def __str__(self):
return Truncator(self.text).chars(128)
```
2. Register your models using `StreamBlockModelAdmin` class.
```python
# blocks/admin.py
from django.contrib import admin
from streamfield.admin import StreamBlockModelAdmin
from .models import HeadingBlock, TextBlock
@admin.register(HeadingBlock)
class HeadingBlockAdmin(StreamBlockModelAdmin):
list_display = ["__str__", "rank"]
@admin.register(TextBlock)
class TextBlockAdmin(StreamBlockModelAdmin):
pass
```
3. Create templates for each block model, named as lowercase
model name or _snake_cased_ model name.
```html
<!-- blocks/templates/blocks/headingblock.html -->
<!-- or -->
<!-- blocks/templates/blocks/heading_block.html -->
<h{{ block.rank }}>{{ block.text }}</h{{ block.rank }}>
```
```html
<!-- blocks/templates/blocks/textblock.html -->
<!-- or -->
<!-- blocks/templates/blocks/text_block.html -->
<div>{{ block.text|linebreaks }}</div>
```
4. Add a `StreamField` to your model:
```python
# app/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _
from streamfield.field.models import StreamField
class Page(models.Model):
stream = StreamField(
_("stream"),
models=[
"blocks.HeaderBlock",
"blocks.TextBlock",
]
)
class Meta:
verbose_name = "Page"
```
Result:

Now you can create some blocks:

5. Use `render_stream` template tag to render the stream field.
```html
<!-- app/templates/index.html -->
{% load streamfield %}
{% render_stream page.stream %}
```
Result:

> When working with block templates, it's important to note that
> you have access to all variables from the parent context.
## Special cases
### Use custom template name or template engine
You can specify a template name or engine to render a specific block
with `StreamBlockMeta` class in your block model:
```python
class HeadingBlock(models.Model):
# ...
class StreamBlockMeta:
template_engine = "jinja2"
template_name = "blocks/heading.html"
```
### Caching the rendered HTML of a block
You can enable caching for specific blocks to optimize rendering.
```python
class HeadingBlock(models.Model):
# ...
class StreamBlockMeta:
cache = True
cache_ttl = 3600
```
Once caching is enabled for the block, the rendered HTML will be stored
in cache, and subsequent requests will retrieve the cached content,
reducing the need for re-rendering.
> Note that the specified block will **not** be invalidated
> when something changes in it.
### Adding context variables to all blocks
You can add context variables to all blocks in your StreamField by providing them
through the `render_stream` template tag. This allows you to pass common context data
to customize the rendering of all content blocks consistently:
```html
<!-- app/templates/index.html -->
{% load streamfield %}
{% render_stream page.stream classes="text text--small" %}
```
```html
<!-- blocks/templates/blocks/textblock.html -->
<div class="{{ classes }}">{{ block.text|linebreaks }}</div>
```
### Adding context variables to a specific block
To add context variables to a specific content block,
you must create a custom processor. A processor provides a mechanism for
customizing the context data and the rendering process of an individual block.
1. Create a custom processor class that inherits from
`streamfield.processors.DefaultProcessor`.
```python
from streamfield.processors import DefaultProcessor
from reviews.models import Review
class ReviewsBlockProcessor(DefaultProcessor):
def get_context(self, block):
context = super().get_context(block)
context["reviews"] = Review.objects.all()[:5]
return context
```
2. In your block's model, specify the processor to use:
```python
class ReviewsBlock(models.Model):
# ...
class StreamBlockMeta:
processor = "your_app.processors.ReviewsBlockProcessor"
```
You can utilize the `exceptions.SkipBlock` feature to conditionally skip the rendering
of a block. This can be useful, for example, when dealing with a block like "Articles"
that should only render when there are articles available. Example:
```python
from streamfield.processors import DefaultProcessor
from streamfield.exceptions import SkipBlock
from articles.models import Article
class ArticlesBlockProcessor(DefaultProcessor):
def get_context(self, block):
context = super().get_context(block)
articles = Article.object.all()[:3]
if len(articles) < 3:
# Skip block if not enough article instances
raise SkipBlock
context["articles"] = articles
return context
```
### Using `render_block` template tag
In some cases, you may have a page that references a specific block through
a `ForeignKey` relationship, and you want to render that referenced block on the page.
You can achieve this using the render_block template tag. Here's an example:
```python
# page/models.py
from django.db import models
from blocks.models import TextBlock
class Page(models.Model):
text_block = models.ForeignKey(TextBlock, on_delete=models.SET_NULL, blank=True, null=True)
class Meta:
verbose_name = "Page"
```
```html
<!-- app/templates/page.html -->
{% load streamfield %}
<div>
<h1>Page Title</h1>
<div>
<h2>Text Block:</h2>
{% render_block page.text_block %}
</div>
</div>
```
### Customize block in admin interface
You can customize how a block is rendered in the admin interface
by specifying `stream_block_template` field in the `StreamBlockModelAdmin`
class:
```python
from django.contrib import admin
from streamfield.admin import StreamBlockModelAdmin
from .models import ImageBlock
@admin.register(ImageBlock)
class ImageBlockAdmin(StreamBlockModelAdmin):
stream_block_template = "blocks/admin/image.html"
list_display = ["__str__", "title", "alt"]
```
```html
<!-- blocks/admin/image.html -->
{% extends "streamfield/admin/block.html" %}
{% block content %}
<div class="d-flex">
<div class="flex-grow-0 mr-2">
<img class="preview"
src="{{ instance.image }}"
width="48"
height="36"
title="{{ instance.title }}"
alt="{{ instance.alt }}"
style="object-fit: cover">
</div>
{{ block.super }}
</div>
{% endblock content %}
```
## Settings
`PAPER_STREAMFIELD_DEFAULT_PROCESSOR`<br>
Default processor for content blocks.<br>
Default: `"streamfield.processors.DefaultProcessor"`
`PAPER_STREAMFIELD_DEFAULT_TEMPLATE_ENGINE`<br>
Default template engine for `render_stream` template tag.<br>
Default: `None`
Raw data
{
"_id": null,
"home_page": "https://github.com/dldevinc/paper-streamfield",
"name": "paper-streamfield",
"maintainer": "Mihail Mishakin",
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": "x896321475@gmail.com",
"keywords": "",
"author": "Mihail Mishakin",
"author_email": "x896321475@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/0c/07/d899717e4c34228a588aea88d9395f8f6e7b30400aab843f41d2d446721c/paper-streamfield-0.8.0.tar.gz",
"platform": "OS Independent",
"description": "# paper-streamfield\n\nImplementation of the Wagtail's StreamField block picker for paper-admin.\n\n[](https://pypi.org/project/paper-streamfield/)\n[](https://github.com/dldevinc/paper-streamfield)\n[](https://pypi.org/project/paper-streamfield/)\n\n## Compatibility\n\n- `python` >= 3.9\n- `django` >= 3.1\n- `paper-admin` >= 6.0\n\n## Installation\n\nInstall the latest release with pip:\n\n```shell\npip install paper-streamfield\n```\n\nAdd `streamfield` to your INSTALLED_APPS in django's `settings.py`:\n\n```python\nINSTALLED_APPS = (\n # other apps\n \"streamfield\",\n)\n```\n\nAdd `streamfield.urls` to your URLconf:\n\n```python\nurlpatterns = patterns('',\n ...\n path(\"streamfields/\", include(\"streamfield.urls\")),\n)\n```\n\n## How to use\n\n1. Create some models that you want to use as blocks:\n\n ```python\n # blocks/models.py\n \n from django.core.validators import MaxValueValidator, MinValueValidator\n from django.db import models\n from django.utils.text import Truncator\n \n \n class HeadingBlock(models.Model):\n text = models.TextField()\n rank = models.PositiveSmallIntegerField(\n default=1,\n validators=[\n MinValueValidator(1),\n MaxValueValidator(6)\n ]\n )\n \n class Meta:\n verbose_name = \"Heading\"\n \n def __str__(self):\n return Truncator(self.text).chars(128)\n \n \n class TextBlock(models.Model):\n text = models.TextField()\n \n class Meta:\n verbose_name = \"Text\"\n \n def __str__(self):\n return Truncator(self.text).chars(128)\n ```\n\n2. Register your models using `StreamBlockModelAdmin` class.\n\n ```python\n # blocks/admin.py\n \n from django.contrib import admin\n from streamfield.admin import StreamBlockModelAdmin\n from .models import HeadingBlock, TextBlock\n \n \n @admin.register(HeadingBlock)\n class HeadingBlockAdmin(StreamBlockModelAdmin):\n list_display = [\"__str__\", \"rank\"]\n \n \n @admin.register(TextBlock)\n class TextBlockAdmin(StreamBlockModelAdmin):\n pass\n ```\n\n3. Create templates for each block model, named as lowercase\n model name or _snake_cased_ model name.\n\n ```html\n <!-- blocks/templates/blocks/headingblock.html -->\n <!-- or -->\n <!-- blocks/templates/blocks/heading_block.html -->\n <h{{ block.rank }}>{{ block.text }}</h{{ block.rank }}>\n ```\n \n ```html\n <!-- blocks/templates/blocks/textblock.html -->\n <!-- or -->\n <!-- blocks/templates/blocks/text_block.html -->\n <div>{{ block.text|linebreaks }}</div>\n ```\n\n4. Add a `StreamField` to your model:\n\n ```python\n # app/models.py\n \n from django.db import models\n from django.utils.translation import gettext_lazy as _\n from streamfield.field.models import StreamField\n \n \n class Page(models.Model):\n stream = StreamField(\n _(\"stream\"), \n models=[\n \"blocks.HeaderBlock\",\n \"blocks.TextBlock\",\n ]\n )\n \n class Meta:\n verbose_name = \"Page\"\n ```\n \n Result:\n \n \n Now you can create some blocks:\n \n\n5. Use `render_stream` template tag to render the stream field.\n\n ```html\n <!-- app/templates/index.html -->\n {% load streamfield %}\n \n {% render_stream page.stream %}\n ```\n \n Result:\n \n\n\n> When working with block templates, it's important to note that \n> you have access to all variables from the parent context.\n\n## Special cases\n\n### Use custom template name or template engine\n\nYou can specify a template name or engine to render a specific block \nwith `StreamBlockMeta` class in your block model:\n\n```python\nclass HeadingBlock(models.Model):\n # ...\n\n class StreamBlockMeta:\n template_engine = \"jinja2\"\n template_name = \"blocks/heading.html\"\n```\n\n### Caching the rendered HTML of a block\n\nYou can enable caching for specific blocks to optimize rendering.\n\n```python\nclass HeadingBlock(models.Model):\n # ...\n\n class StreamBlockMeta:\n cache = True\n cache_ttl = 3600\n```\n\nOnce caching is enabled for the block, the rendered HTML will be stored \nin cache, and subsequent requests will retrieve the cached content, \nreducing the need for re-rendering.\n\n> Note that the specified block will **not** be invalidated\n> when something changes in it.\n\n### Adding context variables to all blocks\n\nYou can add context variables to all blocks in your StreamField by providing them \nthrough the `render_stream` template tag. This allows you to pass common context data \nto customize the rendering of all content blocks consistently:\n\n```html\n<!-- app/templates/index.html -->\n{% load streamfield %}\n\n{% render_stream page.stream classes=\"text text--small\" %}\n```\n\n```html\n<!-- blocks/templates/blocks/textblock.html -->\n<div class=\"{{ classes }}\">{{ block.text|linebreaks }}</div>\n```\n\n### Adding context variables to a specific block\n\nTo add context variables to a specific content block, \nyou must create a custom processor. A processor provides a mechanism for \ncustomizing the context data and the rendering process of an individual block.\n\n1. Create a custom processor class that inherits from \n `streamfield.processors.DefaultProcessor`.\n ```python\n from streamfield.processors import DefaultProcessor\n from reviews.models import Review\n\n class ReviewsBlockProcessor(DefaultProcessor):\n def get_context(self, block):\n context = super().get_context(block)\n context[\"reviews\"] = Review.objects.all()[:5]\n return context\n ```\n2. In your block's model, specify the processor to use:\n ```python\n class ReviewsBlock(models.Model):\n # ...\n \n class StreamBlockMeta:\n processor = \"your_app.processors.ReviewsBlockProcessor\"\n ```\n\nYou can utilize the `exceptions.SkipBlock` feature to conditionally skip the rendering \nof a block. This can be useful, for example, when dealing with a block like \"Articles\" \nthat should only render when there are articles available. Example:\n\n```python\nfrom streamfield.processors import DefaultProcessor\nfrom streamfield.exceptions import SkipBlock\nfrom articles.models import Article\n\n\nclass ArticlesBlockProcessor(DefaultProcessor):\n def get_context(self, block):\n context = super().get_context(block)\n\n articles = Article.object.all()[:3]\n if len(articles) < 3:\n # Skip block if not enough article instances\n raise SkipBlock\n\n context[\"articles\"] = articles\n return context\n```\n\n### Using `render_block` template tag\n\nIn some cases, you may have a page that references a specific block through \na `ForeignKey` relationship, and you want to render that referenced block on the page. \nYou can achieve this using the render_block template tag. Here's an example:\n\n```python\n# page/models.py\n\nfrom django.db import models\nfrom blocks.models import TextBlock\n\nclass Page(models.Model):\n text_block = models.ForeignKey(TextBlock, on_delete=models.SET_NULL, blank=True, null=True)\n\n class Meta:\n verbose_name = \"Page\"\n```\n\n```html\n<!-- app/templates/page.html -->\n{% load streamfield %}\n\n<div>\n <h1>Page Title</h1>\n <div>\n <h2>Text Block:</h2>\n {% render_block page.text_block %}\n </div>\n</div>\n```\n\n### Customize block in admin interface\n\nYou can customize how a block is rendered in the admin interface\nby specifying `stream_block_template` field in the `StreamBlockModelAdmin`\nclass:\n\n```python\nfrom django.contrib import admin\nfrom streamfield.admin import StreamBlockModelAdmin\nfrom .models import ImageBlock\n\n\n@admin.register(ImageBlock)\nclass ImageBlockAdmin(StreamBlockModelAdmin):\n stream_block_template = \"blocks/admin/image.html\"\n list_display = [\"__str__\", \"title\", \"alt\"]\n```\n\n```html\n<!-- blocks/admin/image.html -->\n{% extends \"streamfield/admin/block.html\" %}\n\n{% block content %}\n <div class=\"d-flex\">\n <div class=\"flex-grow-0 mr-2\">\n <img class=\"preview\"\n src=\"{{ instance.image }}\"\n width=\"48\"\n height=\"36\"\n title=\"{{ instance.title }}\"\n alt=\"{{ instance.alt }}\"\n style=\"object-fit: cover\">\n </div>\n \n {{ block.super }}\n </div>\n{% endblock content %}\n```\n\n## Settings\n\n`PAPER_STREAMFIELD_DEFAULT_PROCESSOR`<br>\nDefault processor for content blocks.<br>\nDefault: `\"streamfield.processors.DefaultProcessor\"`\n\n`PAPER_STREAMFIELD_DEFAULT_TEMPLATE_ENGINE`<br>\nDefault template engine for `render_stream` template tag.<br>\nDefault: `None`\n",
"bugtrack_url": null,
"license": "BSD license",
"summary": "Implementation of the Wagtail's StreamField block picker for paper-admin.",
"version": "0.8.0",
"project_urls": {
"Homepage": "https://github.com/dldevinc/paper-streamfield"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "e6b84c674da537dc7b930426d8f1688ae96b406a541224f3e8ae64f1f89c1b99",
"md5": "9eb6c363bbd728ad1abd199559719b9b",
"sha256": "e9cd194dd2b35648deb7faf55c666a7ae3798fe4f4326a9f4e8e5acb017664ba"
},
"downloads": -1,
"filename": "paper_streamfield-0.8.0-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "9eb6c363bbd728ad1abd199559719b9b",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": ">=3.9",
"size": 33236,
"upload_time": "2023-12-03T12:40:30",
"upload_time_iso_8601": "2023-12-03T12:40:30.510419Z",
"url": "https://files.pythonhosted.org/packages/e6/b8/4c674da537dc7b930426d8f1688ae96b406a541224f3e8ae64f1f89c1b99/paper_streamfield-0.8.0-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "0c07d899717e4c34228a588aea88d9395f8f6e7b30400aab843f41d2d446721c",
"md5": "b433d8a825c774b712dac583b2d8e35f",
"sha256": "c4730f036abb363c68b8a3004c937d5f5c6a8470a23fc31ac70c087f316c965d"
},
"downloads": -1,
"filename": "paper-streamfield-0.8.0.tar.gz",
"has_sig": false,
"md5_digest": "b433d8a825c774b712dac583b2d8e35f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 28500,
"upload_time": "2023-12-03T12:40:32",
"upload_time_iso_8601": "2023-12-03T12:40:32.463461Z",
"url": "https://files.pythonhosted.org/packages/0c/07/d899717e4c34228a588aea88d9395f8f6e7b30400aab843f41d2d446721c/paper-streamfield-0.8.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-12-03 12:40:32",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "dldevinc",
"github_project": "paper-streamfield",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"tox": true,
"lcname": "paper-streamfield"
}