paper-streamfield


Namepaper-streamfield JSON
Version 0.8.0 PyPI version JSON
download
home_pagehttps://github.com/dldevinc/paper-streamfield
SummaryImplementation of the Wagtail's StreamField block picker for paper-admin.
upload_time2023-12-03 12:40:32
maintainerMihail Mishakin
docs_urlNone
authorMihail Mishakin
requires_python>=3.9
licenseBSD license
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # paper-streamfield

Implementation of the Wagtail's StreamField block picker for paper-admin.

[![PyPI](https://img.shields.io/pypi/v/paper-streamfield.svg)](https://pypi.org/project/paper-streamfield/)
[![Build Status](https://github.com/dldevinc/paper-streamfield/actions/workflows/tests.yml/badge.svg)](https://github.com/dldevinc/paper-streamfield)
[![Software license](https://img.shields.io/pypi/l/paper-streamfield.svg)](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:
   ![](https://user-images.githubusercontent.com/6928240/190413272-14b95712-de0f-4a9b-a815-40e3fb0a2d85.png)
   
   Now you can create some blocks:
   ![](https://user-images.githubusercontent.com/6928240/190414025-dfe364a9-524e-4529-835d-a3e507d1ee19.png)

5. Use `render_stream` template tag to render the stream field.

   ```html
   <!-- app/templates/index.html -->
   {% load streamfield %}
   
   {% render_stream page.stream %}
   ```
   
   Result:
   ![](https://user-images.githubusercontent.com/6928240/190416377-e2ba504f-8aa0-44ed-b59d-0cf1ccea695e.png)


> 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[![PyPI](https://img.shields.io/pypi/v/paper-streamfield.svg)](https://pypi.org/project/paper-streamfield/)\n[![Build Status](https://github.com/dldevinc/paper-streamfield/actions/workflows/tests.yml/badge.svg)](https://github.com/dldevinc/paper-streamfield)\n[![Software license](https://img.shields.io/pypi/l/paper-streamfield.svg)](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   ![](https://user-images.githubusercontent.com/6928240/190413272-14b95712-de0f-4a9b-a815-40e3fb0a2d85.png)\n   \n   Now you can create some blocks:\n   ![](https://user-images.githubusercontent.com/6928240/190414025-dfe364a9-524e-4529-835d-a3e507d1ee19.png)\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   ![](https://user-images.githubusercontent.com/6928240/190416377-e2ba504f-8aa0-44ed-b59d-0cf1ccea695e.png)\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"
}
        
Elapsed time: 0.14805s