Skip to content

Commit

Permalink
Use fullpage markdown widget for documentation pages (#3762)
Browse files Browse the repository at this point in the history
Use the fullpage markdown editor for documentation pages. This splits up
content and metadata editing.

Related to #3682
  • Loading branch information
koopmant authored Dec 19, 2024
1 parent 9982797 commit 91fee2e
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 25 deletions.
19 changes: 14 additions & 5 deletions app/grandchallenge/documentation/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from crispy_forms.layout import Submit
from django import forms

from grandchallenge.core.widgets import MarkdownEditorInlineWidget
from grandchallenge.core.forms import SaveFormInitMixin
from grandchallenge.core.widgets import MarkdownEditorFullPageWidget
from grandchallenge.documentation.models import DocPage


Expand All @@ -14,13 +15,21 @@ def __init__(self, *args, **kwargs):

class Meta:
model = DocPage
fields = ("title", "content", "parent")
widgets = {"content": MarkdownEditorInlineWidget}
fields = ("title", "parent")


class DocPageUpdateForm(DocPageCreateForm):
"""Like the create form but you can also move the page."""
class DocPageMetadataUpdateForm(DocPageCreateForm):
"""Like the create form, but you can also move the page."""

position = forms.IntegerField()
position.label = "Move to index position"
position.required = False


class DocPageContentUpdateForm(SaveFormInitMixin, forms.ModelForm):
class Meta:
model = DocPage
fields = ("content",)
widgets = {
"content": MarkdownEditorFullPageWidget,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% extends "documentation/docpage_form.html" %}
{% load crispy from crispy_forms_tags %}

{% block container %}container-fluid{% endblock %}

{% block outer_content %}

<h2>Update Page</h2>

{% crispy form %}
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,14 @@
<div class="row mt-3">
<div class="col-12 col-sm-6 col-md-7 d-inline-block text-left">
{% if 'documentation.change_docpage' in perms %}
<a class="btn btn-md btn-outline-dark d-inline-flex" href="{% url 'documentation:list' %}">Page overview</a>
<a class="btn btn-md btn-outline-dark d-inline-flex" href="{% url 'documentation:create' %}">Add</a>
<a class="btn btn-md btn-outline-dark" href="{% url 'documentation:update' slug=currentdocpage.slug %}">Edit</a>
<a class="btn btn-md btn-outline-dark" href="{% url 'documentation:list' %}">Page overview</a>
<a class="btn btn-md btn-outline-dark" href="{% url 'documentation:create' %}">Add</a>
<a class="btn btn-md btn-outline-dark" href="{% url 'documentation:content-update' slug=currentdocpage.slug %}" title="Edit page">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-md btn-outline-dark" href="{% url 'documentation:metadata-update' slug=currentdocpage.slug %}" title="Edit metadata">
<i class="fas fa-tools"></i>
</a>
{% endif %}
</div>
<div class="col-12 col-sm-6 col-md-5 justify-content-start justify-content-sm-end form-inline">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ <h2>Documentation Pages</h2>
</td>
{% if 'documentation.change_docpage' in perms %}
<td>
<a href="{% url 'documentation:update' slug=page.slug %}"><i class="fa fa-cog text-primary" aria-hidden="true"></i></a>
<a class="text-primary" title="Edit Page" href="{% url 'documentation:content-update' slug=page.slug %}"><i class="fa fa-edit"></i></a>
<a class="text-primary" title="Edit Metadata" href="{% url 'documentation:metadata-update' slug=page.slug %}"><i class="fa fa-tools"></i></a>
</td>
{% endif %}
</tr>
Expand Down
15 changes: 13 additions & 2 deletions app/grandchallenge/documentation/urls.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
from django.urls import path

from grandchallenge.documentation.views import (
DocPageContentUpdate,
DocPageCreate,
DocPageDetail,
DocPageList,
DocPageUpdate,
DocPageMetadataUpdate,
DocumentationHome,
)

app_name = "documentation"


urlpatterns = [
path("", DocumentationHome.as_view(), name="home"),
path("overview/", DocPageList.as_view(), name="list"),
path("create/", DocPageCreate.as_view(), name="create"),
path("<slug:slug>/", DocPageDetail.as_view(), name="detail"),
path("<slug:slug>/update/", DocPageUpdate.as_view(), name="update"),
path(
"<slug:slug>/content-update/",
DocPageContentUpdate.as_view(),
name="content-update",
),
path(
"<slug:slug>/metadata-update/",
DocPageMetadataUpdate.as_view(),
name="metadata-update",
),
]
31 changes: 27 additions & 4 deletions app/grandchallenge/documentation/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
from guardian.mixins import LoginRequiredMixin

from grandchallenge.documentation.forms import (
DocPageContentUpdateForm,
DocPageCreateForm,
DocPageUpdateForm,
DocPageMetadataUpdateForm,
)
from grandchallenge.documentation.models import DocPage
from grandchallenge.subdomains.utils import reverse_lazy
from grandchallenge.subdomains.utils import reverse, reverse_lazy


class DocPageList(ListView):
Expand Down Expand Up @@ -72,9 +73,11 @@ def get_object(self, queryset=None):
return get_object_or_404(DocPage, order=1)


class DocPageUpdate(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
class DocPageMetadataUpdate(
LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
model = DocPage
form_class = DocPageUpdateForm
form_class = DocPageMetadataUpdateForm
permission_required = "documentation.change_docpage"
raise_exception = True
login_url = reverse_lazy("account_login")
Expand All @@ -85,9 +88,29 @@ def form_valid(self, form):
return response


class DocPageContentUpdate(
LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
model = DocPage
form_class = DocPageContentUpdateForm
template_name_suffix = "_content_update"
permission_required = "documentation.change_docpage"
raise_exception = True
login_url = reverse_lazy("account_login")


class DocPageCreate(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
model = DocPage
form_class = DocPageCreateForm
permission_required = "documentation.add_docpage"
raise_exception = True
login_url = reverse_lazy("account_login")

def get_success_url(self):
"""On successful creation, go to content update."""
return reverse(
"documentation:content-update",
kwargs={
"slug": self.object.slug,
},
)
54 changes: 44 additions & 10 deletions app/tests/documentation_tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@
"view, perm",
[
("documentation:create", "documentation.add_docpage"),
("documentation:update", "documentation.change_docpage"),
("documentation:content-update", "documentation.change_docpage"),
("documentation:metadata-update", "documentation.change_docpage"),
],
)
def test_permissions(client, view, perm):
u1 = UserFactory()
p1 = DocPageFactory()

if view == "documentation:update":
if view in (
"documentation:content-update",
"documentation:metadata-update",
):
reverse_kwargs = {"slug": p1.slug}
else:
reverse_kwargs = None
Expand All @@ -43,6 +47,7 @@ def test_permissions(client, view, perm):
def test_docpage_create(client):
u1 = UserFactory()
assign_perm("documentation.add_docpage", u1)
assign_perm("documentation.change_docpage", u1)

content = "<h1>Example content</h1>"
title = "Test title"
Expand All @@ -51,12 +56,21 @@ def test_docpage_create(client):
viewname="documentation:create",
client=client,
method=client.post,
data={"title": title, "content": content},
data={"title": title},
user=u1,
)

assert response.status_code == 302
assert DocPage.objects.count() == 1
assert response.url.endswith("test-title/content-update/")
response = get_view_for_user(
url=response.url,
client=client,
method=client.post,
data={"content": content},
user=u1,
)
assert response.status_code == 302

response = get_view_for_user(url=response.url, client=client)

Expand All @@ -65,30 +79,50 @@ def test_docpage_create(client):


@pytest.mark.django_db
def test_docpage_update(client):
def test_docpage_content_update(client):
u1 = UserFactory()
p = DocPageFactory()
assign_perm("documentation.change_docpage", u1)

new_content = "<h1>New content</h1>"

# change content of p
response = get_view_for_user(
viewname="documentation:content-update",
client=client,
method=client.post,
reverse_kwargs={"slug": p.slug},
data={"content": new_content},
user=u1,
)

assert response.status_code == 302
p.refresh_from_db()
assert p.content == new_content


@pytest.mark.django_db
def test_docpage_position_update(client):
u1 = UserFactory()
_ = DocPageFactory()
p2 = DocPageFactory()
assign_perm("documentation.change_docpage", u1)

assert p2.order == 2

new_content = "<h1>New content</h1>"

# change content and order of p2
# change order of p2
response = get_view_for_user(
viewname="documentation:update",
viewname="documentation:metadata-update",
client=client,
method=client.post,
reverse_kwargs={"slug": p2.slug},
data={"title": p2.title, "content": new_content, "position": 1},
data={"title": p2.title, "position": 1},
user=u1,
)

assert response.status_code == 302
p2.refresh_from_db()
assert p2.order == 1
assert p2.content == new_content


@pytest.mark.django_db
Expand Down

0 comments on commit 91fee2e

Please sign in to comment.