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
19 changes: 18 additions & 1 deletion src/activity_gallery/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from public.tag_models import ActivityTag, Tag

from .models import ActionPhoto, ActivityAction
from .models import ActionPhoto, ActivityAction, ActivityHistory


class ActivityTagInline(admin.TabularInline):
Expand Down Expand Up @@ -63,3 +63,20 @@ def delete_queryset(self, request, queryset):
# Admin에서 여러 객체 삭제 시 호출됨
for obj in queryset:
obj.delete()


@admin.register(ActivityHistory)
class ActivityHistoryAdmin(admin.ModelAdmin):
"""활동 히스토리 관리자 페이지"""

list_display = ("title", "activity_date")
search_fields = ("title", "content", "activity_date")

def content_preview(self, obj):
"""내용이 길 경우 일부만 미리보기"""
return obj.content[:50] + "..." if len(obj.content) > 50 else obj.content

def delete_queryset(self, request, queryset):
# Admin에서 여러 객체 삭제 시 호출됨
for obj in queryset:
obj.delete()
43 changes: 43 additions & 0 deletions src/activity_gallery/migrations/0008_activityhistory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 5.1.5 on 2025-06-26 15:40

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("activity_gallery", "0007_alter_actionphoto_image_and_more"),
]

operations = [
migrations.CreateModel(
name="ActivityHistory",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("is_deleted", models.BooleanField(default=False, verbose_name="숨김 여부")),
("title", models.CharField(max_length=255, verbose_name="활동명")),
("content", models.TextField(verbose_name="내용")),
(
"thumbnail",
models.ImageField(
blank=True,
null=True,
upload_to="activity/history/thumbnail/",
verbose_name="활동 썸네일 이미지",
),
),
("activity_date", models.DateField(verbose_name="활동 날짜")),
("created_at", models.DateTimeField(auto_now_add=True, verbose_name="생성일")),
("updated_at", models.DateTimeField(auto_now=True, verbose_name="수정일")),
],
options={
"verbose_name": "활동 히스토리",
"verbose_name_plural": "활동 히스토리",
"ordering": ("activity_date",),
},
),
]
20 changes: 20 additions & 0 deletions src/activity_gallery/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.db import models

from core.mixins.models import SoftDeleteModel
from public.mixin.img_models import MultiImageFieldMixin
from public.tag_models import ActivityTag, Tag

Expand Down Expand Up @@ -39,3 +40,22 @@ class ActionPhoto(MultiImageFieldMixin):

def __str__(self):
return f"Photo for {self.activity_action.title if self.activity_action else 'No Activity'}"


class ActivityHistory(SoftDeleteModel):
title = models.CharField(max_length=255, verbose_name="활동명")
content = models.TextField(verbose_name="내용")
thumbnail = models.ImageField(
"활동 썸네일 이미지", upload_to="activity/history/thumbnail/", null=True, blank=True
)
activity_date = models.DateField(verbose_name="활동 날짜")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="생성일")
updated_at = models.DateTimeField(auto_now=True, verbose_name="수정일")

class Meta:
verbose_name = "활동 히스토리"
verbose_name_plural = "활동 히스토리"
ordering = ("activity_date",)

def __str__(self):
return f"History for {self.title} at {self.activity_date}"
13 changes: 12 additions & 1 deletion src/activity_gallery/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from public.serializers import TagSerializer
from public.tag_models import ActivityTag

from .models import ActionPhoto, ActivityAction
from .models import ActionPhoto, ActivityAction, ActivityHistory


# 액션과 연결된 사진 정보를 직렬화하는 Serializer
Expand Down Expand Up @@ -44,3 +44,14 @@ class Meta:
"tags",
"photos",
] # 상세 조회 시 모든 정보 포함


class ActivityHistorySerializer(serializers.ModelSerializer):
class Meta:
model = ActivityHistory
fields = [
"id",
"title",
"content",
"activity_date",
]
55 changes: 55 additions & 0 deletions src/activity_gallery/swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,58 @@ def retrieve(cls):
description="특정 커뮤니티 활동 상세 조회",
responses=responses,
)


class ActivityHistoryAPIDocs(SwaggerSchema):
"""활동 히스토리 API 문서"""

sample_activity_list = [
{
"id": 1,
"title": "활동 제목 1",
"content": "월드 와이드 파이콘",
"activity_date": "2024-01-01",
},
{
"id": 2,
"title": "활동 제목 2",
"content": "월드 와이드 파이콘2",
"activity_date": "2024-06-01",
},
]

@classmethod
def list(cls):
"""활동 히스토리 목록 조회 문서"""
responses = {
"성공": OpenApiResponse(
response=ListSuccessResponseSerializer,
description="활동 히스토리 목록 조회 성공",
examples=[
OpenApiExample(
name="활동 히스토리 목록 조회",
value={
"status": "SUCCESS",
"data": cls.sample_activity_list,
"error": None,
"pagination": {"count": 2, "next": None, "previous": None},
},
),
OpenApiExample(
name="활동 히스토리 목록 조회 (데이터 없음)",
value={
"status": "SUCCESS",
"data": [],
"error": None,
"pagination": {"count": 0, "next": None, "previous": None},
},
),
],
),
"에러": OpenApiResponse(response=ErrorResponseSerializer, description="응답 에러"),
}
return cls.generate_schema(
operation_id="activity_action_list",
description="모든 활동 히스토리 목록 조회",
responses=responses,
)
1 change: 0 additions & 1 deletion src/activity_gallery/tests.py

This file was deleted.

174 changes: 174 additions & 0 deletions src/activity_gallery/tests/test_activity_history_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import pytest
from django.db.utils import DatabaseError

from activity_gallery.models import ActivityHistory


@pytest.mark.django_db
@pytest.mark.feature
@pytest.mark.parametrize(
"title, content, activity_date",
[
("Activity 1", "Content 1", "2024-01-01"),
("Activity 2", "Content 2", "2024-06-01"),
("Activity 3", "Content 3", "2023-12-31"),
],
)
def test_create_activity_history_success_given_valid_data(title, content, activity_date):
# Given
data = {
"title": title,
"content": content,
"activity_date": activity_date,
}
# When
history = ActivityHistory.objects.create(**data)
# Then
assert history.title == title
assert history.content == content
assert str(history.activity_date) == activity_date


@pytest.mark.django_db
@pytest.mark.feature
@pytest.mark.parametrize(
"title, content, activity_date",
[
(None, "Content", "2024-01-01"),
("Title", None, "2024-01-01"),
("Title", "Content", None),
(None, None, None),
],
)
def test_create_activity_history_fail_given_invalid_data(title, content, activity_date):
# Given
data = {
"title": title,
"content": content,
"activity_date": activity_date,
}
# Then
with pytest.raises((DatabaseError, ValueError, TypeError)):
# When
ActivityHistory.objects.create(**data)


@pytest.mark.django_db
@pytest.mark.feature
@pytest.mark.parametrize(
"title, content, activity_date",
[
("Activity 1", "Content 1", "2024-01-01"),
("Activity 2", "Content 2", "2024-06-01"),
],
)
def test_read_activity_history_given_exist_id(title, content, activity_date):
# Given
history = ActivityHistory.objects.create(
title=title, content=content, activity_date=activity_date
)
# When
fetched = ActivityHistory.objects.get(id=history.id)
# Then
assert fetched.title == title
assert fetched.content == content
assert str(fetched.activity_date) == activity_date


@pytest.mark.django_db
@pytest.mark.feature
@pytest.mark.parametrize(
"history_id",
[-1, 0],
)
def test_read_activity_history_given_non_exist_id(history_id):
# Given
# When / Then
with pytest.raises(ActivityHistory.DoesNotExist):
ActivityHistory.objects.get(id=history_id)


@pytest.mark.django_db
@pytest.mark.feature
@pytest.mark.parametrize(
"title, content, activity_date, new_title, new_content, new_activity_date",
[
("Activity 1", "Content 1", "2024-01-01", "Updated 1", "Updated Content 1", "2024-02-01"),
("Activity 2", "Content 2", "2024-06-01", "Updated 2", "Updated Content 2", "2024-07-01"),
],
)
def test_update_activity_history_success_given_valid_data(
title, content, activity_date, new_title, new_content, new_activity_date
):
# Given
history = ActivityHistory.objects.create(
title=title, content=content, activity_date=activity_date
)
# When
history.title = new_title
history.content = new_content
history.activity_date = new_activity_date
history.save()
updated = ActivityHistory.objects.get(id=history.id)
# Then
assert updated.title == new_title
assert updated.content == new_content
assert str(updated.activity_date) == new_activity_date


@pytest.mark.django_db
@pytest.mark.feature
@pytest.mark.parametrize(
"title, content, activity_date, new_title, new_content, new_activity_date",
[
("Activity 1", "Content 1", "2024-01-01", None, "Updated Content", "2024-02-01"),
("Activity 2", "Content 2", "2024-06-01", "Updated", None, "2024-07-01"),
("Activity 3", "Content 3", "2024-06-01", "Updated", "Updated Content", None),
],
)
def test_update_activity_history_fail_given_invalid_data(
title, content, activity_date, new_title, new_content, new_activity_date
):
# Given
history = ActivityHistory.objects.create(
title=title, content=content, activity_date=activity_date
)
# When
history.title = new_title
history.content = new_content
history.activity_date = new_activity_date
# Then
with pytest.raises((DatabaseError, ValueError, TypeError)):
history.save()


@pytest.mark.django_db
@pytest.mark.feature
def test_delete_activity_history_hard_delete():
# Given
history = ActivityHistory.objects.create(
title="To Delete", content="Delete me", activity_date="2024-01-01"
)
history_id = history.id
# When
# If using soft delete, use hard delete method; otherwise, keep as is
history.delete(force_delete=True)
# Then
with pytest.raises(ActivityHistory.DoesNotExist):
ActivityHistory.objects.get(id=history_id)


@pytest.mark.django_db
@pytest.mark.feature
def test_delete_activity_history_when_soft_delete_operation() -> None:
# Given
history = ActivityHistory.objects.create(
title="To Delete", content="Delete me", activity_date="2024-01-01"
)

# When
history.delete()

# Then
deleted = ActivityHistory.objects.get(id=history.id)
assert deleted.is_deleted is True
Loading