django 07. 세번째 장고앱 04. 사진 업로드 모델 생성 / MEDIA_URL 설정

|

파이썬 웹 프로그래밍 - Django로 웹 서비스 개발하기
세번째 장고앱 ‘킬로그램’에 이미지 업로드 모델을 구현한다.

이미지 업로드 구현하기

  • Photo 모델을 만들어 이미지 업로드 하고,
  • 업로드한 이미지가 MEDIA_URL을 사용해서 저장이 되고 잘 보여지는지 확인한다.

media url 설정하기

  • mysite/settings.py 수정
# Media files - 업로드를 하는 URL과 디렉토리 설정
MEDIA_URL = '/files/' # 업로드 할 경로
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') #로컬 디렉토리 어디에 저장할 것인지

photo model 생성하기

kilogram/model.py 수정

from django.db import models
from django.conf import settings
# Create your models here.

def user_path(instance, filename): #파라미터 instance는 Photo 모델을 의미 filename은 업로드 된 파일의 파일 이름
    from random import choice
    import string # string.ascii_letters : ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
    arr = [choice(string.ascii_letters) for _ in range(8)]
    pid = ''.join(arr) # 8자리 임의의 문자를 만들어 파일명으로 지정
    extension = filename.split('.')[-1] # 배열로 만들어 마지막 요소를 추출하여 파일확장자로 지정
    # file will be uploaded to MEDIA_ROOT/user_<id>/<random>
    return '%s/%s.%s' % (instance.owner.username, pid, extension) # 예 : wayhome/abcdefgs.png

class Photo(models.Model):
    image = models.ImageField(upload_to = user_path) # 어디로 업로드 할지 지정
    owner = models.ForeignKey(settings.AUTH_USER_MODEL) # 로그인 한 사용자, many to one relation
    thumname_image = models.ImageField(blank = True) # 필수입력 해제
    comment = models.CharField(max_length = 255)
    pub_date = models.DateTimeField(auto_now_add = True) # 레코드 생성시 현재 시간으로 자동 생성

pillow 설치

  • ImageField 사용시 에러가 발생하므로 pillow 패키지를 설치한다.
    (Cannot use ImageField because Pillow is not installed.)
$ pip install pillow

migrate 수행

$ python manage.py makemigrations
$ python manage.py migrate

admin.py 수정 및 admin을 통한 확인

from .models import Photo

# Register your models here.
admin.site.register(Photo)

media url 을 static url로 설정하기

  • admin에서 업로드한 이미지 경로로 접속하면 Page not found 문제 발생 (배포관련 문제)
  • 디버깅모드에서는 해당 문제 해결 옵션이 존재 (디버그 모드에서만 동작)
  • mysite/urls.py 수정
from django.conf import settings
from django.conf.urls.static import static

# 어떤 URL을 정적으로 추가할래? > MEDIA_URL을 static 파일 경로로 추가
# 실제 파일은 어디에 있는데? > MEDIA_ROOT 경로내의 파일을 static 파일로 설정
urlpatterns +=
  static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),


django 07. 세번째 장고앱 03. 회원가입

|

파이썬 웹 프로그래밍 - Django로 웹 서비스 개발하기
세번째 장고앱 ‘킬로그램’에 회원가입 기능을 구현한다.

auth - 회원 가입 구현하기

model

역시 모델의 수정은 필요 없다. (내장된 모델 사용)

mysite/urls.py 수정

  • 장고의 auth.urls에는 회원 가입 url이 따로 없기 때문에 구현해 주어야 한다.
    • 회원가입의 경우, view, template 경로가 따로 준비되어 있지 않다. (로그인, 로그아웃과 다름)
  • 추후에 settings.py에 관련 속성 값들도 몇가지 추가를 해 줄 예정.
urlpatterns = [
  url(r'^accounts/signup$', kilogram_views.CreateUserView.as_view(), name = 'signup'),
  url(r'^accounts/login/done$', kilogram_views.RegisteredView.as_view(), name = 'create_user_done'),
]

내장폼 CreateUserForm을 활용해서 CreateUserView (CBV) 만들기

  • View 는 generic view 중 폼을 이용해 오브젝트를 생성하는 CreateView를 사용한다.
  • reverse_lazy는 reverse와 같은 기능인데 generic view에서 주로 사용한다.
    타이밍 로딩 문제로 generic view에서는 reverse는 사용할 수 없고 reverse_lazy를 사용해야한다.

  • views.py 수정
from django.views.generic import TemplateView
from django.views.generic.edit import CreateView # 오브젝트를 생성하는 뷰 (form 혹은 model과 연결되서 새로운 데이터를 넣을 때 CreateView - generic view를 사용)
# from django.contrib.auth.forms import UserCreationForm  >>  장고의 기본 회원가입 폼 (ID, PW만 확인한다 - 뒤에서 이메일 추가 커스터미아징 예정)
from .forms import CreateUserForm # 장고의 기본 회원가입 폼을 커스터마이징 한 폼
from django.core.urlresolvers import reverse_lazy # generic view에서는 reverse_lazy를 사용한다.

# CBV (Class Based View 작성!)
class CreateUserView(CreateView): # generic view중에 CreateView를 상속받는다.
    template_name = 'registration/signup.html' # 템플릿은?
    form_class =  CreateUserForm # 푸슨 폼 사용? >> 내장 회원가입 폼을 커스터마지징 한 것을 사용하는 경우
    # form_class = UserCreationForm >> 내장 회원가입 폼 사용하는 경우
    success_url = reverse_lazy('create_user_done') # 성공하면 어디로?

class RegisteredView(TemplateView): # generic view중에 TemplateView를 상속받는다.
    template_name = 'registration/signup_done.html' # 템플릿은?

login.html 에 내용 추가

  • 아래 내용 추가

<br>
<p>아이디가 없으신가요? <a href="{% url 'signup'%}">회원가입</a></p>

template 생성

  • CreateUserView와 RegisteredView에서 사용할 template을 작성

registration/signup.html


{% extends 'kilogram/base.html' %}
{% block content %}

<form method="post" action="{% url 'signup' %}">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">회원가입</button>
</form>

{% endblock %}

registration/signup_done.html


{% extends 'kilogram/base.html' %}
{% block content %}

<h2>회원가입이 완료되었습니다. </h2>
<br>
<a href="{% url 'login'%}">로그인하기</a>

{% endblock %}

회원가입 폼 클래스(CreateUserForm) 만들기 - 내장 폼 커스터마이징

  • 회원가입 폼을 만드는 방법은 여러가지가 있을 수 있는데, 그 중 가장 간단한 장고에서 제공하는 UserCreationForm(django.contrib.auth.forms.UserCreationForm)을 사용한다.
  • 다만 요즘 대부분의 사이트에서 필수적으로 받는 이메일도 입력받기 위해서 추가적으로 구현을 한다.

  • forms.py 생성
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from django import forms

class CreateUserForm(UserCreationForm): # 내장 회원가입 폼을 상속받아서 확장한다.
    email = forms.EmailField(required=True) # 이메일 필드 추가

    class Meta:
        model = User
        fields = ("username", "email", "password1", "password2")

    def save(self, commit=True): # 저장하는 부분 오버라이딩
        user = super(CreateUserForm, self).save(commit=False) # 본인의 부모를 호출해서 저장하겠다.
        user.email = self.cleaned_data["email"]
        if commit:
            user.save()
        return user

참고 링크

django 07. 세번째 장고앱 02 - 로그인, 로그아웃, form 커스터마이징

|

파이썬 웹 프로그래밍 - Django로 웹 서비스 개발하기

세번째 장고앱 ‘킬로그램’에 로그인, 로그아웃 기능을 구현한다.

static 파일 추가 - css

  • static 파일경로 : kilogram/static/kilogram/style.css
  • (참고) template 파일경로 : kilogram/templates/kilogram/index.html
  • style.css
a {
    color: green;
    text-decoration: none;
}
  • base.html 템플릿에는 load static 구문이 있는데 load staticfiles 로 고칠 수 있다. 관련 링크
  • 이 경우 python manage.py collectstatic 명령을 서버 실행 전에 수행해야 한다.
  • 상기 명령을 통해서 최상위 폴더 내 static 폴더에 모든 static 파일이 모인다. settings.py 설정 내용 : STATIC_ROOT = os.path.join(BASE_DIR, 'static')
  • 이는 배포할때 중요한 커멘드로, 정적파일은 로컬이 아닌 클라우드 같은 다른 strage에 저장되는 경우가 많다. 그럴 경우에 collectstatic 명령어가 사용된다. (다음 배포관련 부분에서 자세히 다룰 예정)

로그인 구현순서

  • MVT 순서로 구현해 본다.
  • Model : django.contrib.auth.models.User 내장 클래스 사용
  • View : 내장 로그인 View 사용
  • Template : 간단하게 작성

model

  • 장고의 django.contrib.auth.models.User 라는 클래스를 그대로 사용한다. (Full framework)
  • 특별한 코딩은 필요하지 않다.

auth 관련 url 추가하기

  • 장고에 기본으로 내장된 인증 기능을 include하여 활용한다.
  • settings/urls.py 수정
urlpatterns = [
  url(r'^accounts/', include('django.contrib.auth.urls')),
]
  • auth.urls를 include 할 경우 아래와 같은 url들이 포함된다. 관련문서
^login/$ [name='login']
^logout/$ [name='logout']
^password_change/$ [name='password_change']
^password_change/done/$ [name='password_change_done']
^password_reset/$ [name='password_reset']
^password_reset/done/$ [name='password_reset_done']
^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$ [name='password_reset_confirm']
^reset/done/$ [name='password_reset_complete']

view 만들기

login 관련 기능에는 별도로 view를 작성할 필요 없이 템플릿만 작성하면 된다.

template 만들기

  • 로그인과 로그아웃에 사용할 템플릿을 만든다.
  • 경로와 이름이 이미 정해져 있는데 로그인은 만들지 않으면 에러가 발생하고, 로그 아웃은 관리자용 페이지를 사용한다.(둘 다 모두 직접 만들어 주는 것이 좋다.)

base.html 수정

  • 로그인 및 로그아웃 링크를 추가 한다.
  • 로그인한 상태와 로그인하지 않은 상태에서 보여줄 링크도 변경한다
  • if user.is_active : 유저가 로그인 상태일때 (django 템플릿 문법)

<ul class="nav navbar-nav navbar-right">
  {% if user.is_active %}
  <li><a href="{%url 'login' %}"> <span class="glyphicon glyphicon-heart"></span> {{user.username}}</a></li>
  <li><a href="{% url 'logout' %}">Logout</a></li>
  {% else %}
  <li><a href="{%url 'login' %}"> <span class="glyphicon glyphicon-user"></span> Login</a></li>
  <li><a href="{% url 'admin:index' %}">Admin</a></li>
  {% endif %}
</ul>

shell에서 로그인용 사용자 생성해 보기

  • 완료 후 admin 툴을 이용해서 확인한다.
  • 템플릿 상에서도 로그인이 잘 되는지 확인한다.
  • 관련문서
$ python manage.py shell
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('chiken1', 'chiken1@out.org', 'pass5678')
>>> user = User.objects.create_user(username = 'test1', email = 'na@na.com', password = '1234')
>>> User.objects.all() # 전체 User 리스트 확인

login.html 템플릿 작성

  • 로그인 페이지 템플릿을 작성한다.
  • 파일 경로 : registration/login.html (내장된 인증모델의 로그인 템플릿의 경로는 해당 경로와 파일명으로 지정되어 있다.)
  • 관련문서

  {% extends 'kilogram/base.html' %}
  {% block content %}

  <!-- 로그인 되어있는 경우 -->
  {% if user.is_active %}
  <h2> Welcome, {{user.username}} </h2>
  <a href="{% url 'logout' %}">로그아웃</a>

  <!-- 로그인 되어있지 않은 경우 -->
  {% else %}
  {% if form.errors %}
    <!-- 에러발생시 -->
  <p>ID나 비밀번호가 일치하지 않습니다.</p>
  {% endif %}
    <!-- 로그인 폼 -->
  <form method="post" action="{% url 'login' %}">
  {% csrf_token %}
  <input type="hidden" name="next" value="" />
  {{ form.as_p }}
  <button type="submit">로그인</button>
  </form>

  {% endif %}
  {% endblock %}

(참고) login.html 템플릿에서 로그인 폼 커스터마이징

  • registration/login.html에서 사용하는 내장된 로그인 폼 (위 코드에서 form.as_p 로 사용한)을 커스터마이징 할 수 있다.
  • 참고문서

<form method="POST" action="{% url 'login' %}" class="sign-in-form">
  {% csrf_token %}
  <h2 class="sub-title"> 로그인 </h2>
  <!-- <input type="hidden" name="next" value="">
  {{form.as_p}} >> 기본으로 내장된 로그인 폼을 아래와 같이 커스터마이징 한다-->
  <div class="form-group">
    <label for="{{ form.username.id_for_label }}">닉네임</label>
    <input class="form-control" id="{{ form.username.id_for_label }}" maxlength="15" name="{{ form.username.html_name }}" type="text" />
  </div>
  <div class="form-group">
    <label for="{{ form.password.id_for_label }}">패스워드</label>
    <input class="form-control" id="{{ form.password.id_for_label }}" maxlength="120" name="{{ form.password.html_name }}" type="password" />
  </div>
  <input type="submit" class="save btn btn-success" value="로그인">
  <a href="{% url 'index' %}">
    <button type="button" class="btn btn-danger">취소</button>
  </a>
  <input type="hidden" name="next" value="">
</form>

settings.py 수정

  • 로그인후 리다이렉트 페이지는 기본적으로 /accounts/profile 로 지정되어 있는데 이를 변경한다.
  • settings.py의 가장 아래에 아래 내용을 추가한다.
# Auth settings
LOGIN_REDIRECT_URL = '/kilogram/'

로그아웃용 template 만들기

  • 파일경로 : registration/logged_out.html (내장된 인증모델의 로그아웃 템플릿의 경로는 해당 경로와 파일명으로 지정되어 있다.)
  • 템플릿 이름이 다르면 안된다. 파일이름은 장고 소스의 auth.views.logout()을 보면 확인할 수 있다. 참고
  • settings.py 에서 INSTALLED_APPS 내용 중 app 파일이 admin 파일보다 위에 와야한다. 그렇지 않으면 템플릿 파일 override가 동작하지 않는다.


{% extends 'kilogram/base.html' %}
{% block content %}

<h2> 잘 가요, 안녕. </h2>
<p><a href="{%url 'login'%}">다시 로그인하기</a></p>

{% endblock %}

로그아웃 후 바로 홈으로 보내기

  • 내장된 인증 관련 views를 활용하여 별도의 로그인 템플릿 지정 및 로그아웃 후 바로 홈으로 리다이렉트가 가능하다.
  • urls.py
from django.contrib.auth import views as auth_views

urlpatterns = [
url(r'^logout/$', auth_views.logout, {'next_page' : '/'}),
url(r'^login/$', auth_views.login,  {'template_name':'memo_app/login.html'}),
]

참고 링크

django 07. 세번째 장고앱 01 - 기본 설정 및 프로젝트 초기화

|

파이썬 웹 프로그래밍 - Django로 웹 서비스 개발하기

세번째 장고앱 ‘킬로그램’을 시작하기 위한 기본 설정 및 프로젝트 초기화를 진행한다.

기획

  • 앱이름: 킬로그램
  • 유사 OOO그램: 이미지 업로드 앱

사용자 시나리오

  • 회원 가입 및 로그인 기능
  • 소셜 로그인도 가능(?)
  • 비회원 열람 불가 - 공개된 인기 이미지
  • 회원 전용 페이지 - 내 이미지와 친구들 이미지 보기
  • 하트 주기
  • 댓글 달기

핵심 기능

  • 인증
  • 이미지 업로드

페이지들

  • 메인페이지: 인기 이미지들 보여주기
  • 마이페이지: 로그인한 사용자에게 내 이미지들 보여주기
  • 친구찾기 및 친구추가 페이지:

기본 설정 및 프로젝트 초기화

start project and app

  • 프로젝트명을 mysite로 만들고 나서 app3로 변경한다.
  • 처음부터 app3로 만들지 않는 이유는, 프로젝트 폴더 내에 대표 폴더가 같은 폴더명 mysite로 생성이 되는데 그게 편하기 때문이다.
/*프로젝트 생성*/
$ django-admin startproject mysite
/*폴더명 변경*/
$ mv mysite app3
/*앱 생성*/
$ python manage.py startapp kilogram

settings.py 수정

  • INSTALLED_APPS에 kilogram 앱을 추가하는데, 위치는 admin 위에 추가해야한다.
  • LANGUAGE_CODE, TIME_ZONE을 수정한다.
  • STATIC_ROOT를 통해 Static file 경로를 추가한다.
INSTALLED_APPS = [
    'kilogram',


LANGUAGE_CODE = 'ko-kr'

TIME_ZONE = 'Asia/Seoul'


STATIC_ROOT = os.path.join(BASE_DIR, 'static')
  • shell에서 migrate 진행
$ python manage.py migrate

super user 생성

$ python manage.py createsuperuser

model

아직까지는 특별히 생성 또는 수정할 model이 없다.

kilogram/urls.py 생성

  • include 방식으로 kilogram (앱폴더) 내에 urls.py 를 따로 작성한다.
    • 이 방법이 더 좋다.
  • 앱 폴더, 프로젝트 폴더 내부 각각에 urls.py를 갖는다.
  • app_name 네임스페이스를 지정하여 url name 중복 문제를 해결한다.
  • FBV(function based view) 보다 CBV(Class based view)에서 이미 정의되어 있는 generic view를 활용하는 것이 편리하다.
from django.conf.urls import url
from . import views

app_name = 'kilogram'

urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name = 'index'),
    # /kilogram 으로 접속시 (include 됨)
    # CBV의 generic view를 이용하여 url을 처리하겠다는 말
]

mysite/urls.py 수정

  • 파이썬 기본 문법인 as를 적용해서 views를 구별 가능하도록 한다.
  • include 할 urls.py 의 경로와 url 패턴을 지정한다.
from django.conf.urls import url, include
from django.contrib import admin
from kilogram import views as kilogram_views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', kilogram_views.IndexView.as_view(), name = "root"),
    url(r'^kilogram/', include('kilogram.urls')),
]

kilogram/views.py 수정

  • 간단히 템플릿을 적용하기 위해서 generic view인 TemplateView를 사용했습니다.
    • 아무 기능 없이 템플릿을 그대로 표시해준다.
from django.views.generic.base import TemplateView

class IndexView(TemplateView): # TemplateView를 상속 받는다.
    template_name = 'kilogram/index.html'

기본 템플릿 작성

  • 템플릿 경로(앱 폴더 내) : kilogram/templates/kilogram/

base.html (kilogram/base.html)


<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
  <title>Kilogram</title>

  <!-- Bootstrap -->
  <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

  {% load static %}
  <link rel="stylesheet" type="text/css" href="{% static 'kilogram/style.css' %}" />

  <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
  <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
  <!--[if lt IE 9]>
  <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
  <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
  <![endif]-->
</head>
<body>
  <nav class="navbar navbar-default navbar-static-top">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="{% url 'kilogram:index' %}"> <span class="glyphicon glyphicon-camera"> </span> Kilogram </a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse">
      <ul class="nav navbar-nav navbar-right">
        <li><a href="#"> <span class="glyphicon glyphicon-user"></span> Login</a></li>
        <li><a href="#">Logout</a></li>
        <li><a href="{% url 'admin:index' %}">Admin</a></li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
  <div class="container">
    <div>
      {% block content %}
      {% endblock %}
    </div>
  </div>
  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <!-- Include all compiled plugins (below), or include individual files as needed -->
  <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</body>
</html>

index.html (kilogram/index.html)

  • base.html 템플릿을 확장하여 작성

{% extends 'kilogram/base.html' %}
{% block content %}

<h1>Kilogram Main Page</h1>

{% endblock %}

170313_TIL

|

오늘 한 일

  • django 강의를 들었다. 3번째 실습 내용은 인스타그램과 비슷한 사진 SNS 서비스 만들기이다. 실습 내용중에 로그인, 좋아요, 이미지 업로드, 댓글 달기가 있는데 새로운 기능이라 재미있을 것 같다. 특히 로그인, 좋아요는 개인 프로젝트를 진행할때 문서를 찾아보면서 고생해서 구현했던 부분이다. 정호영님이 어떻게 설명해 주실지 기대가 크다.
  • 첫 장고 개인프로젝트에 수업에서 배운 탬플릿 확장을 적용해 보았다. 항상 궁금했던 부분이 해결되는 기분이었다. 특히 jekyll로 블로그를 처음 시작했을때 미리 만들어진 템플릿을 수정해서 적용했었다. 그때 봤었던 기본 골격 html과 각 메뉴별 확장 html의 연관성을 이번 템플릿 확장을 통해서 이해할 수 있었다. 장고의 내장 폼인 User Form을 커스터마이징 해서 로그인 화면과 회원가입 화면을 새롭게 꾸며보았다. 새로운 기능을 추가 할 때마다 배우는게 정말 많다고 느낀다.
  • 아이폰 개발이 전부터 궁금해서 iOS10 아이폰 웹브라우저 Swift3 로 만들기 실습 내용을 쭉 살펴보았다. 디자인 요소를 드래그앤드롭으로 화면에 끌어다 쓰는 것이나, 스토리보드를 활용하는 것이 신선했다. xcode 내에서 가이드 문서를 쉽게 찾아볼수 있다는 점도 마음에 들었다.

내일 할 일

  • 장고 강의 듣고 내용 블로그에 정리하기
  • 점프투 파이썬 읽기 (제어문)