오늘의 코드 [2022.10.28]
중요해서 반드시 기억할 필요가 있거나, 주의해야 할 코드를 답안 형식으로 주석(저의 코멘트)과 함께 정리해 보았습니다.
Q. Django Rest Framework 강의
프론트 화면 작성 시, index.js 에서
console.log("자바스크립트 불러왔음!")
window.onload = async function loadArticles() {
const response = await fetch('http://127.0.0.1:8000/articles/', {
method: 'GET'
})
response_json = await resonse.json()
console.log(response_json)
}
이렇게 코드를 작성해서 html웹브라우저 devtool의 console 창을 확인하면 다음과 같은 오류가 뜬다.
(오류내용)
Access to fetch at 'http://127.0.0.1:8000/articles/' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
GET http://127.0.0.1:8000/articles/ net::ERR_FAILED 200 index.js:4
Uncaught (in promise) TypeError: Failed to fetch at loadArticles (index.js:4:28) index.js:4
-> CORS는 요청을 보낸 origin(도메인 주소, 포트)이 다른 경우 보안상의 이유로 허용을 해주어야 하는 것을 의미한다. (해당 과정은 다음 출처를 참고할 것)
출처 : https://pypi.org/project/django-cors-headers/
setttings.py 에 CORS를 허용하고자 하는 URL을 함께 추가해주면 된다. (수업에서는 세번째 방식을 사용하였다.)
· CORS_ALLOWED_ORIGINS
· CORS_ALLOWED_ORIGIN_REGEXES
· CORS_ALLOW_ALL_ORIGINS
처음엔 CORS_ALLOWED_ORIGINS should be a sequence of strings 라는 에러가 계속 발생해서 원인을 한참 찾았는데, 보시다시피 위에 두 개는 ALLOWED인 반면 마지막 세 번째만 ALLOW로 표기된 것을 확인할 수 있는데, 타이핑하는 과정에서 CORS_ALLOWED_ORIGINS 라고 쳐서 그렇게 나왔다. (정말 사소한 실수인데 원인을 한참 찾아야 할 수도 있는 부분이라 정말 단순하게 오탈자는 없는지 확인하는 습관도 중요한 것 같다.)
(자바스크립트 코드 부분은 다음과 같이 정리했는데, 아직 JS 기초가 부족하여 좀 더 학습하고 충분히 기초개념 학습한 후에 다시 정리하고자 합니다.)
console.log("자바스크립트 불러왔음!")
window.onload = async function loadArticles() {
const response = await fetch('http://127.0.0.1:8000/articles/', { method: 'GET' })
response_json = await response.json()
console.log(response_json)
const articles = document.getElementById("articles")
response_json.forEach(element => {
console.log(element.title)
const newArticle = document.createElement("div")
newArticle.innerText = element.title
articles.appendChild(newArticle)
});
}
아래부터는 3주차 강의 (drf_project) 에 대한 내용입니다.
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
Users/urls.py에서 TokenObtainPairView와 TokenRefreshView는 simplejwt 라이브러리에서 제공하는 토큰에 대한 정보로 실제로, TokenObtainPairView 는 토큰 생성 뷰, TokenVerifyView 는 토큰 유효성 확인 뷰, TokenRefreshView 는 refresh token으로 access token을 재발급하는 뷰라고 볼 수 있다.
JSON에서 다음과 같이 http://127.0.0.1:8000/users/api/token/ 에 POST 방식으로 입력하면
(올바른 예)
{
"username":"admin",
"password":"1234"
}
{
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey…(생략)
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ……(생략)
}
TokenObtainPairView를 통해 생성된 토큰을 확인할 수 있으며,
(잘못된 예)
{
"username":"admin",
"password":"12345"
}
{
"detail": "No active account found with the given credentials"
}
(다음과 같은 계정 정보에 오류가 있음을 알려준다.)
access token은 만료일이 정해있으므로 만료가 되면, refresh token을 활용하여 재발급 받을 수 있도록 할 수 있다. 토큰은 쿠키와 세션에 비해 보안 효과가 훨씬 높다.
장고 공식문서 (출처 : https://docs.djangoproject.com/en/4.1/topics/auth/customizing/)
Users/models.py (-> myUser를 User로 모두 변경)
from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
class UserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
date_of_birth=date_of_birth,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, date_of_birth, password=None):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
user = self.create_user(
email,
password=password,
date_of_birth=date_of_birth,
)
user.is_admin = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['date_of_birth']
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
@property
def is_staff(self):
"Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_admin
- (주의) 생년월일에 대한 정보를 제외하고 싶을 때 date_of_birth를 지우고 사용할 수도 있는데, 이때 class User에서 REQUIRED_FIELDS = [‘’] 이렇게 공란으로 두면 createsuperuser 실행 시, FieldDoesNotExist(django.core.exceptions.FieldDoesNotExist: User has no field <- 이러한 에러가 발생한다. 따라서 key값을 입력하거나 지우거나 주석 처리해야 정상적으로 처리됨을 알 수 있다.
- 장고에서 제공하는 위의 model들은 대부분 필수기능이므로, 그냥 삭제하면 오류가 발생할 가능성이 높다.
Users/admin.py
from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError
from users.models import User
class UserCreationForm(forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password."""
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
class Meta:
model = User
fields = ('email',)
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
"""A form for updating users. Includes all the fields on
the user, but replaces the password field with admin's
disabled password hash display field.
"""
password = ReadOnlyPasswordHashField()
class Meta:
model = User
fields = ('email', 'password', 'is_active', 'is_admin')
class UserAdmin(BaseUserAdmin):
# The forms to add and change user instances
form = UserChangeForm
add_form = UserCreationForm
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference specific fields on auth.User.
list_display = ('email', 'is_admin')
list_filter = ('is_admin',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Permissions', {'fields': ('is_admin',)}),
)
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
# overrides get_fieldsets to use this attribute when creating a user.
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2'),
}),
)
search_fields = ('email',)
ordering = ('email',)
filter_horizontal = ()
# Now register the new UserAdmin...
admin.site.register(User, UserAdmin)
# ... and, since we're not using Django's built-in permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)
- Search_fields = (‘email’,)은 어드민 페이지에서 검색어를 통해 해당 문자열을 포함한 이메일 조회할 수 있도록 해준다.
Users/views.py에서 마지막 부분 Response에서 f-string 구조로 표현하면 좀 더 효과적이다.
(주의) 앞에서 했던 실수를 반복했는데 serializer 뒤엔 .error 가 아닌 .errors 가 와야 한다.
class UserView(APIView):
def post(self, request):
serializer = UserSerializer(data = request.data)
if serializer.is_valid():
serializer.save()
return Response({"message":"가입완료!"}, status=status.HTTP_201_CREATED)
else:
return Response({"message":f"${serializer.errors}"}, status=status.HTTP_400_BAD_REQUEST)
마찬가지로 이미 가입한 계정을 POST 하면
{
"email":"admin@gmail.com",
"password":"1234"
}
이미 가입되어 있는 계정이라고 오류가 뜬다.
{
"message": "${'email': [ErrorDetail(string='user with this email address already exists.', code='unique')]}"
}
'스파르타코딩 AI웹개발 3기' 카테고리의 다른 글
| 내일배움캠프_TIL_2022.10.31 (0) | 2022.11.01 |
|---|---|
| 내일배움캠프_WIL_2022.10.30 (0) | 2022.10.31 |
| 내일배움캠프_TIL_2022.10.27 (0) | 2022.10.28 |
| 내일배움캠프_TIL_2022.10.26 (0) | 2022.10.26 |
| 내일배움캠프_TIL_2022.10.24 (0) | 2022.10.24 |