Django 3.0 ASGI > 실시간 TodoApp 만들어보기 - 1
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
Django 3.0 ASGI > 실시간 TodoApp 만들어보기 - 1
카테고리 없음
Django 3.0 ASGI > 실시간 TodoApp 만들어보기 - 1
1. 설치(Channels 공식사이트를 참고했다.)
https://channels.readthedocs.io/en/latest/tutorial/part_1.html
cmd > cd { project Folder} django-admin startproject config . django-admin startapp todoapp python -m venv myvenv cd myvenv cd scripts activate.bat cd .. cd .. (Project Path) python -m pip install --upgrade pip pip install django channels channels_redis code . (in vscode)
나의 경우 보통 초기 세팅은 cmd로 하는 편이다.
cmd를 통해 구성하고 싶은 폴더로 들어가서
django-admin startproject config .
을 통해 현재 폴더에 config라는 프로젝트를 생성한다.
그리고 django-admin startapp todoapp
을 통해 앱을 생성한다.
그 뒤,
python -m venv myvenv
를 통해 myvenv라는 가상환경을 만들고,
cd myvenv
cd scripts
activate.bat
를 통해 가상환경을 실행한 뒤, cd .. 으로 빠져나온다.
대충 이런 식으로 설정하고
python -m pip install --upgrade pip
를 통해 pip를 업그레이드 시키고,
pip install django channels channels_redis
를 통해 한 방에 원샷으로 장고, 채널스, 채널스레디스까지 모두 일괄 설치해준다.
그 다음에서야 Visual Code를
code . 이라는 명령어로 실행시킨다.
2. 세팅(초기 설정)
제일 먼저 필요한 것이 세팅이다.
# in settings.py # ... ALLOWED_HOSTS = ['*'] # ... # ... INSTALLED_APPS = [ # ... 'todoapp', # 추가 'channels', # 추가 ] # ... TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR,'templates')], # 추가 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] # ... WSGI_APPLICATION = 'config.wsgi.application' ASGI_APPLICATION = "config.routing.application" # 추가 CHANNEL_LAYERS = { # 추가 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', 'CONFIG': { "hosts": [('127.0.0.1', 6379)], }, }, } # .. STATIC 설정은 패스..
그리고 settings.py 옆에 routing.py를 작성해준다.
# config.routing # filepath : config/routing.py from channels.routing import ProtocolTypeRouter application = ProtocolTypeRouter({ # (http->django views is added by default) })
setting.py 중
ASGI_APPLICATION = "config.routing.application" # 추가
이 부분은 바로 위에 작성한 routing.application을 일컫는거다.
3. TodoApp 만들기
대충 이런 느낌의 체크박스 형식이면 좋을것 같다.
3-1 TodoApp 모델 만들기
장고는 뭘 하려면 제일 먼저 세팅, 그다음 모델, 그다음 뷰, URL 세팅, 그리고 나머지 디테일은 html에서 처리해야 하는것 같다.
제일 먼저 만들어야 할 것은 모델이다.
모델은 그냥 간단히 만들어보았다.
# todoapp.models from django.db import models # Create your models here. class ToDoAppModel(models.Model): main_text = models.TextField(verbose_name="메인 텍스트") is_checked = models.BooleanField(default=False) # todoapp.admin # admin.py ## admin에 위의 모델을 등록하자. from django.contrib import admin from todoapp.models import ToDoAppModel # Register your models here. admin.site.register(ToDoAppModel)
main_text가 있고, 위의 느낌의 check 박스용 체크 값이 있다. 디폴트 값이 있으므로, 그냥 따로 지정하지 않아도 된다.
3-2 Form 만들기
그다음은 뷰에 쓸 form 부터 만들어야겠다.
아무래도 귀찮으니까..
# todoapp.forms # 작성 from django import forms from todoapp.models import ToDoAppModel class TodoAppForm(forms.ModelForm): class Meta: model = ToDoAppModel fields = ('main_text',)
일단은 뷰에서 써보자.
3-3 View 만들기
index 페이지부터 띄어보자.
# views.py # todoapp.view from django.shortcuts import render from .forms import TodoAppForm # Create your views here. def index_view(request): form = TodoAppForm() context = { 'form':form, } return render(request,"todoapp/index.html",context)
파일 위치를 잘 해야하한다.(폴더를 새로 만들어서 해야한다.)
Document {% csrf_token %} {{ form.as_p }}
그 다음 url 을 연결해주자.
# urls.py # config.urls from django.contrib import admin from django.urls import path from todoapp.views import index_view urlpatterns = [ path('admin/', admin.site.urls), path('todoapp/', index_view,name="index"), ]
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
해준 뒤, 뙇
스무스하다. 아직까진.
물론 아직 저장이 되진 않는다.
모델 내 저장이 되는지 확인해보려면
shell을 통해 확인해볼 수 있다 아까 등록했었던 admin을 통해서도 확인 가능하다.
3-4 form 데이터 저장 및 저장 값 불러오기
이제 폼을 submit 하면서, 데이터를 저장하게끔 해보자.
아 그리고, 페이지 내에 결과값 들도 보여주도록 해보자.
# todoapp.views # views.py # 함수 수정 from .forms import TodoAppForm from .models import ToDoAppModel from django.shortcuts import render def index_view(request): form = TodoAppForm() # 폼을 불러오고 todo = ToDoAppModel.objects.all() # 모델 내 데이터들을 불러온다. if(request.method == 'POST'): # 작성하는 부분 form = TodoAppForm(request.POST or None) # request가 POST면, 폼 안에 요청값을 넣는다. if(form.is_valid()): form.save() # 폼을 저장하고 form = TodoAppForm() # 폼을 전부 비운다. context = { 'form':form, 'todo':todo, } return render(request,"todoapp/index.html",context)
todo 리스트를 출력하려면,
index.html 페이지도 손을 봐줘야한다.
Document .list li{ display: inline; font-size: 40px; vertical-align: middle; } .list input{ vertical-align: middle; } .list li .checked{ text-decoration: line-through; } {% csrf_token %} {{ form.as_p }} {% for content in todo %} {{ content.main_text }} {% endfor %}
(CSS도 조금 손봐줬다. Edit은 하려다가 포기했다.)
대충 이런 모양새가 나온다.
3-5 Delete버튼 작동
자 이제 저 Delete 버튼을 작동시킬 수 있게 해보자.
# todoapp.views # views.py 추가 from django.http import HttpResponseRedirect # 추가 # ... # ... def deletetodo(request, todo_id ): # todo_id 를 URL로부터 받아서 삭제토록 한다. item_to_delete = ToDoAppModel.objects.get(id=todo_id) item_to_delete.delete() return HttpResponseRedirect('/todoapp/') # config.urls # urls.py 추가 from todoapp.views import index_view, deletetodo # 추가 urlpatterns = [ path('admin/', admin.site.urls), path('todoapp/', index_view,name="index"), path('deletetodo//', deletetodo), # 추가 ]
url까지 한 방에 설정한다.
이 뜻은
todo_id로 View 단에 넘기되, 이 형식은 int로 하겠다. 라는 뜻으로 이해하면 되겠다.
그리고 index.html에 스크립트 부분을 조금 추가해준다.
Document .list li{ display: inline; font-size: 40px; vertical-align: middle; } .list input{ vertical-align: middle; } .list li .checked{ text-decoration: line-through; } {% csrf_token %} {{ form.as_p }} {% for content in todo %} {{ content.main_text }} {% endfor %} function href_location(id){ current = window.location.href window.location.href = '/deletetodo/' + id + '/' }
잘 들어가있다.
3-6 Todoapp 중간완성
자, 일단 간단한 TodoApp을 만들었다.
언뜻 실시간 처럼 보이지만,
실시간이 아니다.
4-1 Channels routing 설정(공식홈페이지를 참고하였음)
# todoapp.routing # routing.py from django.urls import re_path, path from . import consumer websocket_urlpatterns = [ path('ws/todoapp/', consumer.TodoConsumer), ]
todoapp 내에 routing.py 를 만든다
routing 은 url과 같은 기능을 한다.
consumer는 view와 같은 기능을 한다.
그래서, ws/todoapp/ 이라는 경로로 소켓이 접근 시 TodoConsumer를 실행시켜라
라는 의미와 같다.
이제 config.routing 내의 내용을 수정해보자.
# config.routing # routing.py from channels.routing import ProtocolTypeRouter from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter import todoapp.routing application = ProtocolTypeRouter({ 'websocket': AuthMiddlewareStack( URLRouter( todoapp.routing.websocket_urlpatterns ) ), })
대략적인 그림설명이다.
이제 위에서 정의한 TodoConsumer를 만들어보자.
4-2 TodoConsumer 작성
공식설명 내 기본틀
Function description def connect(self) 연결 시 호출되는 함수 def disconnect(self, close_code) 연결 해제시 호출되는 함수 def receive(self, text_data) 받을 수 호출되는 함수
정도다
이제 이걸 입맛에 맞게 바꿔보려 한다.
from asgiref.sync import async_to_sync from channels.generic.websocket import WebsocketConsumer import json class TodoConsumer(WebsocketConsumer): def connect(self): print('someone connected!') #'/ws/todoapp/' self.room_group_name = 'todousers' async_to_sync(self.channel_layer.group_add)( self.room_group_name, self.channel_name ) self.accept() def disconnect(self, close_code): async_to_sync(self.channel_layer.group_discard)( self.room_group_name, self.channel_name ) # Receive message from WebSocket def receive(self, text_data): text_data_json = json.loads(text_data) message = text_data_json['message'] print('receive_message') self.send(text_data=json.dumps({ 'message': message })) # Send message to room group async_to_sync(self.channel_layer.group_send)( self.room_group_name, { # running class below 'type': 'todo_message', 'message': message } ) # Receive message from room group def todo_message(self, event): message = event['message'] print('todo_message') # Send message to WebSocket self.send(text_data=json.dumps({ 'message': message }))
그리고, 정작 컨슈머를 만들어놓고 쓰질 못한다면 의미가 없다.
그래서 index.html에 사용하게끔 할 함수를 추가하고, 구동해본다.
Document .list li{ display: inline; font-size: 40px; vertical-align: middle; } .list input{ vertical-align: middle; } .list li .checked{ text-decoration: line-through; } {% csrf_token %} {{ form.as_p }} {% for content in todo %} {{ content.main_text }} {% endfor %} function href_location(id){ current = window.location.href window.location.href = '/deletetodo/' + id + '/' } var todoSocket = new WebSocket( 'ws://' + window.location.host + '/ws/todoapp/'); todoSocket.onmessage = function(e) { var data = JSON.parse(e.data); console.log(data); // var message = data['message']; }; todoSocket.onclose = function(e) { console.error('Chat socket closed unexpectedly'); }; function todoSocket_test(){ todoSocket.send(JSON.stringify({ 'message': 'testtest!!' })); }
자
이처럼 연결도 정상적으로 진행되고, 메세지도 잘 보내진다.
하지만, 메세지가 두 번씩 보내지는데 이 이유는 무엇일까?
프론트단(html)에서 만든 소켓은 요청(request)를 한다. (이러이러한 자료를 보낸다.)
그리고, 서버단(Consumer)에서는 이 요청을 받는다.(receive)
하기 때문에, receive가 먼저 실행된다.
그 뒤 설정한 그룹에 속한 이들에게 메세지를 전송하게 되며,
todo_message(self, evnet)를 호출하게 된다.
이 todo_message는 따로 분기하여,
삭제할건지 내용을 추가할 건지 따로 정의해 분기시킬 수 있게끔 해준다.
결국, 위에서 send가 두번 쓰였기 때문에 사용자가 두 번의 요청을 받는 것인데,
이를 구분해보자면,
send 종류 설명 self.send 전체 채널 이용자에게 전달 async_to_sync(self.channel_layer.group_send) 명명한 그룹 이용자에게만 전달
하는 역할을 한다.
지금은 채널 그룹을 하나만 쓸 예정이기에 뭘 써도 사실상 관계는 없어보인다.
이제 [추가, 삭제] 와 관련한 consumer를 작성해보자.
from http://nadure.tistory.com/14 by ccl(A)
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
댓글
댓글 쓰기