group membership invitations

- fix display of group information on user and group pages
- send, receive, accept and reject invitations
This commit is contained in:
Hugh Rundle 2021-10-02 12:30:48 +10:00
parent 89dea44614
commit 0984972b05
10 changed files with 72 additions and 59 deletions

View file

@ -20,6 +20,7 @@
<span class="is-sr-only">Manager</span> <span class="is-sr-only">Manager</span>
</span> </span>
{% endif %} {% endif %}
<!-- TODO: change this to a remove_from_group button -->
{% include 'snippets/add_to_group_button.html' with user=member group=group minimal=True %} {% include 'snippets/add_to_group_button.html' with user=member group=group minimal=True %}
{% if member.mutuals %} {% if member.mutuals %}
<p class="help"> <p class="help">

View file

@ -37,7 +37,7 @@
</div> </div>
{% endfor %} {% endfor %}
{% else %} {% else %}
No users found for {{ query }} No potential members found for "{{ query }}"
{% endif %} {% endif %}
</div> </div>

View file

@ -3,7 +3,8 @@
{% load interaction %} {% load interaction %}
<div class="columns is-multiline"> <div class="columns is-multiline">
{% for group in groups %} {% for membership in memberships %}
{% with group=membership.group %}
<div class="column is-one-quarter"> <div class="column is-one-quarter">
<div class="card is-stretchable"> <div class="card is-stretchable">
<header class="card-header"> <header class="card-header">
@ -31,5 +32,6 @@
</div> </div>
</div> </div>
</div> </div>
{% endwith %}
{% endfor %} {% endfor %}
</div> </div>

View file

@ -42,7 +42,7 @@
<span class="icon icon-comment"></span> <span class="icon icon-comment"></span>
{% elif notification.notification_type == 'REPLY' %} {% elif notification.notification_type == 'REPLY' %}
<span class="icon icon-comments"></span> <span class="icon icon-comments"></span>
{% elif notification.notification_type == 'FOLLOW' or notification.notification_type == 'FOLLOW_REQUEST' %} {% elif notification.notification_type == 'FOLLOW' or notification.notification_type == 'FOLLOW_REQUEST' or notification.notification_type == 'INVITE' or notification.notification_type == 'ACCEPT' %}
<span class="icon icon-local"></span> <span class="icon icon-local"></span>
{% elif notification.notification_type == 'BOOST' %} {% elif notification.notification_type == 'BOOST' %}
<span class="icon icon-boost"></span> <span class="icon icon-boost"></span>
@ -122,6 +122,17 @@
{% else %} {% else %}
{% blocktrans with book_path=notification.related_list_item.book.local_path book_title=notification.related_list_item.book.title list_path=notification.related_list_item.book_list.local_path list_name=notification.related_list_item.book_list.name %} suggested adding <em><a href="{{ book_path }}">{{ book_title }}</a></em> to your list "<a href="{{ list_path }}/curate">{{ list_name }}</a>"{% endblocktrans %} {% blocktrans with book_path=notification.related_list_item.book.local_path book_title=notification.related_list_item.book.title list_path=notification.related_list_item.book_list.local_path list_name=notification.related_list_item.book_list.name %} suggested adding <em><a href="{{ book_path }}">{{ book_title }}</a></em> to your list "<a href="{{ list_path }}/curate">{{ list_name }}</a>"{% endblocktrans %}
{% endif %} {% endif %}
{% elif notification.notification_type == 'INVITE' %}
{% if notification.related_group %}
{% blocktrans with group_name=notification.related_group.name group_path=notification.related_group.local_path %} invited you to join the group <a href="{{ group_path }}">{{ group_name }}</a> {% endblocktrans %}
<div class="row shrink">
{% include 'snippets/join_invitation_buttons.html' with group=notification.related_group %}
</div>
{% endif %}
{% elif notification.notification_type == 'ACCEPT' %}
{% if notification.related_group %}
{% blocktrans with group_name=notification.related_group.name group_path=notification.related_group.local_path %} accepted an invitation to join your group "<a href="{{ group_path }}">{{ group_name }}</a>"{% endblocktrans %}
{% endif %}
{% endif %} {% endif %}
{% elif notification.related_import %} {% elif notification.related_import %}
{% url 'import-status' notification.related_import.id as url %} {% url 'import-status' notification.related_import.id as url %}

View file

@ -24,22 +24,22 @@
<span><em>Remote User</em></span> <span><em>Remote User</em></span>
{% endif %} {% endif %}
</form> </form>
<form action="{% url 'uninvite-group-member' %}" method="POST" class="interaction add_{{ user.id }} {% if not group|is_member:user or not group|is_invited:user %}is-hidden{% endif %}" data-id="add_{{ user.id }}"> <form action="{% url 'remove-group-member' %}" method="POST" class="interaction add_{{ user.id }} {% if not group|is_member:user and not group|is_invited:user %}is-hidden{% endif %}" data-id="add_{{ user.id }}">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="group" value="{{ group.id }}"> <input type="hidden" name="group" value="{{ group.id }}">
<input type="hidden" name="user" value="{{ user.username }}"> <input type="hidden" name="user" value="{{ user.username }}">
{% if group|is_invited:user %} {% if not group|is_member:user %}
<button class="button is-small is-danger is-light" type="submit"> <button class="button is-small is-danger is-light" type="submit">
{% trans "Undo Invitation" %} {% trans "Undo Invitation" %}
</button> </button>
{% else %} {% else %}
<button class="button is-small is-danger is-light" type="submit"> <button class="button is-small is-danger is-light" type="submit">
{% if show_username %} {% if show_username %}
{% blocktrans with username=user.localname %}Remove @{{ username }}{% endblocktrans %} {% blocktrans with username=user.localname %}Remove @{{ username }}{% endblocktrans %}
{% else %} {% else %}
{% trans "Remove" %} {% trans "Remove" %}
{% endif %} {% endif %}
</button> </button>
{% endif %} {% endif %}
</form> </form>
</div> </div>

View file

@ -0,0 +1,16 @@
{% load i18n %}
{% load bookwyrm_group_tags %}
{% if group|is_invited:request.user %}
<div class="field is-grouped">
<form action="/accept-group-invitation/" method="POST">
{% csrf_token %}
<input type="hidden" name="group" value="{{ group.id }}">
<button class="button is-link is-small" type="submit">{% trans "Accept" %}</button>
</form>
<form action="/reject-group-invitation/" method="POST">
{% csrf_token %}
<input type="hidden" name="group" value="{{ group.id }}">
<button class="button is-danger is-light is-small" type="submit" class="warning">{% trans "Delete" %}</button>
</form>
</div>
{% endif %}

View file

@ -24,7 +24,7 @@
{% block panel %} {% block panel %}
<section class="block"> <section class="block">
<form name="create-group" method="post" action="{% url 'user-groups' request.user.name %}" class="box is-hidden" id="create_group"> <form name="create-group" method="post" action="{% url 'user-groups' request.user.username %}" class="box is-hidden" id="create_group">
<header class="columns"> <header class="columns">
<h3 class="title column">{% trans "Create group" %}</h3> <h3 class="title column">{% trans "Create group" %}</h3>
<div class="column is-narrow"> <div class="column is-narrow">
@ -34,9 +34,9 @@
{% include 'groups/form.html' %} {% include 'groups/form.html' %}
</form> </form>
{% include 'groups/user_groups.html' %} {% include 'groups/user_groups.html' with memberships=memberships %}
</section> </section>
<div> <div>
{% include 'snippets/pagination.html' with page=user_groups path=path %} {% include 'snippets/pagination.html' with page=user.memberships path=path %}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -4,6 +4,7 @@
{% load utilities %} {% load utilities %}
{% load markdown %} {% load markdown %}
{% load layout %} {% load layout %}
{% load bookwyrm_group_tags %}
{% block title %}{{ user.display_name }}{% endblock %} {% block title %}{{ user.display_name }}{% endblock %}
@ -75,7 +76,7 @@
<a href="{{ url }}">{% trans "Lists" %}</a> <a href="{{ url }}">{% trans "Lists" %}</a>
</li> </li>
{% endif %} {% endif %}
{% if is_self or user.bookwyrm_groups %} {% if is_self or user|has_groups %}
{% url 'user-groups' user|username as url %} {% url 'user-groups' user|username as url %}
<li{% if url in request.path %} class="is-active"{% endif %}> <li{% if url in request.path %} class="is-active"{% endif %}>
<a href="{{ url }}">{% trans "Groups" %}</a> <a href="{{ url }}">{% trans "Groups" %}</a>

View file

@ -32,7 +32,7 @@ from .follow import follow, unfollow
from .follow import accept_follow_request, delete_follow_request from .follow import accept_follow_request, delete_follow_request
from .get_started import GetStartedBooks, GetStartedProfile, GetStartedUsers from .get_started import GetStartedBooks, GetStartedProfile, GetStartedUsers
from .goal import Goal, hide_goal from .goal import Goal, hide_goal
from .group import BookwyrmGroup, UserGroups, FindUsers, invite_member, remove_member, uninvite_member, accept_membership, reject_membership from .group import BookwyrmGroup, UserGroups, FindUsers, invite_member, remove_member, accept_membership, reject_membership
from .import_data import Import, ImportStatus from .import_data import Import, ImportStatus
from .inbox import Inbox from .inbox import Inbox
from .interaction import Favorite, Unfavorite, Boost, Unboost from .interaction import Favorite, Unfavorite, Boost, Unboost

View file

@ -57,11 +57,11 @@ class UserGroups(View):
def get(self, request, username): def get(self, request, username):
"""display a group""" """display a group"""
user = get_user_from_username(request.user, username) user = get_user_from_username(request.user, username)
groups = user.bookwyrmgroup_set.all() # follow the relationship backwards, nice memberships = models.BookwyrmGroupMember.objects.filter(user=user).all()
paginated = Paginator(groups, 12) paginated = Paginator(memberships, 12)
data = { data = {
"groups": paginated.get_page(request.GET.get("page")), "memberships": paginated.get_page(request.GET.get("page")),
"is_self": request.user.id == user.id, "is_self": request.user.id == user.id,
"user": user, "user": user,
"group_form": forms.GroupForm(), "group_form": forms.GroupForm(),
@ -89,8 +89,10 @@ class FindUsers(View):
def get(self, request, group_id): def get(self, request, group_id):
"""basic profile info""" """basic profile info"""
query = request.GET.get("query") query = request.GET.get("query")
group = models.BookwyrmGroup.objects.get(id=group_id)
user_results = ( user_results = (
models.User.viewer_aware_objects(request.user) models.User.viewer_aware_objects(request.user)
.exclude(memberships__in=group.memberships.all()) # don't suggest users who are already members
.annotate( .annotate(
similarity=Greatest( similarity=Greatest(
TrigramSimilarity("username", query), TrigramSimilarity("username", query),
@ -149,7 +151,7 @@ def invite_member(request):
@require_POST @require_POST
@login_required @login_required
def uninvite_member(request): def remove_member(request):
"""invite a member to the group""" """invite a member to the group"""
group = get_object_or_404(models.BookwyrmGroup, id=request.POST.get("group")) group = get_object_or_404(models.BookwyrmGroup, id=request.POST.get("group"))
@ -160,51 +162,31 @@ def uninvite_member(request):
if not user: if not user:
return HttpResponseBadRequest() return HttpResponseBadRequest()
if not group.user == request.user: is_member = models.BookwyrmGroupMember.objects.filter(group=group,user=user).exists()
return HttpResponseBadRequest() is_invited = models.GroupMemberInvitation.objects.filter(group=group,user=user).exists()
try: if is_invited:
invitation = models.GroupMemberInvitation.objects.get( try:
user=user, invitation = models.GroupMemberInvitation.objects.get(
group=group user=user,
) group=group
)
invitation.reject() invitation.reject()
except IntegrityError: except IntegrityError:
pass pass
return redirect(user.local_path) if is_member:
@require_POST try:
@login_required membership = models.BookwyrmGroupMember.objects.get(group=group,user=user)
def remove_member(request): membership.delete()
"""remove a member from the group"""
# TODO: send notification to user telling them they have been removed except IntegrityError:
# TODO: remove yourself from a group!!!! (except owner) pass
# FUTURE TODO: transfer ownership of group
# THIS LOGIC SHOULD BE IN MODEL
# TODO: if groups become AP values we need something like get_group_from_group_fullname # TODO: should send notification to all members including the now ex-member that they have been removed.
# group = get_object_or_404(models.Group, id=request.POST.get("group")) # NOTE: does this not work?
group = models.BookwyrmGroup.objects.get(id=request.POST["group"])
if not group:
return HttpResponseBadRequest()
user = get_user_from_username(request.user, request.POST["user"])
if not user:
return HttpResponseBadRequest()
if not group.user == request.user:
return HttpResponseBadRequest()
try:
membership = models.BookwyrmGroupMember.objects.get(group=group,user=user) # BUG: wrong
membership.delete()
except IntegrityError:
pass
return redirect(user.local_path) return redirect(user.local_path)
@ -227,7 +209,7 @@ def accept_membership(request):
except IntegrityError: except IntegrityError:
pass pass
return redirect(request.user.local_path) return redirect(group.local_path)
@require_POST @require_POST
@login_required @login_required