forked from mirrors/bookwyrm
add and remove users from groups
This commit is contained in:
parent
e800106be4
commit
b645d75303
6 changed files with 100 additions and 33 deletions
|
@ -43,3 +43,10 @@ class GroupMember(models.Model):
|
||||||
|
|
||||||
group = models.ForeignKey("Group", on_delete=models.CASCADE)
|
group = models.ForeignKey("Group", on_delete=models.CASCADE)
|
||||||
user = models.ForeignKey("User", on_delete=models.CASCADE)
|
user = models.ForeignKey("User", on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["group", "user"], name="unique_member"
|
||||||
|
)
|
||||||
|
]
|
|
@ -30,36 +30,37 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<ul start="{{ members.start_index }}" class="ordered-list">
|
<ul start="{{ members.start_index }}" class="ordered-list">
|
||||||
{% for member in group.members.all %}
|
|
||||||
<div class="column is-flex is-flex-grow-0">
|
<div class="column is-flex is-flex-grow-0">
|
||||||
<div class="box has-text-centered is-shadowless has-background-white-bis m-0">
|
{% for member in group.members.all %}
|
||||||
<a href="{{ user.local_path }}" class="has-text-black">
|
<span class="box has-text-centered is-shadowless has-background-white-bis m-0">
|
||||||
{% include 'snippets/avatar.html' with user=user large=True %}
|
<a href="{{ member.local_path }}" class="has-text-black">
|
||||||
<span title="{{ user.display_name }}" class="is-block is-6 has-text-weight-bold">{{ user.display_name|truncatechars:10 }}</span>
|
{% include 'snippets/avatar.html' with user=member large=True %}
|
||||||
<span title="@{{ user|username }}" class="is-block pb-3">@{{ user|username|truncatechars:8 }}</span>
|
<span title="{{ member.display_name }}" class="is-block is-6 has-text-weight-bold">{{ member.display_name|truncatechars:10 }}</span>
|
||||||
|
<span title="@{{ member|username }}" class="is-block pb-3">@{{ member|username|truncatechars:8 }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% include 'snippets/add_to_group_button.html' with user=user minimal=True %}
|
{% include 'snippets/add_to_group_button.html' with user=member minimal=True %}
|
||||||
{% if user.mutuals %}
|
{% if member.mutuals %}
|
||||||
<p class="help">
|
<p class="help">
|
||||||
{% blocktrans trimmed with mutuals=user.mutuals|intcomma count counter=user.mutuals %}
|
{% blocktrans trimmed with mutuals=member.mutuals|intcomma count counter=member.mutuals %}
|
||||||
{{ mutuals }} follower you follow
|
{{ mutuals }} follower you follow
|
||||||
{% plural %}
|
{% plural %}
|
||||||
{{ mutuals }} followers you follow{% endblocktrans %}
|
{{ mutuals }} followers you follow{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
{% elif user.shared_books %}
|
{% elif member.shared_books %}
|
||||||
<p class="help">
|
<p class="help">
|
||||||
{% blocktrans trimmed with shared_books=user.shared_books|intcomma count counter=user.shared_books %}
|
{% blocktrans trimmed with shared_books=member.shared_books|intcomma count counter=member.shared_books %}
|
||||||
{{ shared_books }} book on your shelves
|
{{ shared_books }} book on your shelves
|
||||||
{% plural %}
|
{% plural %}
|
||||||
{{ shared_books }} books on your shelves
|
{{ shared_books }} books on your shelves
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
{% elif request.user in user.following.all %}
|
{% elif request.user in member.following.all %}
|
||||||
<p class="help">
|
<p class="help">
|
||||||
{% trans "Follows you" %}
|
{% trans "Follows you" %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
|
@ -7,21 +7,21 @@
|
||||||
|
|
||||||
<div class="field{% if not minimal %} has-addons{% else %} mb-0{% endif %}">
|
<div class="field{% if not minimal %} has-addons{% else %} mb-0{% endif %}">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<!-- TODO: make this "add-to-group" -->
|
<form action="{% url 'add-group-member' %}" method="POST" class="interaction add_{{ user.id }} {% if user in group.members.all %}is-hidden{%endif %}" data-id="add_{{ user.id }}">
|
||||||
<form action="{% url 'follow' %}" method="POST" class="interaction follow_{{ user.id }} {% if request.user in user.followers.all or request.user in user.follower_requests.all %}is-hidden{%endif %}" data-id="follow_{{ user.id }}">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="group" value="{{ group.id }}">
|
||||||
<input type="hidden" name="user" value="{{ user.username }}">
|
<input type="hidden" name="user" value="{{ user.username }}">
|
||||||
<button class="button is-small{% if not minimal %} is-link{% endif %}" type="submit">
|
<button class="button is-small{% if not minimal %} is-link{% endif %}" type="submit">
|
||||||
{% if show_username %}
|
{% if show_username %}
|
||||||
{% blocktrans with username=user.localname %}Follow @{{ username }}{% endblocktrans %}
|
{% blocktrans with username=user.localname %}Add @{{ username }}{% endblocktrans %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Follow" %}
|
{% trans "Add" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<!-- TODO: make this "remove-from-group" -->
|
<form action="{% url 'remove-group-member' %}" method="POST" class="interaction add_{{ user.id }} {% if user not in group.members.all %}is-hidden{%endif %}" data-id="add_{{ user.id }}">
|
||||||
<form action="{% url 'unfollow' %}" method="POST" class="interaction follow_{{ user.id }} {% if not request.user in user.followers.all and not request.user in user.follower_requests.all %}is-hidden{%endif %}" data-id="follow_{{ user.id }}">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
<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 user.manually_approves_followers and request.user not in user.followers.all %}
|
{% if user.manually_approves_followers and request.user not in user.followers.all %}
|
||||||
<button class="button is-small is-danger is-light" type="submit">
|
<button class="button is-small is-danger is-light" type="submit">
|
||||||
|
@ -30,9 +30,9 @@
|
||||||
{% 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 %}Unfollow @{{ username }}{% endblocktrans %}
|
{% blocktrans with username=user.localname %}Remove @{{ username }}{% endblocktrans %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Unfollow" %}
|
{% trans "Remove" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -255,7 +255,9 @@ urlpatterns = [
|
||||||
re_path(rf"{USER_PATH}/groups/?$", views.UserGroups.as_view(), name="user-groups"),
|
re_path(rf"{USER_PATH}/groups/?$", views.UserGroups.as_view(), name="user-groups"),
|
||||||
re_path(r"^create-group/?$", views.create_group, name="create-group"),
|
re_path(r"^create-group/?$", views.create_group, name="create-group"),
|
||||||
re_path(r"^group/(?P<group_id>\d+)(.json)?/?$", views.Group.as_view(), name="group"),
|
re_path(r"^group/(?P<group_id>\d+)(.json)?/?$", views.Group.as_view(), name="group"),
|
||||||
re_path(r"^group/(?P<group_id>\d+)/add-users/?$", views.FindAndAddUsers.as_view(), name="group-find-users"),
|
re_path(r"^group/(?P<group_id>\d+)/add-users/?$", views.FindUsers.as_view(), name="group-find-users"),
|
||||||
|
re_path(r"^add-group-member/?$", views.add_member, name="add-group-member"),
|
||||||
|
re_path(r"^remove-group-member/?$", views.remove_member, name="remove-group-member"),
|
||||||
# lists
|
# lists
|
||||||
re_path(rf"{USER_PATH}/lists/?$", views.UserLists.as_view(), name="user-lists"),
|
re_path(rf"{USER_PATH}/lists/?$", views.UserLists.as_view(), name="user-lists"),
|
||||||
re_path(r"^list/?$", views.Lists.as_view(), name="lists"),
|
re_path(r"^list/?$", views.Lists.as_view(), name="lists"),
|
||||||
|
|
|
@ -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 Group, UserGroups, FindAndAddUsers, create_group
|
from .group import Group, UserGroups, FindUsers, create_group, add_member, remove_member
|
||||||
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
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
"""group views"""
|
"""group views"""
|
||||||
from typing import Optional
|
|
||||||
from urllib.parse import urlencode
|
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.db import IntegrityError
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.http import HttpResponseNotFound, HttpResponseBadRequest, HttpResponse
|
from django.http import HttpResponseNotFound, HttpResponseBadRequest
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
@ -16,7 +13,7 @@ from django.db.models.functions import Greatest
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.suggested_users import suggested_users
|
from bookwyrm.suggested_users import suggested_users
|
||||||
from .helpers import privacy_filter
|
from .helpers import privacy_filter # TODO:
|
||||||
from .helpers import get_user_from_username
|
from .helpers import get_user_from_username
|
||||||
|
|
||||||
class Group(View):
|
class Group(View):
|
||||||
|
@ -58,9 +55,9 @@ class UserGroups(View):
|
||||||
return TemplateResponse(request, "user/groups.html", data)
|
return TemplateResponse(request, "user/groups.html", data)
|
||||||
|
|
||||||
@method_decorator(login_required, name="dispatch")
|
@method_decorator(login_required, name="dispatch")
|
||||||
class FindAndAddUsers(View):
|
class FindUsers(View):
|
||||||
"""find friends to add to your group"""
|
"""find friends to add to your group"""
|
||||||
"""this is taken from the Get Started friend finder"""
|
"""this is mostly taken from the Get Started friend finder"""
|
||||||
|
|
||||||
def get(self, request, group_id):
|
def get(self, request, group_id):
|
||||||
"""basic profile info"""
|
"""basic profile info"""
|
||||||
|
@ -103,3 +100,63 @@ def create_group(request):
|
||||||
# add the creator as a group member
|
# add the creator as a group member
|
||||||
models.GroupMember.objects.create(group=group, user=request.user)
|
models.GroupMember.objects.create(group=group, user=request.user)
|
||||||
return redirect(group.local_path)
|
return redirect(group.local_path)
|
||||||
|
|
||||||
|
@require_POST
|
||||||
|
@login_required
|
||||||
|
def add_member(request):
|
||||||
|
"""add a member to the group"""
|
||||||
|
|
||||||
|
# TODO: if groups become AP values we need something like get_group_from_group_fullname
|
||||||
|
# group = get_object_or_404(models.Group, id=request.POST.get("group"))
|
||||||
|
group = models.Group.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.manager == request.user:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
try:
|
||||||
|
models.GroupMember.objects.create(
|
||||||
|
group=group,
|
||||||
|
user=user
|
||||||
|
)
|
||||||
|
|
||||||
|
except IntegrityError:
|
||||||
|
print("no integrity")
|
||||||
|
pass
|
||||||
|
|
||||||
|
# TODO: how do we return and update AJAX data?
|
||||||
|
return redirect(user.local_path)
|
||||||
|
|
||||||
|
@require_POST
|
||||||
|
@login_required
|
||||||
|
def remove_member(request):
|
||||||
|
"""remove a member from the group"""
|
||||||
|
|
||||||
|
# TODO: if groups become AP values we need something like get_group_from_group_fullname
|
||||||
|
# group = get_object_or_404(models.Group, id=request.POST.get("group"))
|
||||||
|
group = models.Group.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.manager == request.user:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
try:
|
||||||
|
membership = models.GroupMember.objects.get(group=group,user=user)
|
||||||
|
membership.delete()
|
||||||
|
|
||||||
|
except IntegrityError:
|
||||||
|
print("no integrity")
|
||||||
|
pass
|
||||||
|
|
||||||
|
# TODO: how do we return and update AJAX data?
|
||||||
|
return redirect(user.local_path)
|
Loading…
Reference in a new issue