중간 정리
지금까지 강의를 들었던 내용들에 대해 중간 정리를 하겠다.
크게 배운 내용은 아래 항목과 같다.
- [x] JWT Authentication
- [x] Login (JWT)
- [x] Create Account
- [x] See Profile
- [x] Edit Profile
- [x] Add/Room From Favourites
- [x] List Rooms
- [x] See Room
- [x] Create Room
- [x] Edit Room
- [x] Delete Room
- [x] Search Rooms
아직 viewset을 통해, 코드를 더욱 간결하게 만드는 부분은 하지 않았다. 이부분은 다음 문서에 작성 해보자!
각 앱 or 파일 별로 쭉 내용을 적은 후, 각 코드들을 분석하는 식으로 내용을 정리 해보자 ~ ^^ 얏호!
config
1. config / settings.py
DJANGO_APPS는 기본 값이고, PROJECT_APPS는 사용자가 만든 APPS 를 추가해주어야 한다. 그리고 REST_FRAMEWORK 설치 후 THIRD_PARTY_APPS부분에 추가
DJANGO_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
PROJECT_APPS = [
"core.apps.CoreConfig",
"users.apps.UsersConfig",
"rooms.apps.RoomsConfig",
]
THIRD_PARTY_APPS = [
"rest_framework",
]
INSTALLED_APPS = DJANGO_APPS + PROJECT_APPS + THIRD_PARTY_APPS
-Panigation 셋팅 -> rooms/views.py 에 활용됨
-그 아래 두개는 JWT (Jason Web Token) 및 인증관련 부분에 활용이 됨.
Session ~ 부분이 없으면 로그인이 불가능함.
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
"DEFAULT_AUTHENTICATION_CLASSES": [
"config.authentication.JWTAuthentication",
"rest_framework.authentication.SessionAuthentication",
],
}
2. config / authentication.py
바로 위에, settings 부분에 설정한 JWTauthentication 부분의 세부 내용이다.
로그인 시, POST로 encode된 token을 서버에 보낸다. 이 암호화된 token을 decode 해주어야 하는데, 이 decode하는 과정에 대한 코드 내용들이다.
일반적으로, xjwt token을 서버로 보내주게 된다. 이때 token 값을 아래에서는 jwt_token이라는 변수명에 담았고, 이를 decode하는 과정이다. 아래 encode하는 부분이 설명을 할텐데, eocde할때는 username과 password 값만 서버에 보내며, encode값은 user.pk로 해줄 것이기 때문에, decode시 user의 pk값을 얻어 낼 수 있다. 그 후 얻어낸 user.pk값에 실제 모든 User객체들 중 동일한 User.pk 객체를 찾은 후 해당 user의 객체를 return해 주는 프로세스 이다.
import jwt
from django.conf import settings
from rest_framework import authentication
from users.models import User
class JWTAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
try:
token = request.META.get("HTTP_AUTHORIZATION")
if token is None:
return None
xjwt, jwt_token = token.split(" ")
decoded = jwt.decode(jwt_token, settings.SECRET_KEY, algorithms=["HS256"])
pk = decoded.get("pk")
user = User.objects.get(pk=pk)
return (user, None)
except (ValueError, jwt.exceptions.DecodeError, User.DoesNotExist):
return None
3. config/urls.py
url는 크게, 1) admin 2) rooms 3) users로 나뉜다.
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
path("api/v1/rooms/", include("rooms.urls")),
path("api/v1/users/", include("users.urls")),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
rooms
1. rooms/urls.py
1) rooms - 전체 방 리스트를 볼 수 있음.
2) rooms/search/ - url 부분에 내가 원하는 조건의 값을 넣으면 해당 조건에 맞는 방들만 조회되서 출력됨
(rooms/search/?proce=30&beds=1 이런식으로)
3)rooms/<int:pk> - 각 방 객체에 맞는 pk 값을 url에 값에 넣어면 해당 방의 값(세부사항)을 볼 수 있음.
from django.urls import path
from . import views
app_name = "rooms"
urlpatterns = [
path("", views.RoomsView.as_view()),
path("search/", views.room_search),
path("<int:pk>/", views.RoomView.as_view()),
]
2. rooms/models.py
from django.db import models
from core.models import CoreModel
class Room(CoreModel):
name = models.CharField(max_length=140)
address = models.CharField(max_length=140)
price = models.IntegerField(help_text="USD per night")
beds = models.IntegerField(default=1)
lat = models.DecimalField(max_digits=10, decimal_places=6)
lng = models.DecimalField(max_digits=10, decimal_places=6)
bedrooms = models.IntegerField(default=1)
bathrooms = models.IntegerField(default=1)
check_in = models.TimeField(default="00:00:00")
check_out = models.TimeField(default="00:00:00")
instant_book = models.BooleanField(default=False)
user = models.ForeignKey(
"users.User", on_delete=models.CASCADE, related_name="rooms"
)
def __str__(self):
return self.name
def photo_number(self):
return self.photos.count()
photo_number.short_description = "Photo Count"
class Meta:
ordering = ["-pk"]
class Photo(CoreModel):
file = models.ImageField()
room = models.ForeignKey(
"rooms.Room", related_name="photos", on_delete=models.CASCADE
)
caption = models.CharField(max_length=140)
def __str__(self):
return self.room.name
3. rooms/views.py
RoomsView에는 get(Read) 와 post(Create)의 기능 두개가 있다.
각 기능을 어떻게 구현했는지 코딩 흐름을 눈으로 따라가 보자.
Roomview에는 get, put(Update), delete의 기능 3개가 있다.
room_search (함수) 는 get 기능만 있다.
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.pagination import PageNumberPagination
from .models import Room
from .serializers import RoomSerializer
class OwnPagination(PageNumberPagination):
page_size = 20
class RoomsView(APIView):
def get(self, request):
paginator = OwnPagination()
rooms = Room.objects.all()
results = paginator.paginate_queryset(rooms, request)
serializer = RoomSerializer(results, many=True, context={"request": request})
return paginator.get_paginated_response(serializer.data)
def post(self, request):
if not request.user.is_authenticated:
return Response(status=status.HTTP_401_UNAUTHORIZED)
serializer = RoomSerializer(data=request.data)
if serializer.is_valid():
room = serializer.save(user=request.user)
room_serializer = RoomSerializer(room).data
return Response(data=room_serializer, status=status.HTTP_200_OK)
else:
return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class RoomView(APIView):
def get_room(self, pk):
try:
room = Room.objects.get(pk=pk)
return room
except Room.DoesNotExist:
return None
def get(self, request, pk):
room = self.get_room(pk)
if room is not None:
serializer = RoomSerializer(room).data
return Response(serializer)
else:
return Response(status=status.HTTP_404_NOT_FOUND)
def put(self, request, pk):
room = self.get_room(pk)
if room is not None:
if room.user != request.user:
return Response(status=status.HTTP_403_FORBIDDEN)
serializer = RoomSerializer(room, data=request.data, partial=True)
if serializer.is_valid():
room = serializer.save()
return Response(RoomSerializer(room).data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response()
else:
return Response(status=status.HTTP_404_NOT_FOUND)
def delete(self, request, pk):
room = self.get_room(pk)
if room is not None:
if room.user != request.user:
return Response(status=status.HTTP_403_FORBIDDEN)
room.delete()
return Response(status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_404_NOT_FOUND)
@api_view(["GET"])
def room_search(request):
max_price = request.GET.get("max_price", None)
min_price = request.GET.get("min_price", None)
beds = request.GET.get("beds", None)
bedrooms = request.GET.get("bedrooms", None)
bathrooms = request.GET.get("bathrooms", None)
lat = request.GET.get("lat", None)
lng = request.GET.get("lng", None)
filter_kwargs = {}
if max_price is not None:
filter_kwargs["price__lte"] = max_price
if min_price is not None:
filter_kwargs["price__gte"] = min_price
if beds is not None:
filter_kwargs["beds__gte"] = beds
if bedrooms is not None:
filter_kwargs["bedrooms__gte"] = bedrooms
if bathrooms is not None:
filter_kwargs["bathrooms__gte"] = bathrooms
if lat is not None and lng is not None:
filter_kwargs["lat_gte"] = float(lat) - 0.005
filter_kwargs["lat_lte"] = float(lat) + 0.005
filter_kwargs["lng_gte"] = float(lng) - 0.005
filter_kwargs["lng_lte"] = float(lng) + 0.005
paginator = OwnPagination()
try:
rooms = Room.objects.filter(**filter_kwargs)
except ValueError:
rooms = Room.objects.all()
results = paginator.paginate_queryset(rooms, request)
serializer = RoomSerializer(results, many=True)
return paginator.get_paginated_response(serializer.data)
4. rooms/serializers.py
from rest_framework import serializers
from users.serializers import UserSerializer
from .models import Room
class RoomSerializer(serializers.ModelSerializer):
user = UserSerializer()
is_fav = serializers.SerializerMethodField()
class Meta:
model = Room
exclude = ("modified",)
read_only_fields = ("user", "id", "created", "updated")
def validate(self, data):
if self.instance:
check_in = data.get("check_in", self.instance.check_in)
check_out = data.get("check_out", self.instance.check_out)
else:
check_in = data.get("check_in")
check_out = data.get("check_out")
if check_in == check_out:
raise serializers.ValidationError("Not enough time between changes")
return data
def get_is_fav(self, obj):
request = self.context.get("request")
if request:
user = request.user
if user.is_authenticated:
return obj in user.favs.all()
return False
users
1. users/urls.py
1) users/ - user를 등록(회원가입) 하는 기능
2) users/token/ - user로그인 기능 (token 발급)
3) users/me - user 본인 data 확인
4) users/me/favs/ - user가 즐겨찾기 한 방들 확인 및 수정 기능 (등록은 X)
5) users/<int:pk>/ - users pk값을 통해 각 user 세부정보 확인
from django.urls import path
from . import views
app_name = "users"
urlpatterns = [
path("", views.UsersView.as_view()),
path("token/", views.login),
path("me/", views.MeView.as_view()),
path("me/favs/", views.FavsView.as_view()),
path("<int:pk>/", views.user_detail),
]
2. users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
avatar = models.ImageField(upload_to="avatars", blank=True)
superhost = models.BooleanField(default=False)
favs = models.ManyToManyField("rooms.Room", related_name="favs")
def room_count(self):
return self.rooms.count()
room_count.short_description = "Room Count"
3. users/views.py
import jwt
from django.conf import settings
from django.contrib.auth import authenticate
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import api_view
from rest_framework.views import APIView
from rest_framework import status
from rooms.serializers import RoomSerializer
from rooms.models import Room
from .models import User
from .serializers import UserSerializer
class UsersView(APIView):
def post(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
new_user = serializer.save()
return Response(UserSerializer(new_user).data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class MeView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
if request.user.is_authenticated:
return Response(UserSerializer(request.user).data)
def put(self, request):
serializer = UserSerializer(request.user, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response()
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class FavsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
user = request.user
serializer = RoomSerializer(user.favs.all(), many=True).data
return Response(serializer)
def put(self, request):
pk = request.data.get("pk", None)
user = request.user
if pk is not None:
try:
room = Room.objects.get(pk=pk)
if room in user.favs.all():
user.favs.remove(room)
else:
user.favs.add(room)
return Response()
except Room.DoesNotExist:
pass
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
@api_view(["GET"])
def user_detail(request, pk):
try:
user = User.objects.get(pk=pk)
return Response(UserSerializer(user).data)
except User.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
@api_view(["POST"])
def login(request):
username = request.data.get("username")
password = request.data.get("password")
if not username or not password:
return Response(status=status.HTTP_400_BAD_REQUEST)
user = authenticate(username=username, password=password)
if user is not None:
encoded_jwt = jwt.encode(
{"pk": user.pk}, settings.SECRET_KEY, algorithm="HS256"
)
return Response(data={"token": encoded_jwt})
else:
return Response(status=status.HTTP_401_UNAUTHORIZED)