Skip to content
Draft
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
4 changes: 1 addition & 3 deletions backend/authentication/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,14 @@
from django.forms import ModelForm
from django.http import HttpRequest

from authentication.models import Support, SupportEntityType, UserFlag, UserModel
from authentication.models import UserFlag, UserModel

logger = logging.getLogger(__name__)

# MARK: Register

# Remove default Group.
admin.site.unregister(Group)
admin.site.register(Support)
admin.site.register(SupportEntityType)

# MARK: User Creation

Expand Down
45 changes: 17 additions & 28 deletions backend/authentication/enums.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Enums for the authentication app.
"""

from enum import Enum


class StatusTypes(Enum):
"""
Represents the possible statuses of a user.
"""

PENDING = 1
ACTIVE = 2
SUSPENDED = 3
BANNED = 4


class SupportEntityTypes(Enum):
"""
Defines the types of entities that can support users.
"""

ORGANIZATION = 1
GROUP = 2
EVENT = 3
USER = 4
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Enums for the authentication app.
"""

from enum import Enum


class StatusTypes(Enum):
"""
Represents the possible statuses of a user.
"""

PENDING = 1
ACTIVE = 2
SUSPENDED = 3
BANNED = 4
39 changes: 0 additions & 39 deletions backend/authentication/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,10 @@

from authentication.models import (
SessionModel,
Support,
SupportEntityType,
UserFlag,
UserModel,
)

# MARK: Support


class SupportEntityTypeFactory(factory.django.DjangoModelFactory):
"""
Factory for creating SupportEntityType model instances.
"""

class Meta:
model = SupportEntityType

name = factory.Faker("word")


class SupportFactory(factory.django.DjangoModelFactory):
"""
Factory for creating Support model instances.

Notes
-----
This class generates mock `Support` instances, which associate supporters with supported entities.
It uses other factories like `SupportEntityTypeFactory` to generate related data.
"""

class Meta:
model = Support

supporter_type = factory.SubFactory(SupportEntityTypeFactory)
supporter_entity = factory.SubFactory(
"communities.organizations.factories.OrganizationFactory"
)
supported_type = factory.SubFactory(SupportEntityTypeFactory)
supported_entity = factory.SubFactory(
"communities.organizations.factories.OrganizationFactory"
)


# MARK: Session


Expand Down
55 changes: 2 additions & 53 deletions backend/authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,59 +135,6 @@ def create_user(
logger.exception(f"Failed to create user {username}: {str(e)}")
raise


# MARK: Support


class SupportEntityType(models.Model):
"""
Represents a type of support entity, such as organization, group, event, or user.

Notes
-----
This model is used in the `Support` relationship to define the type of entity
involved in the support system.
"""

id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
name = models.CharField(max_length=255)

def __str__(self) -> str:
return self.name


class Support(models.Model):
"""
Represents a support relationship between two entities.

Notes
-----
A `Support` connects a supporter entity (like an organization) to a supported entity.
Both entities are represented by their type and specific instances.
"""

supporter_type = models.ForeignKey(
"SupportEntityType", on_delete=models.CASCADE, related_name="supporter"
)
supporter_entity = models.ForeignKey(
"communities.Organization",
on_delete=models.CASCADE,
related_name="supporter",
)
supported_type = models.ForeignKey(
"SupportEntityType", on_delete=models.CASCADE, related_name="supported"
)
supported_entity = models.ForeignKey(
"communities.Organization",
on_delete=models.CASCADE,
related_name="supported",
)
creation_date = models.DateTimeField(auto_now_add=True)

def __str__(self) -> str:
return str(self.id)


# MARK: Session


Expand Down Expand Up @@ -258,6 +205,7 @@ class UserModel(AbstractUser, PermissionsMixin):
through="authentication.UserFlag",
)


def __str__(self) -> str:
return self.username

Expand All @@ -275,3 +223,4 @@ class UserFlag(models.Model):
)
created_by = models.ForeignKey("authentication.UserModel", on_delete=models.CASCADE)
creation_date = models.DateTimeField(auto_now=True)

20 changes: 19 additions & 1 deletion backend/authentication/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from rest_framework_simplejwt.tokens import RefreshToken

from authentication.models import SessionModel, UserFlag, UserModel
from events.models import Event, EventText

logger = logging.getLogger(__name__)
USER = get_user_model()
Expand Down Expand Up @@ -195,13 +196,29 @@ def validate(self, data: dict[str, str | Any]) -> dict[str, str | Any]:
raise

return data
class UserSupportedEventTextSerializer(serializers.ModelSerializer["EventText"]):
"""
Lightweight serializer for the texts associated with a user's supported events.
"""

class Meta:
model = EventText # resolved at module level; see import note below
fields = "__all__"
class UserSupportedEventSerializer(serializers.ModelSerializer["Event"]):
"""
Lightweight event serializer for a user's supported events.
"""
texts = UserSupportedEventTextSerializer(many=True, read_only=True)
class Meta:
model = Event # resolved at module level; see import note below
fields = ["id", "name", "tagline", "type", "location_type", "creation_date", "texts"]

class UserSerializer(serializers.ModelSerializer[UserModel]):
class UserSerializer(serializers.ModelSerializer[UserModel]):
"""
Serializer for the user model.
"""

supported_events = UserSupportedEventSerializer(many=True, read_only=True)
class Meta:
model = UserModel
fields = [
Expand All @@ -212,6 +229,7 @@ class Meta:
"is_active",
"is_staff",
"is_superuser",
"supported_events",
]


Expand Down
1 change: 1 addition & 0 deletions backend/authentication/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@
views.VerifyAccountResetPassword.as_view(),
name="verify_email_password",
),
path(route="users/<uuid:id>", view=views.UserDetailAPIView.as_view(), name="user"),
]
23 changes: 23 additions & 0 deletions backend/authentication/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,3 +534,26 @@ def delete(self, request: Request, id: str | uuid.UUID) -> Response:
return Response(
{"message": "Flag deleted successfully."}, status=status.HTTP_204_NO_CONTENT
)

class UserDetailAPIView(GenericAPIView[UserModel]):
queryset = UserModel.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAdminStaffCreatorOrReadOnly]
def get(self, request: Request, id: str | uuid.UUID) -> Response:
logger.info(
f"User detail requested: ID {id} by user {request.user.username}"
)
try:
user = UserModel.objects.get(id=id)

except UserModel.DoesNotExist:
logger.warning(f"User not found: ID {id}")
return Response(
{"detail": "Failed to retrieve the user."},
status=status.HTTP_404_NOT_FOUND,
)

self.check_object_permissions(request, user)

serializer = UserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK)
49 changes: 48 additions & 1 deletion backend/events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ class Event(models.Model):
topics = models.ManyToManyField("content.Topic", blank=True)

# Explicit type annotation required for mypy compatibility with django-stubs.
flags: Any = models.ManyToManyField("authentication.UserModel", through="EventFlag")
flags: Any = models.ManyToManyField("authentication.UserModel", through="EventFlag", related_name="flagged_events")
supporters: Any = models.ManyToManyField(
"authentication.UserModel",
through="EventSupport",
through_fields=("event", "supporter_user"),
related_name="supported_events",
)

def save(self, *args: Any, **kwargs: Any) -> None:
"""
Expand Down Expand Up @@ -173,6 +179,8 @@ class Meta:
ordering = ["order"]




# MARK: Flag


Expand All @@ -187,6 +195,45 @@ class EventFlag(models.Model):
creation_date = models.DateTimeField(auto_now=True)


# MARK: Support



class EventSupport(models.Model):
"""
Model for support received by an event.

Notes
-----
Only users can support events, so the supporter is a plain FK.
"""

id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
event = models.ForeignKey(
"Event",
on_delete=models.CASCADE,
related_name="supports_received",
)
supporter_user = models.ForeignKey(
"authentication.UserModel",
on_delete=models.CASCADE,
related_name="event_supports_given",
)
creation_date = models.DateTimeField(auto_now_add=True)

class Meta:
constraints = [
# A user can support a given event at most once.
models.UniqueConstraint(
fields=["event", "supporter_user"],
name="unique_event_support_per_user",
),
]

def __str__(self) -> str:
return f"{self.supporter_user} supports {self.event}"


# MARK: Format


Expand Down
Loading
Loading