diff --git a/core/templates/core/community-list.html b/core/templates/core/community-list.html index 0732c013..d865ca71 100644 --- a/core/templates/core/community-list.html +++ b/core/templates/core/community-list.html @@ -8,6 +8,9 @@

Communities

{% for community in communities %} {{ community.name }} + {% if not community.has_access %} + 🔒 + {% endif %} {% empty %}

No communities available.

diff --git a/core/tests/test_views.py b/core/tests/test_views.py index 6129ffb5..654f94b5 100644 --- a/core/tests/test_views.py +++ b/core/tests/test_views.py @@ -268,7 +268,7 @@ def test_reported_detail_post_by_user(client: Client, user: User, post: Post, re client.force_login(user) response = client.get(reverse("reported-post", kwargs={"pk": post.pk})) assert response.status_code == 403 - assert "

403 Forbidden

" in response.content.decode() + assert "You don't have access to this community." in response.content.decode() def test_reported_detail_post_by_anonymous_user(client: Client, post_report: PostReport) -> None: @@ -554,3 +554,21 @@ def test_add_non_existing_moderator(client: Client, community: Community, user: messages = list(response.context["messages"]) assert any("Invalid user or nickname." in message.message for message in messages) + + +def test_remove_non_existing_moderator(client: Client, community: Community, user: User) -> None: + admin_password = generate_random_password() + + admin = User.objects.create_user(email="admin@example.com", password=admin_password, nickname="adminnick") + + client.force_login(admin) + CommunityMember.objects.create(community=community, user=admin, role=CommunityMember.ADMIN) + + url = reverse("community-detail", kwargs={"slug": community.slug}) + form_data = {"nickname": user.nickname} + + response = client.post(url, {"action": "remove_moderator", **form_data}) + assert response.status_code == 200 + + messages = list(response.context["messages"]) + assert any("User is not a moderator of this community." in message.message for message in messages) diff --git a/core/views.py b/core/views.py index beafa381..fb8389bd 100644 --- a/core/views.py +++ b/core/views.py @@ -6,7 +6,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.db import models -from django.db.models import QuerySet +from django.db.models import Exists, OuterRef, QuerySet from django.http import Http404, HttpRequest, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect from django.template.loader import render_to_string @@ -135,6 +135,24 @@ class CommunityListView(ListView): context_object_name = "communities" paginate_by = 10 + def get_queryset(self: "CommunityListView") -> models.QuerySet: + user = self.request.user + return ( + super() + .get_queryset() + .prefetch_related("members") + .annotate(has_access=Exists(CommunityMember.objects.filter(community=OuterRef("pk"), user=user))) + ) + + def get_context_data(self: "CommunityListView", **kwargs: any) -> dict[str, any]: + context = super().get_context_data(**kwargs) + communities = context["communities"] + + for community in communities: + community.has_access = community.privacy != "30_PRIVATE" or community.has_access + + return context + class CommunityCreateView(LoginRequiredMixin, CreateView): model = Community @@ -193,6 +211,7 @@ def post_add_moderator(self: "CommunityDetailView", request: "HttpRequest", *arg if add_moderator_form.is_valid(): user = add_moderator_form.cleaned_data["nickname"] self.object.add_moderator(user) + messages.success(request, f"{user.nickname} is now a moderator of this community.") else: messages.error(request, "Invalid user or nickname.") return self.get(request, *args, **kwargs) @@ -203,7 +222,13 @@ def post_remove_moderator(self: "CommunityDetailView", request: "HttpRequest", * remove_moderator_form = RemoveModeratorForm(request.POST) if remove_moderator_form.is_valid(): user = remove_moderator_form.cleaned_data["nickname"] + if not CommunityMember.objects.filter( + community=self.object, user=user, role=CommunityMember.MODERATOR + ).exists(): + messages.error(request, "User is not a moderator of this community.") + return self.get(request, *args, **kwargs) self.object.remove_moderator(user) + messages.success(request, f"{user.nickname} was successfully removed from moderators.") else: messages.error(request, "Invalid user or nickname.") return self.get(request, *args, **kwargs) @@ -239,6 +264,11 @@ def handle_no_permission(self: "CommunityUpdateView") -> HttpResponse: messages.error(self.request, "You do not have permission to update this community.") return redirect("community-detail", slug=self.get_object().slug) + def form_valid(self: "CommunityUpdateView", form: forms.ModelForm) -> HttpResponseRedirect: + response = super().form_valid(form) + messages.success(self.request, "Community updated successfully.") + return response + def get_success_url(self: "CommunityUpdateView") -> str: return reverse_lazy("community-detail", kwargs={"slug": self.object.slug}) diff --git a/reddit/settings.py b/reddit/settings.py index 4ec46fdd..9f935b6b 100644 --- a/reddit/settings.py +++ b/reddit/settings.py @@ -18,7 +18,6 @@ # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ @@ -30,13 +29,11 @@ DEBUG = config("DEBUG") - # SECURITY WARNING: don't run with debug turned on in production! ALLOWED_HOSTS = json.loads(config("ALLOWED_HOSTS")) - # Application definition INSTALLED_APPS = [ @@ -86,7 +83,6 @@ WSGI_APPLICATION = "reddit.wsgi.application" - # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases @@ -97,7 +93,6 @@ }, } - # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators @@ -116,7 +111,6 @@ }, ] - # Internationalization # https://docs.djangoproject.com/en/5.0/topics/i18n/ @@ -128,7 +122,6 @@ USE_TZ = True - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ @@ -168,6 +161,5 @@ LIMIT_WARNINGS = 5 LOGIN_URL = reverse_lazy("login") - REST_FRAMEWORK = {"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "PAGE_SIZE": 10} DEFAULT_AVATAR_URL = "/media/users_avatars/default.png" diff --git a/templates/403.html b/templates/403.html new file mode 100644 index 00000000..98facb58 --- /dev/null +++ b/templates/403.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} +{% load static %} + +{% block content %} +
+
+
+

You don't have access to this community.

+ Back to Community List +
+
+
+{% endblock %} \ No newline at end of file