Skip to content

Commit ae38f81

Browse files
committed
Merge branch 'develop' of https://github.com/pirogramming/Moodico into feature/nyc
2 parents c9d7430 + 6aa9557 commit ae38f81

20 files changed

Lines changed: 689 additions & 182 deletions

File tree

moodico/main/admin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
from django.contrib import admin
2+
from .models import RankedProduct, VotingSession
23

34
# Register your models here.
5+
6+
admin.site.register(RankedProduct)
7+
admin.site.register(VotingSession)

moodico/main/management/__init__.py

Whitespace-only changes.

moodico/main/management/commands/__init__.py

Whitespace-only changes.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from django.core.management.base import BaseCommand
2+
from django.utils import timezone
3+
from django.db import transaction
4+
from moodico.main.models import RankedProduct, VotingSession
5+
from moodico.products.utils.common_utils import get_top_liked_products
6+
7+
# 매일 자정, 현재의 랭킹 최상위 두 개 제품으로 투표 세션을 생성하는 함수
8+
class Command(BaseCommand):
9+
10+
def handle(self, *args, **options):
11+
self.stdout.write(f"[{timezone.now()}] 투표 세션 생성 시작")
12+
13+
active_sessions = VotingSession.objects.filter(is_active=True)
14+
if active_sessions.exists():
15+
for session in active_sessions:
16+
session.close_session()
17+
self.stdout.write(self.style.WARNING(f"{len(active_sessions)}개의 기존 활성 세션을 종료했습니다."))
18+
19+
try:
20+
ranked1_data, ranked2_data = get_top_liked_products(2)
21+
except (ValueError, IndexError):
22+
self.stdout.write(self.style.ERROR("상위 2개 제품을 가져오는 데 실패했습니다. 작업을 중단합니다."))
23+
return
24+
25+
# 가져온 데이터로 Product 객체 생성 및 업데이트
26+
product1, created1 = RankedProduct.objects.update_or_create(
27+
product_id=ranked1_data["product_id"],
28+
defaults={
29+
'name': ranked1_data["product_name"],
30+
'brand': ranked1_data["product_brand"],
31+
'price': ranked1_data["product_price"],
32+
'image_url': ranked1_data["product_image"],
33+
'like_count': ranked1_data["like_count"]
34+
}
35+
)
36+
if created1:
37+
self.stdout.write(f"새로운 제품 '{product1.name}'이 랭킹 1위로 투표 대상 객체가 생성")
38+
39+
product2, created2 = RankedProduct.objects.update_or_create(
40+
product_id=ranked2_data["product_id"],
41+
defaults={
42+
'name': ranked2_data["product_name"],
43+
'brand': ranked2_data["product_brand"],
44+
'price': ranked2_data["product_price"],
45+
'image_url': ranked2_data["product_image"],
46+
'like_count': ranked2_data["like_count"]
47+
}
48+
)
49+
if created2:
50+
self.stdout.write(f"새로운 제품 '{product2.name}'이 랭킹 2위로 투표 대상 객체가 생성")
51+
52+
# 투표 세션 생성
53+
new_session = VotingSession.objects.create(
54+
product1=product1,
55+
product2=product2
56+
)
57+
58+
self.stdout.write(self.style.SUCCESS(
59+
f"새로운 투표 세션(ID: {new_session.id})을 생성했습니다: '{product1.name}' vs '{product2.name}'"
60+
))
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Generated by Django 5.2.4 on 2025-08-17 12:07
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
initial = True
10+
11+
dependencies = [
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='RankedProduct',
17+
fields=[
18+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19+
('product_id', models.CharField(db_index=True, max_length=100, unique=True)),
20+
('name', models.CharField(max_length=200)),
21+
('brand', models.CharField(max_length=100)),
22+
('price', models.PositiveIntegerField(default=0)),
23+
('image_url', models.CharField(max_length=500)),
24+
('like_count', models.PositiveIntegerField(default=0)),
25+
('created_at', models.DateTimeField(auto_now_add=True)),
26+
],
27+
),
28+
migrations.CreateModel(
29+
name='VotingSession',
30+
fields=[
31+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
32+
('product1_votes', models.PositiveIntegerField(default=0)),
33+
('product2_votes', models.PositiveIntegerField(default=0)),
34+
('is_active', models.BooleanField(default=True)),
35+
('start_time', models.DateTimeField(auto_now_add=True)),
36+
('end_time', models.DateTimeField(blank=True, null=True)),
37+
('product1', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='product1_sessions', to='main.rankedproduct')),
38+
('product2', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='product2_sessions', to='main.rankedproduct')),
39+
('winner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='won_sessions', to='main.rankedproduct')),
40+
],
41+
),
42+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2.4 on 2025-08-17 15:28
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('main', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='rankedproduct',
15+
name='price',
16+
field=models.CharField(max_length=100, verbose_name='가격'),
17+
),
18+
]
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Generated by Django 5.2.4 on 2025-08-17 18:18
2+
3+
import django.db.models.deletion
4+
from django.conf import settings
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('main', '0002_alter_rankedproduct_price'),
12+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='Vote',
18+
fields=[
19+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20+
('created_at', models.DateTimeField(auto_now_add=True)),
21+
('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.votingsession')),
22+
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
23+
('voted_product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.rankedproduct')),
24+
],
25+
options={
26+
'unique_together': {('session', 'user')},
27+
},
28+
),
29+
]

moodico/main/migrations/__init__.py

Whitespace-only changes.

moodico/main/models.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,59 @@
11
from django.db import models
2+
from django.utils import timezone
3+
from django.conf import settings
24

35
# Create your models here.
6+
class RankedProduct(models.Model):
7+
product_id = models.CharField(max_length=100, unique=True, db_index=True) # 제품 id
8+
name = models.CharField(max_length=200)
9+
brand = models.CharField(max_length=100)
10+
price = models.CharField(max_length=100, verbose_name="가격")
11+
image_url = models.CharField(max_length=500)
12+
like_count = models.PositiveIntegerField(default=0)
13+
created_at = models.DateTimeField(auto_now_add=True)
14+
15+
def __str__(self):
16+
return f"[{self.brand}] {self.name}"
17+
18+
class VotingSession(models.Model):
19+
"""
20+
하나의 투표 세션 - 24시간 간격으로 생성
21+
"""
22+
#session_id =
23+
product1 = models.ForeignKey(RankedProduct, on_delete=models.CASCADE, related_name='product1_sessions')
24+
product2 = models.ForeignKey(RankedProduct, on_delete=models.CASCADE, related_name='product2_sessions')
25+
product1_votes = models.PositiveIntegerField(default=0)
26+
product2_votes = models.PositiveIntegerField(default=0)
27+
is_active = models.BooleanField(default=True)
28+
29+
winner = models.ForeignKey(RankedProduct, on_delete=models.SET_NULL, null=True, blank=True, related_name='won_sessions')
30+
31+
start_time = models.DateTimeField(auto_now_add=True)
32+
end_time = models.DateTimeField(null=True, blank=True)
33+
34+
def __str__(self):
35+
status = "Active" if self.is_active else "Finished"
36+
return f"Session {self.id}: {self.product1.name} vs {self.product2.name} ({status})"
37+
38+
# session is_active false 변경 함수
39+
def close_session(self):
40+
self.end_time = timezone.now()
41+
self.is_active = False
42+
43+
if self.product1_votes > self.product2_votes:
44+
self.winner = self.product1
45+
elif self.product2_votes > self.product1_votes:
46+
self.winner = self.product2
47+
48+
self.save()
49+
50+
# 세션 - 투표 - 사용자를 잇는 투표 모델
51+
class Vote(models.Model):
52+
session = models.ForeignKey(VotingSession, on_delete=models.CASCADE)
53+
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
54+
voted_product = models.ForeignKey(RankedProduct, on_delete=models.CASCADE)
55+
created_at = models.DateTimeField(auto_now_add=True)
56+
57+
class Meta:
58+
# 한 사용자는 한 세션에 하나의 투표만 가능
59+
unique_together = ('session', 'user')

moodico/main/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
urlpatterns = [
55
path('', views.main, name='main'), #메인 페이지
66
path('personalcolor/', views.personalcolor, name='personalcolor'), #퍼스널 컬러 페이지
7+
path('voting_api', views.voting_api, name='voting_api'), # 투표 기능 api
78
]

0 commit comments

Comments
 (0)