Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/clubs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,11 @@ def filter_is_admin(self):
"""Filter memberships that are admin memberships."""

return self.filter(roles__role_type=RoleType.ADMIN)

def filter_is_not_admin(self):
"""Filter memberships that are not admin memberships."""

return self.exclude(roles__role_type=RoleType.ADMIN)


class ClubMembership(ClubScopedModel, ModelBase):
Expand Down Expand Up @@ -598,6 +603,7 @@ def team_memberships(self):

# Fallback to DB query
return self.user.team_memberships.filter(team__club_id=self.club_id)


@cached_property
def is_admin(self) -> bool:
Expand Down
60 changes: 59 additions & 1 deletion app/clubs/tests/test_club_apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from users.tests.utils import create_test_user
from utils.testing import create_test_uploadable_image

from clubs.models import ClubApiKey, ClubFile, ClubRole
from clubs.models import ClubApiKey, ClubFile, ClubRole, RoleType
from clubs.services import ClubService
from clubs.tests.utils import (
CLUBS_JOIN_URL,
Expand Down Expand Up @@ -421,3 +421,61 @@ def test_get_club_members(self):
# Accepted, has permission
res = self.client.get(url)
self.assertResOk(res)

def test_is_admin_none(self):
"""Tests club response if is_admin is None"""

MY_CLUB_COUNT = 3


#clubs = create_test_clubs(CLUBS_COUNT)

ClubService(self.clubs[0]).add_member(self.user, roles=["President"])
ClubService(self.clubs[1]).add_member(self.user, roles=["Member"])
ClubService(self.clubs[2]).add_member(self.user, roles=["Member"])

url = club_list_url_member()

res = self.client.get(url)

self.assertResOk(res)
data = res.json()

self.assertEqual(len(data), MY_CLUB_COUNT)


def test_is_admin_true(self):
"""Tests club response if is_admin is True"""

MY_CLUB_COUNT = 3

ClubService(self.clubs[0]).add_member(self.user, roles=["President"])
ClubService(self.clubs[1]).add_member(self.user, roles=["Member"])
ClubService(self.clubs[2]).add_member(self.user, roles=["Member"])

url = club_list_url_member(is_admin=True)

res = self.client.get(url)

self.assertResOk(res)
data = res.json()

self.assertEqual(len(data), MY_CLUB_COUNT)

def test_is_admin_false(self):
"""Tests club response if is_admin is False"""

MY_CLUB_COUNT = 3

ClubService(self.clubs[0]).add_member(self.user, roles=["President"])
ClubService(self.clubs[1]).add_member(self.user, roles=["Member"])
ClubService(self.clubs[2]).add_member(self.user, roles=["Member"])

url = club_list_url_member(is_admin=False)

res = self.client.get(url)

self.assertResOk(res)
data = res.json()

self.assertEqual(len(data), MY_CLUB_COUNT)
18 changes: 16 additions & 2 deletions app/clubs/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
from typing import Optional

from django.urls import reverse
from django.utils.http import urlencode

from clubs.models import Club, ClubFile, ClubRole, RoleType, Team, TeamRole
from clubs.services import ClubService
from lib.faker import fake
from users.models import User
from utils.testing import create_test_image
Expand Down Expand Up @@ -39,8 +43,18 @@ def club_apikey_list_url(club_id: int):
CLUBS_PREVIEW_LIST_URL = reverse("api-clubs:clubpreview-list")


def club_list_url_member():
return reverse("api-clubs:club-list")
def club_list_url_member(is_admin:bool=None):
url = reverse("api-clubs:club-list")

query_params = {}

if is_admin:
query_params["is_admin"] = is_admin

if query_params:
return f"{url}?{urlencode(query_params)}"

return url


def club_file_list_url(club_id: int):
Expand Down
22 changes: 21 additions & 1 deletion app/clubs/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,33 @@ class IsClubAdminFilter(FilterBackendBase):
def filter_queryset(self, request, queryset, view):
is_admin = request.query_params.get("is_admin", None)

if is_admin is not None:
is_admin_bool = False


if is_admin is None:
all_clubs = list(
request.user.club_memberships.values_list(
"club__id", flat=True
)
)
queryset = queryset.filter(id__in=all_clubs)

# When type conversion works and is_admin is a boolean, update the code
if is_admin == "true":
admin_clubs = list(
request.user.club_memberships.filter_is_admin().values_list(
"club__id", flat=True
)
)
queryset = queryset.filter(id__in=admin_clubs)

elif is_admin == "false":
member_clubs = list(
request.user.club_memberships.filter_is_not_admin().values_list(
"club__id", flat=True
)
)
queryset = queryset.filter(id__in=member_clubs)

return queryset

Expand Down
6 changes: 6 additions & 0 deletions app/polls/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
PollQuestionAnswer,
PollSubmission,
PollSubmissionLink,
PollTemplate,
ScaleInput,
TextInput,
UploadInput,
Expand Down Expand Up @@ -107,6 +108,10 @@ def sync_submission_links(self, request, queryset):
return


class PollTemplateAdmin(PollAdmin):
"""Manage poll templates in admin"""


class TextInputInlineAdmin(admin.TabularInline):
"""Manage text inputs in questions admin."""

Expand Down Expand Up @@ -192,3 +197,4 @@ class PollSubmissionAdmin(ModelAdminBase):
admin.site.register(PollMarkup)
admin.site.register(ChoiceInput, ChoiceInputAdmin)
admin.site.register(PollSubmission, PollSubmissionAdmin)
admin.site.register(PollTemplate, PollTemplateAdmin)
6 changes: 6 additions & 0 deletions app/polls/apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
basename="pollchoiceoption",
)

router.register(
r"polltemplates",
viewsets.PollTemplateViewSet,
basename="polltemplate",
)

app_name = "api-polls"

urlpatterns = [path("", include(router.urls))]
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 5.2.7 on 2025-10-20 21:43

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("clubs", "0054_clubfile_origin"),
("django_celery_beat", "0019_alter_periodictasks_options"),
("events", "0024_alter_event_clubs_alter_event_is_draft_and_more"),
("polls", "0023_remove_numberinput_decimal_places_and_more"),
]

operations = [
migrations.RemoveConstraint(
model_name="poll",
name="only_poll_templates_allow_null_club",
),
migrations.AddConstraint(
model_name="poll",
constraint=models.CheckConstraint(
condition=models.Q(
("club__isnull", True), ("poll_type", "standard"), _connector="OR"
),
name="only_poll_templates_allow_null_club",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 5.2.7 on 2025-10-23 00:42

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("clubs", "0054_clubfile_origin"),
("django_celery_beat", "0019_alter_periodictasks_options"),
("events", "0024_alter_event_clubs_alter_event_is_draft_and_more"),
("polls", "0024_remove_poll_only_poll_templates_allow_null_club_and_more"),
]

operations = [
migrations.RemoveConstraint(
model_name="poll",
name="only_poll_templates_allow_null_club",
),
migrations.AddConstraint(
model_name="poll",
constraint=models.CheckConstraint(
condition=models.Q(
("club__isnull", False), ("poll_type", "template"), _connector="OR"
),
name="only_poll_templates_allow_null_club",
),
),
]
17 changes: 11 additions & 6 deletions app/polls/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ class Poll(ClubScopedModel, ModelBase):
related_name="+",
)

# Hopefully this works
# poll_template = models.ForeignKey(
# "polls.pollTemplate", null=True, blank=True, on_delete=models.SET_NULL
# )

# Foreign Relationships
fields: models.QuerySet["PollField"]
submissions: models.QuerySet["PollSubmission"]
Expand Down Expand Up @@ -299,10 +304,8 @@ class Meta:
models.CheckConstraint(
name="only_poll_templates_allow_null_club",
check=(
~(
models.Q(club__isnull=True)
& models.Q(poll_type=PollType.STANDARD)
)
models.Q(club__isnull=False)
| models.Q(poll_type=PollType.TEMPLATE.value)
),
),
]
Expand Down Expand Up @@ -353,8 +356,10 @@ class PollSubmissionLink(Link):
class PollTemplateManager(ManagerBase["PollTemplate"]):
"""Manage poll template queries."""

def create(self, template_name: str, poll_name: str, **kwargs):
return super().create(template_name=template_name, name=poll_name, **kwargs)
def create(self, template_name: str, poll_name: str = None, **kwargs):
kwargs.setdefault("name", poll_name)
kwargs.setdefault("poll_type", PollType.TEMPLATE)
return super().create(template_name=template_name, **kwargs)


class PollTemplate(Poll):
Expand Down
36 changes: 35 additions & 1 deletion app/polls/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
UpdateListSerializer,
)
from django.shortcuts import get_object_or_404
from events.models import Event
from events.models import Event, EventType
from rest_framework import exceptions, serializers
from users.models import User
from users.serializers import UserNestedSerializer
Expand Down Expand Up @@ -368,6 +368,40 @@ def update(self, instance, validated_data):
return super().update(instance, validated_data)


class PollTemplateSerializer(PollSerializer):
"""Json definition for poll templates"""

template_name = serializers.CharField()
event_type = serializers.ChoiceField(
choices=EventType.choices, allow_blank=True, required=True
)
club = PollClubNestedSerializer(required=False, allow_null=True)

# Hiding Fields
submissions_download_url = None
event = None
is_published = None

class Meta:
model = models.PollTemplate
exclude = ["open_task", "close_task"]
read_only_fields = ["id", "created_at", "updated_at"]

def create(self, validated_data):
# is_published = validated_data.pop("is_published")
event = validated_data.pop("event")

club = validated_data.pop("club", None)
if club is not None:
validated_data["club"] = get_object_or_404(Club, id=club.get("id"))
else:
validated_data["club"] = club

poll_name = validated_data.pop("name")

return models.PollTemplate.objects.create(poll_name=poll_name, **validated_data)


class PollPreviewSerializer(ModelSerializer):
"""Fields guest users can see for polls."""

Expand Down
Loading
Loading