[PlanTo #8] 채용공고 일정 추가

이원진·2023년 7월 29일
0

Planto

목록 보기
8/9
post-thumbnail
post-custom-banner

목차


  1. 서론

  2. app 생성

  3. 채용공고(Job) 모델 구현

  4. 채용공고 뷰 구현

  5. 테스트



0. 서론


이번 글에서는 사람인 open API를 활용해 사용자에게 채용공고를 보여주고, 사용자가 선택한 채용공고를 일정(Task)으로 변환해 추가하는 기능을 구현해보겠습니다.



1. app 생성


채용공고와 관련된 기능을 전담하는 job_announcement라는 app을 생성해 settings.py에 등록해보겠습니다.

python manage.py startapp job_announcement

projectname/settings.py

INSTALLED_APPS = [
    ...
    'job_announcement.apps.JobAnnouncementConfig',
]
  • app 이름에 언더바가 들어가서 Job_AnnouncementConfig와 JobAnnouncementConfig 둘 중에 어느 것이 맞는지 헷갈렸는데, apps.py에 정의된 config 클래스를 등록하는 것이기 때문에 후자가 맞음

  • INSTALLED_APPS에 app 이름을 바로 등록할 수도 있고 위와 같이 config 클래스를 등록할 수도 있음

    • app 이름을 바로 등록하는 것이 더 직관적이고 편하지만, app의 출력 이름이나 동작 등을 커스터마이징할 수 있어 더 유연한 config 클래스를 등록했음



2. 채용공고(Job) 모델 구현


사람인 API에서 받아온 채용공고 데이터를 DB에 저장하기 위한 Job 모델을 구현해보겠습니다.

job_announcement/models.py

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"
  • 사용자가 선택한 채용공고의 목록을 프로필 페이지에서 열람할 수 있도록, User 모델을 ForeignKey로 연결

  • due_date: 채용공고 마감일

job_announcement/serializers.py

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 모델을 연결해줬습니다.

authentication/serializers.py

...

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())

    ...


3. 채용공고 뷰 구현


이제 사용자가 채용공고 화면을 볼 수 있도록 뷰를 구현해보겠습니다. JobList 뷰에서 사용자가 마음에 드는 채용공고를 선택하면, 해당하는 데이터를 JSON 형태로 받아서 Deserialize한 뒤 Job 모델과 Task 모델로 변환해 저장합니다.

job_announcement/views.py

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(): 사용자가 마음에 드는 채용공고를 일정에 추가하는 역할

job_announcement/urls.py

from django.urls import path
from .views import *

urlpatterns = [
    path('', JobList.as_view()),
]


4. 테스트


위에서 구현한 뷰를 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에 잘 저장되었음을 확인할 수 있습니다.

post-custom-banner

0개의 댓글