├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── changes2.0.md
├── frontend
├── babel.config.json
├── images
│ └── icons.svg
├── package.json
├── src
│ ├── admin_popup_response.js
│ ├── components
│ │ ├── AbstractBlock.vue
│ │ ├── AddBlockHere.vue
│ │ ├── App.vue
│ │ ├── BlockHeader.vue
│ │ ├── BlockList.vue
│ │ ├── BlockOptions.vue
│ │ └── StreamBlock.vue
│ ├── streamfield_widget.js
│ ├── style.sass
│ └── utils.js
└── webpack.config.js
├── setup.py
├── streamfield
├── __init__.py
├── admin.py
├── apps.py
├── base.py
├── fields.py
├── forms.py
├── locale
│ ├── en
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── fr
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── it
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ └── ru
│ │ └── LC_MESSAGES
│ │ ├── django.mo
│ │ └── django.po
├── models.py
├── settings.py
├── static
│ └── streamfield
│ │ ├── admin_popup_response.js
│ │ ├── streamfield_widget.css
│ │ └── streamfield_widget.js
├── templates
│ └── streamfield
│ │ ├── admin
│ │ ├── abstract_block_template.html
│ │ ├── change_form.html
│ │ ├── change_form_render_template.html
│ │ ├── fields
│ │ │ ├── default.html
│ │ │ ├── file_browse_widget.html
│ │ │ ├── select.html
│ │ │ └── stream_field_widget.html
│ │ └── streamfield_popup_response.html
│ │ ├── default_block_tmpl.html
│ │ ├── streamfield_admin_help.html
│ │ ├── streamfield_texts.js
│ │ └── streamfield_widget.html
├── templatetags
│ ├── __init__.py
│ └── streamfield_tags.py
├── tests.py
├── urls.py
└── views.py
└── test_project
├── manage.py
├── pages
├── __init__.py
├── admin.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── templates
│ └── pages
│ │ ├── index.html
│ │ └── page_detail.html
├── tests.py
└── views.py
├── streamblocks
├── __init__.py
├── admin.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── templates
│ └── streamblocks
│ │ ├── admin
│ │ ├── fields
│ │ │ └── textarea.html
│ │ └── richtext.html
│ │ ├── column.html
│ │ └── richtext.html
├── tests.py
└── views.py
└── test_project
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .DS_Store
3 | build/*
4 | dist/*
5 | django_streamfield.egg-info/*
6 | test_project/db.sqlite3
7 | package-lock.json
8 | frontend/node_modules/*
9 | streamfield/static/streamfield/index.html
10 | streamfield/static/streamfield/streamfield_widget.js.LICENSE.txt
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | django-streamfield
2 | ----------------
3 |
4 | Copyright (c) 2019 Lapshinov Yury (y.raagin@gmail.com)
5 |
6 | Redistribution and use in source and binary forms, with or without modification,
7 | are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice,
10 | this list of conditions and the following disclaimer.
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation and/or
13 | other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
16 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18 | THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include LICENSE
3 | recursive-include streamfield/static *
4 | recursive-include streamfield/templates *
5 | recursive-include streamfield/locale *
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Django StreamField
2 |
3 | This is a simple realisation of StreamField's idea of Wagtail CMS for plain Django admin or with Grappelli skin.
4 | Stable version: 2.4.0
5 | Django <= 5.2
6 |
7 | [Major changes (1.4.5 > 2)](changes2.0.md)
8 |
9 | ## Highlights
10 | You can build your page with different kind of blocks.
11 | Sort them and sort the lists inside the blocks.
12 |
13 | **The blocks here are regular instances of Django models.** For editing content inside the blocks, it use native popup mechanism in Django admin interface.
14 | This allow you to use other field's widgets inside the blocks as is.
15 | For example, if you want to use in your blocks FileBrowseField
16 | from django-filebrowser, it will perfectly working
17 | without any additional settings.
18 |
19 | Module also working with [Grappelli Interface](https://github.com/sehmaschine/django-grappelli) (Optional)
20 |
21 | 
22 |
23 | ## Contents
24 |
25 | - [Installation](#installation)
26 | - [How to use](#how-to-use)
27 | - [Admin](#admin)
28 | - [Custom admin class for block's models](#custom-admin-class-for-blocks-models)
29 | - [Custom templates for render block models in admin](#custom-templates-for-render-block-models-in-admin)
30 | - [Override how to render block's fields in admin](#override-how-to-render-blocks-fields-in-admin)
31 | - [Override list of blocks for your StreamField in admin.py](#override-list-of-blocks-for-your-streamfield-in-adminpy)
32 | - [Block options](#block-options)
33 | - [Special cases](#special-cases)
34 | - [Complex Blocks](#complex-blocks)
35 | - [Blocks without data in database. Only templates](#blocks-without-data-in-database-only-templates)
36 | - [Add extra context to blocks](#add-extra-context-to-blocks)
37 | - [Get field data as list](#get-field-data-as-list)
38 | - [Cache for reduce the number of database requests](#cache-for-reduce-the-number-of-database-requests)
39 | - [Create a copy](#create-a-copy)
40 | - [Add block programarly](#add-block-programarly)
41 | - [Set size of block's popup window](#set-size-of-blocks-popup-window)
42 | - [Settings](#settings)
43 | - [Migrations](#migrations)
44 |
45 | ## Installation
46 |
47 | Requirements: `django>=3.1`
48 |
49 | `pip install django-streamfield`
50 |
51 | ## How to use
52 | - Create streamblocks app with your models
53 | - Add streamfield and streamblocks to INSTALLED_APPS
54 | - Add streamfield.urls
55 | - Create templates for streamblocks
56 | - Add StreamField to your model
57 | - Use it in templates
58 |
59 | **1. Create new app called `streamblocks` and put there some models**
60 |
61 | ...that you want to use as blocks in your StreamField.
62 | Add them to the list `STREAMBLOCKS_MODELS`.
63 | For example:
64 |
65 | ```python
66 | # streamblocks/models.py
67 |
68 | # one object
69 | class RichText(models.Model):
70 | text = models.TextField(blank=True, null=True)
71 |
72 | def __str__(self):
73 | # This text will be added to block title name.
74 | # For better navigation when block is collapsed.
75 | return self.text[:30]
76 |
77 | class Meta:
78 | # This will use as name of block in admin
79 | # See also STREAMFIELD_BLOCK_TITLE in settings
80 | verbose_name="Text"
81 |
82 | # list of objects
83 | class ImageWithText(models.Model):
84 | image = models.ImageField(upload_to="folder/")
85 | text = models.TextField(null=True, blank=True)
86 |
87 | # StreamField option for list of objects
88 | as_list = True
89 |
90 | def __str__(self):
91 | # This text will be added to block title name.
92 | # For better navigation when block is collapsed.
93 | return self.text[:30]
94 |
95 | class Meta:
96 | verbose_name="Image with text"
97 | verbose_name_plural="Images with text"
98 |
99 | # Register blocks for StreamField as list of models
100 | STREAMBLOCKS_MODELS = [
101 | RichText,
102 | ImageWithText
103 | ]
104 | ```
105 | > Important!: Don't use 'as_list', 'options', 'extra_options' as models field names, because they are used by streamfield.
106 |
107 | **2. Add apps to settings.py and make migrations**
108 |
109 | Add to INSTALLED_APPS
110 |
111 | ```python
112 | INSTALLED_APPS = [
113 | ...
114 | 'streamblocks',
115 | 'streamfield',
116 | ...
117 | ```
118 | Run `python manage.py makemigrations` and `python manage.py migrate`
119 |
120 | **3. Add streamfield.urls to main urls.py**
121 | ```python
122 | urlpatterns += [
123 | path('streamfield/', include('streamfield.urls'))
124 | ]
125 | ```
126 |
127 | **4. Create templates for each block model, named as lowercase names of the models:**
128 |
129 | 1. streamblocks/templates/streamblocks/richtext.html
130 | 2. streamblocks/templates/streamblocks/imagewithtext.html
131 |
132 | And use `block_content` as context.
133 |
134 | > Note: block_content will be single object
135 | if no 'as_list' property in your model,
136 | and will be a list of objects if there is.
137 |
138 | ```html
139 |
140 |
141 | {{ block_content.text|safe }}
142 |
143 | ```
144 | ```html
145 |
146 |
147 | {% for block in block_content %}
148 |
149 |
150 |
{{ block.text }}
151 |
152 | {% endfor %}
153 |
154 | ```
155 |
156 | > Note: You may use also `block_template` option. For specify a block template file.
157 |
158 | ```python
159 | class RichText(models.Model):
160 | ...
161 | block_template = "streamblocks/richtext.html"
162 | ...
163 | ```
164 | > Note: If you need unique string in block template, use `block_model` and `block_unique_id`
165 |
166 | *Full list of variables in template context:*
167 | - `block_model` (lowercase of modelname - "richtext")
168 | - `block_unique_id` (unique string)
169 | - `block_content` (block data from db)
170 | - `as_list` (boolean)
171 | - `options` ([block options](#block-options))
172 |
173 | > Note: For unique identifier inside the lists you may use a combination of `block_unique_id` and `block.id` of subblock.
174 |
175 | **5. Add StreamField to your model in your application**
176 |
177 | And add the models that you want to use in this stream as model_list
178 | ```python
179 | # models.py
180 | from streamfield.fields import StreamField
181 | from streamblocks.models import RichText, ImageWithText
182 |
183 | class Page(models.Model):
184 | stream = StreamField(
185 | model_list=[
186 | RichText,
187 | ImageWithText
188 | ],
189 | verbose_name="Page blocks"
190 | )
191 | ```
192 | **6. Use it in template**
193 | If you have your `page` in context,
194 | you can get content by field's cached property page.stream.render
195 | ```html
196 | ...
197 |
198 | {{ page.stream.render }}
199 |
200 | ...
201 | ```
202 |
203 | Or, if you need extra context in blocks, you may use template tag:
204 | ```html
205 | {% load streamfield_tags %}
206 | ...
207 |
210 | ...
211 | ```
212 | Third way it's to use list. [See bellow](#get-field-data-as-list)
213 |
214 |
215 | ## Admin
216 | ### Custom admin class for block's models
217 | Models will automaticaly register in admin.
218 | If you want provide custom admin class,
219 | first unregister models and register again, using `StreamBlocksAdmin` class.
220 |
221 | ```python
222 | # streamblocks/admin.py
223 |
224 | from django.contrib import admin
225 | from streamfield.admin import StreamBlocksAdmin
226 |
227 | from streamblocks.models import RichText
228 |
229 | admin.site.unregister(RichText)
230 | @admin.register(RichText)
231 | class RichTextBlockAdmin(StreamBlocksAdmin, admin.ModelAdmin):
232 | pass
233 | ```
234 |
235 | ### Custom templates for render block models in admin
236 | If you need to customize admin templates for block models wich you are using, you need to put templates named as
237 | described in section 4 (above). but put it inside "admin" folder.
238 |
239 | For example for RichText block it will be:
240 |
241 | `streamblocks/templates/streamblocks/admin/richtext.html`
242 |
243 | As context use "form" and/or "object" (Not working for abstract blocks):
244 | ```html
245 | {{ form.text.value }}
246 | {{ object }}
247 | ```
248 |
249 | The default admin template is: `streamfield/admin/change_form_render_template.html`
250 | You can extend it
251 | ```html
252 | {% extends "streamfield/admin/change_form_render_template.html" %}
253 | {% block streamblock_form %}
254 | {{ block.super }}
255 | Original object is: {{ object }}
256 | {% endblock streamblock_form %}
257 | ```
258 |
259 |
260 | You may also specify custom template as option:
261 | ```python
262 | class RichText(models.Model):
263 | ...
264 | custom_admin_template = "streamblocks/admin/richtext.html"
265 | ...
266 | ```
267 |
268 | ### Override how to render block's fields in admin
269 | Create custom template for field with name as generated by `django.utils.text.camel_case_to_spaces` from field widget name, and put it inside `.../streamblocks/admin/fields/` folder.
270 |
271 | For example for TextField widget (Textarea) of RichText block, it will be:
272 | `streamblocks/templates/streamblocks/admin/fields/textarea.html`
273 |
274 | And `MyCustomWidget`:
275 | `streamblocks/templates/streamblocks/admin/fields/my_custom_widget.html`
276 |
277 | As context use "field":
278 | ```html
279 | {{ field.value|default:""|safe }}
280 | ```
281 |
282 | ### Override list of blocks for your StreamField in admin.py
283 | Typicaly you set the blocks in your models as `model_list` attribute of StreamField field.
284 | But if you want to change the blocks, for example depending on the object, you can do it in the admin site
285 | of your model. Suppose you want to use only `RichText` on page with id=1.
286 |
287 | ```python
288 | # admin.py
289 | from streamfield.fields import StreamFieldWidget
290 | from streamblocks.models import RichText
291 | from .models import Page
292 |
293 | @admin.register(Page)
294 | class PageAdmin(models.Admin):
295 |
296 | def get_form(self, request, obj=None, **kwargs):
297 | form = super().get_form(request, obj, **kwargs)
298 | if obj and obj.id == 1:
299 | form.base_fields['stream'].widget = StreamFieldWidget(attrs={
300 | 'model_list': [ RichText ]
301 | })
302 | return form
303 | ```
304 | Be careful with already existing blocks in db. If you remove them from admin, it produce error.
305 |
306 | ## Block options
307 | You may use `options` property in your streamblocks models to add some additional options to your block.
308 | This is useful with `as_list` property when you need to add some options to whole block not separatly to each object of this list.
309 |
310 | For example:
311 | ```python
312 | # streamblocks/models.py
313 |
314 | # list of objects as slider
315 | class Slide(models.Model):
316 | image = models.ImageField(upload_to="folder/")
317 | text = models.TextField(null=True, blank=True)
318 |
319 | # StreamField option for list of objects
320 | as_list = True
321 |
322 | options = {
323 | 'autoplay': {
324 | 'label': 'Autoplay slider',
325 | 'type': 'checkbox',
326 | 'default': False
327 | },
328 | 'width': {
329 | 'label': 'Slider size',
330 | 'type': 'select',
331 | 'default': 'wide',
332 | 'options': [
333 | {'value': 'wide', 'name': 'Wide slider'},
334 | {'value': 'narrow', 'name': 'Narrow slider'},
335 | ]
336 | },
337 | 'class_name': {
338 | 'label': 'Class Name',
339 | 'type': 'text',
340 | 'default': ''
341 | }
342 | }
343 |
344 | class Meta:
345 | verbose_name="Slide"
346 | verbose_name_plural="Slider"
347 | ```
348 | In block template you can use this options as `options.autoplay`
349 | In page admin you will see it on the bottom of this block.
350 | > Note: Now only "checkbox", "text" and "select" type is working.
351 | You may apply options for all blocks with `STREAMFIELD_BLOCK_OPTIONS` (See [Settings](#settings))
352 |
353 | If you want to add block options to options, which was set in django settings, you may use `extra_options`.
354 | ```python
355 | class Slide(models.Model):
356 | ...
357 | extra_options = {
358 | "autoplay": {
359 | "label": "Autoplay",
360 | "type": "checkbox",
361 | "default": False
362 | }
363 | }
364 | ...
365 | ```
366 |
367 | If you want to switch off options, which set in django settings, for current block. Set `options={}`
368 |
369 | ## Special cases
370 | ### Complex Blocks
371 | You may use StreamField as part of blocks and create with that way complex structure
372 | and use `{{ block_content..render }}`
373 |
374 | ### Blocks without data in database. Only templates.
375 | You may use it for widgets or separators or for whatever you want...
376 | Just make the block model `abstract`.
377 | ```python
378 | class EmptyBlock(models.Model):
379 | class Meta:
380 | abstract = True
381 | verbose_name='Empty space'
382 | ```
383 | and use `streamblocks/templates/streamblocks/emptyblock.html` for your content.
384 | For admin `streamblocks/templates/streamblocks/admin/emptyblock.html`
385 | > Note: Don't forget to register a block in STREAMBLOCKS_MODELS
386 |
387 | ### Add extra context to blocks
388 | Supose, you need to add some data to blocks from global context.
389 | Instead of using render property in template `{{ page.stream.render }}`,
390 | you need to use template tag `stream_render` from `streamfield_tags` with keywords arguments.
391 |
392 | For example, if you have in page template `request` and `page` objects and want to use it in blocks:
393 | ```html
394 | {% load streamfield_tags %}
395 | ...
396 |
399 | ...
400 | ```
401 |
402 | ### Get field data as list
403 | If you have special case, you can get data as list.
404 |
405 | ```python
406 | # views.py
407 | stream_list = page.stream.as_list()
408 | # You will get list of dictionaries
409 | # print(stream_list)
410 | [{
411 | 'data': {
412 | 'block_model': '.....',
413 | 'block_unique_id': '....',
414 | 'block_content': [...],
415 | 'as_list': True,
416 | 'options': {}
417 | },
418 | 'template': '....'
419 | },
420 | ...
421 | ]
422 | ```
423 |
424 | ```html
425 |
426 | {% for b in page.stream.as_list %}
427 | {% include b.template with block_content=b.data.block_content %}
428 | {% endfor %}
429 | ```
430 |
431 |
432 |
433 | ### Cache for reduce the number of database requests
434 | There is two ways of caching:
435 | - Simple cache view with django cache
436 | - Create additional field, for example: 'stream_rendered'
437 | and render to this field html in save method
438 |
439 | ```python
440 | def save(self, *args, **kwargs):
441 | self.stream_rendered = self.stream.render
442 | super().save(*args, **kwargs)
443 | ```
444 | ...and use this field in your html
445 |
446 | ### Create a copy
447 | StreamObject have a method 'copy', which create a copies of all the instances that used in this field.
448 | For example, if you have object 'page' with the field 'stream', and you need a copy of this object. You can do this:
449 |
450 | ```python
451 | page.pk = None
452 | page.stream = page.stream.copy()
453 | page.save()
454 | ```
455 | > Note: If you will not use `page.stream.copy()` instances will be the same as in the original object
456 |
457 | ### Add block programarly
458 | ```python
459 | r = RichText(text='
Lorem ipsum
')
460 | im1 = ImageWithText.objects.create(image='...')
461 | im2 = ImageWithText.objects.create(image='...')
462 | page.stream.add(r)
463 | page.stream.add([im1, im2])
464 | page.save()
465 | ```
466 | > Note: If you create a new instance of page in shell, before using `add` method, you need to call instance form db. Because field `stream` should be wrapped in StreamObject
467 | ```python
468 | page = Page()
469 | page.save()
470 | page.refresh_from_db()
471 | ```
472 |
473 | ### Set size of block's popup window
474 | Add `popup_size` attribute to StreamField
475 | ```python
476 | ...
477 | stream = StreamField(
478 | model_list=[...],
479 | popup_size=(1000, 500) # default value. Width: 1000px, Height: 500px
480 | )
481 | ...
482 | ```
483 |
484 |
485 | ## Settings
486 | ```python
487 | # settings.py
488 | ```
489 |
490 | ### STREAMFIELD_SHOW_ADMIN_HELP_TEXT
491 | If you want to show "Help" link in admin.
492 | Set:
493 | ```python
494 | STREAMFIELD_SHOW_ADMIN_HELP_TEXT = True
495 | ```
496 |
497 | ### STREAMFIELD_ADMIN_HELP_TEXT
498 | You can setup custom help text in settings
499 | ```python
500 | STREAMFIELD_ADMIN_HELP_TEXT = '
Text
'
501 | ```
502 |
503 | ### STREAMFIELD_DELETE_BLOCKS_FROM_DB
504 | If you want to keep streamblock's instances in db, when you removing it from StreamField. Set:
505 | ```python
506 | STREAMFIELD_DELETE_BLOCKS_FROM_DB = False
507 | ```
508 | It was default behavior in previous releases.
509 | > Note: If you delete entire object which contain StreamField, streamblock's instances will not be deleted. You should care about it by yourself.
510 |
511 | ### STREAMFIELD_BLOCK_TITLE (> 2.0.1)
512 | 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.
513 |
514 | ### STREAMFIELD_BLOCK_OPTIONS
515 |
516 | You may use `STREAMFIELD_BLOCK_OPTIONS` in settings.py to add some options to all blocks.
517 |
518 | For example:
519 | ```python
520 | STREAMFIELD_BLOCK_OPTIONS = {
521 | "margins": {
522 | "label": "Margins",
523 | "type": "checkbox",
524 | "default": True
525 | }
526 | }
527 | ```
528 | In block template use `{{ options.margins }}`
529 |
530 | > Note: Now only "checkbox", "text", and "select" type is working.
531 |
532 | ## Migrations
533 | If you add new options to Block with already existed data, you need to migrate options for adding default values to stored json.
534 | Create empty migration and use `migrate_stream_options` function from `streamfield.base`.
535 | At the moment this only works with unique streamblocks class names.
536 |
537 | Example:
538 | ```python
539 | # migration
540 | from django.db import migrations
541 | from streamfield.base import migrate_stream_options
542 |
543 | def migrate_options(apps, schema_editor):
544 | Page = apps.get_model("main", "Page")
545 | for page in Page.objects.all():
546 | page.stream = migrate_stream_options(page.stream)
547 | page.save()
548 |
549 | class Migration(migrations.Migration):
550 |
551 | dependencies = [
552 | '...'
553 | ]
554 |
555 | operations = [
556 | migrations.RunPython(migrate_options),
557 | ]
558 | ```
559 |
560 |
561 |
--------------------------------------------------------------------------------
/changes2.0.md:
--------------------------------------------------------------------------------
1 | # django-streamfield. Changes 2.0
2 | ## Major changes
3 | 1. Removed string escaping in the database. Now StreamField is stored in the database as a native JSONField, since version 3.1 Django supports JSON in all databases. When resaving the object, escaping in the new version will be automatically removed.
4 | 2. Added new frontend features: You can open/collapse blocks by one (click on the block header) or all together. You can add new block between the others blocks (put cursor between the blocks and wait for plus button).
5 | 3. For better blocks navigation you can add name of the block by using `__str__` method in block definition code. Or you can change the method name in [settings](https://github.com/raagin/django-streamfield#streamfield_block_title-v201)
6 | 4. The collapsed state of the blocks is stored in the database.
7 | 5. For development. Webpack 5 is used to build frontend part. JS scripts is divided into components. Vue updated to version 3. SASS is used for styling.
8 | 6. JS libraries are join to one bundle including streamfield.
9 |
10 | ## Minor changes
11 | 1. StreamBlocksAdminMixin now using for StreamBlocksAdmin class (#21)
12 | 2. Icons changed from png to svg
13 | 3. STREAMFIELD_SHOW_ADMIN_HELP_TEXT bug fixed (#27)
14 | 4. STREAMFIELD_SHOW_ADMIN_HELP_TEXT now is False by default. And you can add your own text by using STREAMFIELD_ADMIN_HELP_TEXT in settings.
15 | 5. Removed STREAMFIELD_SHOW_ADMIN_COLLAPSE from settings.
16 | 6. Fixed migrate_stream_options method.
17 |
--------------------------------------------------------------------------------
/frontend/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "modules": false,
7 | "targets": {
8 | "browsers": [
9 | "> 1%",
10 | "last 2 versions",
11 | "not dead",
12 | "ie > 11"
13 | ]
14 | }
15 | }
16 | ]
17 | ]
18 | }
--------------------------------------------------------------------------------
/frontend/images/icons.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "streamfield_widget.js",
3 | "version": "2.4.0",
4 | "description": "",
5 | "private": true,
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "webpack --mode development --watch",
9 | "build": "webpack --mode production --progress"
10 | },
11 | "author": "Yury Lapshinov ",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "@babel/preset-env": "^7.20.2",
15 | "@vue/compiler-sfc": "^3.2.47",
16 | "axios": "^1.3.4",
17 | "babel-loader": "^9.1.2",
18 | "clean-webpack-plugin": "^4.0.0",
19 | "css-loader": "^6.7.3",
20 | "css-minimizer-webpack-plugin": "^4.2.2",
21 | "file-loader": "^6.2.0",
22 | "html-webpack-plugin": "^5.5.0",
23 | "mini-css-extract-plugin": "^2.7.5",
24 | "sass": "^1.60.0",
25 | "sass-loader": "^13.2.2",
26 | "sortablejs": "^1.15.0",
27 | "style-loader": "^3.3.2",
28 | "terser-webpack-plugin": "^5.3.7",
29 | "vue-loader": "^17.0.1",
30 | "vuedraggable": "^4.1.0",
31 | "webpack": "^5.77.0",
32 | "webpack-cli": "^5.0.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/frontend/src/admin_popup_response.js:
--------------------------------------------------------------------------------
1 | /*global opener */
2 | (function() {
3 | 'use strict';
4 | var initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse);
5 | switch(initData.action) {
6 | case 'change':
7 | opener.streamapps[initData.app_id].updateBlock(initData.block_id, initData.instance_id);
8 | window.close();
9 | break;
10 | case 'delete':
11 | // opener.console.log("delete", initData);
12 | break;
13 | default:
14 | opener.streamapps[initData.app_id].updateBlock(initData.block_id, initData.instance_id);
15 | window.close();
16 | break;
17 | }
18 | })();
19 |
--------------------------------------------------------------------------------
/frontend/src/components/AbstractBlock.vue:
--------------------------------------------------------------------------------
1 |
32 |
33 |
After moving, adding or deleting blocks you need to save the entire "
43 | "object.
\n"
44 | "
If you have deleted some blocks accidentally, you may get them back "
45 | "by reloading the page.\n"
46 | " All edited blocks will remain, in the order of the saved version.\n"
47 | " Newly added blocks will need to be recreated.
After moving, adding or deleting blocks you need to save the entire "
43 | "object.
\n"
44 | "
If you have deleted some blocks accidentally, you may get them back "
45 | "by reloading the page.\n"
46 | " All edited blocks will remain, in the order of the saved version.\n"
47 | " Newly added blocks will need to be recreated.
\n"
48 | "
\n"
49 | msgstr ""
50 | "\n"
51 | "
\n"
52 | "
Les blocs se déplacent par glisser-déposer.
\n"
53 | "
Après avoir déplacé, ajouté ou supprimé des blocks vous devez "
54 | "sauvegarder l’objet au complet.
\n"
55 | "
Si vous avez effacé des blocs par accident, vous pouvez les "
56 | "récupérer en rechargeant la page; les blocs modifiés seront toujours "
57 | "présents, mais dans l’ordre de la version précédente. Les blocs ajoutés "
58 | "devront être recréés.
After moving, adding or deleting blocks you need to save the entire "
43 | "object.
\n"
44 | "
If you have deleted some blocks accidentally, you may get them back "
45 | "by reloading the page.\n"
46 | " All edited blocks will remain, in the order of the saved version.\n"
47 | " Newly added blocks will need to be recreated.
\n"
48 | "
\n"
49 | msgstr ""
50 | "\n"
51 | "
\n"
52 | "
I blocchi si possono spostare tramite il drag-and-drop.
\n"
53 | "
Dopo lo spostamento, l'aggiunta o l'eliminazione di blocchi, è "
54 | "necessario salvare l'intero oggetto.
\n"
55 | "
Se si fossero cancellati accidentalmente dei blocchi, è possibile "
56 | "tornare indietro\n"
57 | " ricaricando la pagina. Così facendo, blocchi già esistenti, anche se "
58 | "modificati, verranno salvati.\n"
59 | "L'ordinamento sarà quello della versione precedente. Eventuali nuovi "
60 | "blocchi \n"
61 | " andranno invece persi.
After moving, adding or deleting blocks you need to save the entire "
45 | "object.
\n"
46 | "
If you have deleted some blocks accidentally, you may get them back "
47 | "by reloading the page.\n"
48 | " All edited blocks will remain, in the order of the saved version.\n"
49 | " Newly added blocks will need to be recreated.
\n"
50 | "
\n"
51 | msgstr ""
52 | "\n"
53 | "
\n"
54 | "
Блоки перемещаются — перетаскиванием.
\n"
55 | "
При перемещении, добавлении или удалении блоков страницы, необходимо "
56 | "сохранение всей страницы.
\n"
57 | "
Если вы удалили какой-то блок случайно. Можно вернуться к "
58 | "сохраненной версии,\n"
59 | " перезагрузив страницу. При этом все отредактированные блоки сохранятся, "
60 | "но сортировка будет взята из сохраненной версии. Новые добавленные объекты "
61 | "так же не сохранятся.
After moving, adding or deleting blocks you need to save the entire object.
6 |
If you have deleted some blocks accidentally, you may get them back by reloading the page.
7 | All edited blocks will remain, in the order of the saved version.
8 | Newly added blocks will need to be recreated.
9 |
10 | {% endblocktrans %}
11 |
--------------------------------------------------------------------------------
/streamfield/templates/streamfield/streamfield_texts.js:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 | window.stream_texts = window.stream_texts || {
4 | 'deleteBlock': '{% trans "Are you sure that you want to delete this block?" %}',
5 | 'deleteInstance': '{% trans "Are you sure that you want to delete this subblock?" %}',
6 | 'Collapse all': '{% trans "Collapse all" %}',
7 | 'Open all': '{% trans "Open all" %}',
8 | 'AddOneMore': '{% trans "Add one more" %}',
9 | 'Change': '{% trans "Change" %}',
10 | 'AddContent': '{% trans "Add content" %}',
11 | 'Help?': '{% trans "Help?" %}',
12 | 'AddNewBlock': '{% trans "Add new block" %}'
13 | }
14 |
--------------------------------------------------------------------------------
/streamfield/templates/streamfield/streamfield_widget.html:
--------------------------------------------------------------------------------
1 |