Board project - 1

[ Board project ]

  • url이 있는 형태로 S3 버킷 만들기(media 파일용)

    • board.[도메인 주소]로 버킷 이름을 설정한다.
    • 퍼블릭 ACL 관리 체크 항목을 모두 해제한다.
    • 버킷이 생성되면 속성 탭에서 정적 웹 사이트 호스팅을 활성화한다.
    • AWS ROUTE53에서 레코드를 생성한다.
    • pycharm project의 settings.py에 다음과 같이 추가한다.

      1
      2
      # settings.py
      DEFAULT_FILE_STORAGE = 'config.asset_storage.MediaStorage'
    • config/asset_storage.py를 생성하고 작성한다.

      1
      2
      3
      4
      5
      6
      7
      8
      from storages.backends.s3boto3 import S3Boto3Storage

      class MediaStorage(S3Boto3Storage):
      location = ''
      # 오버라이드하는 방법을 항상 찾아보고 별도로 추가해보자.
      bucket_name = 'images.jinukk.me'
      custom_domain = 'images.jinukk.me'
      file_overwrite = False
  • ‘board’ 앱을 만들고 모델 설계하기

    • ‘board’라는 이름의 앱을 생성한다.

      1
      $ python manage.py startapp board
    • settings.py에 앱을 등록한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      INSTALLED_APPS = [
      'django.contrib.admin',
      'django.contrib.auth',
      'django.contrib.contenttypes',
      'django.contrib.sessions',
      'django.contrib.messages',
      'django.contrib.staticfiles',
      'storages',
      # board 앱 등록
      'board',
      ]
    • 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
      53
      54
      55
      from django.db import models

      """
      Category - slug, name
      Document - author, category, title, slug, text, image, created, updated
      """

      # User 모델을 커스텀한 경우 불러다가 써야하기 때문에 사용
      from django.contrib.auth import get_user_model
      from django.urls import reverse

      # class Board(models.Model):
      # Board 모델을 만들어서 Category나 Document에 참조하여 사용하는 방법도 있다.
      # pass

      class Category(models.Model):
      # CharField vs TextField의 차이 = 글자 수 제한
      name = models.CharField(max_length=20)
      # DB에는 index라는 키 값이 걸려있다.
      # 대량의 데이터를 입력, 수정, 삭제, 검색 등을 잘 수행하기 위해 설계한 것이 DB
      # -> file 형태 대신 DB를 쓰는 이유
      # 미리 검색과 정렬에 적합하도록 설계하는 것이 index
      # allow_unicode = 한글 처리 가능
      slug = models.SlugField(max_length=30, db_index=True, unique=True, allow_unicode=True)
      description = models.CharField(max_length=200, blank=True)
      # 검색 엔진에게 제공해주는 것이 meta_description
      meta_description = models.CharField(max_length=200, blank=True)

      class Meta:
      # DB에 기본적으로 설정될 정렬값
      ordering = ['slug']

      def __str__(self):
      return self.name

      class Document(models.Model):
      category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, related_name='documents')
      author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, related_name='documents')
      title = models.CharField(max_length=100)
      slug = models.SlugField(max_length=120, db_index=True, unique=True, allow_unicode=True)
      text = models.TextField()
      # upload_to 동적으로 경로 설정 가능
      image = models.ImageField(upload_to='board_images/%Y/%m/%d')
      created = models.DateTimeField(auto_now_add=True)
      updated = models.DateTimeField(auto_now=True)

      def __str__(self):
      return self.title

      def get_absolute_url(self):
      return reverse('board:detail', args=[self.id])

      # 작성이 끝나면 makemigrantions와 migrate를 한다.
      # AWS DB에 저장되지 않는다면 ip가 변경된 것일 수도 있다.
      # AWS RDS에서 해당 데이터베이스의 인바운드를 확인한다.
    • 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
      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
      from django.shortcuts import render, get_object_or_404, redirect

      # QuerySet은 모델의 디폴트 매니저를 통해 실행한다.
      from .models import Document

      def document_list(request):
      # QuerySet
      # 1. 객체를 선택
      # 2. 객체 생성
      # 3. 객체 필터링
      # 4. 객체 삭제

      # Models.objects -> 매니저 -> 도와주는 객체
      # 1. 모델의 전체 데이터 불러오기
      documents = Document.objects.all()
      return render(request, 'board/document_list.html',{'object_list':documents})

      from .forms import DocumentForm
      from django.contrib.auth.decorators import login_required
      from django.urls import reverse

      @login_required
      def document_create(request):
      # Document.objects.create() - 실행과 동시에 DB에 삽입을 한다.
      # 분기 - post, get
      if request.method == "POST":
      # 처리
      # request.POST : 폼에서 입력한 텍스트 데이터가 넘어온다.
      # request.FILES : 파일
      form = DocumentForm(request.POST, request.FILES)
      form.instance.author_id = request.user.id
      if form.is_valid():
      document = form.save()
      return redirect(document)
      else:
      # 입력 창
      form = DocumentForm()

      return render(request, 'board/document_create.html', {'form':form})

      def document_update(request, document_id):
      # 객체를 불러와서, 데이터를 수정
      if request.method == "POST":
      document = Document.objects.get(pk=document_id)
      # 모델폼을 사용할 때 instance를 넘겨주면, 해당 인스턴스 값으로 초기화가 되고
      # 만약 pk가 있는 instance라면 update를 수행한다.
      # request.POST와 instance가 같이 전달되면, POST 데이터가 우선순위가 높다.
      form = DocumentForm(request.POST, request.FILES, instance=document)
      # instance가 매개변수로 있어야 create가 아닌 update로 인식한다.
      if form.is_valid():
      document = form.save()
      return redirect(document)
      else:
      document = Document.objects.get(pk=document_id)
      # modelform init with instance(model object)
      form = DocumentForm(instance=document) # instance 매개변수 사용

      return render(request, 'board/document_update.html',{'form':form})

      def document_detail(request, document_id):
      document = Document.objects.get(pk=document_id)
      return render(request, 'board/document_detail.html', {'object':document})

      def document_delete(request, document_id):
      # 객체를 불러와서 delete만 호출
      return render(request, 'board/document_delete.html',{'object':document})
    • admin.py를 작성한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      from django.contrib import admin
      from .models import Category,Document

      class CategoryOption(admin.ModelAdmin):
      list_display = ['id', 'name', 'slug']
      prepopulated_fields = {'slug':('name',)}

      class DocumentOption(admin.ModelAdmin):
      list_display = ['id', 'author', 'title', 'slug', 'created', 'updated']
      prepopulated_fields = {'slug':('title',)}

      admin.site.register(Category, CategoryOption)
      admin.site.register(Document, DocumentOption)
    • config/urls.py를 작성한다.

      1
      2
      3
      4
      5
      6
      7
      from django.contrib import admin
      from django.urls import path, include

      urlpatterns = [
      path('site_config/', admin.site.urls),
      path('', include('board.urls')),
      ]
    • board/urls.py를 생성하고 작성한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      from django.urls import path
      from django.contrib.auth.views import LoginView, LogoutView

      from .views import document_list, document_create, document_update, document_delete, document_detail

      app_name = 'board'

      urlpatterns = [
      path('delete/<int:document_id>', document_delete, name='delete'),
      path('update/<int:document_id>', document_update, name='update'),
      path('create/', document_create, name='create'),
      path('detail/<int:document_id>/',document_detail, name='detail'),
      path('', document_list, name='list'),
      ]
    • templates 파일을 생성하여 작성한다.

      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
      <!--//
      board_project/layout/base.html
      settings.py의 TEPLATES에서 DIRS를 수정하는 것을 잊지 말자.
      //-->
      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <title>{% block title %}{% endblock %}</title>
      <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>

      <div class="container">
      {% block content %}
      {% endblock %}
      </div>

      <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>


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

      {% block content %}
      <div class="row">
      <div class="col"></div>
      <div class="col-6">
      {% for object in object_list %}
      {{object.title}} {{object.text}} {{object.image.url}} {{object.author.username}}<br>
      {% endfor %}
      </div>
      <div class="col"></div>
      </div>
      {% endblock %}


      <!--//
      board/templates/board/document_detail.html
      //-->
      {% extends 'base.html' %}
      {% block title %}
      Photo Detail
      {% endblock %}

      {% block content %}
      <div class="row">
      <div class="col"></div>
      <div class="col-6">
      {{object.title}} {{object.text}} {{object.image.url}} {{object.author.username}}<br>
      </div>
      <div class="col"></div>
      </div>
      {% endblock %}


      <!--//
      board/templates/board/document_create.html
      //-->
      {% extends 'base.html' %}
      {% block title %}
      Board Create
      {% endblock %}

      {% block content %}
      <div class="row">
      <div class="col"></div>
      <div class="col-6">
      <!--// 파일을 보내는 경우 enctype를 추가해야 한다. //-->
      <form action="" method="post" enctype="multipart/form-data">
      {% csrf_token %}
      <table>
      {{form.as_table}}
      </table>
      <input type="submit" value="Write">
      </form>
      </div>
      <div class="col"></div>
      </div>
      {% endblock %}


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

      {% block content %}
      <div class="row">
      <div class="col"></div>
      <div class="col-6">
      <form action="" method="post" enctype="multipart/form-data">
      {% csrf_token %}
      <table>
      {{form.as_table}}
      </table>
      <input type="submit" value="Update">
      </form>
      </div>
      <div class="col"></div>
      </div>
      {% endblock %}
    • 서버를 실행하여 이미지 파일을 올려보고 S3에 업로드되는지 확인하자.

jQuery Practice

  • html 파일을 생성한다.
  • jQuery.com - Download - google CDN - jQuery/3.x snippet의 내용을 html 파일에 작성한다.
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <div>
    내용
    </div>
    <a href="#">내용 토글</a>
    <a href="#" class="btn_menu">MENU</a>

    <ul class="main_menu" style="display:none;">
    <li>메뉴1</li>
    <li>메뉴2</li>
    <li>메뉴3</li>
    <li>메뉴4</li>
    </ul>

    <textarea class="contents"></textarea>
    <button class="btn_change">내용바꾸기</button>
    <br>

    <form method="post" action="" id="register_form">
    username : <input type="text" name="username"><br>
    주소 : <input type="text" name="address"><br>
    <input type="submit" value="회원가입">
    </form>

    <!--//
    jQuery : 액션에 대해서 리액션
    액션 : 마우스 클릭, 롤오버, 롤아웃, 키 프레스, 키 다운, 키 업, 스크롤
    리액션 : 어떤 요소를 보여준다. 감춘다. 변경한다.
    어떤 버튼을 클릭하면 - 메뉴가 나타난다.
    어떤 키를 누르면 - 페이지를 이동한다.

    들여쓰기를 따로 하지 않아도 동작에는 문제가 없다.
    //-->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
    <script type="text/javascript">
    // 기본 코드 : 읽자마자 실행
    $(function() {
    // 초기화 코드
    });

    $(document).ready(function() {
    // 문서 로드가 끝난 후에 실행
    // 어떤 액션에 대해서 무슨 리액션을 할 것인지 연결 목록
    // 액션 : a태그 클릭 - 어떤 요소 + 어떤 이벤트
    // 리액션 : div태그 보이기/숨기기 - 어떤 함수
    $('a').click(function() {
    $('div').toggle();
    // $('div').hide(); - 사라지게 하고 싶다면
    // $('div').show(); - 보이게 하고 싶다면
    });

    $('.btn_menu').click(function() {
    //$('.main_menu').toggle();
    if ($('.main_menu').is(":visible")) {
    $('.main_menu').hide(1000);
    } else {
    $('.main_menu').show(1000);
    }
    });

    $('.btn_change').click(function() {
    // 요소.html() - 요소 안에 있는 html 코드 불러오기
    // 요소.html(내용) - 요소 안에 있는 html 코드를 내용으로 교체한다.
    $('.contents').html("내용채우기 실습!");
    });

    $('#register_form').submit(function() {
    // 위에서 validation을 하고
    // val() - 비어있으면 값 읽기
    // val(값) - 값이 있으면 값 할당
    username = $('input[name="username"]').val();
    $('input[name="address"]').val(username+"@nate.com");

    // validation이 실패했다면 return false; 성공했다면 return true;
    return false;
    });
    });
    /*
    - 기명함수
    function [함수이름]([매개변수]){
    }

    - 무기명함수, 일회용
    function([매개변수]){
    }

    - 셀렉터 만드는 법
    tag : tag이름
    class : .class이름
    id : #id
    */
    </script>
    </body>
    </html>
Share