Skip to content

Commit 46a9462

Browse files
committed
Completely redo the documentation
1 parent 3d8d389 commit 46a9462

File tree

11 files changed

+605
-561
lines changed

11 files changed

+605
-561
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Next version
99
admin enhancements (currently save shortcuts). The ``ContentEditor`` now
1010
inherits from this base class, and ``RefinedModelAdmin`` can be used
1111
independently for regular model admins that want these tweaks.
12+
- Reorganized the documentation and modified the quickstart section to no
13+
longer mention or recommend CKEditor 4.
1214

1315

1416
7.3 (2025-06-23)

docs/admin-classes.rst

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
=============
2+
Admin Classes
3+
=============
4+
5+
ContentEditor
6+
=============
7+
8+
The ``ContentEditor`` class extends ``RefinedModelAdmin`` and provides the full
9+
content editing interface for managing heterogeneous collections of content blocks.
10+
11+
It automatically handles:
12+
13+
- Plugin management and ordering
14+
- Region-based content organization
15+
- Drag-and-drop reordering of content blocks
16+
- Integration with ``ContentEditorInline`` classes
17+
18+
Basic usage:
19+
20+
.. code-block:: python
21+
22+
from content_editor.admin import ContentEditor, ContentEditorInline
23+
from .models import Article, RichText, Download
24+
25+
@admin.register(Article)
26+
class ArticleAdmin(ContentEditor):
27+
inlines = [
28+
ContentEditorInline.create(model=RichText),
29+
ContentEditorInline.create(model=Download),
30+
]
31+
32+
ContentEditorInline
33+
===================
34+
35+
``ContentEditorInline`` is a specialized ``StackedInline`` that serves as a marker
36+
for content editor plugins. It provides additional functionality for:
37+
38+
- Region restrictions
39+
- Custom icons and buttons
40+
- Plugin-specific configuration
41+
42+
The ``.create()`` class method provides a convenient way to quickly create inlines:
43+
44+
.. code-block:: python
45+
46+
ContentEditorInline.create(
47+
model=MyPlugin,
48+
icon="description", # Material icon name
49+
color="#ff5722", # Custom color
50+
regions={"main"}, # Restrict to specific regions
51+
)
52+
53+
Restricting plugins to regions
54+
------------------------------
55+
56+
You may want to allow certain content blocks only in specific regions. For example,
57+
you may want to allow rich text content blocks only in the ``main`` region. There
58+
are several ways to do this; you can either hardcode the list of allowed regions:
59+
60+
.. code-block:: python
61+
62+
from content_editor.admin import ContentEditor, allow_regions
63+
64+
class ArticleAdmin(ContentEditor):
65+
inlines = [
66+
# Explicit:
67+
RichTextInline.create(regions={"main"}),
68+
# Using the helper which does exactly the same:
69+
RichTextInline.create(regions=allow_regions({"main"})),
70+
]
71+
72+
Or you may want to specify a list of denied regions. This may be less
73+
repetitive if you have many regions and many restrictions:
74+
75+
.. code-block:: python
76+
77+
from content_editor.admin import ContentEditor, deny_regions
78+
79+
class ArticleAdmin(ContentEditor):
80+
inlines = [
81+
RichTextInline.create(regions=deny_regions({"sidebar"})),
82+
]
83+
84+
RefinedModelAdmin
85+
=================
86+
87+
The ``RefinedModelAdmin`` class provides a lightweight base class that extends
88+
Django's ``ModelAdmin`` with commonly useful tweaks. It serves as the foundation
89+
for the ``ContentEditor`` class, but can also be used independently for regular
90+
Django admin classes that want to benefit from these enhancements.
91+
92+
Currently, ``RefinedModelAdmin`` includes:
93+
94+
- **Save shortcuts**: Keyboard shortcuts (Ctrl+S / Cmd+S) for quickly saving forms
95+
- **Deletion safety check**: Shows a confirmation dialog when attempting to delete
96+
the whole object instead of saving changes (including marked inline deletions)
97+
98+
To use ``RefinedModelAdmin`` for your own admin classes:
99+
100+
.. code-block:: python
101+
102+
from content_editor.admin import RefinedModelAdmin
103+
104+
@admin.register(MyModel)
105+
class MyModelAdmin(RefinedModelAdmin):
106+
# Your admin configuration here
107+
pass
108+
109+
This gives you the save shortcut functionality without the full content editor
110+
interface, making it useful for any Django model admin where you want these
111+
convenience features.

docs/changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
=========
2+
Changelog
3+
=========
4+
5+
.. include:: ../CHANGELOG.rst

docs/conf.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
import os
23
import re
34
import subprocess
@@ -8,7 +9,7 @@
89

910
project = "django-content-editor"
1011
author = "Feinheit AG"
11-
copyright = "2016-2017," + author # noqa: A001
12+
copyright = f"2009-{datetime.date.today().year}, {author}"
1213
version = __import__("content_editor").__version__
1314
release = subprocess.check_output(
1415
"git fetch --tags; git describe", shell=True, text=True
@@ -30,8 +31,17 @@
3031
pygments_style = "sphinx"
3132
todo_include_todos = False
3233

33-
html_theme = "alabaster"
34+
html_theme = "sphinx_rtd_theme"
3435
html_static_path = ["_static"]
36+
37+
# Theme options
38+
html_theme_options = {
39+
"navigation_depth": 3,
40+
"collapse_navigation": False,
41+
"sticky_navigation": True,
42+
"includehidden": True,
43+
"titles_only": False,
44+
}
3545
htmlhelp_basename = project_slug + "doc"
3646

3747
latex_elements = {

docs/contents.rst

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
=====================
2+
Contents and Regions
3+
=====================
4+
5+
Regions
6+
=======
7+
8+
The included ``Contents`` class and its helpers (``contents_*``) and
9+
the ``ContentEditor`` admin class expect a ``regions`` attribute or
10+
property (**not** a method) on their model which returns a list of ``Region`` instances.
11+
12+
Regions have the following attributes:
13+
14+
* ``title``: Something nice, will be visible in the content editor.
15+
* ``key``: The region key, used in the content proxy as attribute name
16+
for the list of plugins. Must contain a valid Python identifier.
17+
``"regions"`` and names starting with an underscore cannot be used. The
18+
recommendation is to use ``"main"`` when you only have a single region and no
19+
better idea.
20+
* ``inherited``: Only has an effect if you are using the
21+
``inherit_from`` argument to ``contents_for_item``: Model instances
22+
inherit content from their other instances if a region with
23+
``inherited = True`` is empty.
24+
25+
You are free to define additional attributes -- simply pass them
26+
when instantiating a new region.
27+
28+
Example:
29+
30+
.. code-block:: python
31+
32+
from content_editor.models import Region
33+
34+
class Article(models.Model):
35+
title = models.CharField(max_length=200)
36+
37+
regions = [
38+
Region(key="main", title="Main content"),
39+
Region(key="sidebar", title="Sidebar", inherited=True),
40+
]
41+
42+
Contents class and helpers
43+
==========================
44+
45+
The ``content_editor.contents`` module offers a few helpers for
46+
fetching content blocks from the database. The ``Contents`` class
47+
knows how to group content blocks by region and how to merge
48+
contents from several main models. This is especially useful in
49+
inheritance scenarios, for example when a page in a hierarchical
50+
page tree inherits some aside-content from its ancestors.
51+
52+
.. note::
53+
54+
**Historical note**
55+
56+
The ``Contents`` class and the helpers replace the monolithic
57+
``ContentProxy`` concept in FeinCMS_.
58+
59+
Contents class
60+
--------------
61+
62+
Simple usage is as follows:
63+
64+
.. code-block:: python
65+
66+
from content_editor.contents import Contents
67+
68+
article = Article.objects.get(...)
69+
c = Contents(article.regions)
70+
for item in article.app_richtext_set.all():
71+
c.add(item)
72+
for item in article.app_download_set.all():
73+
c.add(item)
74+
75+
# Returns a list of all items, sorted by the definition
76+
# order of article.regions and by item ordering
77+
list(c)
78+
79+
# Returns a list of all items from the given region
80+
c["main"]
81+
# or
82+
c.main
83+
84+
# How many items do I have?
85+
len(c)
86+
87+
# Inherit content from the given contents instance if one of my
88+
# own regions is empty and has its "inherited" flag set.
89+
c.inherit_regions(some_other_contents_instance)
90+
91+
# Plugins from unknown regions end up in _unknown_region_contents:
92+
c._unknown_region_contents
93+
94+
For most use cases you'll probably want to take a closer look at the
95+
following helper methods instead of instantiating a ``Contents`` class
96+
directly:
97+
98+
contents_for_items
99+
-------------------
100+
101+
Returns a contents instance for a list of main models:
102+
103+
.. code-block:: python
104+
105+
articles = Article.objects.all()[:10]
106+
contents = contents_for_items(
107+
articles,
108+
plugins=[RichText, Download],
109+
)
110+
111+
something = [
112+
(article, contents[article])
113+
for article in articles
114+
]
115+
116+
contents_for_item
117+
------------------
118+
119+
Returns the contents instance for a given main model (note that this
120+
helper calls ``contents_for_items`` to do the real work):
121+
122+
.. code-block:: python
123+
124+
# ...
125+
contents = contents_for_item(
126+
article,
127+
plugins=[RichText, Download],
128+
)
129+
130+
It is also possible to add additional items for inheriting regions.
131+
This is most useful with a page tree where i.e. sidebar contents are
132+
inherited from ancestors (this example uses methods added by
133+
django-tree-queries_ as used in feincms3_):
134+
135+
.. code-block:: python
136+
137+
page = ...
138+
contents = contents_for_item(
139+
page,
140+
plugins=[RichText, Download],
141+
page.ancestors().reverse(), # Prefer content closer to the
142+
# current page
143+
)
144+
145+
.. _FeinCMS: https://github.com/feincms/feincms/
146+
.. _django-tree-queries: https://github.com/matthiask/django-tree-queries/
147+
.. _feincms3: https://feincms3.readthedocs.io/

docs/design-decisions.rst

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
================
2+
Design Decisions
3+
================
4+
5+
About rich text editors
6+
========================
7+
8+
We have been struggling with rich text editors for a long time. To
9+
be honest, I do not think it was a good idea to add that many
10+
features to the rich text editor. Resizing images uploaded into a
11+
rich text editor is a real pain, and what if you'd like to reuse
12+
these images or display them using a lightbox script or something
13+
similar? You have to resort to writing loads of JavaScript code
14+
which will only work on one browser. You cannot really filter the
15+
HTML code generated by the user to kick out ugly HTML code generated
16+
by copy-pasting from word. The user will upload 10mb JPEGs and
17+
resize them to 50x50 pixels in the rich text editor.
18+
19+
All of this convinced me that offering the user a rich text editor
20+
with too much capabilities is a really bad idea. The rich text
21+
editor in FeinCMS only has bold, italic, bullets, link and headlines
22+
activated (and the HTML code button, because that's sort of
23+
inevitable -- sometimes the rich text editor messes up and you
24+
cannot fix it other than going directly into the HTML code. Plus,
25+
if someone really knows what they are doing, I'd still like to give
26+
them the power to shot their own foot).
27+
28+
If this does not seem convincing you can always add your own rich
29+
text plugin with a different configuration (or just override the
30+
rich text editor initialization template in your own project). We do
31+
not want to force our world view on you, it's just that we think
32+
that in this case, more choice has the bigger potential to hurt than
33+
to help.
34+
35+
Plugins
36+
=======
37+
38+
Images and other media files are inserted via objects; the user can
39+
only select a file and a display mode (f.e. float/block for images
40+
or something...). An article's content could look like this:
41+
42+
* Rich Text
43+
* Floated image
44+
* Rich Text
45+
* YouTube Video Link, embedding code is automatically generated from
46+
the link
47+
* Rich Text
48+
49+
It's of course easier for the user to start with only a single rich
50+
text field, but I think that the user already has too much confusing
51+
possibilities with an enhanced rich text editor. Once the user
52+
grasps the concept of content blocks which can be freely added,
53+
removed and reordered using drag/drop, I'd say it's much easier to
54+
administer the content of a webpage. Plus, the content blocks can
55+
have their own displaying and updating logic; implementing dynamic
56+
content inside the CMS is not hard anymore, on the contrary. Since
57+
content blocks are Django models, you can do anything you want
58+
inside them.
59+
60+
Glossary
61+
========
62+
63+
- **Main model**: (Bad wording -- not happy with that). The model to
64+
which plugins may be added. This model uses the content editor
65+
admin class.
66+
67+
- **Plugin**: A content element type such as rich text, download,
68+
and image or whatever.
69+
70+
- **Content block**: A content element instance belonging to a main
71+
model instance. Also called **item** sometimes in the documentation
72+
above.

0 commit comments

Comments
 (0)