1. 프론트엔드의 UI에 맞게 백엔드 수정하기
프로젝트를 진행하면서 프론트엔드를 아주 계획적이면서 튼튼하고 확고하게 짰다면 백엔드 작업이 한층 수월해졌을거라는 경험이 생겼다.
그 이전에 기획 단계가 구체적이어야 프론트엔드 또는 백엔드가 믿음을 갖고 일을 진행할수 있다는 것이다.
나는 첫 웹앱 작성을 하는 아마추어이기 때문에 많은 시행착오가 필요했다.
이제 어느정도 프론트엔드 UI가 갖춰졌고 어떤 데이터를 사용자로 부터 받아올지 예측이 되기 때문에 다시 백엔드 작업을 이어서 하기로 했다.
2. 코드 작성
데이터를 어떻게 주고 받을지 잠시 고민했다. 함수를 쪼개서 해야할지 통합을 해야될지 시도를 하루 정도 해봤는데, 쪼갤수록 코드가 중복되고 내가 원하는 결과를 뽑아내는데 더 복잡한 코드를 요구했기 때문에 통계 자료 하나당 하나의 함수를 사용하기로 결정하였다.
전체코드
# db 폴더 - db_population_crud.py
from collections import defaultdict
from sqlalchemy.orm.session import Session
from sqlalchemy import func, and_
from db.models import DbPopulation
from routers.schemas import PopulationQuestion, PopulationDisplay
def population_ans(db: Session, request: PopulationQuestion):
# 성별 및 지역별 인구 쿼리 준비
filtered_population = db.query(DbPopulation)
filtered_man = filtered_population.filter(DbPopulation.gender == '남')
filtered_woman = filtered_population.filter(DbPopulation.gender == '여')
# 남성과 여성의 총 인구 계산
total_population = filtered_population.with_entities(func.sum(DbPopulation.total_population)).scalar()
man_population = filtered_man.with_entities(func.sum(DbPopulation.total_population)).scalar()
woman_population = filtered_woman.with_entities(func.sum(DbPopulation.total_population)).scalar()
# 성별과 연령대에 따른 인구수 계산
man_population_in_range = None
woman_population_in_range = None
if request.age_start is not None and request.age_end is not None:
man_population_in_range = filtered_man.filter(
and_(
DbPopulation.age >= request.age_start,
DbPopulation.age <= request.age_end
)
).with_entities(func.sum(DbPopulation.total_population)).scalar()
woman_population_in_range = filtered_woman.filter(
and_(
DbPopulation.age >= request.age_start,
DbPopulation.age <= request.age_end
)
).with_entities(func.sum(DbPopulation.total_population)).scalar()
else:
# todo 연령조건이 없으면 위에서 계산한 man_population, woman_population 값과 같기 때문에
# If age_start or age_end is None, include all ages
man_population_in_range = filtered_man.with_entities(func.sum(DbPopulation.total_population)).scalar()
woman_population_in_range = filtered_woman.with_entities(func.sum(DbPopulation.total_population)).scalar()
# 특정 지역 인구 총합 계산
man_by_all_region = defaultdict(int)
woman_by_all_region = defaultdict(int)
man_by_region, woman_by_region = 0, 0
if request.location:
if request.age_start is not None and request.age_end is not None:
filtered_man = filtered_man.filter(
and_(
DbPopulation.age >= request.age_start,
DbPopulation.age <= request.age_end
)
)
filtered_woman = filtered_woman.filter(
and_(
DbPopulation.age >= request.age_start,
DbPopulation.age <= request.age_end
)
)
# DB 지역 칼럼 이름 가져오기
region_columns = [column.key for column in DbPopulation.__table__.columns \
if column.key not in ['id', 'gender', 'age']]
# 남, 여 지역별 총합구하기
for region in region_columns:
total_man_region = filtered_man.with_entities(func.sum(getattr(DbPopulation, region.lower()))).scalar()
man_by_all_region[region] = total_man_region
total_woman_region = filtered_woman.with_entities(func.sum(getattr(DbPopulation, region.lower()))).scalar()
woman_by_all_region[region] = total_woman_region
# 유저가 선택한 지역의 총합.
region_list = [location for location in request.location]
if '전국' in region_list:
man_by_region = man_by_all_region[total_population]
woman_by_region = woman_by_all_region[total_population]
else:
for region in region_list:
man_by_region += man_by_all_region[region]
woman_by_region += woman_by_all_region[region]
# PopulationDisplay 객체 생성
population_res = PopulationDisplay(
gender=request.gender,
man_gender_population=man_population,
woman_gender_population=woman_population,
total_population=total_population,
man_age_range_population=man_population_in_range,
woman_age_range_population=woman_population_in_range,
total_population_in_range=man_population_in_range + woman_population_in_range
if man_population_in_range is not None and woman_population_in_range is not None
else None,
man_population_by_all_region=dict(man_by_all_region),
woman_population_by_all_region=dict(woman_by_all_region),
man_population_by_region=man_by_region,
woman_population_by_region=woman_by_region
)
return population_res
# routers 폴더 - schemas.py
from pydantic import BaseModel
from datetime import datetime
from pydantic import Field
from typing import Union, Dict
class UserBase(BaseModel):
username: str
password: str
# todo 암호관련 작업 필요하다.
class UserDisplay(BaseModel):
username: str
class Config():
from_attributes = True
class CommentBase(BaseModel):
text: str
username: str
timestamp: datetime
class PopulationQuestion(BaseModel):
gender: str
age_start: int | None = None
age_end: int | None = None
location: list | None = None
class PopulationDisplay(BaseModel):
gender: str | None = None
man_gender_population: int | None = None
woman_gender_population: int | None = None
total_population: int | None = None
man_age_range_population: int | None = None
woman_age_range_population: int | None = None
total_population_in_range: int | None = None
man_population_by_region: int | None = None
woman_population_by_region: int | None = None
man_population_by_all_region: Dict[str, int] | None = None # 수정된 부분
woman_population_by_all_region: Dict[str, int] | None = None # 수정된 부분
# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from routers import user, population
app = FastAPI()
app.include_router(user.router)
app.include_router(population.router)
origins = [
"http://localhost:3000",
"http://localhost:8000",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get('/')
def root():
return "hello world"
# PopulationDisplay 객체 생성
population_res = PopulationDisplay(
gender=request.gender,
man_gender_population=man_population,
woman_gender_population=woman_population,
total_population=total_population,
man_age_range_population=man_population_in_range,
woman_age_range_population=woman_population_in_range,
total_population_in_range=man_population_in_range + woman_population_in_range
if man_population_in_range is not None and woman_population_in_range is not None
else None,
man_population_by_all_region=dict(man_by_all_region),
woman_population_by_all_region=dict(woman_by_all_region),
man_population_by_region=man_by_region,
woman_population_by_region=woman_by_region
)
return population_res
먼저 결과를 반환할 객체이다.
프론트엔드에서 그래프를 그려야하기 때문에 관련 데이터를 고민해봤다.
남, 여 인구 비율 : 남자 인구, 여자 인구, 전체인구
연령으로 필터링한 남녀 인구 : (연령으로 필터링 된) 남자 인구, 여자 인구, 전체 인구
지역으로 필터링한 남녀 인구 : (지역으로 필터링 된) 남자 인구, 여자 인구, 전체 인구 + 각 지역의 남자, 여자인구
class PopulationDisplay(BaseModel):
gender: str | None = None
man_gender_population: int | None = None
woman_gender_population: int | None = None
total_population: int | None = None
man_age_range_population: int | None = None
woman_age_range_population: int | None = None
total_population_in_range: int | None = None
man_population_by_region: int | None = None
woman_population_by_region: int | None = None
man_population_by_all_region: Dict[str, int] | None = None # 수정된 부분
woman_population_by_all_region: Dict[str, int] | None = None # 수정된 부분
스키마도 그에 맞게 조정하였다.
대부분의 데이터 타입은 int 이고 성별만 str이다. or 조건으로 값이 없어도 통과되며 기본값은 None으로 설정하였다.
왜냐하면 남, 여 성별만 필수 데이터로 설정하고 나머지는 연령조건과 지역조건은 임의의(없어도 되는) 데이터로 설정했기 때문이다.
각 지역별 인구는 딕셔너리 자료형으로 반환한다.
def population_ans(db: Session, request: PopulationQuestion):
# 성별 및 지역별 인구 쿼리 준비
filtered_population = db.query(DbPopulation)
filtered_man = filtered_population.filter(DbPopulation.gender == '남')
filtered_woman = filtered_population.filter(DbPopulation.gender == '여')
# 남성과 여성의 총 인구 계산
total_population = filtered_population.with_entities(func.sum(DbPopulation.total_population)).scalar()
man_population = filtered_man.with_entities(func.sum(DbPopulation.total_population)).scalar()
woman_population = filtered_woman.with_entities(func.sum(DbPopulation.total_population)).scalar()
DB에서 데이터를 가져와서 담고
request.gender 값이 있든 없든 남, 여로 필터링 한다.
전체 인구, 남자 인구, 여자 인구를 각각 합산한다.
# 성별과 연령대에 따른 인구수 계산
man_population_in_range = None
woman_population_in_range = None
if request.age_start is not None and request.age_end is not None:
man_population_in_range = filtered_man.filter(
and_(
DbPopulation.age >= request.age_start,
DbPopulation.age <= request.age_end
)
).with_entities(func.sum(DbPopulation.total_population)).scalar()
woman_population_in_range = filtered_woman.filter(
and_(
DbPopulation.age >= request.age_start,
DbPopulation.age <= request.age_end
)
).with_entities(func.sum(DbPopulation.total_population)).scalar()
else:
# If age_start or age_end is None, include all ages
man_population_in_range = filtered_man.with_entities(func.sum(DbPopulation.total_population)).scalar()
woman_population_in_range = filtered_woman.with_entities(func.sum(DbPopulation.total_population)).scalar()
연령 조건이 있을때 작동한다.
이상~이하 조건을 적용해서 남자, 여자 따로 계산하여 변수에 담았다.
else 조건은 모든 연령의 값을 계산하는 건데. 위에서 계산한 man_population, woman_population 값과 동일하기 때문에 따로 계산을 안해도 될 것같다. 나중에 수정한다.
# 특정 지역 인구 총합 계산
man_by_all_region = defaultdict(int)
woman_by_all_region = defaultdict(int)
man_by_region, woman_by_region = 0, 0
if request.location:
if request.age_start is not None and request.age_end is not None:
filtered_man = filtered_man.filter(
and_(
DbPopulation.age >= request.age_start,
DbPopulation.age <= request.age_end
)
)
filtered_woman = filtered_woman.filter(
and_(
DbPopulation.age >= request.age_start,
DbPopulation.age <= request.age_end
)
)
# DB 지역 칼럼 이름 가져오기
region_columns = [column.key for column in DbPopulation.__table__.columns \
if column.key not in ['id', 'gender', 'age']]
# 남, 여 지역별 총합구하기
for region in region_columns:
total_man_region = filtered_man.with_entities(func.sum(getattr(DbPopulation, region.lower()))).scalar()
man_by_all_region[region] = total_man_region
total_woman_region = filtered_woman.with_entities(func.sum(getattr(DbPopulation, region.lower()))).scalar()
woman_by_all_region[region] = total_woman_region
# 유저가 선택한 지역의 총합.
region_list = [location for location in request.location]
if '전국' in region_list:
man_by_region = man_by_all_region[total_population]
woman_by_region = woman_by_all_region[total_population]
else:
for region in region_list:
man_by_region += man_by_all_region[region]
woman_by_region += woman_by_all_region[region]
지역별 인구를 구하는 코드
나이 조건이 있으면 필터링하여 계산한다.
DB 지역 칼럼을 가져와 리스트에 담는다. id, gender, age 칼럼을 제외한다.
칼럼 리스트를 반복문으로 딕셔너리로 자료구조를 설정한 남자, 여자 분리하여 담는다.
유저가 입력한 지역을 가져와서 변수에 담는다.
'전국'이라는 데이터가 있으면 이미 계산된 total_population 칼럼의 값을 이용한다.
그렇지 않으면 각각 지역의 값을 더해서 합산한다.
테스트 입력값이다.
남성이면서 1~10세, 부산지역
결과에서 각 지역별 인구가 딕셔너리 자료형으로 반환되는 것을 확인할수 있다.
연령조건, 지역조건을 제외하더라도 작동하는지 테스트 해보았다.
지역 조건을 여러개 입력해도 잘 작동하였다.
이제 이 데이터를 가지고 리액트에서 실시간으로 반응하도록 작성해본다.
'파이썬 > 육각남 찾기 프로젝트(FastAPI+React)' 카테고리의 다른 글
육각남 찾기 프로젝트 20. 나머지 통계 자료 구현 세번째-끝 (0) | 2024.03.31 |
---|---|
육각남 찾기 프로젝트 19. 나머지 통계 자료 구현 두번째 (0) | 2024.03.29 |
육각남 찾기 프로젝트 18. 나머지 통계 자료 구현 첫번째 (0) | 2024.03.29 |
육각남 찾기 프로젝트 17. 백엔드 DB 데이터를 웹브라우져 그래프로 나타내기 (0) | 2024.03.27 |
육각남 찾기 프로젝트 14. 나머지 입력 박스 추가하기 (0) | 2024.03.18 |
육각남 찾기 프로젝트 12. 챗 GPT로 프론트앤드 UI 꾸미기 (0) | 2024.03.17 |
육각남 찾기 프로젝트 11. React 헬로 월드 (0) | 2024.03.16 |
육각남 찾기 프로젝트 10. 첫번째 질문에 답하기 (0) | 2024.03.15 |