Skip to content

Commit

Permalink
Merge pull request #24 from withlogicco/release-1.2.0
Browse files Browse the repository at this point in the history
Release 1.2.0
  • Loading branch information
parisk authored Jan 27, 2023
2 parents 6bbb58b + 25b9890 commit 866865a
Show file tree
Hide file tree
Showing 30 changed files with 358 additions and 282 deletions.
23 changes: 23 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "Django Prose",
"dockerComposeFile": [
"../docker-compose.yml",
"docker-compose.yml"
],
"service": "web",
"workspaceFolder": "/usr/src/app",
"settings": {
"python.formatting.provider": "black",
"editor.formatOnSave": true,
"eslint.format.enable": true
},
"extensions": [
"batisteo.vscode-django",
"editorconfig.editorconfig",
"ms-python.python"
],
"features": {
"ghcr.io/devcontainers/features/common-utils:1": {},
"ghcr.io/devcontainers/features/git:1": {}
}
}
5 changes: 5 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
version: "3.8"

services:
web:
command: sleep infinity
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ on: push

jobs:
deploy:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: pipx install poetry==1.1.13
- run: pipx install poetry==1.3.2
- uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
cache: poetry
- run: poetry install
- run: poetry run black --check .
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ on:

jobs:
deploy:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: pipx install poetry==1.1.13
- run: pipx install poetry==1.3.2
- uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'
cache: poetry
- run: poetry install
- run: poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }}
Expand Down
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Django: Run server",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/example/manage.py",
"args": [
"runserver",
"0.0.0.0:8000"
],
"django": true,
"justMyCode": true
}
]
}
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ghcr.io/withlogicco/poetry:1.1.13-python-3.10-slim
FROM ghcr.io/withlogicco/poetry:1.3.2-python-3.11

COPY ./ ./
RUN --mount=type=cache,target=/root/.cache/pip --mount=type=cache,target=/root/.cache/pypoetry poetry install
Expand Down
51 changes: 36 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

![PyPI - Downloads](https://img.shields.io/pypi/dw/django-prose?color=purple) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-prose)

Django Prose provides your Django applications with wonderful rich-text editing.
Django Prose provides your Django applications with wonderful rich-text editing capabilities.

## Requirements

- Python 3.6.2 or later
- Django 2.2 or later
- Python 3.8 or later
- Django 3.2 or later
- Bleach 4.0 or later

## Getting started
Expand All @@ -18,7 +18,7 @@ To get started with Django Prose, first make sure to install it. We use and sugg
poetry add django-prose
```

Then, add `prose` in Django's installed apps (example: [`prose_example/prose_example/settings.py`](https://github.com/withlogicco/django-prose/blob/55fb9319e55d873afe43968817a2f5ea3f055d11/prose_example/prose_example/settings.py#L46)):
Then, add `prose` in Django's installed apps (example: [`example/example/settings.py`](https://github.com/withlogicco/django-prose/blob/9e24cc794eae6db48818dd15a483d106d6a99da0/example/example/settings.py#L46)):

```python
INSTALLED_APPS = [
Expand All @@ -42,9 +42,17 @@ Now, you are ready to go 🚀.

There are different ways to use Django prose according to your needs. We will examine all of them here.

### Small rich-text information
### Rendering rich-text in templates

You might want to add rich-text information in a model that is just a few characters (e.g. 140), like an excerpt from an article. In that case we suggest using the `RichTextField`. Example:
Rich text content essentially is HTML. For this reason it needs to be manually marked as [`safe`](https://docs.djangoproject.com/en/4.2/ref/templates/builtins/#safe), when rendered in Django templates. Example:

```django
{{ document.content | safe}}
```

### Small rich-text content

You might want to add rich-text content in a model that is just a few characters (e.g. 140), like an excerpt from an article. In that case we suggest using the `RichTextField`. Example:

```py
from django.db import models
Expand All @@ -54,15 +62,15 @@ class Article(models.Model):
excerpt = RichTextField()
```

Then you can display the article excerpt in your HTML templates by marking it as [`safe`](https://docs.djangoproject.com/en/4.0/ref/templates/builtins/#safe)
As mentioned above, you need to mark the article excerpt as `safe`, in order to render it:

```django
<div class="article-excerpt">{{ article.excerpt | safe}}</div>
```

### Large rich-text information
### Large rich-text content

In case you want to store large rich-text information, like the content of an article, which can span to quite a few thousand characters, we suggest you use the `AbstractDocument` model. This will save large rich-text information in a separate database table, which is better for performance. Example:
In case you want to store large rich-text content, like the body of an article, which can span to quite a few thousand characters, we suggest you use the `AbstractDocument` model. This will save large rich-text content in a separate database table, which is better for performance. Example:

```py
from django.db import models
Expand All @@ -77,7 +85,7 @@ class Article(models.Model):
body = models.OneToOneField(ArticleContent, on_delete=models.CASCADE)
```

Similarly here you can display the article's body by marking it as `safe`
Similarly here as well, you need to mark the article's body as `safe`, in order to render it:

```django
<div class="article-body">{{ article.body.content | safe}}</div>
Expand All @@ -104,10 +112,14 @@ The same is true also, if you are rendering the forms field manually.

Django Prose can also handle uploading attachments with drag and drop. To set this up, first you need to:

- [x] Set up the `MEDIA_ROOT` of your Django project (example in [`prose_example/prose_example/settings.py`](https://github.com/withlogicco/django-prose/blob/55fb9319e55d873afe43968817a2f5ea3f055d11/prose_example/prose_example/settings.py#L132)))
- [x] Include the Django Prose URLs (example in [`prose_example/prose_example/urls.py`](https://github.com/withlogicco/django-prose/blob/9073d713f8d3febe5c50705976dbb31063270886/prose_example/prose_example/urls.py#L9-L10))
- [x] Set up the `MEDIA_ROOT` and `MEDIA_URL` of your Django project (example in [`example/example/settings.py`](https://github.com/withlogicco/django-prose/blob/9e24cc794eae6db48818dd15a483d106d6a99da0/example/example/settings.py#L130-L131)))
- [x] Include the Django Prose URLs (example in [`example/example/urls.py`](https://github.com/withlogicco/django-prose/blob/9e24cc794eae6db48818dd15a483d106d6a99da0/example/example/urls.py#L13-L14))
- [x] (Optional) Set up a different Django storage to store your files (e.g. S3)

### Full example

You can find a full example of a blog, built with Django Prose in the [`example`](./example/) directory.

## 🔒 A note on security

As you can see in the examples above, what Django Prose does is provide you with a user friendly editor ([Trix](https://trix-editor.org/)) for your rich text content and then store it as HTML in your database. Since you will mark this HTML as safe in order to use it in your templates, it needs to be **sanitised**, before it gets stored in the database.
Expand All @@ -121,21 +133,30 @@ For this reason Django Prose is using [Bleach](https://bleach.readthedocs.io/en/

### Django Prose Documents in Django Admin


![Django Prose Document in Django Admin](./docs/django-admin-prose-document.png)

## Real world use cases
- **Remote Work Café**: Used to edit location pagess, like [Amsterdam | Remote Work Café](https://remotework.cafe/locations/amsterdam/)
- In production by multiple clients of [LOGIC](https://withlogic.co), from small companies to the public sector.

If you are using Django Prose in your application too, feek free to open a [Pull Request](https://github.com/withlogicco/django-prose/pulls) to include it here. We would love to have it.

## Development for Django Prose

If you plan to contribute code to Django Prose, this section is for you. All development tooling for Django Prose has been set up with Docker. To get started run these commands in the provided order:
If you plan to contribute code to Django Prose, this section is for you. All development tooling for Django Prose has been set up with Docker and Development Containers.

To get started run these commands in the provided order:

```console
docker compose run --rm migrate
docker compose run --rm createsuperuser
docker compose up
```

If you are using Visual Studio code, just open this repository in a container using the [`Dev Containers: Open Folder in Container`](https://code.visualstudio.com/docs/devcontainers/containers#_quick-start-open-an-existing-folder-in-a-container).

---

<p align="center">
<i>🦄 Built with ❤️ by <a href="https://withlogic.co/">LOGIC</a>. 🦄</i>
<i>🦄 Built with <a href="https://withlogic.co/">LOGIC</a>. 🦄</i>
</p>
4 changes: 2 additions & 2 deletions bin/server
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

set -e

python prose_example/manage.py migrate
python prose_example/manage.py runserver 0.0.0.0:${PORT:-8000}
python example/manage.py migrate
python example/manage.py runserver 0.0.0.0:${PORT:-8000}

14 changes: 7 additions & 7 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,42 @@ x-base:
- .:/usr/src/app
- media:/mnt/media
- static:/mnt/static
working_dir: /usr/src/app/prose_example
working_dir: /usr/src/app/

services:
web:
<<: *base
ports:
- ${PROSE_EXAMPLE_EXTERNAL_PORT:-8000}:8000
command: python manage.py runserver 0.0.0.0:8000
command: ./bin/server

shell:
<<: *base
command: python manage.py shell
command: python example/manage.py shell
profiles:
- tools

makemigrations:
<<: *base
command: python manage.py makemigrations
command: python example/manage.py makemigrations
profiles:
- tools

migrate:
<<: *base
command: python manage.py migrate
command: python example/manage.py migrate
profiles:
- tools

createsuperuser:
<<: *base
command: python manage.py createsuperuser
command: python example/manage.py createsuperuser
profiles:
- tools

black:
<<: *base
command: black /usr/src/app
command: black .
profiles:
- tools

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
19 changes: 19 additions & 0 deletions example/blog/migrations/0002_article_excerpt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 4.1.5 on 2023-01-27 13:26

from django.db import migrations
import prose.fields


class Migration(migrations.Migration):

dependencies = [
("blog", "0001_initial"),
]

operations = [
migrations.AddField(
model_name="article",
name="excerpt",
field=prose.fields.RichTextField(blank=True),
),
]
18 changes: 18 additions & 0 deletions example/blog/migrations/0003_rename_content_article_body.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.1.5 on 2023-01-27 13:39

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("blog", "0002_article_excerpt"),
]

operations = [
migrations.RenameField(
model_name="article",
old_name="content",
new_name="body",
),
]
File renamed without changes.
6 changes: 4 additions & 2 deletions prose_example/blog/models.py → example/blog/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.conf import settings
from django.db import models

from prose.fields import RichTextField
from prose.models import Document


Expand All @@ -10,7 +11,8 @@ class Article(models.Model):
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
content = models.OneToOneField(Document, on_delete=models.CASCADE)
excerpt = RichTextField(blank=True)
body = models.OneToOneField(Document, on_delete=models.CASCADE)

def __str__(self):
return f"{self.title} by {self.author.username}: {self.content}"
return f"{self.title} by {self.author.username}: {self.body}"
10 changes: 10 additions & 0 deletions example/blog/templates/blog/article.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% extends 'blog/base.html' %}

{% block title %}{{ article.title }} | Blog example | Django Prose{% endblock title %}
{% block description %}{{ article.excerpt }}{% endblock description %}

{% block content %}
<h1>{{ article.title }}</h1>
<div>{{ article.author.username }}</div>
<div>{{ article.body.content | safe }}</div>
{% endblock content %}
19 changes: 19 additions & 0 deletions example/blog/templates/blog/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Blog example | Django Prose{% endblock %}</title>
<meta name="description" content="{% block description %}This is a blog example built with Django Prose{% endblock %}">
<style>
body {
font-family: sans-serif;
font-size: 14px;
}
</style>
</head>
<body>
{% block content %}{% endblock content %}
</body>
</html>
18 changes: 18 additions & 0 deletions example/blog/templates/blog/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{% extends 'blog/base.html' %}

{% block title %}Articles | Blog example | Django Prose{% endblock title %}
{% block description %}Read {{ articles.count }} in this blog built with Django Prose{% endblock description %}

{% block content %}
<ul>
{% for article in articles %}
<li>
<h2>{{ article.title }}</h2>
<p>{{ article.excerpt | safe }}</p>
<a href="{% url 'blog_article' article.pk %}">Read more</a>
</li>
{% empty %}
<li>No articles written yet!</li>
{% endfor %}
</ul>
{% endblock content %}
File renamed without changes.
19 changes: 19 additions & 0 deletions example/blog/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.shortcuts import render

from blog.models import Article


def blog_index(request):
articles = Article.objects.all()
context = {
"articles": articles,
}
return render(request, "blog/index.html", context)


def blog_article(request, pk):
article = Article.objects.get(pk=pk)
context = {
"article": article,
}
return render(request, "blog/article.html", context)
File renamed without changes.
Loading

0 comments on commit 866865a

Please sign in to comment.