-
{{_('Create board')}}
-
{{_('Fill up the form to create or edit this conversation board')}}
+ {% block form_identification %}
+
{{ _('Create board') }}
+
{{ _('Fill up the form to create a new conversation board') }}
+ {% endblock %}
{% if can_add_board %}
- {{ action_button(_('Create a new board!'), 'boards:create') }}
+ {{ action_button(_('Create a new board!'), 'boards:board-create') }}
{% endif %}
{% endblock %}
diff --git a/src/ej_boards/jinja2/ej_boards/create-conversation.jinja2 b/src/ej_boards/jinja2/ej_boards/conversation-create.jinja2
similarity index 100%
rename from src/ej_boards/jinja2/ej_boards/create-conversation.jinja2
rename to src/ej_boards/jinja2/ej_boards/conversation-create.jinja2
diff --git a/src/ej_boards/jinja2/ej_boards/edit-conversation.jinja2 b/src/ej_boards/jinja2/ej_boards/conversation-edit.jinja2
similarity index 100%
rename from src/ej_boards/jinja2/ej_boards/edit-conversation.jinja2
rename to src/ej_boards/jinja2/ej_boards/conversation-edit.jinja2
diff --git a/src/ej_boards/jinja2/ej_boards/conversation-list.jinja2 b/src/ej_boards/jinja2/ej_boards/conversation-list.jinja2
index 2cad9643d..d67ee262c 100644
--- a/src/ej_boards/jinja2/ej_boards/conversation-list.jinja2
+++ b/src/ej_boards/jinja2/ej_boards/conversation-list.jinja2
@@ -1,33 +1 @@
-{% extends 'base.jinja2' %}
-
-
-{% block content %}
-
-
-
{{ title }}
-
{{ subtitle }}
-
-
- {% if is_a_board is defined %}
-
-
-
- {{ link(_('public conversations'), href='/conversations/') }}
-
- {% if not owns_board %}
-
- {{ link(_('go to my conversations'), href='/profile/conversations/') }}
-
-
- {% endif %}
-
- {% endif %}
-
- {# Cards #}
-
- {% for conversation in conversations %}
- {{ conversation|role('card', board=board) }}
- {% endfor %}
-
-
-{% endblock %}
+{% extends 'ej_conversations/list.jinja2' %}
diff --git a/src/ej_boards/jinja2/ej_boards/moderate-conversation.jinja2 b/src/ej_boards/jinja2/ej_boards/conversation-moderate.jinja2
similarity index 100%
rename from src/ej_boards/jinja2/ej_boards/moderate-conversation.jinja2
rename to src/ej_boards/jinja2/ej_boards/conversation-moderate.jinja2
diff --git a/src/ej_boards/jinja2/ej_boards/create-conversation-stereotype.jinja2 b/src/ej_boards/jinja2/ej_boards/conversation-stereotype-create.jinja2
similarity index 100%
rename from src/ej_boards/jinja2/ej_boards/create-conversation-stereotype.jinja2
rename to src/ej_boards/jinja2/ej_boards/conversation-stereotype-create.jinja2
diff --git a/src/ej_boards/jinja2/ej_boards/edit-conversation-stereotype.jinja2 b/src/ej_boards/jinja2/ej_boards/conversation-stereotype-edit.jinja2
similarity index 100%
rename from src/ej_boards/jinja2/ej_boards/edit-conversation-stereotype.jinja2
rename to src/ej_boards/jinja2/ej_boards/conversation-stereotype-edit.jinja2
diff --git a/src/ej_boards/jinja2/ej_boards/edit.jinja2 b/src/ej_boards/jinja2/ej_boards/edit.jinja2
deleted file mode 100644
index 7f5d36034..000000000
--- a/src/ej_boards/jinja2/ej_boards/edit.jinja2
+++ /dev/null
@@ -1 +0,0 @@
-{% extends 'ej_boards/create.jinja2' %}
diff --git a/src/ej_boards/jinja2/ej_boards/report-scatter.jinja2 b/src/ej_boards/jinja2/ej_boards/report-scatter.jinja2
new file mode 100644
index 000000000..8f263356f
--- /dev/null
+++ b/src/ej_boards/jinja2/ej_boards/report-scatter.jinja2
@@ -0,0 +1 @@
+{% extends 'ej_reports/scatter.jinja2' %}
diff --git a/src/ej_boards/jinja2/ej_boards/report.jinja2 b/src/ej_boards/jinja2/ej_boards/report.jinja2
new file mode 100644
index 000000000..f9afd796a
--- /dev/null
+++ b/src/ej_boards/jinja2/ej_boards/report.jinja2
@@ -0,0 +1 @@
+{% extends 'ej_reports/index.jinja2' %}
diff --git a/src/ej_boards/middleware.py b/src/ej_boards/middleware.py
index 818eb9c8f..d8073a047 100644
--- a/src/ej_boards/middleware.py
+++ b/src/ej_boards/middleware.py
@@ -1,9 +1,12 @@
from django.conf import settings
+from django.utils.text import slugify
+from django.urls import resolve
+from django.http import Http404
from .models import Board
-def BoardFallbackMiddleware(get_response): # noqa: N802
+def BoardFallbackMiddleware(get_response): # noqa: N802, C901
"""
Look for board urls after 404 errors.
"""
@@ -17,11 +20,28 @@ def middleware(request):
# noinspection PyBroadException
try:
- slug = request.path.strip('/')
+ slugfied_terms = [slugify(x) for x in request.path.split('/')]
+ slug = slugfied_terms[1]
+
if '/' in slug:
return response
+
board = Board.objects.get(slug=slug)
- return view_function(request, board=board)
+
+ # make a url with the real board slug e.g.: /Slug/edit/ becomes /slug/edit/
+ new_path = '/'.join(slugfied_terms)
+
+ try:
+ view, args, kwargs = resolve(new_path)
+ new_response = view(request, **kwargs)
+ return new_response
+ except Http404:
+ # accessing /board-slug/
+ if '/' not in request.path.strip('/'):
+ return view_function(request, board=board)
+
+ return response
+
except Board.DoesNotExist:
return response
except Exception:
diff --git a/src/ej_boards/migrations/0001_first_migration.py b/src/ej_boards/migrations/0001_first_migration.py
index e82ef5022..1372c97e8 100644
--- a/src/ej_boards/migrations/0001_first_migration.py
+++ b/src/ej_boards/migrations/0001_first_migration.py
@@ -24,7 +24,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
- ('slug', models.SlugField(help_text='Short text used to identify the board URL (e.g.: "johns-board")', unique=True, validators=[ej_boards.validators.validate_board_url], verbose_name='Slug')),
+ ('slug', models.SlugField(help_text='Short text used to identify the board URL (e.g.: "johns-board")', unique=True, validators=[ej_boards.validators.validate_board_slug], verbose_name='Slug')),
('title', models.CharField(max_length=50, verbose_name='Title')),
('description', models.TextField(blank=True, verbose_name='Description')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='boards', to=settings.AUTH_USER_MODEL)),
diff --git a/src/ej_boards/migrations/0002_add_slug_field.py b/src/ej_boards/migrations/0002_add_slug_field.py
index d1d73db8c..515239cf0 100644
--- a/src/ej_boards/migrations/0002_add_slug_field.py
+++ b/src/ej_boards/migrations/0002_add_slug_field.py
@@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='board',
name='slug',
- field=models.SlugField(unique=True, validators=[ej_boards.validators.validate_board_url], verbose_name='Slug'),
+ field=models.SlugField(unique=True, validators=[ej_boards.validators.validate_board_slug], verbose_name='Slug'),
),
]
diff --git a/src/ej_boards/models.py b/src/ej_boards/models.py
index 573b3c96a..2653696e9 100644
--- a/src/ej_boards/models.py
+++ b/src/ej_boards/models.py
@@ -1,10 +1,12 @@
from django.conf import settings
+from django.core.exceptions import ValidationError
from django.db import models
+from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
from model_utils.models import TimeStampedModel
from ej_conversations.models import Conversation, ConversationTag
-from .validators import validate_board_url
+from .validators import validate_board_slug
class Board(TimeStampedModel):
@@ -14,7 +16,7 @@ class Board(TimeStampedModel):
slug = models.SlugField(
_('Slug'),
unique=True,
- validators=[validate_board_url],
+ validators=[validate_board_slug],
)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
@@ -45,6 +47,19 @@ class Meta:
def __str__(self):
return self.title
+ def save(self, *args, **kwargs):
+ if self.pk is None:
+ self.slug = slugify(self.slug)
+ super().save(*args, **kwargs)
+
+ def clean(self):
+ try:
+ board = Board.objects.get(slug=self.slug)
+ if board.slug == self.slug and board.id != self.id:
+ raise ValidationError(_('Slug already exists.'))
+ except Board.DoesNotExist:
+ pass
+
def get_absolute_url(self):
return f'/{self.slug}/'
@@ -54,6 +69,12 @@ def add_conversation(self, conversation):
"""
self.board_subscriptions.get_or_create(conversation=conversation)
+ def has_conversation(self, conversation):
+ """
+ Return True if conversation is present in board.
+ """
+ return bool(self.board_subscriptions.filter(conversation=conversation))
+
class BoardSubscription(models.Model):
"""
diff --git a/src/ej_boards/routes.py b/src/ej_boards/routes.py
new file mode 100644
index 000000000..81313e27a
--- /dev/null
+++ b/src/ej_boards/routes.py
@@ -0,0 +1,237 @@
+from django.db import transaction
+from django.http import Http404
+from django.shortcuts import redirect
+from django.urls import reverse
+from django.utils.translation import ugettext_lazy as _
+
+from boogie.router import Router
+from ej_boards.models import Board
+from ej_clusters.models import Stereotype
+from ej_clusters.routes import create_stereotype_context, edit_stereotype_context
+from ej_conversations import forms
+from ej_conversations.models import Conversation
+from ej_conversations.routes import (get_conversation_detail_context,
+ get_conversation_moderate_context,
+ get_conversation_edit_context)
+from ej_reports import routes as report_routes
+from .forms import BoardForm
+
+app_name = 'ej_boards'
+
+#
+# Board management
+#
+urlpatterns = Router(
+ template=['ej_boards/{name}.jinja2', 'generic.jinja2'],
+ object='conversation',
+ models={
+ 'board': Board,
+ 'conversation': Conversation,
+ 'stereotype': Stereotype,
+ },
+ lookup_field={'conversation': 'slug', 'board': 'slug'},
+ lookup_type={'conversation': 'slug', 'board': 'slug'},
+)
+
+
+#
+# Conversation URLs
+#
+@urlpatterns.route('