Dstargram project - 1

[ Dstargram project ]

  • 두 가지 앱을 만들어서 진행
    • accounts : 로그인, 로그아웃, 회원가입 관련
    • photo : 사진 업로드 관련
  1. 파이썬 프로젝트 생성
  2. 장고 설치

    1
    $ pip install django
  3. 장고 프로젝트 생성

    1
    $ django-admin startproject config .
  4. DB 초기화

    1
    $ python manage.py migrate
  5. 관리자 계정 생성

    1
    2
    3
    4
    $ python manage.py createsuperuser

    # 비밀번호 변경
    $ python manage.py changepassword [계정명]
  6. 앱 생성

    1
    2
    3
    4
    5
    # 로그인, 로그아웃, 회원가입 관련
    $ python manage.py startapp accounts

    # 사진 업로드 관련
    $ python manage.py startapp photo
  7. config/settings.py에 앱 추가

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'accounts',
    'photo',
    ]
  8. photo 앱 설계

    • models.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
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      from django.db import models

      # 기본 모델
      """
      작성자 : author
      본문글 : text
      사진 : image
      작성일 : created
      수정일 : updated

      + tag, like
      --> comment
      """
      from django.contrib.auth.models import User
      # User 모델은 확장 가능
      # settings.AUTH_USER_MODEL
      from django.contrib.auth import get_user_model

      # url pattern 이름을 가지고 주소를 만들어주는 함수
      from django.urls import reverse

      class Photo(models.Model):
      # ForeignKey(연결되는 모델, 삭제 시 동작, 연관 이름)
      author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='photos')
      # User 모델을 커스텀했을 시에 models.ForeignKey(get_user_model(),)를 쓰는 것이 좋다.

      # CASCADE : 연속해서 지운다. = 탈퇴하면 사진도 싹 지운다.
      # PROTECT : 사진을 다 지우지 않으면 탈퇴할 수 없다. = 탈퇴 프로세스에 사진을 우선 삭제하고 탈퇴 시킨다는 메시지를 띄움
      # 특정값으로 셋팅하는 방법도 있음

      # related_name으로 연관 데이터를 얻을 수 없다면 쿼리를 별도로 실행해야 한다,
      # 내 프로필 페이지에서 내가 올린 사진만 뜬다. -> related_name을 이용하여 별도의 퀴리없이도 불러올 수 있다.

      text = models.TextField(blank=True) # 필수 필드가 아닐 수 있기 때문에 blank = True
      image = models.ImageField(upload_to='timeline_photo/%Y/%m/%d')
      # %Y = 2019로 표시
      # %y = 19로 표시
      # upload_to는 함수를 사용해서 폴더를 동적으로 설정할 수 있다.
      # pip install pillow 설치

      created = models.DateTimeField(auto_now_add= True) # 생성 당시에 날짜를 저장
      updated = models.DateTimeField(auto_now=True) # 다시 생성할 때마다 날짜 갱신

      class Meta:
      ordering = ['-created'] # 해당 필드값을 내림차순

      def get_absolute_url(self):
      # detail/<int:pk>/
      # <int:pk>에 들어갈 값을 아래와 같이 args=[self.id] 방법으로 넣어준다.
      return reverse('photo:detail', args=[self.id])

      # models.py를 수정했다면 makemigrations와 migrate를 진행한다.
    • views.py 작성

      • models.py에서 저장한 이미지를 관리하는 폴더 바꾸기
        • settings.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
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          # settings.py
          MEDIA_URL = '/res/' # 가상 URL
          MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
          # 루트 폴더를 만들어줌으로써 앞으로 사진을 찾을 때 MEDIA_ROOT 경로 아래에서만 찾게 할 수 있다.
          # 이미지 저장 시 해당 이름을 가진 루트 폴더가 자동으로 생성된다.


          # views.py
          from django.shortcuts import render

          from .models import Photo

          from django.views.generic.list import ListView
          from django.views.generic.edit import CreateView,UpdateView,DeleteView
          from django.views.generic.detail import DetailView

          # Create your views here.
          # CRUDL - 이미지를 띄우는 방법
          # 제네릭 뷰 활용
          # 쿼리셋 변경하기, context_data 추가하기, 권한 체크
          # 함수형 뷰 <-> 클래스형 뷰

          class PhotoList(ListView):
          model = Photo
          template_name = 'photo/photo_list.html' # 파일 이름 전체를 지정

          from django.shortcuts import redirect

          class PhotoCreate(CreateView):
          model = Photo

          fields = ['text', 'image']
          template_name = 'photo/photo_create.html'
          # template_name_suffix = '_create' # 뒤에 붙는 이름만 바꿈
          success_url = '/'

          def form_valid(self, form):
          # 입력된 자료가 올바른지 체크
          form.instance.author_id = self.request.user.id
          if form.is_valid():
          # 올바르다면
          # form : 모델 폼
          # form.instance는 model의 객체
          form.instance.save()
          return redirect('/')
          else:
          # 올바르지 않다면
          return self.render_to_response({'form':form})

          class PhotoUpdate(UpdateView):
          model = Photo

          fields = ['text', 'image']
          template_name = 'photo/photo_update.html'
          # success_url를 쓰지 않고, models.py에 get_absolute_url() 이용
          # success_url을 지정해주면 get_absolute_url()보다 먼저 적용된다.

          class PhotoDelete(DeleteView):
          model = Photo
          template_name = 'photo/photo_delete.html'
          success_url = '/'

          class PhotoDetail(DetailView):
          model = Photo
          template_name = 'photo/photo_detail.html'
    • photo/urls.py 작성

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      from django.urls import path

      from .views import PhotoList, PhotoCreate, PhotoUpdate, PhotoDelete, PhotoDetail

      app_name = 'photo'

      urlpatterns = [
      path('', PhotoList.as_view(), name = 'index'),
      path('create/', PhotoCreate.as_view(), name = 'create'),
      path('update/<int:pk>', PhotoUpdate.as_view(), name = 'update'),
      path('delete/<int:pk>', PhotoDelete.as_view(), name = 'delete'),
      path('detail/<int:pk>', PhotoDetail.as_view(), name = 'detail'),
      ]
    • config/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.contrib import admin
      from django.urls import path, include

      urlpatterns = [
      # 배포하는 프로젝트에서는 admin 주소의 이름을 바꿔서 사용하도록 하자.
      # admin 이름을 그대로 쓰게 되면 접근이 쉬워져서 주의가 필요하다.
      path('admin/', admin.site.urls),
      path('', include('photo.urls')), # photo 앱 관련
      path('accounts/', include('accounts.urls')), # accounts 앱 관련
      ]

      # 특정 리소스를 static 형태로 응답
      from django.conf.urls.static import static

      # 장고의 셋팅 값을 불러다 주는 역할
      from django.conf import settings

      # 개발 상태일 때만 사용 -> Deploy, Live 일 때는 사용하지 않는다.
      # 1) 웹 서버가 해줘야 할 일
      # 2) 파일 서버를 별도로 셋
      urlpatterns += static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
    • template 파일 작성

      • base.html를 작성하여 분기 사용

        • 프로젝트 최상단에서 layout 폴더를 만들고 base.html 생성 및 작성
        • settings.py에서 TEPLATES의 ‘DIRS’ 수정
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          TEMPLATES = [
          {
          'BACKEND': 'django.template.backends.django.DjangoTemplates',
          'DIRS': [os.path.join(BASE_DIR, 'layout')],
          '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',
          ],
          },
          },
          ]
      • CRUDL 관련 html 작성

        • photo/templates/photo에 CRUDL html 생성 및 작성
      • bootstrap 적용
        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
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116
        117
        118
        119
        120
        121
        122
        123
        124
        125
        126
        127
        128
        129
        130
        131
        132
        133
        134
        135
        136
        137
        138
        139
        140
        141
        142
        143
        144
        145
        146
        147
        148
        149
        150
        151
        152
        153
        154
        155
        156
        157
        158
        159
        160
        161
        162
        163
        164
        165
        166
        167
        168
        169
        170
        171
        172
        173
        174
        175
        176
        177
        178
        179
        180
        181
        182
        183
        184
        185
        186
        187
        188
        189
        190
        191
        192
        193
        194
        195
        196
        197
        198
        199
        200
        201
        202
        203
        204
        205
        206
        207
        <!--// dstargram_project/layout/base.html //-->
        <!DOCTYPE html>
        <html lang="en">
        <head>
        <meta charset="UTF-8">
        <title>{% block title %}{% endblock %}</title>
        <!--// CSS 적용 //-->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
        </head>
        <body>
        <!--// nav bar 적용 //-->
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="{% url 'photo:index' %}#">Dstargram</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
        <ul class="navbar-nav">
        <li class="nav-item active">
        <a class="nav-link" href="{% url 'photo:index' %}">Home</a>
        </li>
        <li class="nav-item">
        <a class="nav-link" href="#">Tags</a>
        </li>
        <!--// user가 로그인되어 있다면 업로드와 로그아웃을 할 수 있게 설정 //-->
        {% if user.is_authenticated %}
        <li class="nav-item">
        <a class="nav-link" href="{% url 'photo:create' %}">Upload</a>
        </li>
        <li class="nav-item">
        <a class="nav-link" href="{% url 'accounts:signout' %}">Sign-out</a>
        </li>
        {% else %}
        <li class="nav-item">
        <a class="nav-link" href="{% url 'accounts:signin' %}">Sign-in</a>
        </li>
        <li class="nav-item">
        <a class="nav-link" href="#">Sign-up</a>
        </li>
        {% endif %}
        </ul>
        </div>
        </nav>

        <!--// grid system 적용 //-->
        <div class="container">
        {% block content %}

        {% endblock %}
        </div>
        <!--// JS 적용 //-->
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
        </body>
        </html>

        <!--// photo/templates/photo/photo_list.html //-->
        {% extends 'base.html' %}
        {% block title %}
        Photo List
        {% endblock %}

        {% block content %}
        <!--// grid system 적용 //-->
        <div class="row">
        <div class="col"></div>
        <div class="col-6">
        {% for object in object_list %}
        <!--// cards 적용 //-->
        <div class="card" style="margin-top:2em;">
        <div class="card-header">
        {{object.author.username}}
        </div>
        <img src="{{object.image.url}}" class="card-img-top">
        {% if object.text %}
        <div class="card-body">
        <p class="card-text">{{object.text}}</p>
        </div>
        {% endif %}
        <ul class="list-group list-group-flush">
        <li class="list-group-item">Like Save</li>
        </ul>
        <div class="card-body">
        <a href="{{object.get_absolute_url}}#" class="card-link">댓글 달기</a>
        </div>
        </div>
        {% endfor %}
        </div>
        <div class="col"></div>
        </div>
        {% endblock %}


        <!--// photo/templates/photo/photo_create.html //-->
        {% extends 'base.html' %}
        {% block title %}
        Photo Create
        {% endblock %}

        {% block content %}
        <div class="row">
        <div class="col"></div>
        <div class="col-6">
        <!--// 파일(ex: image file)을 보내는 경우 enctype를 추가하여 인코딩 //-->
        <!--// form 적용 //-->
        <form action="" method="post" enctype="multipart/form-data">
        <!--// alert 적용 //-->
        <div class="alert alert-info">Please enter your photo information</div>
        {% csrf_token %}
        {{form.as_p}}
        <!--// button 적용 //-->
        <input type="submit" value="Create" class="btn btn-outline-primary">
        </form>
        </div>
        <div class="col"></div>
        </div>
        {% endblock %}


        <!--// photo/templates/photo/photo_update.html //-->
        {% extends 'base.html' %}
        {% block title %}
        Photo Update
        {% endblock %}

        {% block content %}
        <div class="row">
        <div class="col"></div>
        <div class="col-6">
        <!--// 현재 로그인 사용자가 다른 사용자 글을 수정할 수 없도록 조건 지정 //-->
        {% if user.id == object.author.id %}
        <form action="" method="post" enctype="multipart/form-data">
        <div class="alert alert-info">Please change your photo information</div>
        {% csrf_token %}
        {{form.as_p}}
        <input type="submit" value="Update" class="btn btn-outline-warning">
        </form>
        {% else %}
        <div class="alert alert=primary">You don't have access.</div>
        <a href="{% url 'photo:index' %}" class="btn btn-outline-primary">home</a>
        {% endif %}

        </div>
        <div class="col"></div>
        </div>
        {% endblock %}


        <!--// photo/templates/photo/photo_delete.html //-->
        {% extends 'base.html' %}
        {% block title %}
        Photo Delete
        {% endblock %}

        {% block content %}
        <div class="row">
        <div class="col"></div>
        <div class="col-6">
        <!--// 현재 로그인 사용자가 다른 사용자 글을 삭제할 수 없도록 조건 지정 //-->
        {% if user.id == object.author.id %}
        <form action="" method="post">
        <div class="alert alert-info">Do you want to delete {{object}}?</div>
        {% csrf_token %}
        <input type="submit" value="Upload" class="btn btn-outline-danger">
        </form>
        {% else %}
        <div class="alert alert=primary">You don't have access.</div>
        <a href="{% url 'photo:index' %}" class="btn btn-outline-primary">home</a>
        {% endif %}
        </div>
        <div class="col"></div>
        </div>
        {% endblock %}

        <!--// photo/templates/photo/photo_detail.html //-->
        {% extends 'base.html' %}
        {% block title %}
        Photo Create
        {% endblock %}

        {% block content %}
        <div class="row">
        <div class="col"></div>
        <div class="col-6">
        <!--// cards 적용//-->
        <div class="card" style="margin-top:2em;">
        <div class="card-header">
        {{object.author.username}}
        </div>
        <img src="{{object.image.url}}" class="card-img-top">

        {% if object.text %}
        <div class="card-body">
        <p class="card-text">{{object.text}}</p>
        </div>
        {% endif %}

        <div class="card-body">
        <a href="{% url 'photo:update' object.id %}" class="card-link">수정</a>
        <a href="{% url 'photo:delete' object.id %}" class="card-link">삭제</a>
        </div>
        </div>
        </div>
        <div class="col"></div>
        </div>
        {% endblock %}
  9. accounts 앱 설계

    • urls.py 작성

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      from django.urls import path
      from django.contrib.auth.views import LoginView, LogoutView
      # 장고에 내장되어 있는 로그인, 로그아웃 기능 이용

      app_name = 'accounts'

      # views.py를 작성하지 않고도 적용하는 방법
      urlpatterns = [
      path('singin/', LoginView.as_view(template_name='accounts/signin.html'), name='signin'),
      path('signout/', LogoutView.as_view(template_name='accounts/signout.html'), name='signout'),
      ]
    • template 파일 작성

      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
      37
      38
      39
      40
      41
      <!--// accounts/templates/accounts/signin.html //-->
      {% extends 'base.html' %}

      {% block title %}
      Sign In
      {% endblock %}

      {% block content %}
      <div class="row">
      <div class="col"></div>
      <div class="col-6">
      <div class="alert alert=primary">Please enter your login information</div>
      <form action="" method="post">
      {% csrf_token %}
      {{form.as_p}}
      <input type="submit" value="Signin" class="btn btn-outline-primary">
      </form>
      </div>
      <div class="col"></div>
      </div>
      {% endblock %}


      <!--// accounts/templates/accounts/signout.html //-->
      {% extends 'base.html' %}

      {% block title %}
      Sign Out
      {% endblock %}

      {% block content %}
      <div class="row">
      <div class="col"></div>
      <div class="col-6">
      <!--// 로그아웃이 되면 메시지와 함께 로그인 페이지로 이동할 수 있도록 함 //-->
      <div class="alert alert=primary">Your successfully sign out.</div>
      <a href="{% url 'accounts:signin' %}" class="btn btn-outline-success">Sign In</a>
      </div>
      <div class="col"></div>
      </div>
      {% endblock %}
    • settings.py 수정

      • 로그인이 완료되면 이동할 경로 지정하기 위해 추가
        1
        LOGIN_REDIRECT_URL = '/'
Share