이번엔 is_anonymous 와 is_authenticated 를 알아보겠습니다.


1. is_anonymous 소개 및 예시

is_anonymous는 로그아웃 여부를 묻습니다. 즉 그 유저가 로그아웃된 상태라면 True를 반환하게 됩니다. 
그렇다면 views.py 파일에선 어떻게 사용될까요?
 
1
2
3
4
5
6
if request.user.is_anonymous:
    pass
    # do something if user is logged out
else:
    pass
    # do something if user is logged in
cs


이렇게 사용될 수 있을 것입니다. 이번엔 templates 파일에서 어떻게 사용될지 알아봅시다.


1
2
3
{% if user.is_anonymous %}
    <p>This user is logged out</p>
{% endif %}
cs


이렇게 사용될 수 있습니다. django의 강력한 기능인 템플릿파일의 템플릿 태그에서도 그대로 쓰인다는게 참 편리합니다. 


2. is_authenticated 소개 및 예시

is_authenticated는 위에서 살펴 본 is_anonymous와 반대된다고 생각하시면 됩니다. 저 코드와 반대로 로그인 여부를 묻는 것입니다. 만약 로그인 되어 있다면 이 코드는 True를 반환합니다. 그렇다면 이 코드가 views.py에서 어떻게 사용되는지 알아볼까요?


1
2
3
4
5
6
if request.user.is_authenticated:
    pass
    # do something if user is logged in
else:
    pass
    # do something if user is logged_out
cs


이렇게 위와는 반대로 사용되게 됩니다. 그렇다면 템플릿 태그는 어떨지 알아보겠습니다. 


1
2
3
{% if user.is_authenticated %}
    <p>이 유저는 로그인 되어 있습니다.</p>
{% endif %}
cs


이렇게 사용되게 됩니다. 템플릿 태그를 이용하면 둘 중 하나만 알아도 대부분의 코드를 구현할 수 있을 것입니다. 


1
2
3
4
5
{% if user.is_authenticated %}
    <p>이 유저는 로그인 되어 있습니다.</p>
{% else %}
    <p>이 유저는 로그아웃 되어 있습니다..</p>
{% endif %}
cs


이렇게 사용하면 위에서 {% if user.is_authenticated %}가 False를 반환하게 될 경우 {% else %}안의 코드가 실행됩니다. 그러므로 한 가지만 알면 두 가지 경우를 다 처리할 수 있습니다.

3. is_active 소개 및 예시

그렇다면 is_active는 어떨까요? 우선 템플릿 태그는 이렇게 쓸 수 있습니다. 


1
2
3
4
5
{% if user.is_active %}
    <p>이 유저는 활성화 되어 있습니다.</p>
{% else %}
    <p>이 유저는 비활성화 되어 있습니다..</p>
{% endif %}
cs


is_active는 단순히 로그인, 로그아웃을 넘어서 그 유저의 현재 활동 가능 상태를 판단합니다. is_authenticated의 조금 더 발전된 버전입니다.



1. ASGI 소개

ASGI 는 django-channels 를 사용할 때 알아야 하는 개념입니다. ASGI는 Asynchronous Server Gateway Interface 의 줄임말입니다. django-channels가 사용하고 있는 Daphne와 django-channels가 작동하는 기반입니다. ASGI는 WSGI와 비슷한 구조를 갖고 있으며 반드시 비동기 통신만을 지원하지는 않습니다. 


ASGI : https://github.com/django/asgiref/blob/master/specs/asgi.rst


ASGI 어플리케이션은 스코프를 다루는 데에 사용됩니다. 스코프를 인자로 받게 됩니다. 스코프는 django-channels에서 하나의 연결 인스턴스이라고 설명할 수 있습니다. django-channels에서 하나의 연결이 이뤄지면 그 연결을 스코프 인스턴스라고 생각하고 진행하셔도 될 것 같습니다. 먼저 ASGI에서 사용하는 Application 클래스를 살펴보겠습니다.


1
2
3
4
class Application:
 
    def __init__(self, scope):
    async def __call__(self, receive, send):
cs


이 것이 Application 클래스입니다. 보시다시피 시작시에 scope 인자를 받습니다. scope를 전적으로 관리하는 것을 의미합니다. 그렇다면 이번엔 scope를 살펴보겠습니다. scope는 django의 Websocket 관련 문서에 설명이 나와있습니다. 


2. webSocket Connection Scope

django에서 scope설명 : https://github.com/django/asgiref/blob/master/specs/www.rst


여길 보시면 WebSocket 설명에서 Connection Scope에 대한 설명이 있습니다. 웹소켓 연결 스코프는 소켓이 살아있는 한 지속된다고 나와있습니다. 결국 스코프는 웹소켓 연결 인스턴스라는 뜻입니다. 스코프는 여러 메타데이터를 갖습니다. type, scheme, path, query_string, root_path, headers, client, server, subprotocols 등의 메타 데이터를 갖습니다. 자세한 설명은 위의 링크를 참조하시기 바랍니다.


3. 예제

1
2
async def send(self, message):
    await self.base_send(message)

cs


이제 이 method의 의미를 알 수 있습니다. scope에서 message 인자를 send 한다는 것입니다. 그 이유는 send를 받는 클래스가 인스턴스로 스코프를 갖고 있기 때문입니다. 그래서 이 method가 가능한 것입니다.

자세한 설명은 https://wikidocs.net/22 에서 찾으실 수 있습니다.


for문은 프로그래밍에서 매우 자주 쓰이는 문법입니다. 반복이 조건에 맞춰 이뤄지기 때문에 자주 쓰일 수 밖에 없습니다. 이번엔 for 문을 알아보도록 하겠습니다.


1. for문 기본

파이썬에서 for문은 기본적으로 이런 형태를 갖습니다.

for something in some_list_tuple_wordarray:
    go something
    some method()
cs


in 이후의 리스트, 튜플, 문자열이 for문의 내용을 반복할 횟수를 결정합니다. 만약 리스트의 요소가 30개라면 30번 for문을 반복하게 되는 것입니다. 


2. django에서 응용

django 에서 for 문은 어떻게 사용될지 알아보겠습니다.

제가 만약 사용되면 안되는 password의 리스트를 갖고 있다고 해보겠습니다.


BAN_PASSWORD = [
    'password''123456''1234567''12345678''123456789''1234567890''012345''0123456',
    '01234567''012345678''0123456789''hello''111111''aaaaaa'
]
cs


이렇게 지정되어선 안되는 password 리스트를 만들었습니다. 그 후 request로 입력받은 password 중 무언가가 이와 일치한다면 금지시킬 것입니다. 그러면 다시 render를 return 하도록 하겠습니다.


파이썬의 유용한 도구인 리스트와 for문을 결합한 형태를 사용하겠습니다. 이 방법은 리스트 내포라고 불립니다.


[what_you_want_to_do for i in some_list_tuple_wordarray if condition]
cs


리스트 내포는 for문을 통해 나온 결과를 즉시 리스트 형태로 구성합니다.

django 에서 써 보겠습니다.


match = [pswd for pswd in BAN_PASSWORD if pswd in password]
if match is True:
clue = {'message': 'it's banned password'}
return render(request, 'create.html', {'clue': clue})
cs


이렇게 하시면 됩니다. 그러면 만약 비밀번호가 위에 있는 목록에 부합하면 다시 render하여 return하게 됩니다.

위에서 clue는 create.html 템플릿에서 사용하기 위해 임의로 지정한 값입니다.

1. django form 서론

django 에서는 form 이라는 편리한 도구를 제공합니다. models.py에서 작성한 모델을 바탕으로 템플릿 파일에서 models.py의 model에 맞게 폼을 형성해주는 것입니다. 물론 django에서 제공하기에 views.py에서도 연동하여 이용할 수 있습니다. 여러가지 귀찮은 검사를 django에게 맡길 수 있습니다. 그리고 복잡한 html 코드를 단순하게 줄여줍니다.


django form documentation : https://docs.djangoproject.com/en/2.0/topics/forms/


2. django form 유무에 따른 html 코드 비교 

먼저 form을 사용하지 않았을 때의 html 코드입니다.

<form action="." method="post">
    {% csrf_token %}
    <div>
        <label for="name">username:</label>
        <input type="text" id="name" />
    </div>
    <div>
        <label for="mail">E-mail:</label>
        <input type="email" id="mail" />
    </div>
    <div>
        <label for="password">password:</label>
        <textarea id="password"></textarea>
    </div>
     <div>    
        <label for="password_confirm">password_confirm:</label>
        <textarea id="password_confirm"></textarea>
    </div>
    <div class="button">
        <button type="submit">Submit</button>
    </div>
</form>
cs


하나의 입력 폼을 작성할 때 두 줄의 코드를 필요로 하게 됩니다.


이번엔 form을 사용했을 때의 코드입니다.


<form class="form-horizontal" method="post" action=".">
 
  {% csrf_token %}
 
 
  <div class="form-group">
    <label class="control-label col-sm-2" for="id_username">Username(ID)</label>
    <div class="col-sm-10">{{ form.username }}</div>
  </div>
  <div class="form-group">
    <label class="control-label col-sm-2" for="id_email">Email</label>
    <div class="col-sm-10">
      {{ form.email }}
    </div>
  </div>
  <div class="form-group">
    <label class="control-label col-xs-2 col-sm-2" for="id_password">Password</label>
    <div class="col-sm-10">
      {{ form.password }}
    </div>
  </div>
  <div class="form-group">
    <label class="control-label col-xs-2 col-sm-2" for="id_password_confirm">Password</label>
    <div class="col-sm-10">
      {{ form.password_confirm }}
    </div>
  </div>
 
  <div class="form-group">
    <div class="col-xs-offset-5 col-sm-offset-5">
      <button class="btn btn-default" type="submit">Create</button>
    </div>
  </div>
</form>
cs


얼핏 보면 더 코드가 증가한 것 같지만 부트스트랩을 이용하기 위해 들어간 코드들을 제외하면 훨씬 더 간결해졌음을 알 수 있습니다.

특히 하나의 입력 폼이 {{ form.email }} 처럼 짧아지게 됩니다. 코드의 재사용시에도 {{ form.email }} 같이 짧게 이용할 수 있으니 효율적입니다.


3. models.py

form을 적용할 모델을 정하겠습니다. 위의 form은 기본 User모델을 참고로 만들었으므로 User모델을 확인해보겠습니다.

class User(AbstractUser):
cs


이러므로 이번엔 AbstractUser를 살펴보겠습니다.


class AbstractUser(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(
        _('username'),
        max_length=150,
        unique=True,
    )
    email = models.EmailField(_('email address'), blank=True)
cs


AbstractBaseUser 까지 살펴보면 이렇습니다.


@python_2_unicode_compatible
class AbstractBaseUser(models.Model):
    password = models.CharField(_('password'), max_length=128)
cs


결국 username과 email, password 까지 위에서 사용된 모든 필드를 찾을 수 있었습니다. 

이 필드들이 바탕이 되어 forms.py에서 형성되게 됩니다. 각각 CharField와 EmailField가 사용되었습니다.


4. forms.py

forms.py 에서는 사용할 모델을 가져오거나 django 의 form 을 가져와줘야 합니다.

from django import forms
from django.contrib.auth.models import User
cs

그 다음에 필요한 폼을 작성하시면 됩니다.


class UserCreateForm(forms.ModelForm):
    username = forms.CharField(widget=forms.TextInput(attrs={'class''form-control ''placeholder''Username'}))
    email = forms.EmailField(widget=forms.EmailInput(attrs={'class''form-control''placeholder''email'}))
    password = forms.CharField(widget=forms.PasswordInput(attrs={'class''form-control''placeholder''Password'}))
    password_confirm = forms.CharField(widget=forms.PasswordInput(attrs={'class''form-control',
                                                                         'placeholder''Password_confirm'}))
 
    class Meta:
        model = User
        fields = ['username''email''password']
 
cs


이렇게 작성했습니다. 위에서 언급한 것처럼 CharField와 EmailField가 그대로 사용되었습니다. widget은 html에서 어떤 입력창을 사용하는지를 결정합니다. 그 옆에 괄호 안의 'class', 'placeholder'는 그대로 html에서 반영되는 요소들입니다. 원하는대로 적용해주세요. 저는 부트스트랩을 이용중이니 'class'에 'form-control'을 추가해줬습니다.


4. views.py


views.py의 def create(request): 에서 이 form을 사용한다고 해 봅시다. 그렇게 되면 얼마나 편리한지 알 수 있게 됩니다.


def create(request):
    if request.method == 'POST':
 
        form = UserCreateForm(request.POST)
 
        username = form.data['username']
        email = form.data['email']
        password = form.data['password']
        password_confirm = form.data['password_confirm']
        if form.is_valid:
            pass
    else:
        form = UserCreateForm()
        return render(request, 'create.html', {'form': form})
cs


이게 다입니다. 폼은 request와 함께 views.py로 전송되는데 그 폼을 읽기 위해서 단지 form = UserCreateForm(request.POST) 이 한 줄이 필요합니다. 그 다음엔 form.is_valid 같은 유효성 검사도 django에서 다 지원해주니 원하시는 대로 사용하시면 됩니다.



django channels 는 웹소켓을 django project 에서 사용할 수 있게 해주는 도구입니다. 웹소켓 관련 유명 도구로는 Socket.io 가 있습니다. 그러나 사용언어는 파이썬이 아닌 자바스크립트입니다. channels는 최근 정식으로 django project 안에 들어가게 되었습니다. 

django project 의 channels 공식 채택 : https://www.djangoproject.com/weblog/2016/sep/09/channels-adopted-official-django-project/

channels 의 공식문서 : https://channels.readthedocs.io/en/latest/


1. channels를 쓰기위한 준비


django channels를 쓰기 위해선 몇 가지 파이썬 패키지를 다운받아야 합니다.

그 전에 파이썬 가상환경을 설정합니다.


$ mkdir example-channels
$ cd example-channels
$ python3.6 -m venv myenv
$ source myenv/bin/activate
cs


그 다음에 pip를 이용해서 패키지를 다운받습니다.


(myenv)$ pip install django==1.10.5 channels==1.0.2 asgi_redis==1.0.0
cs


asgi_redis는 redis를 쓸 경우에 필요하겠지만 이 경우엔 redis를 다운받아서 실행되는지 확인해보고 설치하는 것을 추천합니다. 그렇지 않다면 asgi_redis는 굳이 다운받지 않아도 됩니다. 다른 방법으로도 실행되게 할 수 있습니다. 만약 실행된다면 프로젝트와 앱을 생성합니다. 


(myenv)$ django-admin.py startproject example
(myenv)$ cd example
(myenv)$ python manage.py startapp exam
(myenv)$ python manage.py migrate
cs


2. django 설정


그 후 settings.py 에서 exam 앱과 channels를 등록합니다.


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'exam',
]
cs


settings.py 에 DATABASE 설정과 모양이 비슷한 설정을 넣어줍니다.


CHANNEL_LAYERS = {
    'default': {
        'BACKEND''asgi_redis.RedisChannelLayer',
        'ROUTING''exam.routing.channel_routing',
    }
}
cs


여기에 등록한대로 exam 앱에 routing.py를 만들어서 channel_routing을 만들어줍니다. 

그 전에 consumers.py는 이렇게 해줍니다. consumers.py는 exam앱 안에 위치시키는 것을 추천합니다.


from channels import Group
 
def ws_connect(message):
    Group('users').add(message.reply_channel)
 
def ws_disconnect(message):
    Group('users').discard(message.reply_channel)   
cs


그 다음에 routing.py를 만들어줘야 합니다. urls.py와 비슷한 역할을 수행합니다. websocket용 urls.py 라고 생각하시면 됩니다. 


from channels.routing import route
from exam.consumers import ws_connect, ws_disconnect
 
 
channel_routing = [
    route('websocket.connect', ws_connect),
    route('websocket.disconnect', ws_disconnect),
]   
cs


3. client에서 실행


이제 jquery를 이용해 websocket을 클라이언트에서 실행해줍니다. 클라이언트는 django 의 templates에 위치한 html 파일에서 동작합니다.


  <script>
    var socket = new WebSocket('ws://' + window.location.host + '/users/');
 
    socket.onopen = function open() {
      console.log('created.');
    };
 
    if (socket.readyState == WebSocket.OPEN) {
      socket.onopen();
    }
  </script>
cs


기본적인 설정이 끝났습니다. runserver 한 후 클라이언트 상에서 위 페이지에 접속하면 로그가 뜰 것입니다.


 WebSocket HANDSHAKING /users/
 WebSocket DISCONNECT /users/
cs


이런식으로 나오면 됩니다.

django 는 모델을 만들고 그 모델을 이용해 object를 만듭니다. object를 가져오는 방법은 여러가지가 있습니다. 그 중 object가 존재하지 않을 경우의 에러를 다루는 방법으로는 DoesNotExist의 도움을 받는 방법을 고를 수 있습니다.

from django.shortcuts import get_object_or_404, get_list_or_404
 
cs

물론 이런 shortcuts 를 가져와서 처리하는 방법도 있습니다. 하지만 이번엔 DoesNotExist를 써 보겠습니다.


1. DoesNotExist 사용방법

try:
    model_your_model = ModelYourModel.objects.get(you=happy)
except ModelYourModel.DoesNotExist:
    pass
cs


이렇게 DoesNotExist 를 이용해서 예외 구문을 만들게 됩니다.

pass 라고 쓴 자리에 예외가 발생할 경우 하고 싶은 걸 작성하면 됩니다. 


2. Backend 사용방법


그리고 django에서 authenticate 함수를 쓸 일이 있습니다. 이건 인증 함수입니다. settings.py에서 그 흔적을 찾을 수 있습니다.


AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
)
cs

이 부분입니다. 이 부분이 django 의 authentication 을 관장합니다. 이 ModelBackend를 살펴보겠습니다.

 

class ModelBackend:
 
    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        try:
            user = UserModel._default_manager.get_by_natural_key(username)
        except UserModel.DoesNotExist:
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user
 
    def get_user(self, user_id):
        try:
            user = UserModel._default_manager.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None
        return user if self.user_can_authenticate(user) else None
cs


authenticate 함수가 username 과 password 를 받아서 user를 return 하는 것을 알 수 있습니다. 

그러므로 이 backend를 받은 다음에 authenticate 함수를 재정의해 주면 인증에 사용할 수 있게 됩니다.


from django.contrib.auth.backends import ModelBackend
 
class CustomAuthBackend(ModelBackend):
 
    def authenticate(self, username=None, password=None):
        pass
cs


이런 식으로 모양을 맞춰주면 쓸만할 것입니다. 원하는대로 수정하여 사용가능합니다. 비밀번호만을 이용한 로그인도 구현할 수 있을 것입니다.

recaptcha 는 악의적인 공격을 막기 위한 도구입니다. 구글에서 제공하며 사진, 글자 등으로 로봇과 사람을 구별합니다.

https://www.google.com/recaptcha/ 에서 확인 가능합니다. 


1. views.py 설정

django 에서 recaptcha를 쓰려면 django의 views.py 에서 사용하면 됩니다.

url을 이용해서 request와 response를 다뤄야 하므로 몇 가지를 import해서 불러옵니다.


import urllib
from urllib.parse import urlparse
cs


urllib은 파이썬에서 제공하는 url관련 패키지입니다. urlparse는 그 중에서도 url을 바탕으로 파싱하는 작업을 수행하는 모듈입니다.

이렇게 쓸 도구를 불러와서 서버쪽에서 recaptcha와 데이터를 주고받아서 판단하면 됩니다. 

가입은 구글에서 다른 글들을 검색하면 잘 설명되어 있습니다.


        recaptcha_response = request.POST.get('g-recaptcha-response')
        url = 'https://www.google.com/recaptcha/api/siteverify'
        values = {
            'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY,
            'response': recaptcha_response
        }
        recaptcha_data = urllib.parse.urlencode(values).encode()
        recaptcha_req = urllib.request.Request(url, data=recaptcha_data)
        recaptcha_response = urllib.request.urlopen(recaptcha_req)
        recaptcha_result = json.loads(recaptcha_response.read().decode())
 
        if not recaptcha_result['success']:
cs


여기서 settings.GOOGLE_RECAPTCHA_SECRET_KEY 자리에 각자 부여받은 키를 쓰면 됩니다.

개발용 서버에서 이용하고 싶을 땐 127.0.0.1을 쓰면 자연스럽게 이용가능합니다.


2. html 템플릿 설정


html 태그에선 header등에 <script></script>를 써 주고 구글이 알려준 코드를 쓰면 됩니다.


<script src='https://www.google.com/recaptcha/api.js'></script>
cs

이것을 써주면 됩니다.


<div class="g-recaptcha" data-sitekey="some value that google gave to you"></div>
 
cs


html 템플릿에는 위의 div를 써주면 recaptcha를 유저가 클라이언트에서 볼 수 있고 클릭할 수 있게 됩니다.

1. models.py


models.py에서 사전설정을 합니다.


GAME_STATUS_CHOICES = (
    ("F""First turn Player"),
    ("S""Second turn Player"),
    ("W""First Player has Win"),
    ("L""Second Player has Win"),
    ("D""Draw")
)
cs


views.py에서 보내진 인자에 따라 models.py를 결정할 수 있게 됩니다.

각각 머리글자를 따서 튜플 형태로 작성합니다.


class GamesQuerySet(models.QuerySet):
    def games_user(self, user):
        return self.filter(
            Q(first_player=user) | Q(second_player=user)
        )
 
    def is_active(self):
        return self.filter(
            Q(status='F'| Q(status='S')
        )
 
    def drew_games(self):
        return self.filter(status='D')
cs

여기서 Q를 쓰고 filter를 바로 써 줄 수 있습니다. 좀 더 편리한 코드 작성이 가능합니다.


class Game(models.Model):
    first_turn_player = models.ForeignKey(User, on_delete=models.CASCADE, related_name="first_player")
    second_turn_player = models.ForeignKey(User, on_delete=models.CASCADE, related_name="second_player")
 
    start_datetime = models.DateTimeField(auto_now_add=True)
    end_datetime = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=1default='F', choices=GAME_STATUS_CHOICES)
 
    objects = GamesQuerySet.as_manager()
 
    def __str__(self):
        return "{0} vs {1}".format(self.first_player, self.second_player)
cs


데이터베이스 사용시엔 필연적으로 model을 지정해야 합니다.

manager도 사용해봤습니다. 그 후 이동에 관련된 모델을 지정합니다.


class Move(models.Model):
    x = models.IntegerField()
    y = models.IntegerField()
    comment = models.CharField(max_length=300, blank=True)
    by_first_player = models.BooleanField()
 
    game = models.ForeignKey(Game, on_delete=models.CASCADE)
cs


이렇게 이동하는 동작을 모델로 지정해줘도 괜찮습니다. 데이터베이스를 이용할 것이니 모델로 지정해주는 것이 ORM사용에도 유리할 것입니다.

유저의 기본정보를 묻는 모델도 만듭니다.

질문 모델은 이런식으로 할 수 있을 것입니다.


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('published')
 
    def recent_published(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1<= self.pub_date <= now
 
    recent_published.admin_order_field = 'pub_date'
    recent_published.boolean = True
    recent_published.short_description = 'recently created'
 
    def __str__(self):
        return self.question_text
cs


def recent_published(self):를 이용하여 최근에 작성되었는지에 대한 여부를 나타내는 함수를 지정했습니다.

유저는 여기에 있는 질문을 받게 됩니다. 그건 views.py를 이용해서 작성해주시면 됩니다. 

2. admin.py

admin에는 ModelAdmin class를 추가해줍니다.


@admin.register(Game)
class GameAdmin(admin.ModelAdmin):
    display = ("id""first_turn_player""second_turn_player""status")
    editable = ("status", )
 
admin.site.register(Move)
cs


게임플레이 자체는 따로 앱을 지정해서 만듭니다. views.py는 아래와 같이 쓸 수 있을 것입니다.


3. 플레이 관련 앱 - views.py


from gameplay.models import Game
 
@login_required
def home(request):
    my_games = Game.objects.games_for_user(request.user)
    active_games = my_games.active()
    drew_games = my_games.drew_games()
 
    return render(request, "home.html", {"num_games": Game.objects.count(),
                                                "games": active_games,
                                                "drew_games": drew_games})
cs


로그인한 사용자만 이용할 수 있게 한 것입니다.

active()와 drew_games()는 새로 지정해줘야합니다.

render시에 num_games에 게임의 객체의 수를 넣어줍니다. home.html에서 사용될 것입니다.


4. html 템플릿


부트스트랩을 쓰면 템플릿은 이렇게 할 수 있을 것입니다. 기본 로그인 화면입니다. 나머지 게임 화면은 원하시는대로 만드시면 됩니다. 기본 로그인 화면은 예시에 불과합니다.


    <body>
    <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation - 보이지 않는 요소입니다.</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <class="navbar-brand" href="/">게임 제목 입니다.</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
          <form class="navbar-form navbar-right" role="form">
            <div class="form-group">
<p><h2>이메일</h2></p>
              <input type="text" placeholder="Email" class="form-control">
            </div>
            <div class="form-group">
<p><h2>패스워드</h2></p>
              <input type="password" placeholder="Password" class="form-control">
            </div>
            <button type="submit" class="btn btn-success">Sign in or Login</button>
          </form>
        </div>
      </div>
    </nav>
 
    <div class="container">
 
        {% block content %}
        {% endblock %}
 
    </div>



1. settings.py

GeoDjango를 사용하기 위해선 INSTALLED_APPS 와 site_id를 설정해주어야 합니다.


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',
    'django.contrib.gis',
    'blog',
    'rest_framework',
    'rest_framework.authtoken',
    'rest_auth',
    'allauth',
    'allauth.account',
    'rest_auth.registration',
]
SITE_ID = 1
cs


이렇게 rest_framework랑 allauth를 쓰기 위해선 SITE_ID = 1 을 써줘야 합니다.

그리고 현재 사용하고 있는 rest_auth를 위해 settings.py 에 추가할 것이 또 있습니다.


REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
}


이렇게 추가해줘야 token으로 로그인이 가능합니다. 데이터베이스도 gis기능이 있는 걸로 바꿔줘야 합니다. postgresql의 postgis를 쓰겠습니다. 그러면 settings.py에 명시하도록 합니다. postgis 는 postgresql에서 위치 데이터를 위해 개조된 버전이라고 생각됩니다.

postgis : https://postgis.net/


DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': 'postgres',

'USER': 'foryoumydarling',

'PASSWORD': 'darlingdarling',

'HOST': '127.0.0.1',
'PORT': '5432',
}
}

이런식으로 database도 gis시스템에 맞게 설정해줘야 하는 것을 잊으면 안 됩니다. 127.0.0.1은 로컬호스트를 의미합니다. 컴퓨터 한 대로 다 실행할 것이니 127.0.0.1 로 지정해줍니다.


그 다음엔 url을 정리합니다.

2. urls.py

    url(r'^accounts/', include('allauth.urls')),
    url(r'^rest_auth/', include('rest_auth.urls')),
    url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
cs


나머지는 원하는 대로 만드시면 됩니다. 아래는 제 코드입니다.


    url(r'^$', TemplateView.as_view(template_name="home.html"), name='home'),
    url(r'^signup/$', TemplateView.as_view(template_name="signup.html"),
        name='signup'),
    url(r'^email-verification/$',
        TemplateView.as_view(template_name="email_verification.html"),
        name='email-verification'),
    url(r'^login/$', TemplateView.as_view(template_name="login.html"),
        name='login'),
    url(r'^logout/$', TemplateView.as_view(template_name="logout.html"),
        name='logout'),
    url(r'^password-reset/$',
        TemplateView.as_view(template_name="password_reset.html"),
        name='password-reset'),
    url(r'^password-reset/confirm/$',
        TemplateView.as_view(template_name="password_reset_confirm.html"),
        name='password-reset-confirm'),
 
cs


이런식으로도 url을 테스트 해 봤습니다. user_details.html은 제가 만들었습니다.

나머지 템플릿도 취향따라 바꾸셔도 됩니다. TemplateView.as_view가 모든 url에 적용되었으니 views.py에서 할 일은 상당히 줄어들 것입니다.


    url(r'^user-details/$',
        TemplateView.as_view(template_name="user_details.html"),
        name='user-details'),
    url(r'^password-change/$',
        TemplateView.as_view(template_name="password_change.html"),
        name='password-change'),
cs


이것도 써보려고 했습니다. password-reset 시에 url에 pk인자를 넘겼습니다. django 2.0 버전에선 pk 인자를 넘기는 방법이 조금 달라졌으니 유의하시기 바랍니다.


    url(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
        TemplateView.as_view(template_name="password_reset_confirm.html"),
        name='password_reset_confirm'),
 
    url(r'^rest-auth/', include('rest_auth.urls')),
    url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
    url(r'^account/', include('allauth.urls')),
    url(r'^admin/', include(admin.site.urls)),
    url(r'^accounts/profile/$', RedirectView.as_view(url='/', permanent=True), name='profile-redirect'),
cs


include를 통해 전체적인 url구조를 결정했습니다.

이렇게 까지 설정하니 더 설정할 것이 없었습니다. serializers.py를 만들겠습니다.


3. serializers.py

기본적으로 rest_framework에서 제공하는 serializers 모듈을 가져옵니다. serializer는 json같은 형태로 데이터를 변경시켜주는 역할을 합니다.

from rest_framework import serializers
from blog.models import Post
from django.contrib.auth.models import User
from drf_extra_fields.geo_fields import PointField
cs


serializer class를 만들도록 합니다.


class UserSerializer(serializers.ModelSerializer):
    related_postwriter = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    class Meta:
        model = User
        fields = ('username''email''related_postwriter')
cs


User 모델을 위한 serializer입니다. django의 form과 모양이 상당히 닮아있습니다.


class PostSerializer(serializers.ModelSerializer):
    author = serializers.ReadOnlyField(source='author.username')
    point = PointField(required=True) #이것은 선택사항, 사용자가 결정할 일이므로 신중하게 선택하자.
    class Meta:
        model = Post
        # fields = ('author', 'text', 'image', )
        fields = ('author''text''image''point''created_date')
    def create(self, validated_data):
        validated_data['author'= self.context['request'].user
        return super(PostSerializer, self).create(validated_data)
cs


create 같은 함수를 따로 지정해주어 어떻게 REST통신시에 serializer가 함수를 처리할 지 지정할 수 있습니다.

Post 모델을 위한 serializer입니다.

import 를 잊지 맙시다.


이렇게하면 django-rest-framework의 기본 설정이 끝납니다. 

끝으로, drf_extra_fields는 구글에 검색하면 다운받을 수 있습니다. Point를 편리하게 조작할 수 있게 해 줍니다.


drf_extra_fields : https://github.com/Hipo/drf-extra-fields

1. 준비물

GeoDjango는 django project의 일종으로 위치 데이터를 다룰 수 있게 해줍니다. 위치 데이터는 보통 구글맵을 이용한 좌표 데이터를 의미한다고 봐도 됩니다. 그 외에도 3d나 2d 좌표를 이용한 계산 등의 기능도 가능케 합니다.

GeoDjango : https://docs.djangoproject.com/en/2.0/ref/contrib/gis/


models.py 에서 설정하는 여러가지 방법이 있지만 PointField를 쓰기로 했습니다. 


class Post(models.Model):
    
    author = models.ForeignKey(User, related_name='related_postwriter')
    text = models.TextField(null = True, blank = True)
    image = models.ImageField(null = True, blank = True, upload_to='gogo')
    created_date = models.DateTimeField(
        default=timezone.now
        )
    point = models.PointField(blank=False, null=False)
    #여기서 PointField를 쓴다. PointField는 좌표를 저장하기에 좋습니다.
 
    def __str__(self):              # __unicode__ on Python 2
        return self.author
cs


이젠 PointField의 정보를 views.py에서 다룰 차례입니다.

그 전에 admin.py에 Post 모델을 등록합니다.


from .models import Post
 
admin.site.register(Post)
cs


2. views.py 설정


그리고 views.py 에서 import는 이렇게 해 줍니다.


from django.contrib.gis.geos import GEOSGeometry
from django.contrib.gis.measure import D
cs


ViewSet class를 만듭니다.


class PostViewSet(viewsets.ModelViewSet):
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticated]
    queryset = Post.objects.all()
cs


django-rest-framework 를 쓰고 있으니 import할 것이 있습니다. django-rest-framework를 더욱 편리하게 해줍니다.

django-rest-framework는 django 에서 RESTful API를 구성하기 쉽게 해 줍니다. 많이 사용되는 패키지입니다.

django-rest-framework : http://www.django-rest-framework.org/


from rest_framework import filters, viewsets, generics
 
cs


이러면 viewsets.ModelViewSet을 쓸 수 있습니다.


from blog.models import Post
 
cs

모델을 호출하지 않으면 동작하지 않습니다.


    def get_queryset(self) :
        lat = self.request.GET.get('user_lat''15')
        lon = self.request.GET.get('user_lon''13')
        userpoint = GEOSGeometry('POINT(' + lon + ' ' + lat + ')', srid=4326)
        self.result = []
        return self.result
cs


쿼리셋을 불러오는 일부분입니다. 여기에 옵션을 조금 더 추가합니다.


        while i<20:
            elasped_minutes = datetime.now() - timedelta(minutes=10*i)
            list_i = Post.objects.filter(point__distance_lte = (userpoint, D(m=i*500))).order_by("-created_date")
            if len(self.result) > 50:
                self.result = self.result[:50]
                break
            i += 1
cs



이렇게 되면 일정 거리에 있는 결과들을 결과값으로 가져오게 됩니다. 500m 단위로 필터를 반복하게 구성했습니다.


3. 작성자와 request.user 일치시키기

작성자는 글쓴 사람으로 등록되게 하겠습니다.

serializer의 self값에서 request요소를 받아내고 있음을 이용합니다.


    def perform_create(self, serializer):
        serializer.save(author=self.request.user)
cs


+ Recent posts