PyCharm manage.py 메뉴가 사라진 경우

Django 2018. 6. 21. 17:57 Posted by 김한별 behonestar

Django 프로젝트의 settings.py 경로를 변경하면 PyCharm에서 tools > Run manage.py Task... 메뉴가 사라져버린다.


이 경우 File > Settings > Languages & Frameworks > Django > Settings 필드의 settings.py 경로를 다시 잡아주면 된다.



댓글을 달아 주세요

Django logger 설정

Django 2018. 5. 10. 17:10 Posted by 김한별 behonestar

django logger 설정을 위해 setting.py에 아래와 같이 추가한다.


console로 출력하는 설정이다.


import logging.config


LOGGING = {

'version': 1,

'disable_existing_loggers': False,

'handlers': {

'console': {

'class': 'logging.StreamHandler',

},

},

'loggers': {

'': {  # root

'level': 'WARN',

'handlers': ['console'],

},

'qproducts': {  # django app

'level': 'INFO',

'handlers': ['console'],

'propagate': False,  # required to avoid double logging with root logger

},

},

}


logging.config.dictConfig(LOGGING)



아래와 같이 사용한다.


import logging


logger = logging.getLogger(__name__)



def sum(a, b):

    logger.info('sum() called.')

    return a+b


참고

- Django’s logging extensions


댓글을 달아 주세요

Django Custom Auth (로그인 인증, API 인증)

Django 2017. 1. 24. 13:54 Posted by 김한별 behonestar

로그인 인증 시나리오

  • 사용자 계정은 외부의 인증 서버를 통해 인증한다.
  • 인증 성공하면 Django Session에 User 등록하고 Token 반환한다.


사용자 계정 인증 모듈

외부 인증서버를 통해 username, password를 인증하는 custom 인증 모듈을 만듭니다.

# -*- coding: utf-8 -*-

from django.conf import settings

from django.contrib.auth import get_user_model

from cognitoidentityprovider.cognito_user import CognitoUser


import requests


User = get_user_model()



class MyBackend(object):

    """

    Return User instance containing access_token

    """


    def authenticate(self, username=None, password=None, **kwargs):

        if not username or not password:

            return None


        r = requests.get('https://auth.server.com/', auth=(username, password))

        if r.status_code != 200:

            return None


        try:

            user = User.objects.get(username=username)

        except User.DoesNotExist:

            return None


        return user


    def get_user(self, user_id):

        UserModel = get_user_model()

        try:

            return UserModel._default_manager.get(pk=user_id)

        except UserModel.DoesNotExist:

            return None



settings.py

django.contrib.auth.authenticate 함수를 호출하면, MyBackend를 통해 인증하도록 정의합니다.

AUTHENTICATION_BACKENDS = (

    'myapp.backend.MyBackend',

    'django.contrib.auth.backends.ModelBackend',

)


로그인

# -*- coding: utf-8 -*-

from __future__ import unicode_literals

from django.contrib.auth import authenticate, login

from rest_framework import status

from rest_framework import viewsets

from rest_framework.response import Response


import jwt

import time


JWT_SECRET = 'mysecretkey'



class UserViewSet(viewsets.ViewSet):

    authentication_classes = ()

    permission_classes = (AllowAny, )


    def signin(self, request):

        username = request.data.get('username')

        password = request.data.get('password')


        # 사용자 계정 인증 (MyBackend 사용됨)

        user = authenticate(username=username, password=password)

        if user is None:

            return Response(serializer.data, status=status.HTTP_400_BAD_REQUEST)


        # token 생성

        expire_ts = int(time.time()) + 3600

        payload = {'username': username, 'expire':expire_ts}

        token = jwt.encode(payload, JWT_SECRET, algorithm='HS256')


        if getattr(settings, 'REST_SESSION_LOGIN', True):

            # django session에 user 등록

            login(self.request, user)


        return Response(token)





API 인증 시나리오

  • API를 호출하면 Session 인증을 수행한다.
  • Session 인증에 실패하면 Token 인증을 수행한다.
    • token에서 expire를 추출하여 만료 여부를 검사한다.
    • token에서 username을 추출하여 user 객체를 반환한다.


Token 인증 모듈

# -*- coding: utf-8 -*-

from rest_framework import exceptions

from django.contrib.auth import get_user_model


import jwt

import time


User = get_user_model()

JWT_SECRET = 'mysecretkey'



def get_authorization_header(request):

    auth = request.META.get('HTTP_AUTHORIZATION', b'')

    if isinstance(auth, type('')):

        auth = auth.encode(HTTP_HEADER_ENCODING)

    return auth


class MyTokenAuthentication():

    def authenticate(self, request):

        token = get_authorization_header(request)

        if not token:

            return None


        # token 디코딩

        payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])


        # token 만료 확인

        expire = payload.get('expire')

        if int(time.time()) > expire:

            return None


        # user 객체

        username = payload.get('username')

        if not username:

            return None


        try:

            user = User.objects.get(username=username)

        except User.DoesNotExist:

            raise exceptions.AuthenticationFailed('No such user')


        return (user, token)


settings.py

Rest framework의 APIView에 의해 호출될 Session 인증과 Token 인증 모듈을 정의한다.

REST_FRAMEWORK = {

    'DEFAULT_AUTHENTICATION_CLASSES': (   # Session 인증 실패하면 Token 인증

        'rest_framework.authentication.SessionAuthentication',

        'myapp.authentication.MyTokenAuthentication',

    ),

    ...

}


API 구현

# -*- coding: utf-8 -*-

from rest_framework import viewsets

from rest_framework.response import Response



class ApiViewSet(viewsets.ViewSet):

    def myapi(self, request):

        return Response('authentication & permission passed')



정리

유저가 로그인에 성공하면 Django Session에 해당 유저가 등록되고 Token도 리턴됩니다.

API를 호출할 때 Session 인증을 사용할지 Token 인증을 사용할지는 유저의 몫입니다.

서버는 양쪽의 인증을 모두 대비하면 됩니다.


참고로 AJAX로 API를 호출할 때 Session 인증을 사용하려면 CSRF 토큰을 POST Body에 포함시키거나, X-CSRFToken을 Header에 포함시켜야 합니다. 자세한 방법은 여기를 참고하세요.


댓글을 달아 주세요

Django Rest Framework 인증 관련 설정

Django 2017. 1. 23. 20:31 Posted by 김한별 behonestar

Django 프로젝트의 settings.py에는 인증 관련 설정이 몇가지 있는데 각각 어떤 역할을 하는지 정리해보았습니다.

AUTHENTICATION_BACKENDS

로그인 시 username, password을 인증할 Backend를 정의합니다.

Django의 authenticate 함수에 의해 호출됩니다.

명시적으로 정의하지 않으면 기본값으로 아래의 설정이 적용됩니다.

AUTHENTICATION_BACKENDS = (

'django.contrib.auth.backends.ModelBackend',

)


Django로 로그인 기능을 만든다면 아래처럼 구현할 것입니다.

authenticate 함수가 실행되면 ModelBackend를 사용하여 인증을 합니다.

from django.contrib.auth import authenticate, login


def login_view(request):

    username = request.POST['username']

    password = request.POST['password']

    user = authenticate(username=username, password=password)


    if user is not None:  

        # 인증 성공 -> 로그인(session framework에 user 등록됨)

        login(request, user)

        ...

    else:

        # 인증 실패

        ...



DEFAULT_AUTHENTICATION_CLASSES

API가 호출됐을 때 session이나 token을 인증할 클래스들을 정의합니다.

Django Rest Framework의 APIView에 의해 호출됩니다.

명시적으로 정의하지 않으면 기본값으로 아래의 설정이 적용됩니다.

REST_FRAMEWORK = {

    'DEFAULT_AUTHENTICATION_CLASSES': (

        'rest_framework.authentication.SessionAuthentication',

        'rest_framework.authentication.BasicAuthentication'

    ),

}


아래 예제에서 list()에 해당하는 API를 호출하면, 가장 먼저 위의 인증 클래스들부터 실행됩니다.

from rest_framework import viewsets

from rest_framework.response import Response



class ExampleViewSet(viewsets.ViewSet):

    def list(self, request):

        return Response('authentication passed')


View에서 호출될 인증 클래스들을 특정하고 싶다면 authentication_classes 변수를 정의하면 됩니다.

from rest_framework import viewsets

from rest_framework.response import Response

from rest_framework.authentication import SessionAuthentication


class ExampleViewSet(viewsets.ViewSet):

    authentication_classes = (SessionAuthentication, )


    def list(self, request):

        return Response('authentication passed')


rest_auth 모듈의 인증 흐름

rest_auth 모듈의 로그인 소스코드입니다. (rest_auth.views.LoginView)

① GenericAPIView도 APIView를 상속하므로 DEFAULT_AUTHENTICATION_CLASSES에 정의된 인증을 거칩니다.

② serializer valid 체크 시 authenticate 함수가 호출되므로 AUTHENTICATION_BACKENDS에 정의된 인증을 거칩니다.

class LoginView(GenericAPIView):

    ...

    def login(self):

        #2. rest token 생성

        self.user = self.serializer.validated_data['user']

        self.token = create_token(self.token_model, self.user, self.serializer)


        #3. django.contrib.auth.login 호출 -> session에 user 등록됨

        if getattr(settings, 'REST_SESSION_LOGIN', True):

            login(self.request, self.user)

    ...

    def post(self, request, *args, **kwargs):

        self.serializer = self.get_serializer(data=self.request.data)


        #1. 인증

        self.serializer.is_valid(raise_exception=True)

            validate()

                _validate_username(username, password)

                    authenticate(username=username, password=password)


        self.login()


        #4. token 리턴

        return self.get_response()



댓글을 달아 주세요

[DRF] prefetch_related와 SerializerMethodField

Django 2016. 12. 16. 21:15 Posted by 김한별 behonestar

N:M 관계의 두 모델이 있다. Device 모델이 Group 모델에 대해 realted_name='devices'라고 정의하였기 때문에, Group Object는 'devices'라는 이름으로 관련된 Device 객체들에 접근할 수 있다.


class Group(models.Model):

    name = models.CharField(max_length=32, null=True)


class Device(models.Model):

    name = models.CharField(max_length=32, null=True)

    owner = models.ForeignKey(UserModel, on_delete=models.CASCADE)

    group = models.ManyToManyField(Group, related_name='devices')


Group을 Serializer하면서 관련된 Device들과 Device의 Owner의 정보까지 한 번에 Serializer하려고 한다. 


그러려면 해당 필드들을 Nested Serializer로 정의해야 한다. GroupSerializer 수행 시 1+N Select 증상이 발생하지 않도록 하려면 관련 객체들을 모두 Prefetch 해둬야 한다.


queryset = Group.objects.filter(name='my_group')

queryset = queryset.prefetch_related('devices')

queryset = queryset.prefetch_related('devices__owner')

        

serializer = GroupSerializer(queryset, many=True)

print(serializer.data)


Group Serializer의 구현을 보면... queryset에 관련 객체들이 prefetch 되어있기 때문에 DeviceSerializer와 UserSerializer 수행 시 별도로 select를 수행하지 않는다.


class GroupSerializer(serializers.ModelSerializer):

    devices = DeviceSerializer(many=True)


    class Meta:

        model = Group

        fields = ('id', 'name', 'devices')


class DeviceSerializer(serializers.ModelSerializer):

    owner = UserSerializer(many=True)


    class Meta:

        model = Device

        fields = ('id', 'name', 'owner')


serializer.SerializerMethodField를 사용하여 prefetch된 데이터를 가공하려는 경우를 보자.

마찬가지로 group.devices.all()을 호출했지만 실제로는 select가 발생하지 않고 prefetch된 데이터를 사용한다.


class GroupSerializer(serializers.ModelSerializer):

    devices = serializers.SerializerMethodField('get_devices_prefetch_related')


    def get_devices_prefetch_related(self, group):

        devices = group.devices.all()

        datas = []

        for device in devices: 

            data = {'id': device.id, 'name':device.name}

            datas.append(data)

        return datas


    class Meta:

        model = Group

        fields = ('id', 'name', 'devices')


댓글을 달아 주세요

윈도우에서 django mysql 설정하기

Django 2015. 12. 27. 17:32 Posted by 김한별 behonestar

MySQL-python 설치

1. MySQL-python 패키지 다운로드

https://pypi.python.org/pypi/MySQL-python


2. virtual environment 진입

venv/Scripts/activate


3. easy-install을 사용하여 패키지 설치

easy-install /path/to/MySQL-python-1.2.5.win32-py2.7.exe



Mysql Table 생성

CREATE DATABASE [dbname] CHARACTER SET utf8;



Django 설정

1. settings.py

DATABASES = {

    'default': {

        'ENGINE': 'django.db.backends.mysql',

        'NAME': 'dbname',

        'UESR': 'id',

        'PASSWORD': 'pw',

        'HOST': 'host',

        'PORT': 'port number'

    }

}


2. migration

python manage.py migrate


댓글을 달아 주세요