diff --git a/scipost_django/forums/forms.py b/scipost_django/forums/forms.py index 6ea0246919982a3ec781d43bf650c2ca91315888..816cee1be93f31b53e88c135efcbf29378c6a2b4 100644 --- a/scipost_django/forums/forms.py +++ b/scipost_django/forums/forms.py @@ -2,12 +2,19 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" +import datetime + from django import forms from django.contrib.auth.models import Group +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Layout, Div, Field, Hidden, ButtonHolder, Submit +from crispy_bootstrap5.bootstrap5 import FloatingField from dal import autocomplete from .models import Forum, Meeting, Post, Motion + +from markup.widgets import TextareaWithPreview from organizations.models import Organization @@ -94,12 +101,34 @@ class PostForm(forms.ModelForm): ] def __init__(self, *args, **kwargs): + self.forum = kwargs.pop("forum") super().__init__(*args, **kwargs) - self.fields["posted_by"].widget = forms.HiddenInput() - self.fields["posted_on"].widget = forms.HiddenInput() - self.fields["needs_vetting"].widget = forms.HiddenInput() - self.fields["parent_content_type"].widget = forms.HiddenInput() - self.fields["parent_object_id"].widget = forms.HiddenInput() + self.fields["text"].widget = TextareaWithPreview() + self.helper = FormHelper() + self.helper.layout = Layout( + Field("posted_by", type="hidden"), + Field("posted_on", type="hidden"), + Field("needs_vetting", type="hidden"), + Field("parent_content_type", type="hidden"), + Field("parent_object_id", type="hidden"), + FloatingField("subject"), + Field("text"), + Submit("submit", "Submit"), + ) + + def clean_posted_on(self): + if self.forum.meeting: + if datetime.date.today() > self.forum.meeting.date_until: + self.add_error( + "posted_on", + "You cannot Post to a Meeting which is finished.", + ) + elif datetime.date.today() < self.forum.meeting.date_from: + self.add_error( + "posted_on", + "This meeting has not started yet, please come back later!", + ) + return self.cleaned_data["posted_on"] class MotionForm(PostForm): diff --git a/scipost_django/forums/models.py b/scipost_django/forums/models.py index f6bf29febe127c9124b7ab446b23cff4f57ef08e..651fd17e4ac1b7100e9f49295d123550f63deadf 100644 --- a/scipost_django/forums/models.py +++ b/scipost_django/forums/models.py @@ -287,9 +287,9 @@ class Post(models.Model): ) def get_absolute_url(self): + if not self.anchor: + self.anchor = self.get_anchor_forum_or_meeting() if not self.absolute_url: - if not self.anchor: - self.anchor = self.get_anchor_forum_or_meeting() self.absolute_url = "%s#post%s" % ( self.anchor.get_absolute_url(), self.id, diff --git a/scipost_django/forums/templates/forums/_hx_forum_quick_links_all.html b/scipost_django/forums/templates/forums/_hx_forum_quick_links_all.html index 5b7b9bd85932de00a603f56bde291c5d02f3f5b3..c86cd4484d51ce27b56f85e48efa2aa7e6cca116 100644 --- a/scipost_django/forums/templates/forums/_hx_forum_quick_links_all.html +++ b/scipost_django/forums/templates/forums/_hx_forum_quick_links_all.html @@ -1,7 +1,7 @@ <table class="m-2 mt-1 table table-dark table-bordered table-hover"> <thead> <tr> - <th>Date</th> + <th>Date<br><small><em>(most recent first)</em></small></th> <th>Posted by</th> <th>Subject</th> </tr> diff --git a/scipost_django/forums/templates/forums/_hx_forum_quick_links_followups.html b/scipost_django/forums/templates/forums/_hx_forum_quick_links_followups.html index f8b1aff25727e31cbd004779cf4763940139e2b8..b30e16fe4eb0e95be5c25a5dce23da7aa5295a2e 100644 --- a/scipost_django/forums/templates/forums/_hx_forum_quick_links_followups.html +++ b/scipost_django/forums/templates/forums/_hx_forum_quick_links_followups.html @@ -1,13 +1,13 @@ <table class="m-2 mt-1 table table-dark table-bordered table-hover"> <thead> <tr> - <th>Date</th> + <th>Date<br><small><em>(most recent first)</em></small></th> <th>Posted by</th> <th>Subject</th> </tr> </thead> <tbody> - {% for post in forum.posts.all %} + {% for post in forum.posts.all reversed %} <tr> <td>{{ post.posted_on|date:"Y-m-d H:i" }}</td> <td>{{ post.posted_by.first_name }} {{ post.posted_by.last_name }}</td> diff --git a/scipost_django/forums/templates/forums/_hx_post_form.html b/scipost_django/forums/templates/forums/_hx_post_form.html new file mode 100644 index 0000000000000000000000000000000000000000..fe1564bfa8ceff4ee13abb6e60ef9996503a11d3 --- /dev/null +++ b/scipost_django/forums/templates/forums/_hx_post_form.html @@ -0,0 +1,18 @@ +{% load bootstrap %} +{% load crispy_forms_tags %} + +<h3 class="highlight"> + Compose a new Post + <a class="ms-4 px-1 py-0 btn btn-sm btn-warning text-white" + hx-get="{% url 'forums:_hx_post_form_button' slug=slug parent_model=parent_model parent_id=parent_id %}" + hx-target="#new-post-form" + > + Cancel + </a> +</h3> +<form hx-post="{% url 'forums:_hx_post_form' slug=slug parent_model=parent_model parent_id=parent_id %}" + hx-confirm="Create this Post?" + hx-target="body" +> + {% crispy form %} +</form> diff --git a/scipost_django/forums/templates/forums/_hx_post_form_button.html b/scipost_django/forums/templates/forums/_hx_post_form_button.html new file mode 100644 index 0000000000000000000000000000000000000000..547149fca0f7b513d40adf16affc58b736fda906 --- /dev/null +++ b/scipost_django/forums/templates/forums/_hx_post_form_button.html @@ -0,0 +1,6 @@ +<button class="btn btn-sm btn-primary" + hx-get="{% url 'forums:_hx_post_form' slug=slug parent_model=parent_model parent_id=parent_id %}" + hx-target="#new-post-form" +> + Add a new Post +</button> diff --git a/scipost_django/forums/templates/forums/forum_detail.html b/scipost_django/forums/templates/forums/forum_detail.html index b53cbd5d09a78d8ef6693e9404364a94de9a3091..91d2641289b6d953b6d369d7dda511fbb786d915 100644 --- a/scipost_django/forums/templates/forums/forum_detail.html +++ b/scipost_django/forums/templates/forums/forum_detail.html @@ -196,9 +196,15 @@ <div class="col-12"> <h2 class="highlight" id="Posts">Posts</h2> <ul> - <li><a href="{% url 'forums:post_create' slug=forum.slug parent_model='forum' parent_id=forum.id %}">Add a new Post</a></li> + <li><a href="{% url 'forums:post_create' slug=forum.slug parent_model='forum' parent_id=forum.id %}">Add a new Post (old link)</a></li> + <li> + <div id="new-post-form"> + {% include "forums/_hx_post_form_button.html" with slug=forum.slug parent_model='forum' parent_id=forum.id %} + </div> + </li> </ul> + {% for post in forum.posts.motions_excluded %} {% include 'forums/post_card.html' with forum=forum post=post can_change_forum=can_change_forum %} {% endfor %} diff --git a/scipost_django/forums/urls.py b/scipost_django/forums/urls.py index ad64d40f78c867a6292d07c1e62af60d8f9475f4..f2fdf4359b6f3305e86897895e60d084ea82c4b2 100644 --- a/scipost_django/forums/urls.py +++ b/scipost_django/forums/urls.py @@ -3,6 +3,8 @@ __license__ = "AGPL v3" from django.urls import path, include +from django.views.generic import TemplateView + from . import views @@ -31,12 +33,12 @@ urlpatterns = [ ), path( "quicklinks/all", - views.HXForumQuickLinksAllView.as_view(), + views.HX_ForumQuickLinksAllView.as_view(), name="_hx_forum_quick_links_all", ), path( "quicklinks/followups", - views.HXForumQuickLinksFollowupsView.as_view(), + views.HX_ForumQuickLinksFollowupsView.as_view(), name="_hx_forum_quick_links_followups", ), path( @@ -64,11 +66,26 @@ urlpatterns = [ ), path( "_hx_forum_permissions/", - views.HXForumPermissionsView.as_view(), + views.HX_ForumPermissionsView.as_view(), name="_hx_forum_permissions", ), ]), ), + path( + "_hx_post_form/<str:parent_model>/<int:parent_id>", + include([ + path( + "button", + views._hx_post_form_button, + name="_hx_post_form_button", + ), + path( + "", + views._hx_post_form, + name="_hx_post_form", + ), + ]), + ), ]), ), path("", views.ForumListView.as_view(), name="forums"), diff --git a/scipost_django/forums/views.py b/scipost_django/forums/views.py index 9dca3266538f8b99e63a2cf727b59a9921dad051..bec03d33a34961ec62c4c25b14ba92bf74d08745 100644 --- a/scipost_django/forums/views.py +++ b/scipost_django/forums/views.py @@ -11,8 +11,8 @@ from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.core import serializers -from django.http import Http404 -from django.shortcuts import get_object_or_404, redirect +from django.http import HttpResponse, HttpResponseRedirect, Http404 +from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse, reverse_lazy from django.utils import timezone from django.views.generic.detail import DetailView @@ -139,7 +139,7 @@ class ForumDetailView(PermissionRequiredMixin, DetailView): return qs -class HXForumQuickLinksAllView(PermissionRequiredMixin, DetailView): +class HX_ForumQuickLinksAllView(PermissionRequiredMixin, DetailView): permission_required = "forums.can_view_forum" model = Forum template_name = "forums/_hx_forum_quick_links_all.html" @@ -153,7 +153,7 @@ class HXForumQuickLinksAllView(PermissionRequiredMixin, DetailView): return qs -class HXForumQuickLinksFollowupsView(PermissionRequiredMixin, DetailView): +class HX_ForumQuickLinksFollowupsView(PermissionRequiredMixin, DetailView): permission_required = "forums.can_view_forum" model = Forum template_name = "forums/_hx_forum_quick_links_followups.html" @@ -167,7 +167,7 @@ class HXForumQuickLinksFollowupsView(PermissionRequiredMixin, DetailView): return qs -class HXForumPermissionsView(PermissionRequiredMixin, DetailView): +class HX_ForumPermissionsView(PermissionRequiredMixin, DetailView): permission_required = "forums.add_forum" model = Forum template_name = "forums/_hx_forum_permissions.html" @@ -450,6 +450,55 @@ class MotionConfirmCreateView(PostConfirmCreateView): return super().form_valid(form) +@permission_required_or_403("forums.can_post_to_forum", (Forum, "slug", "slug")) +def _hx_post_form_button(request, slug, parent_model, parent_id): + context = {"slug": slug, "parent_model": parent_model, "parent_id": parent_id} + return render(request, "forums/_hx_post_form_button.html", context) + + +@permission_required_or_403("forums.can_post_to_forum", (Forum, "slug", "slug")) +def _hx_post_form(request, slug, parent_model, parent_id): + forum = get_object_or_404(Forum, slug=slug) + context = { + "slug": slug, + "parent_model": parent_model, + "parent_id": parent_id, + } + if request.method == "POST": + form = PostForm(request.POST, forum=forum) + if form.is_valid(): + post = form.save() + return HttpResponseRedirect(redirect_to=post.get_absolute_url()) + else: + subject = "" + if parent_model == "forum": + parent_content_type = ContentType.objects.get( + app_label="forums", model="forum" + ) + elif parent_model == "post": + parent_content_type = ContentType.objects.get( + app_label="forums", model="post" + ) + parent = parent_content_type.get_object_for_this_type(pk=parent_id) + if parent.subject.startswith("Re: ..."): + subject = parent.subject + elif parent.subject.startswith("Re:"): + subject = "%s%s" % ("Re: ...", parent.subject.lstrip("Re:")) + else: + subject = "Re: %s" % parent.subject + else: + raise Http404 + initial = { + "posted_by": request.user, + "posted_on": timezone.now(), + "parent_content_type": parent_content_type, + "parent_object_id": parent_id, + "subject": subject, + } + context["form"] = PostForm(initial=initial, forum=forum) + return render(request, "forums/_hx_post_form.html", context) + + @permission_required_or_403("forums.can_post_to_forum", (Forum, "slug", "slug")) def motion_vote(request, slug, motion_id, vote): motion = get_object_or_404(Motion, pk=motion_id)