Skip to content

Commit

Permalink
Merge branch 'main' into test/password-reset-endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewtavis authored Jun 29, 2024
2 parents 7169d1a + 99b25e9 commit e353594
Show file tree
Hide file tree
Showing 132 changed files with 2,964 additions and 1,549 deletions.
22 changes: 15 additions & 7 deletions backend/authentication/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@
UserTopic,
)

# MARK: Main Tables

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

# MARK: Bridge Tables

admin.site.register(UserResource)
admin.site.register(UserTask)
admin.site.register(UserTopic)
admin.site.register(SupportEntityType)

# MARK: Methods


class UserCreationForm(forms.ModelForm[UserModel]):
"""
Expand Down Expand Up @@ -115,12 +130,5 @@ class UserAdmin(BaseUserAdmin):
filter_horizontal = []


# Remove default Group
admin.site.unregister(Group)
admin.site.register(UserResource)
admin.site.register(UserTask)
admin.site.register(UserTopic)
admin.site.register(SupportEntityType)
admin.site.register(Support)
# Now register the new UserAdmin...
admin.site.register(UserModel, UserAdmin)
5 changes: 5 additions & 0 deletions backend/authentication/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
UserTopic,
)

# MARK: Main Tables


class SupportEntityTypeFactory(factory.django.DjangoModelFactory):
class Meta:
Expand Down Expand Up @@ -61,6 +63,9 @@ def verification_partner(
pass


# MARK: Bridge Tables


class UserResourceFactory(factory.django.DjangoModelFactory):
class Meta:
model = UserResource
Expand Down
61 changes: 33 additions & 28 deletions backend/authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,7 @@
from django.contrib.postgres.fields import ArrayField
from django.db import models


class SupportEntityType(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255)

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


class Support(models.Model):
supporter_type = models.ForeignKey(
"SupportEntityType", on_delete=models.CASCADE, related_name="supporter"
)
supporter_entity = models.ForeignKey(
"entities.Organization", on_delete=models.CASCADE, related_name="supporter"
)
supported_type = models.ForeignKey(
"SupportEntityType", on_delete=models.CASCADE, related_name="supported"
)
supported_entity = models.ForeignKey(
"entities.Organization", on_delete=models.CASCADE, related_name="supported"
)
creation_date = models.DateTimeField(auto_now_add=True)

def __str__(self) -> str:
return f"{self.id}"
# MARK: Main Tables


class CustomAccountManager(BaseUserManager["UserModel"]):
Expand Down Expand Up @@ -78,6 +53,33 @@ def create_user(
return user


class SupportEntityType(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255)

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


class Support(models.Model):
supporter_type = models.ForeignKey(
"SupportEntityType", on_delete=models.CASCADE, related_name="supporter"
)
supporter_entity = models.ForeignKey(
"entities.Organization", on_delete=models.CASCADE, related_name="supporter"
)
supported_type = models.ForeignKey(
"SupportEntityType", on_delete=models.CASCADE, related_name="supported"
)
supported_entity = models.ForeignKey(
"entities.Organization", on_delete=models.CASCADE, related_name="supported"
)
creation_date = models.DateTimeField(auto_now_add=True)

def __str__(self) -> str:
return f"{self.id}"


class UserModel(AbstractUser, PermissionsMixin):
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
username = models.CharField(max_length=255, unique=True)
Expand All @@ -92,7 +94,7 @@ class UserModel(AbstractUser, PermissionsMixin):
icon_url = models.ForeignKey(
"content.Image", on_delete=models.SET_NULL, blank=True, null=True
)
verifictaion_code = models.UUIDField(blank=True, null=True)
verification_code = models.UUIDField(blank=True, null=True)
email = models.EmailField(blank=True)
is_confirmed = models.BooleanField(default=False)
social_links = ArrayField(models.CharField(max_length=255), blank=True, null=True)
Expand All @@ -104,14 +106,17 @@ class UserModel(AbstractUser, PermissionsMixin):
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)

objects = CustomAccountManager() # type: ignore
objects: CustomAccountManager = CustomAccountManager() # type: ignore

USERNAME_FIELD = "username"

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


# MARK: Bridge Tables


class UserResource(models.Model):
user_id = models.ForeignKey(UserModel, on_delete=models.CASCADE)
resource_id = models.ForeignKey("content.Resource", on_delete=models.CASCADE)
Expand Down
33 changes: 21 additions & 12 deletions backend/authentication/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,7 @@
USER = get_user_model()


class SupportEntityTypeSerializer(serializers.ModelSerializer[SupportEntityType]):
class Meta:
model = SupportEntityType
fields = "__all__"

def validate(self, data: Dict[str, Union[str, Any]]) -> Dict[str, Union[str, Any]]:
if len(data["name"]) < 3:
raise serializers.ValidationError(
_("The field name must be at least 3 characters long."),
code="invalid_name",
)
return data
# MARK: Main Tables


class SupportSerializer(serializers.ModelSerializer[Support]):
Expand All @@ -57,6 +46,20 @@ def validate(self, data: Dict[str, Union[str, int]]) -> Dict[str, Union[str, int
return data


class SupportEntityTypeSerializer(serializers.ModelSerializer[SupportEntityType]):
class Meta:
model = SupportEntityType
fields = "__all__"

def validate(self, data: Dict[str, Union[str, Any]]) -> Dict[str, Union[str, Any]]:
if len(data["name"]) < 3:
raise serializers.ValidationError(
_("The field name must be at least 3 characters long."),
code="invalid_name",
)
return data


class UserSerializer(serializers.ModelSerializer[UserModel]):
class Meta:
model = UserModel
Expand All @@ -79,6 +82,9 @@ def validate(self, data: Dict[str, Union[str, Any]]) -> Dict[str, Union[str, Any
return data


# MARK: Bridge Tables


class UserResourceSerializer(serializers.ModelSerializer[UserResource]):
class Meta:
model = UserResource
Expand All @@ -97,6 +103,9 @@ class Meta:
fields = "__all__"


# MARK: Methods


class SignupSerializer(serializers.ModelSerializer[UserModel]):
password_confirmed = serializers.CharField(write_only=True)

Expand Down
4 changes: 2 additions & 2 deletions backend/authentication/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def test_signup(client: Client) -> None:
assert response.status_code == 201
assert UserModel.objects.filter(username=username)
# code for Email confirmation is generated and is a UUID
assert isinstance(user.verifictaion_code, UUID)
assert isinstance(user.verification_code, UUID)
assert user.is_confirmed is False
# Confirmation Email was sent
assert len(mail.outbox) == 1
Expand Down Expand Up @@ -149,7 +149,7 @@ def test_signup(client: Client) -> None:
assert UserModel.objects.filter(username=second_username).exists()
assert user.email == ""
assert user.is_confirmed is False
assert user.verifictaion_code is None
assert user.verification_code is None


@pytest.mark.django_db
Expand Down
8 changes: 6 additions & 2 deletions backend/authentication/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@

router = DefaultRouter()

router.register(r"support_entity_types", views.SupportEntityTypeViewSet)
# MARK: Main Tables

router.register(r"supports", views.SupportViewSet)
router.register(r"users", views.UserViewSet)

# MARK: Bridge Tables

router.register(r"support_entity_types", views.SupportEntityTypeViewSet)
router.register(r"user_resources", views.UserResourceViewSet)
router.register(r"user_tasks", views.UserTaskViewSet)
router.register(r"user_topics", views.UserTopicViewSet)


urlpatterns = [
path("", include(router.urls)),
path("signup/", views.SignupView.as_view(), name="signup"),
Expand Down
33 changes: 21 additions & 12 deletions backend/authentication/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@
ACTIVIST_EMAIL = os.getenv("ACTIVIST_EMAIL")


class SupportEntityTypeViewSet(viewsets.ModelViewSet[SupportEntityType]):
queryset = SupportEntityType.objects.all()
pagination_class = CustomPagination
serializer_class = SupportEntityTypeSerializer
# MARK: Main Tables


class SupportViewSet(viewsets.ModelViewSet[Support]):
Expand All @@ -61,6 +58,15 @@ class UserViewSet(viewsets.ModelViewSet[UserModel]):
serializer_class = UserSerializer


# MARK: Bridge Tables


class SupportEntityTypeViewSet(viewsets.ModelViewSet[SupportEntityType]):
queryset = SupportEntityType.objects.all()
pagination_class = CustomPagination
serializer_class = SupportEntityTypeSerializer


class UserResourceViewSet(viewsets.ModelViewSet[UserResource]):
queryset = UserResource.objects.all()
pagination_class = CustomPagination
Expand All @@ -79,6 +85,9 @@ class UserTopicViewSet(viewsets.ModelViewSet[UserTopic]):
serializer_class = UserTopicSerializer


# MARK: Methods


class SignupView(APIView):
queryset = UserModel.objects.all()
permission_classes = (AllowAny,)
Expand All @@ -91,9 +100,9 @@ def post(self, request: Request) -> Response:
user: UserModel = serializer.save()

if user.email != "":
user.verifictaion_code = uuid.uuid4()
user.verification_code = uuid.uuid4()

confirmation_link = f"{FRONTEND_BASE_URL}/confirm/{user.verifictaion_code}"
confirmation_link = f"{FRONTEND_BASE_URL}/confirm/{user.verification_code}"
message = f"Welcome to activist.org, {user.username}!, Please confirm your email address by clicking the link: {confirmation_link}"
html_message = render_to_string(
template_name="signup_email.html",
Expand All @@ -120,12 +129,12 @@ def post(self, request: Request) -> Response:
)

@extend_schema(
parameters=[OpenApiParameter(name="verifictaion_code", type=str, required=True)]
parameters=[OpenApiParameter(name="verification_code", type=str, required=True)]
)
def get(self, request: Request) -> Response:
"""Confirm a user's email address."""
verifictaion_code = request.GET.get("verifictaion_code")
user = UserModel.objects.filter(verifictaion_code=verifictaion_code).first()
verification_code = request.GET.get("verification_code")
user = UserModel.objects.filter(verification_code=verification_code).first()

if user is None:
return Response(
Expand All @@ -134,7 +143,7 @@ def get(self, request: Request) -> Response:
)

user.is_confirmed = True
user.verifictaion_code = ""
user.verification_code = ""
user.save()

return Response(
Expand Down Expand Up @@ -183,9 +192,9 @@ def get(self, request: Request) -> Response:
status=status.HTTP_404_NOT_FOUND,
)

user.verifictaion_code = uuid.uuid4()
user.verification_code = uuid.uuid4()

pwreset_link = f"{FRONTEND_BASE_URL}/pwreset/{user.verifictaion_code}"
pwreset_link = f"{FRONTEND_BASE_URL}/pwreset/{user.verification_code}"
message = "Reset your password at activist.org"
html_message = render_to_string(
template_name="pwreset_email.html",
Expand Down
21 changes: 21 additions & 0 deletions backend/backend/exception_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import logging
from typing import Any

from rest_framework.response import Response
from rest_framework.views import exception_handler

logger = logging.getLogger(__name__)


def bad_request_logger(exception: Any, context: dict[str, Any]) -> Response | None:
# Get the DRF exception handler standard error response
response = exception_handler(exception, context)

if response is not None:
logger.warning(
f"Bad request: {context['request'].path} -"
f"Status Code: {response.status_code} -"
f"Data: {response.data} -"
)

return response
1 change: 1 addition & 0 deletions backend/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
],
"EXCEPTION_HANDLER": "backend.exception_handler.bad_request_logger",
}

SPECTACULAR_SETTINGS = {
Expand Down
9 changes: 7 additions & 2 deletions backend/content/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@
TopicFormat,
)

# MARK: Main Tables

admin.site.register(Faq)
admin.site.register(Image)
admin.site.register(IsoCodeMap)
admin.site.register(Resource)
admin.site.register(ResourceTopic)
admin.site.register(Task)
admin.site.register(Topic)

# MARK: Main Tables

admin.site.register(ResourceTopic)
admin.site.register(TopicFormat)
admin.site.register(IsoCodeMap)
Loading

0 comments on commit e353594

Please sign in to comment.