셀러리란? - 비동기설정을 위해서 필요한 기능
- request요청이 긴 것을 비동기적으로 처리를 위하여 필요 한 기능인 셀러리
- Django EP66 - Celery란 무엇인가? (youtube.com)
셀러리란? - 비동기설정을 위해서 필요한 기능
- request요청이 긴 것을 비동기적으로 처리를 위하여 필요 한 기능인 셀러리
- Django EP66 - Celery란 무엇인가? (youtube.com)
- Workers - 각각 일을 받아서 할 수 있는 작업군
- 병렬적으로 작업을 처리할 수 있음
기존 방식 : 작업을 하나씩 처리해 가면서 동작
elder 음성인식 - Redis: create(1)
문제: Redis를 이용해서 cart에 저장하기 위한 add_to_cart()가 postman상으로 요청이 잘 보내짐에도 불구하고 Redis-cli에서는 해당 key가 조회되지 않음
해결시도:
- Redis 연결 확인해봄 → api call 보냄
- postman상으로는 연결이 잘 된 것으로 판단됐음
- python 코드 내에 cache.set을 이용해서 postman으로 api call 보내봄
하지만 여전히 Redis-cli에서는 해당 key조회가 안됨
def redis_test(request):
r = redis.StrictRedis.from_url(settings.CACHES['default']['LOCATION'])
print("\n\n Pinging Redis...")
try:
if r.ping():
print("\n\n Redis connection successful.")
except redis.ConnectionError:
print("\n\n Redis connection failed.")
cache.set("cache", "됐으면 좋겠다")
- 즉, 문제의 원인이 django와 Redis자체의 연결에 문제인 것으로 파악함
- settings.py의 CASHES를 다시 살펴본 결과 location이 로컬 포트>의 /1으로 되어 있어서 발생한 오류인 것으로 확인함
- 다시 말해, settings에 Redis Location 1번을 쓴다고 지정해 두고, hset도 잘 된 것이음 → 하지만 우리가 Redis-cli에서 조회하는 것이 default인 0번이어서 계속 조회가 안 됐던 것임
문제의 해결은 settings.py의 CACHES의 locaton에서 /1을 지워서 default인 0번으로 지정해 주니 Redis-cli에서도 hset 한 key로 잘 조회됨
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379", # Redis 서버의 위치 # 0번. /1하면 1번 ..
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
## 기존의 /1 로 저장했을 때
127.0.0.1:6379> keys *
1) "vanilla_latte"
2) "name"
3) "americano"
## /1 제거 후 저장했을 때
127.0.0.1:6379> keys *
1) "vanilla_latte"
2) "name"
3) ":1:cache" ## 잘 저장된 것을 확인
4) "americano"
elder 음성인식 - Redis: read (1)
문제 1:
add_to_cart()는 이제 POST요청으로 잘 처리되고 response도 잘 받아와 짐. 하지만 view_cart()는 response를 받아오는 과정에서 계속 오류가 발생함
구체적으로는 view_cart()에서 context에 대한 print문까지는 잘 찍히는데, 그 뒤의 return문에서 계속 오류가 발생함
- username = request.GET.get("username")
- print("\\n\\n\\n username 이 잘 들어왔는지: ", username)
- → 이때의 결과가 아래와 같음
username 이 잘 들어왔는지: None
해결:
정상적으로 잘 작동하고 있는 add_to_cart()와 view_cart()의 request를 print문으로 찍어보면서 비교해 봄
- add_to_cart()의 request:
<WSGIRequest: POST '/orders/add_to_cart/'>
- view_cart()의 request:
<rest_framework.request.Request: GET '/orders/cart/'>
request방식이 달라서 발생한 문제이므로, 일단은 view_cart()에서 username을 받아오는 방식을 username = request.data.get("username")와 같이 변경해 주니 오류 해결됨
그리고 확인차 찍어본 request.data에 대한 print문의 결과는 아래와 같다
request의 data: <QueryDict: {'username': ['mega']}>
@csrf_exempt
def add_to_cart(request):
print("\n\n add to cart의 request는: ", request)
if request.method == "POST":
# username = request.user.username # 나중에 elder_menu에서 연결할 때 다시 구현
# print("\n\n request user >> ", request.user)
username = request.POST.get("username")
item_id = request.POST.get("item_id")
image = request.POST.get("image")
name = request.POST.get("name")
price = int(request.POST.get("price"))
quantity = int(request.POST.get("quantity"))
cart = Cart(username)
item = CartItem(item_id, image, name, price, quantity)
serializer = CartSerializer(item)
cart.add_to_cart(serializer.data)
return JsonResponse({"message": "Item added to cart"})
# 장바구니 페이지 뷰
@api_view(['GET'])
def view_cart(request):
print("\n\n request 객체라도 나오는지: ", request)
print("\n\n request의 data: ", request.data)
# {"username": "mega"}
username = request.data.get("username")
print("\n\n\n username 이 잘 들어왔는지: ", username)
cart = Cart(username)
context = {"cart_items": cart.get_cart()}
print("\n\n\n context가 받아와지는지: ", context)
return Response({"context": context})
문제:
TypeError: keys must be str, int, float, bool or None, not bytes 오류가 발생
- context는 받아와지고 있는데 이후 *return* Response({"context": context}) 에서 오류 발생
context가 받아와지는지: {'cart_items': {b'americano': b'2', b'Vanilla Ice Blended': b'2'}}
해결: Redis가 데이터를 반환하는 형식은 byte-string이고, Json encoder는 str, int, float, bool or None의 형식을 지원하기 때문에 발생하는 오류라고 판단
→ view_cart()의 return형식을 Json encoder에 맞는 형식으로 수정해 줌
def get_cart(self):
redis_conn = get_redis_connection("default")
cart_data = redis_conn.hgetall(self.cart_key)
return {k.decode('utf-8'): v.decode('utf-8') for k, v in cart_data.items()}
그 결과로 print문으로 context도 잘 받아와 지는 걸 확인함
{"context":{"cart_items":{"americano":"2","Vanilla Ice Blended":"2"}}}
Docker - compose up build
docker image를 이용하여 postgreSQL, redis, celery를 구현하는 단계에서 오류가 발생
web-1 | python: can't open file '/app/manage.py': [Errno 2] No such file or directory-
→ 해당 파일이 분명히 존재하는 것을 확인했으나 실행 시 파일을 확인할 수 없다는 오류가 발생하는 문제가 해결이 되지 않아 다음 단계로 넘어갈 수 없는 상태
- 일시적인 해결 방안으로 볼륨 코드 주석 처리 하여 실행하면 실행이 되는 것을 확인
services:
web:
build: .
command: python3 manage.py runserver 0.0.0.0:8000
# volumes:
# - .:/app
ports:
- "8000:8000"
depends_on:
- db
- redis
db:
image: postgres:latest
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypassword
redis:
image: redis:latest
celery_worker:
build: .
command: celery -A SilverLining worker -l info
# volumes:
# - .:/app
depends_on:
- redis
celery_beat:
build: .
command: celery -A SilverLining beat -l info
# volumes:
# - .:/app
depends_on:
- redis
→ 그러나 이렇게 작성하면 변경된 수정사항을 최신화할 수 없어 항상 up —build를 해야 하는 문제가 발생
- 오류문제 해결 방안들→ 프로젝트파일 날리고 새로 clone
- → dockerFile 내용 수정
- → docker 삭제 후 설치
위의 방법들을 동원하여도 동일한 오류 발생
- 문제 해결 : 기존 외부 드라이브 E:에 설치되어 있던 프로젝트 파일을 c드라이브로 이동하여 docker를 생성하여 오류를 해결→ 변경 : C:/User/username/SilverLining/
- → 기존 : E:/python/SilverLining/
- 신한결튜터님 : docker 생성 시 발생되는 오류를 분석하여 해당 오류원인이 파일위치에 있는 것으로 판단하여 경로를 수정해 보기로 건의
- sd카드 폴더 밖으로 옮긴 후 실행 → 오류해결
- 결론 : 주 드라이브가 아닌 외부의 드라이브에서 docker를 생성하게 되면 오류가 발생할 경우가 생기므로 docker생성은 주 드라이브에서 생성하여야 한다는 것을 알게 됨
- 추가 : 기존 번역을 위해서 필요한 django-translation을 사용하기 위에서 gettext를 docker에도 설치하기 위해서는 아래와 같은 명령어를 추가로 dockerFile에 입력해 주어야 함
FROM python:3.8
# GNU gettext 설치
RUN apt-get update && apt-get install -y gettext
# 필요한 작업을 위한 작업 디렉토리 설정
WORKDIR /app
# 여기에 나머지 Dockerfile 명령어를 추가하세요.
# up 후에 입력 필요
docker-compose run --rm web bash
도커로 서버를 실행하게 되면 마이그레이트 및 번역 등록을 시켜주어야 함
#docker로 실행 후 해주어야 하는 것
#마이그래이션
docker-compose run --rm web python manage.py migrate
# 번역 등록
docker-compose run --rm web python manage.py compilemessgae
# 번역시 필요
docker-compose run --rm web bash
elder 음성인식 - Redis: update
- 중요변화
- views.py에 add_quantity() 추가
- Redis에 저장되는 형태를 변화
- 아래의 key는 cart_key로 불러온 value내의 field값과 같음
- {key: value} _ ex. Americano: 2
- {key: value} _ ex. Americano: {"quantity": , "price": , "img": }
- serializer에서 item_id 삭제
- Redis에서의 key(=field)를 “name”으로 사용하고 있기 때문에 별도로 item_id가 필요하지 않아서 삭제해 줌
# 수정 전_cart.py
def update_quantity(self, menu_name, quantity):
redis_conn = get_redis_connection("default")
redis_conn.hincrby(self.cart_key, menu_name, quantity)
# 수정 후_cart.py
def update_quantity(self, item_data):
redis_conn = get_redis_connection("default")
# dict 형식으로 받는다 : item_data {}
# "name"으로 해당 데이터 불러오고 "quantity"로 해당 value를 수정 ?
# 이렇게 해서 hset 으로 저장하나?
print("\n\n item_data 잘 넘어왔나: ", item_data)
name = item_data["name"]
print("\n\n name >>> ", name)
update_data = json.dumps(item_data)
redis_conn.hset(self.cart_key, name, update_data)
# 장바구니 항목 수량 수정
@csrf_exempt
@api_view(['POST'])
def add_quantity(request):
if request.method == "POST":
# username = request.user.username # 나중에 elder_menu에서 연결할 때 다시 구현
# print("\n\n request user >> ", request.user)
username = request.POST.get("username") # mega
print("\n\n add_quantity username: ", username)
name = request.POST.get("name") # 아메리카노
print("\n\n name은 잘 들어왔는지: ", name)
quantity = int(request.POST.get("quantity")) # 3
print("\n\n quantity 잘 들어왔는지: ", quantity)
cart = Cart(username)
# 1. name, quantity, price, img 다 redis에 저장하는 방식 {"name": {"quantity": , "price": , "img": }
# serializer 는 returnDict --> dict 로 바꿔서 사용
# 여기서 serializer로 dict 형식으로 보내준다 {"name": , "quantity": }
# menu = Menu.objects.get(store_id = request.user.id).filter(food_name = name)
menu = Menu.objects.filter(store_id = 2, food_name = name).first()
print("\n\n menu가 이렇게 가져오는 게 맞나: ", menu, menu.food_name, menu.img, menu.price)
image = menu.img
price = menu.price
item = CartItem(image, name, price, quantity)
serializer = CartSerializer(item)
item_data = serializer.data
# =============================
# 2. name, quantity 만 redis에 저장하는 방식 {"name": quantity}
# name과 quantity만 보내준다
# update_quantity에서 수정된 값 돌려주면
# quantity는 수정된 값으로, name 현재 name, 나머지 image, price 를 db에 접근해서 가져오기
cart.update_quantity(item_data)
return Response({"message": "장바구니 수량 수정"})
elder 음성인식 - Redis: delete
- 개별메뉴 삭제
- path("cart/remove/str:menu_name/", views.remove_from_cart, name="remove_from_cart")
- 단, 주의해야 할 점은 postman에서 post요청 시 menu name은 문자열 그대로 띄어쓰기까지 그냥 써주면 됨
- 예를 들어 Redis에 menu_name이 “Hot Chocolate”로 저장된 경우
- cart/remove/Hot_Chocolate (x)
- ``cart/remove/Hot Chocolate` (o)
- 즉, 문자열 그대로 요청을 보내야 오류가 나지 안
- 장바구니 전체삭제
- path("cart/clear/", views.clear_cart, name="clear_cart")
# views.py
# 장바구니 항목 제거 뷰
@csrf_exempt
@api_view(['POST'])
def remove_from_cart(request, menu_name):
print("\n\n remove_from_cart() 타는지>>>" )
# username = request.user.username
username = request.POST.get("username")
print("\n\n remove() username: ", username)
print("\n\n menu_name: ", menu_name)
cart = Cart(username)
cart.remove(menu_name)
return Response({"message": "해당 메뉴 삭제"})
# 장바구니 전체 삭제 뷰
@csrf_exempt
@api_view(['POST'])
def clear_cart(request):
# user_id = request.user.id
username = request.POST.get("username")
cart = Cart(username)
cart.clear()
return Response({"message": "장바구니 전체 삭제"})
# cart.py
## **Removing an Item**:
def remove(self, menu_name):
print("\n\n menu_name: ", menu_name)
redis_conn = get_redis_connection("default")
redis_conn.hdel(self.cart_key, menu_name)
## Clear the cart
def clear(self):
redis_conn = get_redis_connection("default")
redis_conn.delete(self.cart_key)
카트 - 장바구니 수정
- 기존에 코드를 수정하면서 변수명이 변경되어 데이터 값이 넘어가지 않는 문제 발생
- 변수명 selectedItems → cart로 변경
문제점 : 기존 selectedItems에 있는 변수 값을 받아오는 부분에서 값을 받아 오지 못하여 빈 값만 받아오게 됨
해당받아오는 부분까지 변수명을 변경하여 오류 해결
const selectedItem = {} # 삭제 맨 위에 let cart로 이용
# selectedItem를 cart로 변경
const selectedItemsArray = Object.entries(cart).map(([name, item]) => {
return {name: name, count: item.count};
});