Skip to content

Commit 3ed3b89

Browse files
authored
Merge: 상품 페이지 캐싱, 성능 테스트
[Feature/product-caching] 상품 페이지 캐싱, 성능 테스트
2 parents 3a062a3 + 24f3d5b commit 3ed3b89

16 files changed

+306
-113
lines changed

Makefile

+4
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ exec: ## enter the container
134134
locust: ## start locust runserver
135135
$(LCST) -f $(a)
136136

137+
.PHONY: loctime
138+
loctime:
139+
$(LCST) -f $(a) --run-time $(m)
140+
137141
.PHONY: setlocust
138142
setlocust: ## make user, chatroom for locust test
139143
$(PY) $(MNG) $(CTCD)

apps/product/admin.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@admin.register(Product)
1313
class ProductAdmin(admin.ModelAdmin[Product]):
1414
list_display = [
15+
"uuid",
1516
"name",
1617
"lender",
1718
"condition",
@@ -25,8 +26,8 @@ class ProductAdmin(admin.ModelAdmin[Product]):
2526
# "rental_history",
2627
]
2728
list_filter = [
28-
# "product_category",
29-
# "style_category",
29+
"product_category",
30+
"styles",
3031
"status",
3132
"created_at",
3233
"updated_at",

apps/product/tests.py

+225-92
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,26 @@
1-
# from django.test import TestCase
2-
# from django.urls import reverse
3-
# from mypyc.irbuild.builder import IRBuilder
4-
# from mypyc.irbuild.builder.IRBuilder import self
5-
# from rest_framework import status
6-
# from rest_framework.test import APIClient
7-
#
8-
# from apps.product.models import Product
91
from datetime import timedelta
102

3+
from django.contrib.auth.models import AnonymousUser
4+
from django.core.files.uploadedfile import SimpleUploadedFile
5+
from django.test import TestCase
116
from django.urls import reverse
127
from django.utils import timezone
138
from rest_framework import status
14-
from rest_framework.test import APITestCase
9+
from rest_framework.test import (
10+
APIClient,
11+
APIRequestFactory,
12+
APITestCase,
13+
force_authenticate,
14+
)
1515
from rest_framework_simplejwt.tokens import AccessToken
1616

1717
from apps.category.models import Category, Style
18-
from apps.product.models import Product, RentalHistory
18+
from apps.product.models import Product, ProductImage, RentalHistory
19+
from apps.product.permissions import IsLenderOrReadOnly
20+
from apps.product.serializers import ProductImageSerializer, ProductSerializer
21+
from apps.product.views import ProductViewSet
1922
from apps.user.models import Account
2023

21-
# class ProductListAPITest(TestCase):
22-
# def setUp(self):
23-
# self.client = APIClient()
24-
# self.product1 = Product.objects.create(
25-
# name='Product 1',
26-
# user_id=1,
27-
# brand='Brand 1',
28-
# condition='Good',
29-
# purchasing_price=100000,
30-
# rental_fee=5000,
31-
# size='L',
32-
# views=100,
33-
# product_category_id=1,
34-
# style_category_id=1,
35-
# status=True
36-
# )
37-
# self.product2 = Product.objects.create(
38-
# name='Product 2',
39-
# user_id=2,
40-
# brand='Brand 2',
41-
# condition='Bad',
42-
# purchasing_price=80000,
43-
# rental_fee=4000,
44-
# size='S',
45-
# views=0,
46-
# product_category_id=2,
47-
# style_category_id=2,
48-
# status=False
49-
# )
50-
#
51-
# # API 호출
52-
#
53-
# response = self.client.get('/api/products/')
54-
#
55-
# # 응답 확인
56-
# self.assertEqual(response.status_code, status.HTTP_200_OK)
57-
#
58-
# # 응답 데이터의 길이가 2인지 확인 (2개의 상품이 반환되어야 함)
59-
# self.assertEqual(len(response.data), 2)
60-
#
61-
# # 상품의 모든 필드들을 확인
62-
# self.assertEqual(response.data[0]['name'], 'Product 1')
63-
# self.assertEqual(response.data[0]['user'], 1)
64-
# self.assertEqual(response.data[0]['brand'], 'Brand 1')
65-
# self.assertEqual(response.data[0]['condition'], 'New')
66-
# self.assertEqual(response.data[0]['purchasing_price'], 10000)
67-
# self.assertEqual(response.data[0]['rental_fee'], 5000)
68-
# self.assertEqual(response.data[0]['size'], 'M')
69-
# self.assertEqual(response.data[0]['views'], 0)
70-
# self.assertEqual(response.data[0]['product_category_id'], 1)
71-
# self.assertEqual(response.data[0]['style_category_id'], 1)
72-
# self.assertEqual(response.data[0]['status'], True)
73-
#
74-
# self.assertEqual(response.data[1]['name'], 'Product 2')
75-
# self.assertEqual(response.data[1]['user'], 2)
76-
# self.assertEqual(response.data[1]['brand'], 'Brand 2')
77-
# self.assertEqual(response.data[1]['condition'], 'Used')
78-
# self.assertEqual(response.data[1]['purchasing_price'], 8000)
79-
# self.assertEqual(response.data[1]['rental_fee'], 4000)
80-
# self.assertEqual(response.data[1]['size'], 'L')
81-
# self.assertEqual(response.data[1]['views'], 0)
82-
# self.assertEqual(response.data[1]['product_category_id'], 2)
83-
# self.assertEqual(response.data[1]['style_category_id'], 2)
84-
# self.assertEqual(response.data[1]['status'], True)
85-
# def test_product_list_api_view(self):
86-
# url = reverse("product_list")
87-
# response = self.client.get(url)
88-
# self.assertEqual(response.status_code, status.HTTP_200_OK)
89-
# self.assertEqual(len(response.data), Product.objects.count())
90-
#
91-
# def test_product_create_api_view(self):
92-
# url = reverse("product_create")
93-
# response = self.client.post(url, self.product_data, format="json")
94-
# self.assertEqual(response.status_code, status.HTTP_201_CREATED)
95-
#
96-
# def test_product_detail_api_view(self):
97-
# url = reverse("product_detail", args=[self.product.pk])
98-
# response = self.client.get(url)
99-
# self.assertEqual(response.status_code, status.HTTP_200_OK)
100-
# #내가 셋업에 만든 상품이랑 이 뷰에서 조회 하고자 하는 상품의 데이터가 일치 하는 지를 확인해야함
101-
#
102-
10324

10425
class RentalHistoryTestBase(APITestCase):
10526
def setUp(self) -> None:
@@ -270,3 +191,215 @@ def test_대여날짜_변경_테스트(self) -> None:
270191
print(response.data)
271192
self.assertEqual(response.status_code, status.HTTP_200_OK)
272193
self.assertEqual(response.data.get("return_date").split("T")[0], data["return_date"].date().isoformat())
194+
195+
196+
class ProductModelTest(TestCase):
197+
def setUp(self) -> None:
198+
self.user = Account.objects.create_user(email="[email protected]", password="password")
199+
self.category = Category.objects.create(name="category1")
200+
self.style = Style.objects.create(name="style1")
201+
self.data = {
202+
"name": "product1",
203+
"lender": self.user,
204+
"brand": "brand",
205+
"condition": "condition",
206+
"purchase_date": "2024-05-01",
207+
"purchase_price": 100000,
208+
"rental_fee": 10000,
209+
"size": "m",
210+
"product_category": self.category,
211+
"amount": 1,
212+
"region": "Seoul",
213+
}
214+
self.product = Product.objects.create(**self.data)
215+
self.product.styles.add(self.style)
216+
217+
def test_create_product(self) -> None:
218+
self.assertEqual(self.product.name, self.data["name"])
219+
self.assertEqual(self.product.lender, self.data["lender"])
220+
self.assertEqual(self.product.brand, self.data["brand"])
221+
self.assertEqual(self.product.condition, self.data["condition"])
222+
self.assertEqual(self.product.purchase_date, self.data["purchase_date"])
223+
self.assertEqual(self.product.purchase_price, self.data["purchase_price"])
224+
self.assertEqual(self.product.rental_fee, self.data["rental_fee"])
225+
self.assertEqual(self.product.size, self.data["size"])
226+
self.assertEqual(self.product.product_category, self.data["product_category"])
227+
self.assertTrue(self.product.status)
228+
self.assertEqual(self.product.amount, self.data["amount"])
229+
self.assertEqual(self.product.region, self.data["region"])
230+
self.assertIn(self.style, self.product.styles.all())
231+
232+
def test_create_product_image(self) -> None:
233+
image = SimpleUploadedFile("test_product_image.jpg", b"content", content_type="image/jpeg")
234+
product_image = ProductImage.objects.create(product=self.product, image=image)
235+
self.assertEqual(product_image.product, self.product)
236+
self.assertIsNotNone(product_image.get_image_url())
237+
238+
239+
class ProductSerializerTest(TestCase):
240+
def setUp(self) -> None:
241+
self.user = Account.objects.create_user(email="[email protected]", password="password")
242+
self.category = Category.objects.create(name="category1")
243+
self.style = Style.objects.create(name="style1")
244+
data = {
245+
"name": "product1",
246+
"lender": self.user,
247+
"brand": "brand",
248+
"condition": "condition",
249+
"purchase_date": "2024-05-01",
250+
"purchase_price": 100000,
251+
"rental_fee": 10000,
252+
"size": "m",
253+
"product_category": self.category,
254+
"amount": 1,
255+
"region": "Seoul",
256+
}
257+
self.product = Product.objects.create(**data)
258+
self.product.styles.add(self.style)
259+
self.factory = APIRequestFactory()
260+
261+
def test_product_serializer(self) -> None:
262+
request = self.factory.get("/api/products/")
263+
request.user = self.user
264+
serializer = ProductSerializer(instance=self.product, context={"request": request})
265+
data = serializer.data
266+
267+
self.assertEqual(data["name"], self.product.name)
268+
self.assertEqual(data["lender"]["email"], self.user.email)
269+
self.assertEqual(data["brand"], self.product.brand)
270+
self.assertEqual(data["condition"], self.product.condition)
271+
self.assertEqual(data["purchase_date"], self.product.purchase_date)
272+
self.assertEqual(data["purchase_price"], self.product.purchase_price)
273+
self.assertEqual(data["rental_fee"], self.product.rental_fee)
274+
self.assertEqual(data["size"], self.product.size)
275+
self.assertEqual(data["product_category"], self.product.product_category.name)
276+
self.assertEqual(data["status"], self.product.status)
277+
self.assertEqual(data["amount"], self.product.amount)
278+
self.assertEqual(data["region"], self.product.region)
279+
280+
281+
class ProductImageSerializerTest(TestCase):
282+
def setUp(self) -> None:
283+
self.user = Account.objects.create_user(email="[email protected]", password="password")
284+
self.category = Category.objects.create(name="category1")
285+
self.style = Style.objects.create(name="style1")
286+
data = {
287+
"name": "product1",
288+
"lender": self.user,
289+
"brand": "brand",
290+
"condition": "condition",
291+
"purchase_date": "2024-05-01",
292+
"purchase_price": 100000,
293+
"rental_fee": 10000,
294+
"size": "m",
295+
"product_category": self.category,
296+
"amount": 1,
297+
"region": "Seoul",
298+
}
299+
self.product = Product.objects.create(**data)
300+
self.product.styles.add(self.style)
301+
self.image = SimpleUploadedFile("test_product_image.jpg", b"content", content_type="image/jpg")
302+
303+
def test_product_image_serializer(self) -> None:
304+
image = ProductImage.objects.create(product=self.product, image=self.image)
305+
serializer = ProductImageSerializer(image)
306+
data = serializer.data
307+
self.assertEqual(data["id"], image.id)
308+
self.assertIsNotNone(data["image"])
309+
310+
311+
class ProductViewSetTest(APITestCase):
312+
def setUp(self) -> None:
313+
self.client = APIClient()
314+
self.user = Account.objects.create_user(email="[email protected]", password="password")
315+
self.client.force_authenticate(user=self.user)
316+
self.category = Category.objects.create(name="category1")
317+
self.style = Style.objects.create(name="style1")
318+
self.data = {
319+
"name": "product1",
320+
"lender": self.user,
321+
"brand": "brand",
322+
"condition": "condition",
323+
"purchase_date": "2024-05-01",
324+
"purchase_price": 100000,
325+
"rental_fee": 10000,
326+
"size": "m",
327+
"product_category": self.category,
328+
"amount": 1,
329+
"region": "Seoul",
330+
}
331+
332+
def test_list_products(self) -> None:
333+
Product.objects.create(**self.data)
334+
res = self.client.get(reverse("product-list"))
335+
self.assertEqual(res.status_code, status.HTTP_200_OK)
336+
self.assertEqual(res.data.get("count"), 1)
337+
338+
def test_create_product(self) -> None:
339+
url = reverse("product-list")
340+
res = self.client.post(url, data=self.data)
341+
self.assertEqual(res.status_code, status.HTTP_201_CREATED)
342+
self.assertEqual(Product.objects.count(), 1)
343+
self.assertEqual(Product.objects.get().name, self.data["name"])
344+
345+
def test_update_product(self) -> None:
346+
product = Product.objects.create(**self.data)
347+
url = reverse("product-detail", kwargs={"pk": product.pk})
348+
update_data = {"name": "product2"}
349+
res = self.client.patch(url, update_data)
350+
self.assertEqual(res.status_code, status.HTTP_200_OK)
351+
self.assertEqual(Product.objects.get().name, update_data["name"])
352+
353+
def test_delete_product(self) -> None:
354+
product = Product.objects.create(**self.data)
355+
url = reverse("product-detail", kwargs={"pk": product.pk})
356+
res = self.client.delete(url)
357+
self.assertEqual(res.status_code, status.HTTP_204_NO_CONTENT)
358+
self.assertEqual(Product.objects.count(), 0)
359+
360+
361+
class ProductPermissionTest(TestCase):
362+
def setUp(self) -> None:
363+
self.user1 = Account.objects.create_user(email="[email protected]", password="password", nickname="user1")
364+
self.user2 = Account.objects.create_user(email="[email protected]", password="password", nickname="user2")
365+
self.product = Product.objects.create(
366+
name="product1",
367+
lender=self.user1,
368+
brand="brand",
369+
condition="new",
370+
purchase_date="2022-01-01",
371+
purchase_price=100000,
372+
rental_fee=5000,
373+
size="M",
374+
product_category=Category.objects.create(name="category1"),
375+
status=True,
376+
amount=1,
377+
region="Seoul",
378+
)
379+
self.factory = APIRequestFactory()
380+
381+
def test_permission_as_lender(self) -> None:
382+
request = self.factory.put(f"/api/product/{self.product.pk}/")
383+
force_authenticate(request, user=self.user1)
384+
request.user = self.user1
385+
view = ProductViewSet()
386+
permission = IsLenderOrReadOnly()
387+
self.assertTrue(permission.has_permission(request, view))
388+
self.assertTrue(permission.has_object_permission(request, view, self.product))
389+
390+
def test_permission_as_other_user(self) -> None:
391+
request = self.factory.put(f"/api/product/{self.product.pk}/")
392+
force_authenticate(request, user=self.user2)
393+
request.user = self.user2
394+
view = ProductViewSet()
395+
permission = IsLenderOrReadOnly()
396+
self.assertTrue(permission.has_permission(request, view))
397+
self.assertFalse(permission.has_object_permission(request, view, self.product))
398+
399+
def test_permission_as_unauthenticated(self) -> None:
400+
request = self.factory.put(f"/api/product/{self.product.pk}/")
401+
request.user = AnonymousUser()
402+
view = ProductViewSet()
403+
permission = IsLenderOrReadOnly()
404+
self.assertTrue(permission.has_permission(request, view))
405+
self.assertFalse(permission.has_object_permission(request, view, self.product))

apps/product/utils.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import Any
2+
3+
from django.core.cache import cache
4+
from django.db.models import QuerySet
5+
6+
7+
def get_or_set_cache(key: str, queryset: QuerySet[Any], timeout: int) -> QuerySet[Any] | None:
8+
return cache.get_or_set(key, queryset, timeout)
9+
10+
11+
def get_cache(key: str) -> Any:
12+
return cache.get(key)
13+
14+
15+
def set_cache(key: str, value: QuerySet[Any], timeout: int) -> None:
16+
cache.set(key, value, timeout)
17+
18+
19+
def clear_cache(key: str) -> None:
20+
cache.delete(key)

0 commit comments

Comments
 (0)