DRF(Django REST Framework) ๋ž€?

2023. 8. 23. 15:12ยทpython/Django
728x90

๐Ÿ’กDRF๋ž€?

  • Django ๊ธฐ๋ฐ˜ REST API ์„œ๋ฒ„ ๊ตฌ์ถ•์„ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

Django์™€ ๋‹ค๋ฅด๊ฒŒ ํ”„๋ก ํŠธ์—”๋“œ๋ฅผ ๊ฐœ๋ฐœํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— Model๊ณผ View ๊ทธ๋ฆฌ๊ณ  Serializer๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. DRF์˜ ๊ตฌ์กฐ์— ๋Œ€ํ•ด์„œ ์ง€์นญํ•˜๋Š” ํŒจํ„ด์€ ์—†์Šต๋‹ˆ๋‹ค. ๋ฐฑ์—”๋“œ์˜ ์šฉ๋„๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— Template์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹ , JSONํŒŒ์ผ์˜ ํ˜•์‹์œผ๋กœ ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์Šต๋‹ˆ๋‹ค.

  • Django์™€์˜ ์ฐจ์ด์ 

  • DRF ๋กœ์ง

 

๐Ÿง—๐ŸปDRF ๋ชจ๋“ˆ ์„ค๋ช…

DRF ๋ชจ๋“ˆ ์„ค๋ช…

models.py

  • ๋ชจ๋ธ ์ •์˜
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ์ถ”์ƒํ™”ํ•œ ๊ฐœ๋…
  • Django ORM์„ ํ†ตํ•ด ํŒŒ์ด์ฌ ๋ฌธ๋ฒ•์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ
  • Django์—์„œ ๋ฐ์ดํ„ฐ๋Š” JSON์ด๋‚˜ ์œ ์‚ฌํ•œ ํฌ๋งท์ด ์•„๋‹Œ, ํŒŒ์ด์ฌ ๊ฐ์ฒด์˜ ํ˜•ํƒœ๋กœ ์ €์žฅ๋จ
  • class Product(models.Model): """ ์ œํ’ˆ ์ •๋ณด ๋ชจ๋ธ """ name = models.CharField(max_length=255) image = ImageField(upload_to='photos') price = models.IntegerField() quantity = models.IntegerField(default=0) description = models.TextField() pub_date = models.DateTimeField(auto_now_add = True) def __str__(self): return '{} {}'.format(self.name, self.pub_date)

views.py

  • request๋ฅผ ๋ฐ›์•„ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•œ ๋’ค response๋ฅผ ๋ฐ˜ํ™˜
  • ๋ชจ๋ธ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ / ์ƒ์„ฑ / validation์€ serializer๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๊ตฌํ˜„B. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋ณต์žกํ•ด์ง€๋ฉด, ๋ณ„๋„ ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์—ฌ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
    • view์— ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ์ ์œผ๋ฉด ํ•ด๋‹น API์˜ ๊ธฐ๋Šฅ์„ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
    • ํ•˜์œ„ ํ•จ์ˆ˜๋“ค์„ ๋ณ„๋„๋กœ unitํ…Œ์ŠคํŠธ ํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„ ํŒŒ์ผ ๋ฐ ํ•จ์ˆ˜๋กœ ๊ตฌํ˜„
  • A. ๊ฐ„๋‹จํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ view์—์„œ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
# A ์˜ˆ์‹œ
class UserViewSet(viewsets.ModelViewSet):

    serializer_class = UserSerializer(data=request.data)
		def post(self, request):
		        serializer = self.serializer_class(data=request.data)
		
		        if serializer.is_valid(raise_exception=False):
							serializer.save()
							user = serializer.get_object()
							# ๊ฐ„๋‹จํ•œ ๋กœ์ง๋“ค.
							send_push_notification(user, '00 ์„œ๋น„์Šค์— ์˜ค์‹ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค')
							return Response(serializer.data, 200)
				    return Response('error', 400)
# B ์˜ˆ์‹œ
class UserViewSet(viewsets.ModelViewSet):

    serializer_class = UserSerializer(data=request.data)
		def post(self, request):
		        serializer = self.serializer_class(data=request.data)
		
		        if serializer.is_valid(raise_exception=False):
							serializer.save()
							user = serializer.get_object()
							# ๋ณต์žกํ•œ ๋กœ์ง๋“ค
							# ex) ์–ด๋“œ๋ฏผ ์œ ์ €๋ฉด, 00 ๋กœ์ง/ ์ผ๋ฐ˜์œ ์ €๋ฉด 00 ๋กœ์ง / ์ผ๋ฐ˜์œ ์ € ์ค‘ ์•ฑ์œผ๋กœ ๊ฐ€์ž…ํ•˜๋ฉด Push ์•Œ๋ฆผ, ์›น์œผ๋กœ ๊ฐ€์ž…ํ•˜๋ฉด SMS ๋“ฑ๋“ฑ 
							if user.has_role('SYSYEM_ADMIN'):
									# do_something_here
							elif ...:
									# do_something_here
							elif ...:
									# do_something_here
							elif ...:
									# do_something_here
							# do_something_here
							return Response(serializer.data, 200)
				    return Response('error', 400)
# B ๋ณต์žกํ•œ ๋กœ์ง๋“ค์„ ๋ณ„๋„๋กœ ๊ตฌํ˜„ํ•œ ์˜ˆ์‹œ
from user.service import UserService
# ๋˜๋Š”
from user.utils import userUtils

class UserViewSet(viewsets.ModelViewSet):

    serializer_class = UserSerializer(data=request.data)
		def post(self, request):
		        serializer = self.serializer_class(data=request.data)
		
		        if serializer.is_valid(raise_exception=False):
							serializer.save()
							user = serializer.get_object()

							service = UserService(user)				
							service.check_and_set_default_role()
							service.send_notification_by_role()

							return Response(serializer.data, 200)
				    return Response('error', 400)
# ์œ„์˜ UserService ๋˜๋Š” UserUtils ์˜ˆ์‹œ

class UserService:
		user = None
		DEFAULT_ROLE = "NORMAL_USER"

		def __init__(self, user):
				self.user = user

		def check_and_set_default_role(self):
				# if self.roles == [] ๋กค์ด ์—†์œผ๋ฉด ๋””ํดํŠธ ๋กค์„ ๋„ฃ์–ด์ค€๋‹ค.
				...
		
		def send_notification_by_role(self):
				# ex) ์–ด๋“œ๋ฏผ ์œ ์ €๋ฉด, 00 ๋กœ์ง/ ์ผ๋ฐ˜์œ ์ €๋ฉด 00 ๋กœ์ง / ์ผ๋ฐ˜์œ ์ € ์ค‘ ์•ฑ์œผ๋กœ ๊ฐ€์ž…ํ•˜๋ฉด Push ์•Œ๋ฆผ, ์›น์œผ๋กœ ๊ฐ€์ž…ํ•˜๋ฉด SMS ๋“ฑ๋“ฑ 
				# ...
				# ํŒ์œผ๋กœ๋Š”, ๋กค ๊ตฌ๋ถ„ํ•˜๋Š”๊ฑด ์—ฌ๊ธฐ์„œ ํ•˜๊ฒ ์ง€๋งŒ ๋ฉ”์„ธ์ง€ ๋ณด๋‚ด๋Š” ๊ธฐ๋Šฅ์€ MessageService ์—์„œ ๊ฐ€์ ธ๋‹ค ์”๋‹ˆ๋‹ค.
				if ...:
						MessageService.send_push_notification(user, 'ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค!')
				elif ...:
						MessageService.send_SMS(user, 'ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค!')

serializers.py

  • ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” ์—ญํ•  ์ˆ˜ํ–‰
    • ๋ชจ๋ธ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ์ถ”์ƒํ™”ํ•œ ๊ฐœ๋…์ด๋ฉฐ, ํŒŒ์ด์ฌ์˜ ๊ฐ์ฒด ํ˜•์‹์œผ๋กœ ์ €์žฅ์ด ๋ฉ๋‹ˆ๋‹ค.
    • ์šฐ๋ฆฌ๋Š” ํ”„๋ก ํŠธ์—”๋“œ์™€ JSON ํ˜•์‹์œผ๋กœ ์ •๋ณด๋ฅผ ์ฃผ๊ณ  ๋ฐ›๋Š”๋ฐ ์ง๋ ฌํ™” ๊ณผ์ •์„ ์ƒ๋žตํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๊ฒŒ ๋œ๋‹ค๋ฉด ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์ฃผ๊ฒŒ๋˜๊ณ  ํด๋ผ์ด์–ธํŠธ๋Š” ์ด๋ฅผ ์ฝ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.
    • ํŒŒ์ด์ฌ ๋ฐ์ดํ„ฐ ๊ฐ์ฒด๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์ž‘์—…์„ ์ง๋ ฌํ™”(Serialize)๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.
    • ๋ฐ˜๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋ณด๋‚ด์ฃผ๋Š” ๋ฐ์ดํ„ฐ ์—ญ์‹œ ํŒŒ์ด์ฌ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ด์•ผํ•˜๋Š”๋ฐ ์ด๋ฅผ ์—ญ์ง๋ ฌํ™”(Deserialize)๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.DRF ๋‚ด์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๋•Œ Django์˜ ๋ชจ๋ธ์„ ํ†ตํ•ด ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    • ์ผ๋ฐ˜์ ์ธ serializer ์‚ฌ์šฉ๋ฒ•
      1. validation
        1. ์ „์ฒด validation
        2. ํ•„๋“œ validation - ๊ฐ ํ•„๋“œ๋ณ„๋กœ validation์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
        3. validation ๊ณผ์ •์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ •์ œํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.
      2. fields
        • ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์ง€์ •ํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค.
      3. Field ํ•จ์ˆ˜๋“ค
        1. ReadOnlyField - ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์ฐพ๊ธฐ(Join)
        2. SerializerMethodField - ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ๋งŒ๋“ค๊ธฐ
        3. ๋“ฑ๋“ฑ

class UserSerializer(serializers.ModelSerializer):
		is_system_admin = serializers.SerializerMethodField()
		company_name = serializers.ReadOnlyField(source='company.name', read_only=True)
		# ์œ„์ฒ˜๋Ÿผ ์ •์˜ํ•˜๋ฉด , user.company.name ์„ company_name ์— ๋‹ด์•„์ค€๋‹ค.
		
		class Meta:
				model = User
				# 2. fields,
				# tip, List API ์ฒ˜๋Ÿผ ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ์—๋Š” ๋ฌด์กฐ๊ฑด '__all__' ํ•˜๊ธฐ๋ณด๋‹ค ๋”ฑ ์‚ฌ์šฉํ•˜๋Š” ํ•„๋“œ๋งŒ ์“ฐ๋Š”๊ฒŒ์ข‹์Œ.
				# SELECT * ๋ณด๋‹ค SELECT first_name, .. ์ด ํšจ์œจ์ ์ด๊ธฐ์—. ๋ฐ์ดํ„ฐ ํฌ๊ธฐ๋„ ๊ทธ๋ ‡๊ณ .

				fields = [
					"last_name",
					"first_name",
					"full_name", # Model ์ชฝ @property๋Š” ์ด๋ ‡๊ฒŒ ๋ฐ”๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ!
					"is_system_admin",
					"company_name"
				]
		# SerializerMethodField ๋Š” ํ•„๋“œ๋ช… ์•ž์— get_ ๋ถ™์—ฌ์„œ ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„, ์›ํ•˜๋Š” ๊ฐ’์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ.
		def get_is_system_admin(self, obj):
				return obj.has_role('SYSTEM_USER') # ์—ฌ๊ธฐ์„œ๋Š” Model function์„ ์ผ์ง€๋งŒ, ์›ํ•˜๋Š”๋Œ€๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ.
		
		# 1-a. ์ „์ฒด
		def validate(self, data):
		     if data['fisrt_name'] == data['last_name']:
		        raise serializers.ValidationError("fisrt_name ๊ณผ last_name ์ด ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ฐ’ ํ™•์ธํ•˜์„ธ์š”.")

				# 1-c, ๋งŒ์•ฝ full_name ์ด ๋ชจ๋ธ ํ•„๋“œ์˜€๋‹ค๋ฉด, ์—ฌ๊ธฐ์„œ ๋งŒ๋“ค์–ด๋„ ๋ฉ๋‹ˆ๋‹ค.(์ด๊ฑด ์ •์„์ธ์ง€๋Š” ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ, ๊ฐ€๋” ์‚ฌ์šฉ)
				if not data.get('full_name'):
						data['full_name'] = data['last_name'] + data['first_name']

		    return data
		    
		# 1-b. ํ•„๋“œ๋ณ„
		# ํ•„๋“œ๋ช… ์•ž์— validate_ ๋ถ™์ด๋ฉด ์ž๋™์œผ๋กœ ๋™์ž‘, ์•„๋ž˜๋Š” first_name ์ฒดํฌํ•˜๋Š” ๋กœ์ง.
		def validate_fist_name(self, value):
		    if len(value) < 10:
		        raise serializers.ValidationError("first_name should be less than 10 characters")

				# 1-c, ๋งŒ์•ฝ first_name์— ๊ณต๋ฐฑ์„ ์ œ์™ธํ•˜๋Š” ๊ทœ์น™์ด๋ผ๋ฉด, ์—ฌ๊ธฐ์„œ ์ฒ˜๋ฆฌํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค(์ด๊ฑด ์ •์„์ธ์ง€๋Š” ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ, ๊ฐ€๋” ์‚ฌ์šฉ)
				value = value.replace(" ", "")
				return value

urls.py

  • ํ”„๋กœ์ ํŠธ์™€ ์•ฑ์˜ url ์ฃผ์†Œ๋ฅผ ๋“ฑ๋กํ•˜๋Š” ํŒŒ์ผ
  • as_views๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์™€ router๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
# as_view ์‚ฌ์šฉ
from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers

snippet_list = SnippetViewSet.as_view({
    'get': 'list',
    'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})
snippet_highlight = SnippetViewSet.as_view({
    'get': 'highlight'
}, renderer_classes=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
    'get': 'list'
})
user_detail = UserViewSet.as_view({
    'get': 'retrieve'
})

# format_suffix_patterns์ด ํ•„์ˆ˜๋Š” ์•„๋‹™๋‹ˆ๋‹ค. ์ œ๊ฑฐํ•˜๊ณ  ๋ฆฌ์ŠคํŠธ ํ˜•์‹์œผ๋กœ๋งŒ ๋„˜๊ฒจ๋„ ์ข‹์Šต๋‹ˆ๋‹ค.
urlpatterns = format_suffix_patterns([
    path('', api_root),
    path('snippets/', snippet_list, name='snippet-list'),
    path('snippets/<int:pk>/', snippet_detail, name='snippet-detail'),
    path('snippets/<int:pk>/highlight/', snippet_highlight, name='snippet-highlight'),
    path('users/', user_list, name='user-list'),
    path('users/<int:pk>/', user_detail, name='user-detail')
])
# router ์‚ฌ์šฉ
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from snippets import views

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet,basename="snippet")
router.register(r'users', views.UserViewSet,basename="user")

# The API URLs are now determined automatically by the router.
urlpatterns = [
    path('', include(router.urls)),
]

๊ฐœ๋ฐœ ์ˆœ์„œ

  • models → serializers → views → urls ์ˆœ์„œ๋Œ€๋กœ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.
  • ์•„๋ž˜ ๊ทธ๋ฆผ์€ DRF์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“ˆ์„ ๋‚˜์—ดํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

DRF ์‹ฌํ™”๊ฐœ๋…

1. User ๋ชจ๋ธ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

  • User ๋ชจ๋ธ์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•˜๋Š” ์ด์œ 
    • ํ™•์žฅ - ์ œ๊ณต๋˜๋Š” ๊ธฐ๋Šฅ ์™ธ์˜ ๊ธฐ๋Šฅ์„ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•ด์„œ
    • ๋ณ€๊ฒฝ - ์ œ๊ณต๋˜๋Š” ๊ธฐ๋Šฅ์„ ๋‚ด๊ฐ€ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•ด์„œ
  • ์•ž์„œ ์„ค๋ช…ํ•œ ๋‚ด์šฉ๋Œ€๋กœ User ๋ชจ๋ธ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธ ํ•˜๊ธฐ ์œ„ํ•œ ์ˆ˜๋‹จ์œผ๋กœ id๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ์ด ์žˆ๊ณ , ์ด๋ฉ”์ผ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ์ด ์žˆ๋“ฏ์ด ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ชจ๋ธ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฝ”๋“œ๋Š” ์ œ๊ณต๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์Šค์Šค๋กœ ๋ชจ๋ธ์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•ด์„œ ๊ณผ์ œ๋ฅผ ํ’€์–ด๋ณด์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.
# permissions.py
class IsOwnerOrReadOnly(BasePermission):

    """
    ์ž‘์„ฑ์ž ์™ธ ์ฝ๊ธฐ ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌ
    JWT ํ† ํฐ์„ decode, obj์˜ user์™€ ํ•ด๋‹น user๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ
    ERR03
    """
    
    message = "[Access Denied: ERR03] ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •, ์‚ญ์ œ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค."
    

    def has_object_permission(self, request, view, obj):
    
        if request.method in SAFE_METHODS:
            return True
            
        else:
            token = request.headers.get('Authorization').split(" ")[1]
            payload = jwt.decode(token, settings.SECRET_KEY, algorithms=settings.ALGORITHM)
            token_user = payload.get('user_id')

            return obj.user.id == token_user

2. Permission, Test, Statistic, Logging

  • ๊ถŒํ•œ(Permission) - Github
    • ์šฐ๋ฆฌ๋Š” ์•ฑ์„ ๋งŒ๋“ค ๋•Œ ๊ถŒํ•œ์„ ์ œ์–ดํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
    • DRF์—์„œ๋Š” ๊ถŒํ•œ์— ๊ด€ํ•œ ์—ฌ๋Ÿฌ ํด๋ž˜์Šค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
    • BasePermission์„ ์ƒ์†๋ฐ›์•„ ๊ถŒํ•œ ์„ค์ •์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์•„๋ž˜์—๋Š” jwt๋ฅผ HTTP ์š”์ฒญ ํ—ค๋”์— Authorization์œผ๋กœ ๋„ฃ์—ˆ์„ ๋•Œ, Owner๋งŒ ์ฝ์„ ์ˆ˜ ์žˆ๋„๋กํ•˜๋Š” ์ปค์Šคํ…€ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ(Test)
    • TDD(Test Driven Development) - ํ…Œ์ŠคํŠธ ์ฃผ๋„ ํ•˜ ๊ฐœ๋ฐœ์ด๋ผ๋Š” ๋œป์œผ๋กœ ๊ฐœ๋ฐœ์— ์žˆ์–ด ๊ทธ ์ง„ํ–‰์„ ๋‹ด๋‹นํ•˜๋Š” ์ฃผ์ฒด๋ฅผ ํ…Œ์ŠคํŠธ์— ๋‘๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
    • ํ…Œ์ŠคํŠธ์˜ ์ข…๋ฅ˜๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ์œผ๋‚˜, ๋‚ด์šฉ์ด ๋ฐฉ๋Œ€ํ•˜์—ฌ “์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ ๋ฐฉ๋ฒ•๋ก ”์ด๋ผ๋Š” ์ฃผ์ œ๋กœ ์ง์ ‘ ๊ฒ€์ƒ‰ํ•˜์—ฌ ๋ณด์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.
    • ์•„๋ž˜์˜ ํ…Œ์ŠคํŠธ๋Š” django ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ์ถ”๊ฐ€์ ์œผ๋กœ DRF์˜ ํ…Œ์ŠคํŠธ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์‹ถ์œผ์‹œ๋‹ค๋ฉด DRF Testing์„ ํ™•์ธํ•ด์ฃผ์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.
    • TDD ํ”„๋กœ์„ธ์Šค
      1. ๊ตฌํ˜„ํ•˜๋ ค๋Š” ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.
      2. ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ณ , ๊ธฐ๋Šฅ์ด ์—†์œผ๋‹ˆ ์‹คํŒจํ•œ๋‹ค.
      3. ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์†Œํ•œ์˜ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ๋‹ค.
      4. ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ณ , ํ†ต๊ณผ์‹œํ‚ค๋ฉด ์ฝ”๋“œ๋ฅผ ์ •๋ฆฌํ•œ๋‹ค.
      5. ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๋•Œ๊นŒ์ง€ ์ด๋ฅผ ๋ฐ˜๋ณตํ•œ๋‹ค.
    • ์˜ˆ์‹œ
      • ๋ชจ๋ธ์— ๊ด€ํ•œ ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค. ์ž‘์„ฑ์ด ๋๋‚˜๋ฉด ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
      • ์•„๋ฌด๊ฒƒ๋„ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด์ œ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋ธ์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
      from django.db import models
      
      class Book(models.Model):
          title = models.CharField(max_length=128)
          author = models.CharField(max_length=128)
      
      • migration์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
      $ python manage.py makemigrations
      $ python manage.py migrate
      $ python manage.py test
      
      • view ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
      # tests.py
      from django.test import TestCase
      from .models import Book
      
      from rest_framework.test import APIClient
      from rest_framework import status
      
      class ModelTest(TestCase):
          def setUp(self):
              self.book_title = "My Book"
              self.book_author = "Hyeonsoo"
              self.book = Book(title=self.book_title, author=self.book_author)
      
          def test_model_can_create_a_bucketlist(self):
              old_count = Book.objects.count()
              self.book.save()
              new_count = Book.objects.count()
              self.assertNotEqual(old_count, new_count)
      
      class ViewTest(TestCase):
          def setUp(self):
              self.client = APIClient()
              self.book_data = {'title': 'My Book 2', 'author': 'Hyeonsoo'}
              self.response = self.client.post('/api/books/',
                                               self.book_data,
                                               format="json")
      
          def test_api_can_create_a_book(self):
              print(self.response.content)
              self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)
      
      • ์—ญ์‹œ๋‚˜ ์•„๋ฌด๊ฒƒ๋„ ์ž‘์„ฑ๋˜์–ด ์žˆ์ง€ ์•Š์•„์„œ ํ†ต๊ณผํ•  ์ˆ˜ ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด์ œ ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
      # serializers.py
      from rest_framework import serializers
      from .models import Book
      
      class BookSerializer(serializers.ModelSerializer):
          class Meta:
              model = Book
              fields = ('id', 'title', 'author')
      
      # views.py
      from rest_framework import generics
      from .serializers import BookSerializer
      from .models import Book
      
      class CreateView(generics.CreateAPIView):
          queryset = Book.objects.all()
          serializer_class = BookSerializer
      
          def perform_create(self, serializer):
              serializer.save()
      
      # urls.py
      from django.urls import path
      from .views import CreateView
      
      urlpatterns = [
          path('books/', CreateView.as_view()),
      ]
      
  • ํ†ต๊ณ„(Statistic)
    • ์šฐ๋ฆฌ๋Š” ORM์„ ์‚ฌ์šฉํ•˜์—ฌ Database์™€ ์—ฐ๋™ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑ, ์ˆ˜์ •, ์กฐํšŒ, ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.
    • DB์— ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํŒŒ์ด์ฌ์—์„œ ์ฟผ๋ฆฌ๋ฌธ์„ ์ž‘์„ฑํ•˜์—ฌ ๋ฐ์ดํ„ฐ์˜ ํ†ต๊ณ„๋ฅผ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์˜ˆ์‹œ
    # views.py
    class UserGenderStatisticsView(APIView):
        """
        ์œ ์ €์˜ ๋‚จ๋…€ ์ˆ˜๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
        """
        def get(self, request):
            male_cnt = User.objects.filter(gender="Male").count()
            female_cnt = User.objects.filter(gender="Female").count()
            return Response({"male_count": male_cnt, "female_count": female_cnt}, status=status.HTTP_200_OK)
    
    class NoticeBoardGenderStatisticsView(APIView):
        """
        ๊ณต์ง€์‚ฌํ•ญ ๊ฒŒ์‹œํŒ์—์„œ ๋‚จ๋…€์˜ ๋ถ„ํฌ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
        """
        def get(self, request):
            male_cnt = NoticeBoard.objects.filter(user__gender="Male").count()
            female_cnt = NoticeBoard.objects.filter(user__gender="Female").count()
            return Response({"male_count": male_cnt, "female_count": female_cnt}, status=status.HTTP_200_OK)
    
  • ๋กœ๊ทธ(Logging)
    • ๋กœ๊ทธ ๋ฐ์ดํ„ฐ๋Š” ๊ธฐ๋ก์˜ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. IT์ธํ”„๋ผ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ชจ๋“  ์ƒํ™ฉ์˜ ๋ฐ์ดํ„ฐ๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์‚ฌ๊ณ ๋‚˜ ์žฅ์• ๊ฐ€ ๋ฐœ์ƒ ์‹œ, ์›์ธ์„ ํŒŒ์•…ํ•˜๊ณ  ๋Œ€์ฒ˜ํ•  ์ˆ˜ ์žˆ๋Š” ๊ทผ๊ฑฐ๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ค‘์š”ํ•˜๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋˜ํ•œ ๊ธฐ๋ก๋œ ๊ณ ๊ฐ์˜ ๋กœ๊ทธ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ฑฐ๋‚˜ ML ๋ชจ๋ธ์— ํ™œ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๊ด€๋ จ ๋ชจ๋“ˆ
      • drf-tracking
      • # views.py from rest_framework import generics from rest_framework.response import Response from rest_framework_tracking.mixins import LoggingMixin class SampleView(LoggingMixin): def get(self, request): return Response('with logging')
      • django-request-logging
        # loggings.py
        import logging
        
        logger = logging.getLogger(__name__)
        
        def some_view(request):
            ...
            if some_risky_state:
                logger.warning('Platform is running at risk')
        
      • Logging in JSON formatter ๋ ˆํผ๋Ÿฐ์Šค
        • velebit AI Tech Blog
        • ์ฒด๊ณ„์ ์ธ Python Logging Blog
        • pypi python-json-logger
        • Logging HOWTO ๊ณต์‹๋ฌธ์„œ
        • python-json-logger Github
        • Django+json-log-formatter
      • Logging ์ตœ์ข… ๋ชฉ์ ์˜ ํ˜•ํƒœ
        • JSON ํ˜•์‹

3. ๊ถŒํ•œ ์„ค์ •๋œ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

  • JWT Signin(๋˜๋Š” Login) ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ๋ถ€์—ฌ๋ฐ›์€ ACCESS_KEY๋ฅผ HTTP ํ—ค๋”์— Authorization์ด๋ผ๋Š” KEY ๊ฐ’์„ ์ถ”๊ฐ€ํ•œ ํ›„ VALUE์—๋Š” Bearer {ํ‚ค๊ฐ’} ์˜ ํ˜•ํƒœ๋กœ ๋„ฃ์–ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
    • ์—ฌ๊ธฐ์„œ {}๋ฅผ ์ œ๊ฑฐํ•œ ํ˜•ํƒœ๋กœ ํ‚ค๊ฐ’๋งŒ ์ž…๋ ฅํ•ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

DRF ์„ค์น˜ ๋ฐ ์ดˆ๊ธฐ ์„ค์ •

1. DRF ํŒจ์น˜ํ‚ค๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

$ pip install djangorestframework

 

2. INSTALLED APP์— ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # ์•ฑ์— ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.
]

# DRF setting - ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ],
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
    ]
}

TIME_ZONE = 'Asia/Seoul'
  • REST_FRAMEWORK ๋ผ๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์•ˆ์—์„œ ๋‹ค์–‘ํ•œ ์„ค์ •์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋ ˆํผ๋Ÿฐ์Šค
728x90
๋ฐ˜์‘ํ˜•

'python > Django' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

DoesNotExist vs MultipleObjectsReturned - ORM ์˜ˆ์™ธ์ฒ˜๋ฆฌ  (0) 2025.05.08
OperationalError ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• โ€“ DB ์ปค๋„ฅ์…˜ ๋Š๊น€ (MySQL)  (0) 2025.05.08
transaction.atomic() ํŠธ๋ž™์žญ์…˜ ์ฒ˜๋ฆฌํ•˜๊ธฐ  (0) 2025.04.30
ValidationError โ€“ API/Form ๊ฒ€์ฆ  (0) 2025.04.28
IntegrityError โ€“ ๋Œ€๋Ÿ‰ ์ €์žฅ ์ค‘ ์ถฉ๋Œ ํ•ด๊ฒฐ  (0) 2025.04.28
'python/Django' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • OperationalError ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• – DB ์ปค๋„ฅ์…˜ ๋Š๊น€ (MySQL)
  • transaction.atomic() ํŠธ๋ž™์žญ์…˜ ์ฒ˜๋ฆฌํ•˜๊ธฐ
  • ValidationError – API/Form ๊ฒ€์ฆ
  • IntegrityError – ๋Œ€๋Ÿ‰ ์ €์žฅ ์ค‘ ์ถฉ๋Œ ํ•ด๊ฒฐ
Balang
Balang
์Œ์•… ์ „๊ณต์ƒ์˜ ๊ฐœ๋ฐœ์ผ์ง€
  • Balang
    Balang
    Balang
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
  • ๋ฐ˜์‘ํ˜•
    • All Post (146)
      • python (45)
        • selenium (4)
        • algorithm (9)
        • Django (6)
        • Pandas | Numpy (22)
      • SQL (9)
      • Data Engineer (29)
      • Data Scientist (3)
      • Data Analysis (9)
      • Computer Science (35)
      • Why? (15)
      • ๋งˆ์Œ๊ฐ€์ง (1)
  • ์ธ๊ธฐ ๊ธ€

  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • 250x250
  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
Balang
DRF(Django REST Framework) ๋ž€?
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”