diff --git a/scipost_django/forums/forms.py b/scipost_django/forums/forms.py index 816cee1be93f31b53e88c135efcbf29378c6a2b4..b39b4d5c157a3bcae4b4d91cd5896d2db2b90217 100644 --- a/scipost_django/forums/forms.py +++ b/scipost_django/forums/forms.py @@ -63,6 +63,7 @@ class ForumGroupPermissionsForm(forms.ModelForm): queryset=Group.objects.all(), widget=autocomplete.ModelSelect2Multiple(url="/group-autocomplete"), ) + can_administer = forms.BooleanField(required=False) can_view = forms.BooleanField(required=False) can_post = forms.BooleanField(required=False) diff --git a/scipost_django/forums/migrations/0017_alter_forum_options.py b/scipost_django/forums/migrations/0017_alter_forum_options.py new file mode 100644 index 0000000000000000000000000000000000000000..aa15175ddac49f5ea0f097ef8f49270af1f08fd6 --- /dev/null +++ b/scipost_django/forums/migrations/0017_alter_forum_options.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.16 on 2023-02-02 12:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('forums', '0016_forum_cf_nr_posts'), + ] + + operations = [ + migrations.AlterModelOptions( + name='forum', + options={'ordering': ['name'], 'permissions': [('can_administer_forum', 'Can administer Forum'), ('can_view_forum', 'Can view Forum'), ('can_post_to_forum', 'Can add Post to Forum')]}, + ), + ] diff --git a/scipost_django/forums/models.py b/scipost_django/forums/models.py index 651fd17e4ac1b7100e9f49295d123550f63deadf..e914b91f6ff68e09e7fbf7c5ba68794293cb755d 100644 --- a/scipost_django/forums/models.py +++ b/scipost_django/forums/models.py @@ -82,6 +82,7 @@ class Forum(models.Model): "name", ] permissions = [ + ("can_administer_forum", "Can administer Forum"), ("can_view_forum", "Can view Forum"), ("can_post_to_forum", "Can add Post to Forum"), ] diff --git a/scipost_django/forums/templates/forums/forum_detail.html b/scipost_django/forums/templates/forums/forum_detail.html index 4feccf628e4af9cbd6a4f03a1b37190e86b6b455..88a6a246e5e5805fc647f4c71ea8403f419ba55b 100644 --- a/scipost_django/forums/templates/forums/forum_detail.html +++ b/scipost_django/forums/templates/forums/forum_detail.html @@ -1,6 +1,7 @@ {% extends 'forums/base.html' %} {% load bootstrap %} +{% load guardian_tags %} {% load automarkup %} {% load scipost_extras %} @@ -13,216 +14,214 @@ {% block content %} - {% with can_add_forum=perms.forums.add_forum can_change_forum=perms.forums.change_forum meeting=forum.meeting %} - - <div class="row"> - <div class="col-12"> - <h2 class="highlight"> - {% if meeting %} - {% with context_colors=meeting.context_colors %} - <span class="badge bg-{{ context_colors.bg }} mx-0 mb-2 p-2 text-{{ context_colors.text }}"> - {{ context_colors.message }} - <span class="small text-muted"> [{{ meeting.date_from|date:"Y-m-d" }} to {{ meeting.date_until|date:"Y-m-d" }}]</span> - </span> - {% endwith %} - <br/> - {% endif %} - - <span class="d-flex flex-wrap justify-content-between"> - <a href="{{ forum.get_absolute_url }}">{{ forum }}</a> - <span class="badge bg-primary rounded-pill">{{ forum.cf_nr_posts }} post{{ forum.cf_nr_posts|pluralize }}</span> - </span> - </h2> - - {% if forum.parent %} - <p>Parent: <a href="{{ forum.parent.get_absolute_url }}">{{ forum.parent }}</a></p> + {% get_obj_perms request.user for forum as "forum_user_perms" %} + + <div class="row"> + <div class="col-12"> + <h2 class="highlight"> + {% if forum.meeting %} + {% with context_colors=forum.meeting.context_colors %} + <span class="badge bg-{{ context_colors.bg }} mx-0 mb-2 p-2 text-{{ context_colors.text }}"> + {{ context_colors.message }} + <span class="small text-muted"> [{{ forum.meeting.date_from|date:"Y-m-d" }} to {{ forum.meeting.date_until|date:"Y-m-d" }}]</span> + </span> + {% endwith %} + <br/> {% endif %} - {% if forum.child_forums.all|length > 0 %} - <p>Descendants: {% for child in forum.child_forums.all %}<a href="{{ child.get_absolute_url }}">{{ child }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</p> - {% endif %} - - {% if can_change_forum %} - <div class="container border border-danger m-2 p-2"> - <h4>Admin actions:</h4> - <ul> - <li><a href="{% url 'forums:forum_update' slug=forum.slug %}" class="text-warning">Update this {% if meeting %}Meeting{% else %}Forum{% endif %}</a></li> - <li> - {% if not forum.child_forums.all|length > 0 %} - <a href="{% url 'forums:forum_delete' slug=forum.slug %}" class="text-danger">Delete this {% if meeting %}Meeting{% else %}Forum{% endif %} (and all Posts {% if meeting %}and Motions {% endif %}it contains)</a> - {% else %} - <span class="text-danger" style="text-decoration: line-through;">Delete this Forum</span> Please delete descendant Forums first. - {% endif %} - </li> - {% if not meeting %} - <li><a href="{% url 'forums:forum_create' parent_model='forum' parent_id=forum.id %}">Create a (sub)Forum within this one</a></li> - <li><a href="{% url 'forums:meeting_create' parent_model='forum' parent_id=forum.id %}">Create a Meeting within this Forum</a></li> - {% endif %} - </ul> - - {% if can_change_forum %} - <details id="forum-permissions-details" - class="border border-danger bg-danger bg-opacity-10 m-2"> - <summary class="bg-danger bg-opacity-10 p-2"> - Permissions on this {% if forum.meeting %}Meeting{% else %}Forum{% endif %} instance (view/manage) - <span id="forum-permissions-details-contents-indicator" class="htmx-indicator p-2"> - <button class="btn btn-warning" type="button" disabled> - <strong>Loading...</strong> - <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> - </button> - </span> - </summary> - <div id="forum-permissions-details-contents" - class="p-2" - hx-get="{% url 'forums:_hx_forum_permissions' slug=forum.slug %}" - hx-trigger="toggle once from:#forum-permissions-details" - hx-target="#forum-permissions-details-contents" - hx-indicator="#forum-permissions-details-contents-indicator" - > - </div> - </details> - {% endif %} - - </div> - {% endif %} - </div> - </div> - <div class="row"> - <div class="col"> - <h2>Table of Contents</h2> - <div class="m-2"> + <span class="d-flex flex-wrap justify-content-between"> + <a href="{{ forum.get_absolute_url }}">{{ forum }}</a> + <span class="badge bg-primary rounded-pill">{{ forum.cf_nr_posts }} post{{ forum.cf_nr_posts|pluralize }}</span> + </span> + </h2> + + {% if forum.parent %} + <p>Parent: <a href="{{ forum.parent.get_absolute_url }}">{{ forum.parent }}</a></p> + {% endif %} + {% if forum.child_forums.all|length > 0 %} + <p>Descendants: {% for child in forum.child_forums.all %}<a href="{{ child.get_absolute_url }}">{{ child }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</p> + {% endif %} + + {% if "can_administer_forum" in forum_user_perms %} + <div class="container border border-danger m-2 p-2"> + <h4>Admin actions:</h4> <ul> - <li><a href="#Description">Description</a></li> - {% if meeting %} - <li><a href="#Preamble">Preamble</a></li> - <li><a href="#Motions">Motions</a></li> - {% endif %} - <li><a href="#Posts">Posts</a> + <li><a href="{% url 'forums:forum_update' slug=forum.slug %}" class="text-warning">Update this {% if forum.meeting %}Meeting{% else %}Forum{% endif %}</a></li> + <li> + {% if not forum.child_forums.all|length > 0 %} + <a href="{% url 'forums:forum_delete' slug=forum.slug %}" class="text-danger">Delete this {% if forum.meeting %}Meeting{% else %}Forum{% endif %} (and all Posts {% if forum.meeting %}and Motions {% endif %}it contains)</a> + {% else %} + <span class="text-danger" style="text-decoration: line-through;">Delete this Forum</span> Please delete descendant Forums first. + {% endif %} </li> - {% if meeting %} - <li><a href="#Minutes">Minutes</a></li> + {% if not forum.meeting %} + <li><a href="{% url 'forums:forum_create' parent_model='forum' parent_id=forum.id %}">Create a (sub)Forum within this one</a></li> + <li><a href="{% url 'forums:meeting_create' parent_model='forum' parent_id=forum.id %}">Create a Meeting within this Forum</a></li> {% endif %} </ul> + + {% if "can_administer_forum" in forum_user_perms %} + <details id="forum-permissions-details" + class="border border-danger bg-danger bg-opacity-10 m-2"> + <summary class="bg-danger bg-opacity-10 p-2"> + Permissions on this {% if forum.meeting %}Meeting{% else %}Forum{% endif %} instance (view/manage) + <span id="forum-permissions-details-contents-indicator" class="htmx-indicator p-2"> + <button class="btn btn-warning" type="button" disabled> + <strong>Loading...</strong> + <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> + </button> + </span> + </summary> + <div id="forum-permissions-details-contents" + class="p-2" + hx-get="{% url 'forums:_hx_forum_permissions' slug=forum.slug %}" + hx-trigger="toggle once from:#forum-permissions-details" + hx-target="#forum-permissions-details-contents" + hx-indicator="#forum-permissions-details-contents-indicator" + > + </div> + </details> + {% endif %} + </div> - </div> + {% endif %} </div> + </div> - <details id="forum-quick-links-all-details" - class="border border-dark m-2"> - <summary class="bg-dark text-white px-4 py-1"> - <h3> - Quick links: all posts and motions (click to toggle) - <span id="forum-quick-links-all-details-contents-indicator" class="htmx-indicator p-2"> - <button class="btn btn-warning" type="button" disabled> - <strong>Loading...</strong> - <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> - </button> - </span> - </h3> - </summary> - <div id="forum-quick-links-all-details-contents" - class="p-2" - hx-get="{% url 'forums:_hx_forum_quick_links_all' slug=forum.slug %}" - hx-trigger="toggle once from:#forum-quick-links-all-details" - hx-target="#forum-quick-links-all-details-contents" - hx-indicator="#forum-quick-links-all-details-contents-indicator" - > + <div class="row"> + <div class="col"> + <h2>Table of Contents</h2> + <div class="m-2"> + <ul> + <li><a href="#Description">Description</a></li> + {% if forum.meeting %} + <li><a href="#Preamble">Preamble</a></li> + <li><a href="#Motions">Motions</a></li> + {% endif %} + <li><a href="#Posts">Posts</a> + </li> + {% if forum.meeting %} + <li><a href="#Minutes">Minutes</a></li> + {% endif %} + </ul> </div> - </details> - - <details id="forum-quick-links-followups-details" - class="border border-dark m-2"> - <summary class="bg-dark text-white px-4 py-1"> - <h3> - Quick links: anchor posts, latest followup (click to toggle) - <span id="forum-quick-links-followups-details-contents-indicator" class="htmx-indicator p-2"> - <button class="btn btn-warning" type="button" disabled> - <strong>Loading...</strong> - <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> - </button> - </span> - </h3> - </summary> - <div id="forum-quick-links-followups-details-contents" - class="mx-2 mt-0" - hx-get="{% url 'forums:_hx_forum_quick_links_followups' slug=forum.slug %}" - hx-trigger="toggle once from:#forum-quick-links-followups-details" - hx-target="#forum-quick-links-followups-details-contents" - hx-indicator="#forum-quick-links-followups-details-contents-indicator" - > + </div> + </div> + + <details id="forum-quick-links-all-details" + class="border border-dark m-2"> + <summary class="bg-dark text-white px-4 py-1"> + <h3> + Quick links: all posts and motions (click to toggle) + <span id="forum-quick-links-all-details-contents-indicator" class="htmx-indicator p-2"> + <button class="btn btn-warning" type="button" disabled> + <strong>Loading...</strong> + <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> + </button> + </span> + </h3> + </summary> + <div id="forum-quick-links-all-details-contents" + class="p-2" + hx-get="{% url 'forums:_hx_forum_quick_links_all' slug=forum.slug %}" + hx-trigger="toggle once from:#forum-quick-links-all-details" + hx-target="#forum-quick-links-all-details-contents" + hx-indicator="#forum-quick-links-all-details-contents-indicator" + > + </div> + </details> + + <details id="forum-quick-links-followups-details" + class="border border-dark m-2"> + <summary class="bg-dark text-white px-4 py-1"> + <h3> + Quick links: anchor posts, latest followup (click to toggle) + <span id="forum-quick-links-followups-details-contents-indicator" class="htmx-indicator p-2"> + <button class="btn btn-warning" type="button" disabled> + <strong>Loading...</strong> + <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div> + </button> + </span> + </h3> + </summary> + <div id="forum-quick-links-followups-details-contents" + class="mx-2 mt-0" + hx-get="{% url 'forums:_hx_forum_quick_links_followups' slug=forum.slug %}" + hx-trigger="toggle once from:#forum-quick-links-followups-details" + hx-target="#forum-quick-links-followups-details-contents" + hx-indicator="#forum-quick-links-followups-details-contents-indicator" + > + </div> + </details> + + <div class="row"> + <div class="col-12"> + <h2 class="highlight" id="Description">Description</h2> + <div class="m-2"> + {% automarkup forum.description %} </div> - </details> + </div> + </div> + {% if forum.meeting %} <div class="row"> <div class="col-12"> - <h2 class="highlight" id="Description">Description</h2> + <h2 class="highlight" id="Preamble">Preamble</h2> <div class="m-2"> - {% automarkup forum.description %} + {% automarkup forum.meeting.preamble %} </div> </div> </div> - {% if meeting %} - <div class="row"> - <div class="col-12"> - <h2 class="highlight" id="Preamble">Preamble</h2> - <div class="m-2"> - {% automarkup meeting.preamble %} - </div> - </div> - </div> - - <div class="row"> - <div class="col-12"> - <h2 class="highlight" id="Motions">Motions</h2> - <ul> - {% if meeting.future %} - <li>Adding Motions will be activated once the meeting starts</li> - {% elif meeting.past %} - <li><span class="text-danger">Adding Motions is deactivated</span> (Meeting is over)</li> - {% else %} - <li><a href="{% url 'forums:motion_create' slug=forum.slug parent_model='forum' parent_id=forum.id %}">Add a new Motion</a></li> - {% endif %} - </ul> - {% for motion in forum.motions.all %} - {% include 'forums/post_card.html' with forum=forum post=motion can_change_forum=can_change_forum %} - {% endfor %} - </div> - </div> - - {% endif %} - <div class="row"> <div class="col-12"> - <h2 class="highlight" id="Posts">Posts</h2> + <h2 class="highlight" id="Motions">Motions</h2> <ul> - <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 target="new-post-form" text="Add a new Post" %} - </div> - </li> + {% if forum.meeting.future %} + <li>Adding Motions will be activated once the meeting starts</li> + {% elif forum.meeting.past %} + <li><span class="text-danger">Adding Motions is deactivated</span> (Meeting is over)</li> + {% else %} + <li><a href="{% url 'forums:motion_create' slug=forum.slug parent_model='forum' parent_id=forum.id %}">Add a new Motion</a></li> + {% endif %} </ul> - - - {% for post in forum.posts.motions_excluded %} - {% include 'forums/post_card.html' with forum=forum post=post can_change_forum=can_change_forum %} + {% for motion in forum.motions.all %} + {% include 'forums/post_card.html' with forum=forum post=motion %} {% endfor %} - </div> </div> - {% if meeting %} - <div class="row"> - <div class="col-12"> - <h2 class="highlight" id="Minutes">Minutes</h2> - <div class="m-2"> - {% automarkup meeting.minutes %} + {% endif %} + + <div class="row"> + <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 (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 target="new-post-form" text="Add a new Post" %} </div> + </li> + </ul> + + + {% for post in forum.posts.motions_excluded %} + {% include 'forums/post_card.html' with forum=forum post=post %} + {% endfor %} + + </div> + </div> + + {% if forum.meeting %} + <div class="row"> + <div class="col-12"> + <h2 class="highlight" id="Minutes">Minutes</h2> + <div class="m-2"> + {% automarkup forum.meeting.minutes %} </div> </div> - {% endif %} - - {% endwith %} + </div> + {% endif %} {% endblock content %} diff --git a/scipost_django/forums/templates/forums/post_card.html b/scipost_django/forums/templates/forums/post_card.html index a378e6187351df4084b716fa7aaeae4b8affd98e..5265863f675c2e5331e4fe691c303a966e3d9afa 100644 --- a/scipost_django/forums/templates/forums/post_card.html +++ b/scipost_django/forums/templates/forums/post_card.html @@ -1,4 +1,7 @@ {% load automarkup %} +{% load guardian_tags %} + +{% get_obj_perms request.user for forum as "forum_user_perms" %} <div class="card m-2 {% if post.motion %}text-white bg-dark{% else %}text-body{% endif %}" id="post{{ post.id }}"> <div class="card-header"> @@ -54,22 +57,22 @@ </div> <div> <form action="{% url 'forums:motion_vote' slug=forum.slug motion_id=post.motion.id vote='Y' %}" method="post">{% csrf_token %} - <input type="submit" class="btn btn-success" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Agree{% if can_change_forum %}<ul>{% for f in post.motion.in_agreement.all %}<li>{{ f.last_name }}, {{ f.first_name }}</li>{% endfor %}</ul>{% endif %}" value="{{ post.motion.in_agreement.all|length }}"> + <input type="submit" class="btn btn-success" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Agree{% if "can_administer_forum" in forum_user_perms %}<ul>{% for f in post.motion.in_agreement.all %}<li>{{ f.last_name }}, {{ f.first_name }}</li>{% endfor %}</ul>{% endif %}" value="{{ post.motion.in_agreement.all|length }}"> </form> </div> <div> <form action="{% url 'forums:motion_vote' slug=forum.slug motion_id=post.motion.id vote='M' %}" method="post">{% csrf_token %} - <input type="submit" class="btn btn-warning" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Doubt{% if can_change_forum %}<ul>{% for f in post.motion.in_doubt.all %}<li>{{ f.last_name }}, {{ f.first_name }}</li>{% endfor %}</ul>{% endif %}" value="{{ post.motion.in_doubt.all|length }}"> + <input type="submit" class="btn btn-warning" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Doubt{% if "can_administer_forum" in forum_user_perms %}<ul>{% for f in post.motion.in_doubt.all %}<li>{{ f.last_name }}, {{ f.first_name }}</li>{% endfor %}</ul>{% endif %}" value="{{ post.motion.in_doubt.all|length }}"> </form> </div> <div> <form action="{% url 'forums:motion_vote' slug=forum.slug motion_id=post.motion.id vote='N' %}" method="post">{% csrf_token %} - <input type="submit" class="btn btn-danger" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Disagree{% if can_change_forum %}<ul>{% for f in post.motion.in_disagreement.all %}<li>{{ f.last_name }}, {{ f.first_name }}</li>{% endfor %}</ul>{% endif %}" value="{{ post.motion.in_disagreement.all|length }}"> + <input type="submit" class="btn btn-danger" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Disagree{% if "can_administer_forum" in forum_user_perms %}<ul>{% for f in post.motion.in_disagreement.all %}<li>{{ f.last_name }}, {{ f.first_name }}</li>{% endfor %}</ul>{% endif %}" value="{{ post.motion.in_disagreement.all|length }}"> </form> </div> <div> <form action="{% url 'forums:motion_vote' slug=forum.slug motion_id=post.motion.id vote='A' %}" method="post">{% csrf_token %} - <input type="submit" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Abstain{% if can_change_forum %}<ul>{% for f in post.motion.in_abstain.all %}<li>{{ f.last_name }}, {{ f.first_name }}</li>{% endfor %}</ul>{% endif %}" value="{{ post.motion.in_abstain.all|length }}"> + <input type="submit" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Abstain{% if "can_administer_forum" in forum_user_perms %}<ul>{% for f in post.motion.in_abstain.all %}<li>{{ f.last_name }}, {{ f.first_name }}</li>{% endfor %}</ul>{% endif %}" value="{{ post.motion.in_abstain.all|length }}"> </form> </div> <div class="align-self-center px-2"> diff --git a/scipost_django/forums/views.py b/scipost_django/forums/views.py index 9e3f6caf17973deeabcb47c5d3bbc56cd9588264..17168edbaee37d3df4395d98b87abf4df5cf7fea 100644 --- a/scipost_django/forums/views.py +++ b/scipost_django/forums/views.py @@ -185,7 +185,7 @@ class HX_ForumPermissionsView(PermissionRequiredMixin, DetailView): class ForumPermissionsView(PermissionRequiredMixin, UpdateView): - permission_required = "forums.can_change_forum" + permission_required = "forums.can_administer_forum" model = Forum form_class = ForumGroupPermissionsForm template_name = "forums/forum_permissions.html" @@ -204,6 +204,7 @@ class ForumPermissionsView(PermissionRequiredMixin, UpdateView): group = Group.objects.get(pk=self.kwargs.get("group_id")) perms = get_perms(group, self.object) initial["groups"] = group.id + initial["can_administer"] = "can_administer_forum" in perms initial["can_view"] = "can_view_forum" in perms initial["can_post"] = "can_post_to_forum" in perms except Group.DoesNotExist: @@ -212,6 +213,10 @@ class ForumPermissionsView(PermissionRequiredMixin, UpdateView): def form_valid(self, form): for group in form.cleaned_data["groups"]: + if form.cleaned_data["can_administer"]: + assign_perm("can_administer_forum", group, self.object) + else: + remove_perm("can_administer_forum", group, self.object) if form.cleaned_data["can_view"]: assign_perm("can_view_forum", group, self.object) else: @@ -514,6 +519,18 @@ def _hx_post_form(request, slug, parent_model, parent_id, target, text): return render(request, "forums/_hx_post_form.html", context) +@permission_required_or_403("forums.can_post_to_forum", (Forum, "slug", "slug")) +def _hx_thread_from_post(request, slug, post_id): + forum = get_object_or_404(Forum, slug=slug) + post = get_object_or_404(Post, pk=post_id) + context = { + "forum": forum, + "post": post, + } + return render(request, "forums/post_card.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)