Skip to content
Merged
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
5 changes: 4 additions & 1 deletion docker/backend/post_deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ python3 manage.py makemigrations --no-input
python3 manage.py migrate --no-input

# Load fixtures
python3 manage.py loaddata 01_complexities 02_sections 03_axes 04_conditioners 05_ideologies
python3 manage.py loaddata 01_complexities 02_sections 03_axes 04_conditioners 05_ideologies 06_axis_definitions 07_conditioner_definitions

# Populate Test Data & Init MinIO (Only in Non-Prod)
if [ "$ENVIRONMENT" != "prod" ] && [ "$ENVIRONMENT" != "production" ]; then
Expand All @@ -26,6 +26,9 @@ if [ "$ENVIRONMENT" != "prod" ] && [ "$ENVIRONMENT" != "production" ]; then
python3 manage.py init_minio
fi

# Load flags
python3 manage.py import_flags

# Collect static files
python3 manage.py collectstatic --no-input

Expand Down
1 change: 1 addition & 0 deletions src/apps/core/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from . import user_admin
from .geo_admin import *
18 changes: 18 additions & 0 deletions src/apps/core/admin/geo_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from core.models import Country, Region
from django.contrib import admin
from modeltranslation.admin import TabbedTranslationAdmin
from unfold.admin import ModelAdmin


@admin.register(Country)
class CountryAdmin(ModelAdmin, TabbedTranslationAdmin):
list_display = ["name", "code2", "uuid"]
search_fields = ["name", "code2"]


@admin.register(Region)
class RegionAdmin(ModelAdmin, TabbedTranslationAdmin):
list_display = ["name", "country", "uuid"]
search_fields = ["name", "country__name"]
list_filter = ["country"]
autocomplete_fields = ["country"]
3 changes: 2 additions & 1 deletion src/apps/core/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
PasswordResetConfirmSerializer,
)
from .geo_serializers import CountrySerializer, RegionSerializer
from .base_user_serializers import SimpleUserSerializer, PublicUserSerializer
from .user_serializers import (
MeSerializer,
RegisterSerializer,
SimpleUserSerializer,
UserSetPasswordSerializer,
UserVerificationSerializer,
AffinitySerializer,
IdeologyAffinitySerializer,
)
16 changes: 16 additions & 0 deletions src/apps/core/api/serializers/base_user_serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from core.helpers import UUIDModelSerializerMixin
from core.models import User


class SimpleUserSerializer(UUIDModelSerializerMixin):
class Meta:
model = User
fields = ["uuid", "username", "bio", "appearance", "is_public"]
read_only_fields = ["uuid"]


class PublicUserSerializer(UUIDModelSerializerMixin):
class Meta:
model = User
fields = ["uuid", "username", "bio", "is_public"]
read_only_fields = ["uuid", "username", "bio", "is_public"]
4 changes: 2 additions & 2 deletions src/apps/core/api/serializers/geo_serializers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from cities_light.models import Country, Region
from core.models import Country, Region
from rest_framework import serializers


class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = ["id", "name", "code2", "continent"]
fields = ["id", "name", "code2"]


class RegionSerializer(serializers.ModelSerializer):
Expand Down
82 changes: 22 additions & 60 deletions src/apps/core/api/serializers/user_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,69 +7,16 @@
from core.models import User
from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.translation import gettext_lazy as _
from ideology.models import IdeologyAbstractionComplexity, IdeologyAxis, IdeologySection
from ideology.api.serializers import (
ComplexityAffinitySerializer,
TargetIdeologySerializer,
)
from rest_framework import serializers
from rest_framework.serializers import ErrorDetail

logger = logging.getLogger(__name__)


class SimpleUserSerializer(UUIDModelSerializerMixin):
class Meta:
model = User
fields = ["uuid", "username", "bio", "appearance", "is_public"]
read_only_fields = ["uuid"]


class PublicUserSerializer(UUIDModelSerializerMixin):
class Meta:
model = User
fields = ["uuid", "username", "bio", "is_public"]
read_only_fields = ["uuid", "username", "bio", "is_public"]


class SimpleAxisSerializer(UUIDModelSerializerMixin):
class Meta:
model = IdeologyAxis
fields = ["uuid", "name", "left_label", "right_label"]


class SimpleSectionSerializer(UUIDModelSerializerMixin):
class Meta:
model = IdeologySection
fields = ["uuid", "name", "icon"]


class SimpleComplexitySerializer(UUIDModelSerializerMixin):
class Meta:
model = IdeologyAbstractionComplexity
fields = ["uuid", "name", "complexity"]

from .base_user_serializers import PublicUserSerializer, SimpleUserSerializer

class AnswerDetailSerializer(serializers.Serializer):
value = serializers.IntegerField(allow_null=True)
margin_left = serializers.IntegerField()
margin_right = serializers.IntegerField()
is_indifferent = serializers.BooleanField(default=False)


class AxisBreakdownSerializer(serializers.Serializer):
axis = SimpleAxisSerializer(allow_null=True)
my_answer = AnswerDetailSerializer(source="user_a", allow_null=True)
their_answer = AnswerDetailSerializer(source="user_b", allow_null=True)
affinity = serializers.FloatField(min_value=0.0, max_value=100.0, allow_null=True)


class SectionAffinitySerializer(serializers.Serializer):
section = SimpleSectionSerializer(allow_null=True)
affinity = serializers.FloatField(min_value=0.0, max_value=100.0, allow_null=True)
axes = AxisBreakdownSerializer(many=True)


class ComplexityAffinitySerializer(serializers.Serializer):
complexity = SimpleComplexitySerializer(allow_null=True)
affinity = serializers.FloatField(min_value=0.0, max_value=100.0, allow_null=True)
sections = SectionAffinitySerializer(many=True)
logger = logging.getLogger(__name__)


class AffinitySerializer(serializers.Serializer):
Expand All @@ -86,6 +33,20 @@ class AffinitySerializer(serializers.Serializer):
)


class IdeologyAffinitySerializer(serializers.Serializer):
target_ideology = TargetIdeologySerializer(read_only=True, allow_null=True)
total_affinity = serializers.FloatField(
min_value=0.0,
max_value=100.0,
allow_null=True,
source="total",
help_text=_("Overall affinity percentage. Null if no common axes."),
)
complexities = ComplexityAffinitySerializer(
many=True, help_text=_("Affinity grouped by abstraction level.")
)


class UserVerificationSerializer(SimpleUserSerializer):
class Meta(SimpleUserSerializer.Meta):
fields = SimpleUserSerializer.Meta.fields + ["is_verified"]
Expand Down Expand Up @@ -116,8 +77,9 @@ class Meta:
"appearance",
"is_public",
"atlas_onboarding_completed",
"is_superuser",
]
read_only_fields = ["is_verified", "email", "auth_provider"]
read_only_fields = ["is_verified", "email", "auth_provider", "is_superuser"]


class RegisterSerializer(UUIDModelSerializerMixin):
Expand Down
5 changes: 5 additions & 0 deletions src/apps/core/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
core_views.UserAffinityView.as_view(),
name="user-affinity",
),
path(
"users/affinity/ideology/<str:uuid>/",
core_views.UserIdeologyAffinityView.as_view(),
name="user-ideology-affinity",
),
path(
"geography/countries/",
core_views.CountryListView.as_view(),
Expand Down
7 changes: 6 additions & 1 deletion src/apps/core/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@
PasswordResetVerifyTokenView,
)
from .geo_views import CountryListView, RegionListView
from .user_views import MeDetailView, UserSetPasswordView, UserAffinityView
from .user_views import (
MeDetailView,
UserSetPasswordView,
UserAffinityView,
UserIdeologyAffinityView,
)
2 changes: 1 addition & 1 deletion src/apps/core/api/views/geo_views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from cities_light.models import Country, Region
from core.api.serializers import CountrySerializer, RegionSerializer
from core.models import Country, Region
from django.utils.translation import gettext_lazy as _
from django_filters import rest_framework as filters
from drf_spectacular.utils import OpenApiParameter, extend_schema
Expand Down
24 changes: 23 additions & 1 deletion src/apps/core/api/views/user_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
)
from django.utils.translation import gettext_lazy as _
from drf_spectacular.utils import extend_schema
from ideology.models import CompletedAnswer
from ideology.api.serializers import IdeologyAffinitySerializer
from ideology.models import CompletedAnswer, Ideology
from rest_framework import status
from rest_framework.generics import GenericAPIView, RetrieveUpdateAPIView, UpdateAPIView
from rest_framework.permissions import IsAuthenticated
Expand Down Expand Up @@ -74,3 +75,24 @@ def get(self, request, *args, **kwargs):
data = request.user.calculate_detailed_affinity_with(target_answer)
serializer = self.get_serializer(data)
return Response(serializer.data, status=status.HTTP_200_OK)


@extend_schema(
tags=["users"],
summary=_("Get affinity with an Ideology"),
description=_(
"Calculates the ideological affinity (0-100%) between the current user's active answers "
"and the defined values of a specific Ideology (identified by UUID)."
),
)
class UserIdeologyAffinityView(GenericAPIView):
permission_classes = [IsAuthenticated, IsVerified]
queryset = Ideology.objects.all()
lookup_field = "uuid"
serializer_class = IdeologyAffinitySerializer

def get(self, request, *args, **kwargs):
ideology = self.get_object()
data = request.user.calculate_ideology_affinity(ideology)
serializer = self.get_serializer(data)
return Response(serializer.data, status=status.HTTP_200_OK)
26 changes: 6 additions & 20 deletions src/apps/core/factories/country_factories.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import string

import factory
from cities_light.models import Country
from django.utils.text import slugify
from core.factories.abstract import TimeStampedUUIDModelFactory
from core.models import Country


class CountryFactory(factory.django.DjangoModelFactory):
class CountryFactory(TimeStampedUUIDModelFactory):
class Meta:
model = Country
django_get_or_create = ("code2",)
django_get_or_create = ("name",)

name = factory.Sequence(lambda n: f"Country {n}")
code2 = factory.Sequence(
lambda n: (
string.ascii_uppercase[n // 26 % 26] + string.ascii_uppercase[n % 26]
)
)
code3 = factory.Sequence(
lambda n: (
string.ascii_uppercase[n // 676 % 26]
+ string.ascii_uppercase[n // 26 % 26]
+ string.ascii_uppercase[n % 26]
)
)
continent = "EU"
slug = factory.LazyAttribute(lambda o: slugify(o.name))
code2 = factory.Sequence(lambda n: f"{n:02}"[-2:])
flag = factory.django.ImageField(color="blue")
8 changes: 4 additions & 4 deletions src/apps/core/factories/region_factories.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import factory
from cities_light.models import Region
from core.factories.abstract import TimeStampedUUIDModelFactory
from core.factories.country_factories import CountryFactory
from django.utils.text import slugify
from core.models import Region


class RegionFactory(factory.django.DjangoModelFactory):
class RegionFactory(TimeStampedUUIDModelFactory):
class Meta:
model = Region
django_get_or_create = ("name", "country")

name = factory.Faker("state")
country = factory.SubFactory(CountryFactory)
slug = factory.LazyAttribute(lambda o: slugify(o.name))
flag = factory.django.ImageField(color="red")
Loading
Loading