Skip to content

Commit

Permalink
Feat: 등급 별 혜택 관리 API 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
jungmir committed Aug 6, 2024
1 parent e9b08f7 commit eeec32c
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 47 deletions.
26 changes: 26 additions & 0 deletions pyconkr/settings-localtest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os

from pyconkr.settings import * # noqa

DEBUG = True

ALLOWED_HOSTS += [
"*",
]


# RDS
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "local.sqlite3",
}
}

# django-storages: TODO fix to in memory?
del MEDIA_ROOT # noqa
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
STATICFILES_STORAGE = "storages.backends.s3boto3.S3StaticStorage"
AWS_S3_ACCESS_KEY_ID = os.getenv("AWS_S3_ACCESS_KEY_ID")
AWS_S3_SECRET_ACCESS_KEY = os.getenv("AWS_S3_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = "pyconkr-api-v2-static-dev"
59 changes: 59 additions & 0 deletions sponsor/migrations/0007_remove_sponsorbenefit_offer_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Generated by Django 4.1.5 on 2024-08-06 12:41

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("sponsor", "0006_sponsorbenefit"),
]

operations = [
migrations.RemoveField(
model_name="sponsorbenefit",
name="offer",
),
migrations.RemoveField(
model_name="sponsorbenefit",
name="sponsor_level",
),
migrations.CreateModel(
name="BenefitByLevel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("offer", models.PositiveIntegerField(help_text="제공 하는 혜택 개수")),
(
"benefit",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="benefit_by_level",
to="sponsor.sponsorbenefit",
),
),
(
"level",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="benefit_by_level",
to="sponsor.sponsorlevel",
),
),
],
),
migrations.AddConstraint(
model_name="benefitbylevel",
constraint=models.UniqueConstraint(
fields=("benefit_id", "level_id"), name="IX_BENEFIT_BY_LEVEL_1"
),
),
]
40 changes: 28 additions & 12 deletions sponsor/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ def get_queryset(self):
return super(SponsorLevelManager, self).get_queryset().all().order_by("order")


class SponsorBenefit(models.Model):
class Meta:
verbose_name = "후원사 등급 별 혜택"
verbose_name_plural = "후원사 등급 별 혜택 목록"

name = models.CharField(max_length=255, help_text="혜택 이름")
desc = models.TextField(null=True, blank=True, help_text="기타")
unit = models.CharField(max_length=10, help_text="혜택 단위")
is_countable = models.BooleanField(
default=True, help_text="제공 하는 혜택이 셀 수 있는지 여부"
)


class SponsorLevel(models.Model):
class Meta:
verbose_name = "후원사 등급"
Expand All @@ -30,6 +43,10 @@ class Meta:
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

benefits = models.ManyToManyField(
SponsorBenefit, through="BenefitByLevel", related_name="level"
)

objects = SponsorLevelManager()

@property
Expand All @@ -54,22 +71,21 @@ def __str__(self):
return self.name


class SponsorBenefit(models.Model):
class BenefitByLevel(models.Model):
class Meta:
verbose_name = "후원사 등급 별 혜택"
verbose_name_plural = "후원사 등급 별 혜택 목록"
constraints = [
models.UniqueConstraint(
fields=["benefit_id", "level_id"], name="IX_BENEFIT_BY_LEVEL_1"
)
]

name = models.CharField(max_length=255, help_text="혜택 이름")
desc = models.TextField(null=True, blank=True, help_text="기타")
offer = models.PositiveIntegerField(help_text="제공 하는 혜택 개수")
unit = models.CharField(max_length=10, help_text="혜택 단위")
is_countable = models.BooleanField(
default=True, help_text="제공 하는 혜택이 셀 수 있는지 여부"
benefit = models.ForeignKey(
SponsorBenefit, on_delete=models.CASCADE, related_name="benefit_by_level"
)

sponsor_level = models.ForeignKey(
SponsorLevel, related_name="benefits", on_delete=models.CASCADE
level = models.ForeignKey(
SponsorLevel, on_delete=models.CASCADE, related_name="benefit_by_level"
)
offer = models.PositiveIntegerField(help_text="제공 하는 혜택 개수")


def registration_file_upload_to(instance, filename):
Expand Down
47 changes: 29 additions & 18 deletions sponsor/serializers.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
import rest_framework.serializers as serializers
from rest_framework.fields import SerializerMethodField

from sponsor.models import Patron, Sponsor, SponsorLevel, SponsorBenefit
from sponsor.models import Patron, Sponsor, SponsorLevel, SponsorBenefit, BenefitByLevel


class BenefitByLevelSerializer(serializers.ModelSerializer):
benefit_id = serializers.PrimaryKeyRelatedField(
queryset=SponsorBenefit.objects.all(), source="benefit"
)
level_id = serializers.PrimaryKeyRelatedField(
queryset=SponsorLevel.objects.get_queryset(), source="level", write_only=True
)

class Meta:
model = BenefitByLevel
fields = ["benefit_id", "offer", "level_id"]


class SponsorBenefitSerializer(serializers.ModelSerializer):
class Meta:
model = SponsorBenefit
fields = ["name", "desc", "offer", "unit", "is_countable"]
fields = ["id", "name", "desc", "unit", "is_countable"]
read_only_fields = ["id"]


class SponsorBenefitWithOfferSerializer(SponsorBenefitSerializer):
offer = serializers.SerializerMethodField()

class Meta(SponsorBenefitSerializer.Meta):
fields = SponsorBenefitSerializer.Meta.fields + ["offer"]

def get_offer(self, obj):
return obj.benefit_by_level.filter(benefit_id=obj.id).get().offer


class SponsorSerializer(serializers.ModelSerializer):
class Meta:
model = Sponsor
Expand All @@ -36,24 +59,12 @@ class Meta:


class SponsorLevelSerializer(serializers.ModelSerializer):
benefits = SponsorBenefitWithOfferSerializer(many=True, read_only=True)

class Meta:
model = SponsorLevel
fields = [
"name",
"desc",
"visible",
"price",
"limit",
"order",
]
read_only_fields = ["id"]


class SponsorLevelDetailSerializer(SponsorLevelSerializer):
benefits = SponsorBenefitSerializer(many=True)

class Meta(SponsorLevelSerializer.Meta):
fields = [
"id",
"name",
"desc",
"visible",
Expand All @@ -62,7 +73,7 @@ class Meta(SponsorLevelSerializer.Meta):
"order",
"benefits",
]
read_only_fields = ["id", "benefits"]
read_only_fields = ["id"]


class SponsorDetailSerializer(serializers.ModelSerializer):
Expand Down
20 changes: 19 additions & 1 deletion sponsor/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from django.urls import path

from sponsor.viewsets import PatronListViewSet, SponsorViewSet, SponsorLevelViewSet
from sponsor.viewsets import (
PatronListViewSet,
SponsorViewSet,
SponsorLevelViewSet,
SponsorBenefitViewSet,
)

urlpatterns = [
path("list/", SponsorViewSet.as_view({"get": "list"})),
Expand All @@ -22,4 +27,17 @@
{"get": "retrieve", "delete": "destroy", "put": "update"}
),
),
path(
"levels/benefits/",
SponsorLevelViewSet.as_view(
{"post": "assign_benefits", "put": "create_or_update_benefits"}
),
),
path("benefits/", SponsorBenefitViewSet.as_view({"get": "list", "post": "create"})),
path(
"benefits/<int:id>/",
SponsorBenefitViewSet.as_view(
{"get": "retrieve", "delete": "destroy", "put": "update"}
),
),
]
50 changes: 34 additions & 16 deletions sponsor/viewsets.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from typing import Type

from django.db.transaction import atomic

from django.shortcuts import get_object_or_404
from django.db.utils import IntegrityError
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.viewsets import ModelViewSet, ViewSet

from sponsor.models import Patron, Sponsor, SponsorLevel
from sponsor.models import Patron, Sponsor, SponsorLevel, SponsorBenefit, BenefitByLevel
from sponsor.permissions import IsOwnerOrReadOnly, OwnerOnly
from sponsor.serializers import (
PatronListSerializer,
Expand All @@ -15,12 +18,22 @@
SponsorRemainingAccountSerializer,
SponsorSerializer,
SponsorLevelSerializer,
SponsorLevelDetailSerializer,
SponsorBenefitSerializer,
BenefitByLevelSerializer,
)
from sponsor.slack import send_new_sponsor_notification
from sponsor.validators import SponsorValidater


class SponsorBenefitViewSet(ModelViewSet):
lookup_field = "id"
http_method_names = ["get", "post", "put", "delete"]
serializer_class = SponsorBenefitSerializer

def get_queryset(self):
return SponsorBenefit.objects.all()


class SponsorLevelViewSet(ModelViewSet):
lookup_field = "id"
http_method_names = ["get", "post", "put", "delete"]
Expand All @@ -30,26 +43,31 @@ def get_queryset(self):

def get_serializer_class(self):
match self.action:
case "list" | "create":
return SponsorLevelSerializer
case "create_or_update_benefits" | "assign_benefits":
return BenefitByLevelSerializer
case _:
return SponsorLevelDetailSerializer
return SponsorLevelSerializer

def list(self, request, *args, **kwagrs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
@action(detail=False, methods=["POST"])
def assign_benefits(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
serializer.save()
except IntegrityError:
return Response("Already assigned", status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data)

def create(self, request, *args, **kwagrs):
serializer = self.get_serializer(data=request.data)
@action(detail=True, methods=["PUT"])
def create_or_update_benefits(self, request):
level_id = request.data.get("level_id", None)
benefit_id = request.data.get("benefit_id", None)
benefit_by_level = get_object_or_404(
BenefitByLevel, level_id=level_id, benefit_id=benefit_id
)
serializer = self.get_serializer(benefit_by_level, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()

return Response(serializer.data, status=status.HTTP_201_CREATED)

def retrieve(self, request, id, *args, **kwargs):
sponsor_level = self.get_object()
serializer = self.get_serializer(sponsor_level)
return Response(serializer.data)


Expand Down

0 comments on commit eeec32c

Please sign in to comment.