서론
app 생성
채용공고(Job) 모델 구현
채용공고 뷰 구현
테스트
이번 글에서는 사람인 open API를 활용해 사용자에게 채용공고를 보여주고, 사용자가 선택한 채용공고를 일정(Task)으로 변환해 추가하는 기능을 구현해보겠습니다.
채용공고와 관련된 기능을 전담하는 job_announcement라는 app을 생성해 settings.py에 등록해보겠습니다.
python manage.py startapp job_announcement
INSTALLED_APPS = [
...
'job_announcement.apps.JobAnnouncementConfig',
]
app 이름에 언더바가 들어가서 Job_AnnouncementConfig와 JobAnnouncementConfig 둘 중에 어느 것이 맞는지 헷갈렸는데, apps.py에 정의된 config 클래스를 등록하는 것이기 때문에 후자가 맞음
INSTALLED_APPS에 app 이름을 바로 등록할 수도 있고 위와 같이 config 클래스를 등록할 수도 있음
사람인 API에서 받아온 채용공고 데이터를 DB에 저장하기 위한 Job 모델을 구현해보겠습니다.
from django.db import models
from django.conf import settings
class Job(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name = "jobs", on_delete = models.CASCADE, null = True)
title = models.CharField(max_length = 255)
description = models.TextField(null = True)
company = models.CharField(max_length = 255)
position = models.CharField(max_length = 255)
due_date = models.DateField()
location = models.CharField(max_length = 255, null = True)
salary = models.IntegerField(null = True)
class Meta:
db_table = "job"
from rest_framework import serializers
from .models import Job
class JobSerializer(serializers.ModelSerializer):
class Meta:
model = Job
fields = ["title", "description", "company", "position", "due_date", "location", "salary"]
UserSerializer에서도 User모델과 Job 모델을 연결해줬습니다.
...
class UserSerializer(serializers.ModelSerializer):
...
# User가 소유한 Task 모델(일정)과 Job 모델(채용공고)을 PrimaryKeyRelatedField로 연결
tasks = serializers.PrimaryKeyRelatedField(many = True, queryset = Task.objects.all())
jobs = serializers.PrimaryKeyRelatedField(many = True, queryset = Job.objects.all())
...
이제 사용자가 채용공고 화면을 볼 수 있도록 뷰를 구현해보겠습니다. JobList 뷰에서 사용자가 마음에 드는 채용공고를 선택하면, 해당하는 데이터를 JSON 형태로 받아서 Deserialize한 뒤 Job 모델과 Task 모델로 변환해 저장합니다.
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import JobSerializer
from todo.serializers import TaskSerializer
from datetime import datetime
import requests
class JobList(APIView):
# 채용공고 목록 출력
def get(self, request, format = None):
base_url = "https://oapi.saramin.co.kr/job-search?access-key="
api_key = "dF3WMih4YXpczw7vEtUXuGZCZefkMkcK2qA1cIyZ9eqC3zecTVq"
base_url += api_key
# 사용자가 지역, 직무, 검색어를 검색 조건으로 사용 가능
query_parameters = [
"location",
"position",
"keywords"
]
query_params = {}
# 사용자가 보낸 요청에 검색 조건이 있을 경우, 해당 조건을 사람인 API로 보낼 요청의 query_params로 사용
for parameter in query_parameters:
parameter_value = request.query_params.get(parameter, "")
if parameter_value:
query_params[parameter] = parameter_value
# 검색 결과 수 10개로 지정
query_params["count"] = 10
response = requests.get(base_url, params = query_params)
# 응답 코드가 HTTP 200인 경우, JSON 형태로 데이터를 반환하고, 다른 값일 경우 해당 응답 코드와 에러 메시지 반환
if response.status_code == 200:
data = response.json()
return Response(data)
else:
return Response({"error": "채용공고를 불러오는 데 실패했습니다."}, status = response.status_code)
# 사용자가 채용공고 일정에 추가
def post(self, request, format = None):
try:
data = request.data
# Unix timestamp 형태로 전달되는 데이터를 datetime으로 변환
due_date = data.get("due_date")
if due_date:
due_date = datetime.utcfromtimestamp(int(due_date)).date()
data["due_date"] = due_date
# 데이터 Deserialize
job_serializer = JobSerializer(data = data)
task_serializer = TaskSerializer(data = data)
# 데이터가 유효한 경우, 채용공고를 사용자 모델에 추가 및 일정 모델로 변환
if job_serializer.is_valid() and task_serializer.is_valid():
job_serializer.save(owner = request.user)
task_serializer.save(owner = request.user)
return Response({"message": "채용공고가 추가되었습니다."}, status = status.HTTP_201_CREATED)
return Response(job_serializer.errors, status = status.HTTP_400_BAD_REQUEST)
except Exception as e:
return Response({"error": str(e)}, status = status.HTTP_400_BAD_REQUEST)
get(): 사람인 API에 요청을 보내서 받은 채용공고 목록을 출력하는 역할
post(): 사용자가 마음에 드는 채용공고를 일정에 추가하는 역할
from django.urls import path
from .views import *
urlpatterns = [
path('', JobList.as_view()),
]
위에서 구현한 뷰를 Postman을 사용해서 테스트해보겠습니다. 우선 GET 요청을 보내 채용공고 목록을 확인해보겠습니다.
위 사진과 같이 요청을 보내면 아래 사진과 같이 사람인 API가 채용공고 목록을 JSON 형태로 반환해주는 것을 확인할 수 있습니다.
해당 응답을 자세히 살펴보면 아래와 같이 expiration-date 즉, 마감일이 일반적인 날짜 형식이 아닌 Unix timestamp 형식으로 출력되는 것을 알 수 있습니다.
따라서 채용공고를 추가하는 post() 메서드에 아래와 같이 due_date 필드를 날짜 형식으로 변환해 저장하는 로직을 추가했습니다.
# Unix timestamp 형태로 전달되는 데이터를 datetime으로 변환
due_date = data.get("due_date")
if due_date:
due_date = datetime.fromtimestamp(int(due_date))
data["due_date"] = due_date
또한, 아래 사진과 같이 연봉 정보가 정수가 아닌 문자열 형태로 반환되고, 경우의 수가 너무 많기 때문에 Job 모델의 salary 필드를 IntegerField가 아닌 CharField로 변경해줬습니다.
class Job(models.Model):
...
salary = models.CharField(max_length = 255, null = True)
class Meta:
db_table = "job"
사용자가 채용공고에서 마음에 드는 항목을 찾아 버튼을 눌렀을 때, 서버에 아래 사진과 같이 JSON 형태의 응답이 반환된다고 가정해보겠습니다.
APPEND_SLASH 에러
해당 요청을 보낼 경우 아래 사진과 같이 APPEND_SLASH와 관련된 에러가 발생하는 것을 확인할 수 있습니다.
APPEND_SLASH란 django에서 브라우저가 요청한 URL이 urls.py에 정의돼있지 않은 경우해당 URL에 자동으로 슬래시를 붙인 URL은 있는지 확인하는 기능입니다.슬래시를 붙였을 때 일치하는 URL이 있으면 해당 항목으로 redirect하고, 없으면 404 에러를 반환합니다.
위 상황의 경우, 에러 메시지에서 말한 것과 같이 POST 데이터를 유지한 채 슬래시를 추가한 URL로 redirect를 못하기 때문에 에러가 발생합니다. 이를 해결하기 위해서 urls.py에서 슬래시를 제외한 URL로 등록하거나, 브라우저 슬래시를 포함한 URL로 요청을 주는 방법을 사용할 수 있습니다.
전자의 경우 어떤 URL에는 슬래시가 붙어있고 어떤 URL에는 붙어있지 않아서 urlpattern이 일관되지 않습니다. 따라서 프런트엔드에서 요청을 보낼 때 슬래시를 붙여서 보내는 방식으로 구현할 예정입니다.
동일한 JSON 데이터를 슬래시를 붙인 URL을 사용해 POST 요청으로 보낼 경우, 아래 사진과 같이 HTTP 201과 함께 채용공고가 추가되었다는 메시지를 확인할 수 있습니다.
이제 admin 페이지에서 Job, Task 모델을 보면, 아래의 두 사진과 같이 모델이 DB에 잘 저장되었음을 확인할 수 있습니다.