Django 기본(3)

2024. 10. 15. 00:47·Python/Django

Admin페이지 커스터마이징

편집페이지, 목록페이지

1) 편집페이지

QuesionAdmin을 만들고 Question과 함께 등록해줘야한다.

QueestionAdmin에서는

fieldsets에서는 표시되는 순서 정의, 섹션이름 부여, 'collapse'라는 속성 줘서 숨기거나 할 수 있다. (한글은 안됨)

readonly_fields는 편집할 때 수정 못하도록

inlines -> Question과 Choice를 한번에 수정할 수 있도록 

2) 목록페이지

목록에 표시되는 column들 -> list_display로

list_filter와 search_fileds를 통해 필터링, 검색 가능

choice_choice_text를 넣어줌으로써 choice를 통해 검색할 수 있다.

 

목록에서 표시되는 column이름을 정의 하기 위해 verbose_name 속성을

was_published_recently는 field가 아니므로 column을 정의해 주기 위해서  @admin.display(boolean=True)  

from django.contrib import admin
from .models import Choice, Question

admin.site.register(Choice)

class ChoiceInline(admin.TabularInline):
    model = Choice
    extra = 3

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        ('question_section', {'fields': ['question_text']}),
        ('created_date', {'fields': ['pub_date'], 'classes': ['collapse']}),        
    ]
    readonly_fields = ['pub_date']
    inlines = [ChoiceInline]
    list_filter = ['pub_date']
    search_fields = ['question_text', 'choice__choice_text']
    
admin.site.register(Question, QuestionAdmin)


import datetime
from django.db import models
from django.utils import timezone
from django.contrib import admin

class Question(models.Model):
    question_text = models.CharField(max_length=200, verbose_name='질문')
    pub_date = models.DateTimeField(auto_now=True, verbose_name='생성일')
    
    @admin.display(boolean=True, description='최근생성(하루기준)')
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

    def __str__(self):
        return f'제목: {self.question_text}, 날짜: {self.pub_date}'

Serializers

Serialize는 Django 모델 인스턴스나 QuerySet과 같은 데이터를 REST API로 주고받기 위해 JSON 형식으로 변환하는 작업이다.

API 서버에서 데이터를 주고받을 때는 JSON 형식이 기본적으로 사용되므로,

Serializer를 이용해 모델의 데이터를 JSON으로 변환하거나, JSON 데이터를 모델로 변환해 데이터베이스에 저장하는 작업을 한다.

새로운 app을 만들고 serializers 파일

 

Question 모델을 바탕으로 Serializer를 만들어보자

Question에 있는 모델을 Serializer하기 위해서는 field를 매칭해준다. (빼먹는다면 해당 field는serialize 되지 않는다.)

# pip install djangorestframework

from rest_framework import serializers
from polls.models import Question

class QuestionSerializer(serializers.Serializer):  
    id = serializers.IntegerField(read_only=True)
    question_text = serializers.CharField(max_length=200)
    pub_date = serializers.DateTimeField(read_only=True) # 자동으로 생성되는 값이므로

    def create(self, validated_data):
        return Question.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.question_text = validated_data.get('question_text', instance.question_text) #만약 값이 없으면 원래 값으로 유지
        instance.save()
        return instance

 

 

create는 json으로 받은 데이터를 새로운 객체로 저장, update는 기존 객체를 수정

serialize할 때는 유효성을 검사하고 통과한 데이터는 validated_data로 주어진다. 

예를 들어 question_text 필드가 max_length=200 조건을 위반하는지 보고 위반하지 않다면 validated_data로 넘겨주고

이 넘겨받은 data를 가지고 QuestionObject를 만든다.  

update는 있던 값을 instance로 받아오고 받아온값을 validated_data로 변형한다. 

#Serialize
>>> from polls.models import Question
>>> from polls_api.serializers import QuestionSerializer
>>> q = Question.objects.first()
>>> q 
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-02-05 18:52:59+09:00>
>>> serializer = QuestionSerializer(q)
>>> serializer.data
{'id': 1, 'question_text': '휴가를 어디서 보내고 싶으세요?', 'pub_date': '2023-02-05T18:52:59Z'}
>>> from rest_framework.renderers import JSONRenderer
>>> json_str = JSONRenderer().render(serializer.data)
>>> json_str
b'{"id":1,"question_text":"\xed\x9c\xb4\xea\xb0\x80\xeb\xa5\xbc \xec\x96\xb4\xeb\x94\x94\xec\x84\x9c \xeb\xb3\xb4\xeb\x82\xb4\xea\xb3\xa0 \xec\x8b\xb6\xec\x9c\xbc\xec\x84\xb8\xec\x9a\x94?","pub_date":"2023-02-05T18:52:59Z"}'

 

serializer.data(JSON)를 JSONRenderer로 실제 사용하는 JSON 문자열로 바꾼다.

 

# Deserialize
>>> import json
>>> data = json.loads(json_str)
>>> data
{'id': 1, 'question_text': '휴가를 어디서 보내고 싶으세요?', 'pub_date': '2023-02-05T18:52:59Z'}
>>> serializer = QuestionSerializer(data=data)
>>> serializer.is_valid()  
True
>>> serializer.validated_data 
OrderedDict([('question_text', '휴가를 어디서 보내고 싶으세요?')])
>>> new_question = serializer.save() # Create
>>> new_question
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-02-14 18:46:56.209215+00:00>
>>> data={'question_text': '제목수정'}
>>> serializer = QuestionSerializer(new_question, data=data) 
>>> serializer.is_valid()  
True
>>> serializer.save() # Update
<Question: 제목: 제목수정[시리얼라이저에서 업데이트], 날짜: 2023-04-25 13:15:05.852404+00:00>

#Validation이 통과하지 않는 경우
>>> long_text = "abcd"*300
>>> data = {'question_text':long_text}
>>> serializer = QuestionSerializer(data=data)
>>> serializer.is_valid()
False
>>> serializer.errors
{'question_text': [ErrorDetail(string='Ensure this field has no more than 200 characters.', code='max_length')]}

 

항상 Data를 넣어주고 save()나 validated_data를 하기전에 is_valid를 해야된다. 

string을 서버가 받았다면 JSON으로 변환하고 이 JSON을 활용해서 deserialize(model에 load)를 해서

update하거나 create를 한다. string을 JSON으로 바꾸고 모델에 load해준다. 

from rest_framework import serializers
from polls.models import Question

class QuestionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Question
        fields = ['id','question_text', 'pub_date']

 

ModelSerializer를 상속받으면, 일일이 필드를 정의하거나 create와 update 메서드를 직접 작성할 필요가 없다.

ModelSerializer는 자동으로 모델의 필드와 메서드를 처리해주기 때문에 더 간결한 코드 작성이 가능해진다..

>>> from polls_api.serializers import QuestionSerializer
>>> print(QuestionSerializer())
QuestionSerializer():
    id = IntegerField(read_only=True)
    question_text = CharField(max_length=200)
    pub_date = DateTimeField(read_only=True)
>>> serializer = QuestionSerializer(data={'question_text':'모델시리얼라이저로 만들어 봅니다.'})
>>> serializer.is_valid()
True
>>> serializer.save()
<Question: 제목: 모델시리얼라이저로 만들어 봅니다., 날짜: 2023-02-14 19:41:081444+00:00>

 

API 만들기 (JSON 반환)

GET

#installed-app에 rest-framework있는지 확인

from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view

@api_view()
def question_list(request):
    questions = Question.objects.all()
    serializer = QuestionSerializer(questions, many = True)
    return Response(serializer.data)
    

from django.urls import path
from .views import *

urlpatterns = [
    path('question/', question_list, name='question-list')
]

@api_view 데코레이터를 메서드에 적용하면, 해당 메서드가 기본적으로 GET 요청을 처리하게 된다.

모든 Question 객체를 가져와서 이를 직렬화(serialize)한다. 이 때 여러 인스턴스를 처리하기 위해 many=True 옵션을 사용한다.

urls.py에 해당 URL 패턴을 설정하여 외부에서 접근할 수 있게 만든다. 

앱을 처음 만들었을때는 root 프로젝트의 urls에 그 후 해당 app의 view에 접근할 수 있도록 url을 적어준다. 

Django는 json을 쉽게 보여줄 수 있게 해준다. 오른쪽 위에 선택을 통해 원본 그대로를 볼 수 있다.

 

POST

from rest_framework import status

@api_view(['GET','POST'])
def question_list(request):
    if request.method == 'GET':
        questions = Question.objects.all()
        serializer = QuestionSerializer(questions, many = True)
        return Response(serializer.data)
    
    if request.method == 'POST':
        serializer = QuestionSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 

@api_view 에 POST를 명시해주고, request에 따라 처리를 다르게 해준다. 

serializer를 통해 data 받아들이고 valid한 경우에만 save()하도록 한다. 

만약 status를 명시해주지 않으면 error가 나도 200 OK가 가므로 HTTP를 명시해줘야한다.

(request에는 Method가 있다면 응답에는 Status가 ^&^)

 

하나의 question에 대해서는 먼저 어떤 qeustion을 받을건지 알아야하기 때문에 id를 넣어주고 url에서 지정

from django.shortcuts import get_object_or_404

@api_view(['GET', 'PUT', 'DELETE'])
def question_detail(request, id):
    question = get_object_or_404(Question, pk=id)
    
    if request.method == 'GET':
        serializer = QuestionSerializer(question)
        return Response(serializer.data)

    if request.method == 'PUT':
        serializer = QuestionSerializer(question, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:    
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    if request.method == 'DELETE':
        question.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
        
   
urlpatterns = [
    path('question/', question_list, name='question-list'),
    path('question/<int:id>/', question_detail, name='question-detail')
]

 

간단하게 만들기

 

CRUD 기능을 구현할 때, GET, POST, PUT, DELETE 메서드에 따라 분기 처리하는 방식 대신, 클래스를 사용하여 구현할 수 있다.

그러기 위해서는 APIView를 상속받아야한다. (Django에서 제공하는 class들을 활용해서 쉽게 만들 수 있다.) 

from rest_framework.views import APIView

class QuestionList(APIView):
    def get(self, request):
        questions = Question.objects.all()
        serializer = QuestionSerializer(questions, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = QuestionSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class QuestionDetail(APIView):
    def get(self, request, id):
        question = get_object_or_404(Question, pk=id)
        serializer = QuestionSerializer(question)
        return Response(serializer.data)

    def put(self, request, id):
        question = get_object_or_404(Question, pk=id)
        serializer = QuestionSerializer(question, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:    
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        
    def delete(self, request, id):
        question = get_object_or_404(Question, pk=id)
        question.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
        
        
 urlpatterns = [
    path('question/', QuestionList.as_view(), name='question-list'),
    path('question/<int:id>/', QuestionDetail.as_view(), name='question-detail'),
]

 

요청이 들어오면 as_view()가 호출되어 QuestionList 클래스의 인스턴스를 생성하고,

요청의 메서드 타입에 따라 get 또는 post 메서드를 실행. (if 문으로 분기 처리하는 것과 동일)

 

mixins, generics

 

mixins.ListModelMixin은 GET에서 mixins.CreateModelMixin은 POST에서

from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework import mixins
from rest_framework import generics

class QuestionList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class QuestionDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)
        
        
urlpatterns = [
    path('question/', QuestionList.as_view(), name='question-list'),
    path('question/<int:pk>/', QuestionDetail.as_view(), name='question-detail'),
]

 

generics에서 제공하는걸로 더 간단히..

from polls.models import Question
from polls_api.serializers import QuestionSerializer
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

 

사용자와 인증(Authentication)

question모델에 작성자를 추가하고 작성자만 자신의 질문 수정할 수 있도록 해보자.

>>> from django.contrib.auth.models import User
>>> User
<class 'django.contrib.auth.models.User'>
>>> User._meta.get_fields()
>>> User.objects.all()
<QuerySet [<User: admin>]>

>>> from polls.models import * 
>>> user = User.objects.first()
>>> user.questions.all() 
>>> print(user.questions.all().query)
SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date", "polls_question"."owner_id" FROM "polls_question" WHERE "polls_question"."owner_id" = 1

 

이 User는 

djano.contrib.auth.models에 있다. 

하나의 서비스에서 user를 구현하는데 필요한 거의 모든 필드가 구현되어있다.

class Question(models.Model):
    question_text = models.CharField(max_length=200, verbose_name='질문')
    pub_date = models.DateTimeField(auto_now_add=True, verbose_name='생성일')  
    owner = models.ForeignKey('auth.User', related_name='questions', on_delete=models.CASCADE, null=True)
                              
    @admin.display(boolean=True, description='최근생성(하루기준)')
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
    
    def __str__(self):
        return f'제목: {self.question_text}, 날짜: {self.pub_date}'
    ...

 

그리고 UserSerializer를 만들자.

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    questions = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())
    
    class Meta:
        model = User
        fields = ['id', 'username', 'questions']

 

PrimaryKeyRelatedFiel(many=True) 유저의 PrimaryKey를 통해서 여러개의 Questions를 가지고 있다는 것들 명시

Meta에 표시하지 않고 따로 표시하는 것은 questions table에 있기 때문이다. 

User를 통해 가져올 수 ㅇ씨는것이 아니기 때문에

user.questions.all().query는

 

그

from django.contrib.auth.models import User
from polls_api.serializers import UserSerializer

class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    
class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
from django.urls import path
from .views import *

urlpatterns = [
    path('question/', QuestionList.as_view(), name='question-list'),
    path('question/<int:pk>/', QuestionDetail.as_view()),
    path('users/', UserList.as_view(),name='user-list'),
    path('users/<int:pk>/', UserDetail.as_view()),

]

'Python > Django' 카테고리의 다른 글

Django 기본(2)  (0) 2024.10.11
Django 기본(1)  (0) 2024.10.11
'Python/Django' 카테고리의 다른 글
  • Django 기본(2)
  • Django 기본(1)
dev.di
dev.di
devdi 님의 블로그 입니다.
  • dev.di
    개발 블로그
    dev.di
  • 전체
    오늘
    어제
    • 분류 전체보기 (28)
      • Algorithm (9)
        • Basics (9)
      • AWS (0)
        • AWS (0)
        • SAA (0)
      • Computer Science (1)
        • OS 벼락치기 (1)
        • DB 벼락치기 (0)
      • Data Engineer (8)
        • Airflow (0)
        • Data Warehouse (0)
        • Kafka (0)
        • Spark (0)
        • 데브코스 (8)
      • Docker (0)
      • Interviews (1)
      • Network (2)
        • Physical Layer (0)
        • Data Link Layer (0)
      • OOP (3)
        • GoF (3)
      • Python (4)
        • Django (3)
        • Scraping (1)
      • Software Engineering (0)
      • Spring (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    포트포워딩
    sql
    IPv4
    데이터 웨어하우스
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
dev.di
Django 기본(3)
상단으로

티스토리툴바