: User를 활용하여 다양한 동작을 하기 위해서는 통신이 필요하다. 이 때 사용되는 Serializer를 생성하고 View에 등록해준다.
# serializer.py
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
# user의 pk를 통해서 여러개의 questions를 갖는다는 것을 명시.
# question을 통해서 가져오기 때문에 다음처럼 사용
questions = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())
class Meta:
model =User
fields = ['id','username','questions']
#views.py
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
💡 PrimaryKeyRelatedField에 대한 이해 : PrimaryKeyRelatedField는 기본 키를 사용하여 관계의 대상을 나타낼 수 있다. 이에 예시는 아래 코드와 같다.
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
# 결과
{
'album_name': 'Undun',
'artist': 'The Roots',
'tracks': [
89,
90,
91,
...
]
}
: from django.contrib.auth.forms import UserCreationForm 을 활용하여 구현해준다.
from django.views import generic
from django.urls import reverse_lazy
from django.contrib.auth.forms import UserCreationForm
class SignupView(generic.CreateView):
form_class = UserCreationForm
# reverse_lazy를 사용하여 다른 Path로 전송시킬 수 있다.
success_url = reverse_lazy('user-list')
template_name = 'registration/signup.html'
User의 생성 - Serializer을 사용
1. serializers.py에 Serializer 클래스를 생성한다.
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username','password']
extra_kwargs = {'password' : {'write_only':True}}
2. Views.py에 view생성해주기
from polls_api.serializers import RegisterSerializer
class RegisterUser(generics.CreateAPIView):
serializer_class = RegisterSerializer
3. urls에 등록하기
path('register/', RegisterUser.as_view()),
Password-validatior 만들기
# 간단한 패스워드 막기
from django.contrib.auth.password_validation import validate_password
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True)
def validate(self, attrs):
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError({"password": "두 패스워드가 일치하지 않습니다."})
return attrs
# 정상적으로 패스워드가 동일한 경우에 save를 해준다.
def create(self, validated_data):
user = User.objects.create(username=validated_data['username'])
user.set_password(validated_data['password'])
user.save()
return user
class Meta:
model = User
fields = ['username', 'password','password2']
권한 관리
: Question에 owner 필드를 넣어주었지만, 여전히 로그인 하지 않은경우, 소유자가 아닌 다른 계정의 경우에도 편집이 가능하다. 따라서 적절한 권한부여를 통해 이를 막아야한다.
QuestionSerializer를 변경하여 question 생성 시에 소유자 필드를 무조건 현재 로그인된 사용자로 변경
class QuestionSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Question
fields = ['id', 'question_text', 'pub_date', 'owner']
로그인 한 사용자만 수정하게 만드는 경우 rest_framework의 permission를 활용한다.
# Views.py
from rest_framework import generics,permissions
class QuestionList(generics.ListCreateAPIView):
...
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
# save 요청시에는 어떤 값이는 문제 없이 넣을 수 있다.
serializer.save(owner=self.request.user)
추가로 Owner == user인 경우에만 API 요청이 가능하도록 하려면 다음과 같이 permission 코드를 작성한다.
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.owner == request.user
request.method가 SAFE_METHODS ('GET', 'HEAD', 'OPTIONS')에 포함되거나, obj의 user가 로그인 된 user와 같으면 API의 요청 결과를 볼 수 있다.
시리얼라이저 생성 시에 ModelSerializer를 통해 생성해주면 create, update 메서드의 별도 생성없이 구현 가능합니다.
# polls_api/serializers.py
from rest_framework import serializers
from polls.models import Question
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ['id','question_text','pub_date','is_something','average_score','json_field']
Django의 CRUD는 직접 구현하는 방법도 있으나, Mixin,generic과 같은 보다 편리한 메서드를 활용하여 구현도 가능하다.
API_VIEW를 활용한 CRUD
다음은 간단한 GET 메서드를 구현하는 방법이다.
from rest_framework.decorators import api_view # 메소드 정의 시 사용
# 만약 여러 메서드를 사용하고 싶은 경우 @api_view(['GET','POST']) 다음과 같이 리스트로 넣어준다.
@api_view()
def question_list(request):
questions = Question.objects.all()
serializer = QuestionSerializer(questions ,many = True) # 여러개의 인스턴스를 줄 때에 many를 사용
return Response(serializer.data)
Generic
이런 CRUD를 이미 구현하여 보다 쉽고 코드를 간결하게 해준다. rest_framework의 generics를 활용하는 방식이다.
from rest_framework import generics
class QuestionList(generics.ListCreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
이렇게 적어주기만해도 CRUD가 모두 정상 작동한다. -> generics에서 이미 구현해두었기 때문이다. 또한 각 기능을 별도로 설정 가능하다. 'RetrieveUpdateDestroyAPIView'에서 Destroy만 빼면 제거 기능만 제외한 설정이 가능하다.
# detail.html
<h1>
{{question.question_text}}
</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{choice.choice_text}}</li>
{% endfor %}
링크를 사용해서 페이지 이동.
태그를 활용하여 이동시키기 -> a태그 사용
question.id를 활용해서 href 링크를 걸어주면 된다.
{% for q in questions%}
<li><a href="/polls/{{q.id}}">{{q.question_text}}</li>
{% endfor %}
코드를 통해서 얻어오는 방법은 urlpattern의 name을 통해서 가져올 수가 있다.
# urls.py
app_name = 'polls' # app_name을 지정한 경우 app_name:name형식으로 불러와야한다.
urlpatterns = [
path("", views.index, name = 'index'),
path('<int:question_id>',views.detail, name = 'detail'),
path("some_url", views.some_url),
]
# index.html
{% for q in questions%}
<li><a href="{% url 'polls:detail' q.id %}">{{ q.question_text }}</a></li>
{% endfor %}
에러 처리하기
try,except 구문을 사용해도 가능하나 Django에서는 Shortcut을 통해 404 에러메세지를 제공한다.
from django.shortcuts import render,get_object_or_404
# 기존
question = Question.objects.get(pk=question_id)
# 변경
question = get_object_or_404(Question, pk =question_id)
폼과 커스터마이징
폼
: 값을 제출할 용도로 사용한다.
폼을 추가하는 순서에 대해서 간단하게 설명한다.
path를 추가하기. 값이 보내져서 path값이 변경되는 경우 추가해주어야 한다.
# urls.py
path('<int:question_id>/vote/', views.vote, name ='vote'),
views에 메서드 생성 여기서 주목할 점은 입력받은 값을 통해서 db와 연결하는 부분이다. 다음 처럼 request.POST를 활용해서 name이 choice인 값을 받을 수 있다.
selected_choice = question.choice_set.get(pk = request.POST['choice'])
# detail.html에서의 name
<input type = 'radio' **name = "choice"**, id = "choice{{forloop.counter}}" value="{{choice.id}}">
# 서버에 값을 저장시키고 싶을 때에는 save를 사용한다.
selected_choice.save()
admin에서 목록에 column추가하기 : list_display = ('question_text','pub_date') 이렇게 한줄 추가하면된다.
컬럼의 레이블을 바꿔주고싶으면 verbose_name을 추가한다.
# models.py
class Question(models.Model):
question_text = models.CharField(max_length=200 , verbose_name='질문')
pub_date = models.DateTimeField(auto_now_add=True, verbose_name='생성일')
# class로 정의 한 값인 경우
from django.contrib import admin
@admin.display(boolaen=True,description='최근 생성 (하루기준)') # boolean을 하면 아이콘으로 표시된다.
검색 기능
리스트 필터는 list_filter = ['pub_date'] 을 추가하여 사용한다. → 장고가 타입에 맞춰서 옵션들을 제공한다.
검색 필드는 search_fields = ['question_text', 'choice__choice_text'] 를 추가한다. → 리스트를 활용하여 옵션들을 넣을 수 있다.