Django/DRF

[DRF] Django로 일간 및 주간 조회수 구현하기: 코드 예제

Jong_seoung 2024. 6. 4. 13:12
반응형

단축 url 프로젝트를 진행하면서 조회수 관련해서 통계를 내는 부분이 있었는데, 그 부분에 관련된 예제이다.

 

Models.py

from django.db import models

from core.models import TimeStampedModel


class ShortURL(TimeStampedModel):
    user = models.ForeignKey("users.User", related_name="user_url", on_delete=models.CASCADE)
    origin_url = models.URLField(verbose_name="원본 url")
    short_url = models.CharField(max_length=100, verbose_name="단축 url")
    request_count = models.IntegerField(default=0, verbose_name="요청 횟수")
    expiration_date = models.DateTimeField(null=True, blank=True, verbose_name="만료일")
    is_active = models.BooleanField(default=True, verbose_name="활성화 여부")

    class Meta:
        db_table = "short_url"


class DailyCount(models.Model):
    short_url = models.ForeignKey(ShortURL, related_name="daily_counts", on_delete=models.CASCADE)
    date = models.DateField(verbose_name="날짜")
    count = models.IntegerField(default=1, verbose_name="요청 횟수")

    class Meta:
        db_table = "daily_count"
        unique_together = ("short_url", "date")
        indexes = [models.Index(fields=["short_url", "date"])]

원래 존재하던 ShortURL모델과 관계를 맺는 DailyCount라는 모델을 생성하였다.

 

unique_together: 여러 개의 필드 조합이 함께 유일함을 유지하기 위해서 short_url과 date필드의 조합으로 지정해 주었다. 즉, 각각의 필드에 유니크 옵션을 주는 것이 아니라 url정보와 날짜의 조합에 유니크 옵션을 주었다는 이야기이다.

 

indexs: 데이터 베이스에서 특정 필드를 빠르게 검색하기 위한 인덱스이다. short_url과 date필드를 복합 인덱스로 사용하므로써 검색 속도를 올릴 수 있도록 하였다.

 

ViewSet

class ShortenerViewSet(
    CreateModelMixin,
    RetrieveModelMixin,
    UpdateModelMixin,
    DestroyModelMixin,
    MappingViewSetMixin,
    SimpleJWTMixin,
    GenericViewSet,
):
    serializer_action_map = {
	...
        "statistics": ShortenerCreateSerializer,
    }
    queryset = ShortURL.objects.filter(is_active=True)
    lookup_field = "short_url"

   ...

    def statistics(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)

        res = {
            "origin_url": instance.origin_url,
            "request_count": instance.request_count,
            "daily_count": serializer.get_daily_count(instance),
            "weekly_count": serializer.get_weekly_count(instance),
        }
        return Response(res)

viewset는 다른 일반적인 viewset과 차이가 없이 구현하였다. 조금의 차이점이라면 serializer.data가 아니라 원하는 데이터를 추출하여 res 딕셔너리에 넣어서 전송해 주었다는 점이다.

 

serializers.py

    def get_weekly_count(self, instance):
        today = timezone.now().date()
        seven_days_ago = today - timedelta(days=7)
        counts = DailyCount.objects.filter(short_url=instance, date__gte=seven_days_ago)

        weekly_count = sum(count.count for count in counts)
        return weekly_count

    def get_daily_count(self, instance):
        today = timezone.now().date()
        seven_days_ago = today - timedelta(days=7)
        counts = DailyCount.objects.filter(short_url=instance, date__gte=seven_days_ago)

        result = {count.date.isoformat(): count.count for count in counts}
        return result
        
    def plus_daily_count(self, instance):
        today = timezone.now().date()
        try:
            daily_count = DailyCount.objects.get(short_url=instance, date=today)
            daily_count.count += 1
            daily_count.save()
        except DailyCount.DoesNotExist:
            DailyCount.objects.create(short_url=instance, date=today)

get_weekly_count: 주간 조회수를 계산하는 메서드로, 현재 날짜를 시준으로 7일전 부터 오늘까지의 일간 조회수를 가져와 합산하여 주간 조회수를 반환한다.

 

get_daily_count: 일간 조회수를 가지고와 날짜와 조회수를 딕셔너리 형태로 반환한다.

 

plus_daily_count: 조회시 일간 조회수를 증가시킨다. 현재 날짜에 해당하는 DailyCount가 이미 존재하면 조회수를 증가시키고 그렇지 않으면 새로운 객체를 생성하여 조회수를 1로 초기화한다.

반응형