ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [WebPage] 실전 웹 프로젝트 구축 !!(Server Part 1) - 웹 페이지 제작 강좌
    Web/WebPage 2015. 7. 8. 14:22

    이전 장에서의 예제를 뒤로하고 타임라인 서비스를 개발해보도록 하겠습니다.



     




    연결된 강좌이므로 아래 링크대로 따라주시면 이해가 더 쉬우실 것 입니다.

    ===========================================


    1. 개발환경설정

    2. CSS & Bootstrap 

    3. CSS 선택자(Selector)

    4. CSS 요소(Element)

    5. Bootstrap Part. 1

    6. Bootstrap Part. 2

    7. 실전 예제 - 개인용 포트폴리오 페이지 제작

    8. Python & Django

    9. Django

    10. 장고(Django) 프로젝트

    11. JavaScript & jQuery

    12. 실전 웹 프로젝트(예제)


    13. 실전 웹 프로젝트(Timeline) Server Part.1


    ===========================================






    흔히 쓰이는 페이스 북의 기능 축소판을 구현한다고 보시면 됩니다.


     구현하기 위한 기능들은 아래와 같습니다.

     - 글 관리

     - 글 쓰기

     - 글 지우기

     - 글 Like 하기

     - 회원 관리

     - 회원 가입

     - 비밀번호 수정

     - 정보 수정

     - 프로필 관리

     - 프로필 등록

     - 프로필 수정

     - 프로필 조회

     - 로그인






    서버 구현으로 본격적으로 시작하겠습니다.




    1. cmd 창을 열어 장고 프로젝트 생성합니다.




     cd \

     mkdir work

     cd work

     django-admin.py startproject timelineproject

     cd timelineproject

     manage.py runserver







     장고(Django)에서는 기본적으로 8000번 포트를 사용하여 서버를 구동합니다. 따라서 자신의 컴퓨터를 뜻하는 127.0.0.1의 8000번 포트라는 뜻의 127.0.0.1:8000으로 접속하면 아래와 같은 화면을 볼 수 있습니다.









    2. 을 생성하겠습니다.




     manage.py startapp timeline

     cd timeline

     dir











    3. 프로젝트를 설정해야 합니다. Aptana Studio를 열어서 콘솔에서 만든 프로젝트를 추가합니다.






     File -> New -> PyDev Prject 를 선택합니다.







    name은 TimelineProject로 지정하고 Use default 를 체크해제하여 방금 생성한 폴더 경로를 지정해줍니다. 다음 Finish.













    4. 생성된 프로젝트를 오른쪽 클릭하여 PyDev-> Set as Django Project를 선택합니다.

















    5. settings.py 를 수정합니다. 해당되는 부분을 아래와 같이 수정합니다.





     (* Databases, Installed_apps, Middleware_classes, Staticfiles_dirs,template_dirs )


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
     
    DATABASES = {
        'default': {
            'ENGINE''django.db.backends.sqlite3'# Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
            'NAME''database',                      # Or path to database file if using sqlite3.
            'USER''',                      # Not used with sqlite3.
            'PASSWORD''',                  # Not used with sqlite3.
            'HOST''',                      # Set to empty string for localhost. Not used with sqlite3.
            'PORT''',                      # Set to empty string for default. Not used with sqlite3.
        }
    }
     
     
    INSTALLED_APPS = (
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sites',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        # Uncomment the next line to enable the admin:
        'django.contrib.admin',
        # Uncomment the next line to enable admin documentation:
        # 'django.contrib.admindocs',
        'timeline',
    )
     
    MIDDLEWARE_CLASSES = (
        'django.middleware.common.CommonMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        #'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        # Uncomment the next line for simple clickjacking protection:
        # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
    )
    cs




    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import os
    # Additional locations of static files
    STATICFILES_DIRS = (
        # Put strings here, like "/home/html/static" or "C:/www/django/static".
        # Always use forward slashes, even on Windows.
        # Don't forget to use absolute paths, not relative paths.
        os.path.join(os.path.dirname(__file__), '..''static'),
    )
     
     
    TEMPLATE_DIRS = (
        # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
        # Always use forward slashes, even on Windows.
        # Don't forget to use absolute paths, not relative paths.
        os.path.join(os.path.dirname(__file__), '..''templates'),
    )
    cs











    6. 모델을 생성할 차례입니다. Models.py를 열어 작성합니다.







    1
    2
    3
    4
    5
    6
    7
    from django.db import models
    from django.contrib.auth.models import User
    # Create your models here.
     
    class UserProfile(models.Model):
        user = models.OneToOneField(User)
     
    cs




     User 모델을 사용하기 위해 import를 하고 UserProfile 모델은 User에서 제공하지 않는 정보를 다루기 위한 모델로 1:1의 관계를 갖습니다. 

     

     다음으로 UserProfile에 들어갈 정보에 대한 필드(nickname, comment, country, url, ignores)를 작성하겠습니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    from django.db import models
    from django.contrib.auth.models import User
    # Create your models here.
     
    class UserProfile(models.Model):
        user = models.OneToOneField(User)
        nickname = models.CharField(max_length = 128)
        comment = models.TextField()
        country = models.CharField(max_length = 128, blank = True)
        url = models.CharField(max_length = 128, blank = True)
        ignores = models.ManyToManyField(User, related_name = 'ignore_set', blank = True, null = True)
        
    cs





     다음으로 사용자가 쓴 메시지가 저장될 Message 모델을 만들어 보도록 하겠습니다.



    1
    2
    3
    4
    5
    class Message(models.Model):
        user = models.ForeignKey(User)
        message = models.CharField(max_length = 128)
        created = models.DateTimeField(auto_now_add = True)
        
    cs


     각 사용자가 보낸 Message 와의 관계를 통해 메시지가 누가 언제 작성했는지 알수 있습니다.





     다음으로 Like 모델을 작성하겠습니다.



    1
    2
    3
    4
    class Like(models.Model):
        user = models.ForeignKey(User)
        message = models.ForeignKey('Message')
        
    cs




     다음으로 콘솔에서 mange.py syncdb를 실행해서 모델이 정상적으로 생성되었는지 확인합니다. 간단히 계정 생성을 해주시길 바랍니다.



     







    7. 관리자 기능을 제공하기 위해 urls.py를 수정합니다. 간단히 아래와 같이 주석 제거해주시면 됩니다.







    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    from django.conf.urls import patterns, include, url
     
    # Uncomment the next two lines to enable the admin:
    from django.contrib import admin
    admin.autodiscover()
     
    urlpatterns = patterns('',
        # Examples:
        # url(r'^$', 'timelineproject.views.home', name='home'),
        # url(r'^timelineproject/', include('timelineproject.foo.urls')),
     
        # Uncomment the admin/doc line below to enable admin documentation:
        # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
     
        # Uncomment the next line to enable the admin:
        url(r'^admin/', include(admin.site.urls)),
    )
     
    cs









    8. admin.py 파일을 만들어서 각 모델을 매핑(Mapping)해줘야 합니다. 아래와 같이 작성하여서 app 폴더에 넣어줍니다.






     admin.site.register 함수를 통해 관리자 모듈에 등록할 모델을 추가하면 됩니다. User 모듈의 경우 자동으로 admin에 추가되어 있으니 걱정하지 않으셔도 됩니다.




    1
    2
    3
    4
    5
    6
    7
     
    from django.contrib import admin
    from timeline.models import *
     
    admin.site.register(Message)
    admin.site.register(Like)
    admin.site.register(UserProfile)
    cs









    9. admin 사이트에 접속하여 제대로 매핑이 되어 아래처럼 나오는 지 확인합니다.

     http://localhost:8000/admin/





















    10. 간단하게 User Profile을 생성해줍니다.











    기본 폼으로 나오기 때문에 어떤 사용자가 추가되었는 지 자세히 나오지 않습니다. 아래코드를 수정하여 화면을 바꿔봅시다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class UserProfile(models.Model):
        user = models.OneToOneField(User)
        nickname = models.CharField(max_length = 128)
        comment = models.TextField()
        country = models.CharField(max_length = 128, blank = True)
        url = models.CharField(max_length = 128, blank = True)
        ignores = models.ManyToManyField(User, related_name = 'ignore_set', blank = True, null = True)
        
        def __unicode__(self):
            return "%s"%(self.user,)
    cs





    __unicode__ 부분 추가로 사용자 아이디를 보이게 했습니다.















    11. 다음으로 로그인 구현을 하도록 하겠습니다. urls.py를 열어 추가하도록 하겠습니다.







    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    from django.conf.urls import patterns, include, url
    from timeline.views import *
     
    # Uncomment the next two lines to enable the admin:
    from django.contrib import admin
    admin.autodiscover()
     
    urlpatterns = patterns('',
        # Examples:
        # url(r'^$', 'timelineproject.views.home', name='home'),
        # url(r'^timelineproject/', include('timelineproject.foo.urls')),
     
        # Uncomment the admin/doc line below to enable admin documentation:
        # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
     
        # Uncomment the next line to enable the admin:
        url(r'^admin/', include(admin.site.urls)),
        
        url(r'^api/timeline/$', timeline_view),
    )
     
    cs




     urls.py에 입력한 view를 생성하기 위해 views.py를 열어 timeline_view를 생성합니다.


    1
    2
    3
    4
    5
    6
     
    from django.http import HttpResponse
    from timeline.models import *
     
    def timeline_view(request):
        return HttpResponse('Hello World')
    cs





     접속 결과 간단한 View가 생성되었습니다.









     하지만 회원제로 운영해야 하므로 HTTP Basic Authentication 인증을 통해 인증 기능을 추가하도록 하겠습니다.



     단계별로 인증 절차를 살펴봅시다.

      1) 사용자는 브라우저를 통해 서버에 페이지를 요청

      2) 서버는 사용자의 요청에 인증 정보가 들어 있는지 확인

      3) 인증 정보가 확인이 되어 올바르다면 요청한 페이지를 전송

      4) 인증 정보가 없거나 틀리면 사용자에게 인증 요청 페이지를 전송






     12. 401 에러를 통해 페이지를 수정합니다.






    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from django.http import HttpResponse
    from timeline.models import *
     
    def timeline_view(request):
        response = HttpResponse()
        
        response.status_code = 401
        response['WWW-Authenticate'= 'Basic realm="timeLine Service"'
        
        return response
    cs





     인증 화면에 대해 묻는 화면으로 바뀌었습니다.














    13. 인증을 하기위한 인증 정보를 볼수 있는 코드를 더 추가하여 결과를 살펴보도록 하겠습니다.





    * 10번째 라인의 if 문을 유심히 살펴보세요! 자신의 아이디와 비번을 입력하라는 뜻입니다!


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    from django.http import HttpResponse
    from timeline.models import *
    import base64
     
    def timeline_view(request):
        if 'HTTP_AUTHORIZATION' in request.META:
            auth = request.META['HTTP_AUTHORIZATION']
            key = base64.decodestring(auth.split(' ')[1])
            id, pw = key.split(':')
            if id == 'your_id' and pw == 'your_pwd':
                return HttpResponse('Hello id: %s, pwd: %s' %(id, pw))
        
        response = HttpResponse()
        
        response.status_code = 401
        response['WWW-Authenticate'= 'Basic realm="timeLine Service"'
        
        return response
    cs




     계정을 입력해서 로그인이 되면 아래와 같은 화면이 뜹니다.





    하지만 하나의 계정만으로 인증이 가능한 위 방법은 비효율적입니다.


    데코레이터를 만들어서 위와 같은 인증시 문제를 풀어보도록 하겠습니다.







    14. 아래와 같은 코드를 views.py에 작성합니다.







    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    from django.http import HttpResponse
    from django.contrib.auth import authenticate, login, logout
    from timeline.models import *
    import base64
     
    def need_auth(functor):
        def try_auth(request, *args, **kwargs):
            if 'HTTP_AUTHORIZATION' in request.META:
                basicauth = request.META['HTTP_AUTHORIZATION']
                user = None
                try:
                    b64key = basicauth.split(' ')[1]
                    key = base64.decodestring(b64key)
                    (username,pw) = key.split(':')
            
                    user = authenticate(username=username,password=pw)
                except:
                    pass
     
                if user is not None:
                    login(request, user)
                    request.META['user'= user
                    return functor(request, *args, **kwargs)
     
            logout(request)
            response = HttpResponse()
            response.status_code = 401
            response['WWW-Authenticate'= 'Basic realm="timeLine Service"'
            return response
        return try_auth
     
    @need_auth
    def timeline_view(request):
        return HttpResponse('Hello world')
    cs










    15. 이제 본격적으로 API를 구현 할 차례입니다. 사용자 추가하기 API부터 생성해보도록 하겠습니다.






     - 사용자 추가하기: api/user/create/


     URI

     Method 

     api/user/create/

     POST 

     사용자를 추가하는 API 입니다. 아이디와 비밀번호를 파라미터로 받고 유저를 만들어 줍니다.

     Input Parameter

     username - 만들고자 하는 아이디

     password - 비밀번호

     name - 사용자 이름(optional)


     ex)

     username = palpit

     password = papitpw

     name = Yeonsu

     Output

     1. 성공 시 { 'status': 'create success' }

     2. 중복 아이디 {'status': 'duplicate id'} - 400

     3. 에러 (잘못된 파라미터 , 여백 등) { 'status': 'create fail' } - 400





    먼저 url을 추가하기 위해 urls.py 을 수정하도록 하겠습니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    from django.conf.urls import patterns, include, url
    from timeline.views import *
     
    # Uncomment the next two lines to enable the admin:
    from django.contrib import admin
    admin.autodiscover()
     
    urlpatterns = patterns('',
        # Examples:
        # url(r'^$', 'timelineproject.views.home', name='home'),
        # url(r'^timelineproject/', include('timelineproject.foo.urls')),
     
        # Uncomment the admin/doc line below to enable admin documentation:
        # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
     
        # Uncomment the next line to enable the admin:
        url(r'^admin/', include(admin.site.urls)),
     
        url(r'^api/timeline/$', timeline_view),
        url(r'^api/user/(?P<method>create)/$', user_view),
    )
     
    cs



     다음으로 뷰에 user_view를 생성해 줍니다. views.py에 아래 코드를 추가합니다.


    1
    2
    3
    4
    5
    6
    7
     
    def user_view(request, method):
        if method == 'create' and request.method == 'POST':
            return HttpResponse('OK')
        else:
            return HttpResponse('bad request', status = 400)
     
    cs




    이제 접속을 통해 확인해보도록 하겠습니다. http://localhost:8000/api/user/create 에 접속합니다.







    주소창에 입력해서는 GET 요청밖에 할 수 밖에 없으므로 bad request밖에 뜨지 않을 것입니다. 


    이런 상황을 타파하기 위해서 Rest Console을 통해 해소할 것 입니다.



    아래 주소를 통해 Rest Console을 설치합니다.

    http://www.restconsole.com


    Chrome에 추가하는 형식이므로 설치가 간단하므로 스크린 샷을 찍진 않았습니다.




    설치를 다하셨다면 Rest Console을 실행을 시켜줍니다.



    Rest Console의 Request URI에 요청하고자 하는 주소를 입력합니다.


    http://localhost:8000/api/user/create/ 를 입력합니다. (뒤에 슬래시 붙이는 거에 유의하세요!)


    전송방식은 POST로 설정하고 Send를 눌러주세요.







    이렇게하면 자동으로 스크롤이 아래로 내려가면서 어떠한 결과가 나왔는 지 Response에 나오게 됩니다.




    이제 본격적으로 user create api를 완성 해 보도록 하겠습니다.



    Rest Console에 보면 Request Payload부분에 key : value 형식으로 원하는 파라미터를 넣어서 Request 할 수 있습니다. 생성하고자 하는 유저 이름과 비밀번호를 설정해놓겠습니다. (아직 Send 를 누르지 마세요)







    views.py에 user_view부분을 아래와 같이 코드를 작성합니다.





    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
     
    def user_view(request, method):
        if method == 'create' and request.method == 'POST':
            try:
                username = request.POST.get('username')
                password = request.POST.get('password')
                if User.objects.filter(username__exact=username).count():
                    return HttpResponse('duplicate id'400)
                user = User.objects.create_user(username, password=password)
                user.first_name = request.POST.get('name''')
                user.save()
                profile = UserProfile()
                profile.user = user
                profile.save()
                return HttpResponse('create success')
            except:
                return HttpResponse('bad request', status=400)
        else:
            return HttpResponse('bad request', status=400)
     
    cs




    다음으로 Rest Console로 돌아와서 Send를 눌러 확인을 해보면 아래와 같이 Response Preview에 성공을 알리는 문구가 나와야 합니다.










    16. 생성에 관한 부분을 생성했으니 이제 사용자 비밀번호 변경 api를 생성해보도록 하겠습니다.






     - 사용자 비밀번호 변경하기: api/user/update/


     URI

     Method 

     api/user/update/

     POST 

      아이디와 비밀번호를 기반으로 비밀번호를 수정 할 수 있는 API 입니다.

     Input Parameter

     username - 수정하고자 하는 아이디

     oldpassword - 수정 하고자 하는 아이디의 비밀번호

     newpassword - 바꾸고 싶은 비밀번호

     name - 사용자 이름(optional)


     Output

     1. 성공 시 { 'status': 'updated' }

     2. 잘못된 비밀번호 {'status': 'wrong password'} 

     3. 에러 (잘못된 파라미터 , 여백 등) { 'status': 'bad request' } 






     urls.py를 열어 아래 코드를 추가합니다.



    1
    2
        url(r'^api/user/(?P<method>update)/$', user_view),
     
    cs




    생성에 이어서 update 도 user_view를 통해 접근하므로, views.py에 user_view 부분에 코드를 추가해줍니다.

    if 부분은 위에서 작성한 create 부분이므로 생략했습니다!!


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    def user_view(request, method):
        if method == 'create' and request.method == 'POST':
            skip this source...    
     
        elif method == 'update' and request.method == 'POST':
            try:
                username = request.POST.get('username')
                password = request.POST.get('password')
                newpassword = request.POST.get('newpassword')
                user = User.objects.get(username__exact = username)
                if user.check_password(password) is False:
                    return HttpResponse('wrong password', status = 400)
                else:
                    user.set_password(newpassword)
                    user.first_name = request.POST.get('name', user.first_name)
                    user.save()
            except:
                return HttpResponse('bad request', status=400)
            return HttpResponse('updated')
        else:
            return HttpResponse('bad request', status=400)
    cs




     이렇게 작성을 하고 Rest Console을 통해 이전에 생성한 계정의 비밀번호를 변경해보도록 하겠습니다.



    Request URI를 http://localhost:8000/api/user/update/ 설정하고


    POST방식으로 그대로 유지하시고


    Request Payload 에 newpassword를 추가해서 작성합니다.







    send를 눌러 확인해봅니다.


    아래 화면과 같이 updated란 문구가 나와야 정상 작동한 것입니다.










    이번 장이 너무 길어져서 다음 장에서 이어서 서버 구축하겠습니다.




     * 본 포스팅은 이재근 등 4명 저 "Fast Web Service Build up: 웹 서비스를 쉽고 빠르게 구축하는 기술" 저서를 참고하여 작성하였습니다.

    댓글

Designed by Tistory.