📢 공지사항
home

4주차

생성일
2022/01/08 12:00
태그

31강. Profileapp 구현 시작

Profileapp의 CreateView 부분을 집중적으로 구현하고, 이미지 파일을 다루는 과정에서 어떤 문제가 있는지 확인 후, 문제를 디버깅합니다.
manage.py makemigrations
view.py 수정.
from django.shortcuts import render # Create your views here. from django.urls import reverse_lazy from django.views.generic import CreateView from profileapp.forms import ProfileCreationForm from profileapp.models import Profile class ProfileCreateView(CreateView): model = Profile context_object_name = 'target_profile' form_class = ProfileCreationForm success_url = reverse_lazy('accountapp:hello_world') template_name = 'profileapp/create.html'
Python
복사
profileapp/templates/profileapp 까지 경로 디렉토리를 만들어준다.
작은 profileapp 디렉토리에 create.html 파일 만들기.
{% extends 'base.html' %} {% load bootstrap4 %} {% block content %} <div style="text-align:center; max-width: 500px; margin: 4rem auto"> <div class="mb-4"> <h4>Profile Create</h4> </div> <form action="{% url 'profileapp:create' %}" method="post"> {% csrf_token %} {% bootstrap_form form %} <input type="submit" class="btn btn-dark rounded-pill col-6 mt-3"> </form> </div> {% endblock %}
HTML
복사
urls.py 수정.
from django.urls import path from profileapp.views import ProfileCreateView app_name = 'profileapp' urlpatterns = [ path('create/', ProfileCreateView.as_view(), name='create'), ]
Python
복사
detail.html 상단부분 수정.
{% if target_user.profile %} <h2 style="font-family: 'NanumSquareB'"> {{ target_user.profile.nickname }} </h2> {% else %} <a href="{% url 'profileapp:create' %}"> <h2 style="font-family: 'NanumSquareB'"> Create Profile </h2> </a> {% endif %}
HTML
복사
profileapp/create.html 폼 태그 수정
<form action="{% url 'profileapp:create' %}" method="post" enctype="multipart/form-data">
HTML
복사
view.py 수정 (classProfileCreateView 안에 넣어줌)
def form_valid(self, form): temp_profile = form.save(commit=False) temp_profile.user = self.request.user temp_profile.save() return super().form_valid(form)
Python
복사
git add .
git commit -m “django course 31 commit”

32강. Profileapp 마무리

Profileapp의 UpdateView 를 만들면서 Profileapp 구축 마무리를 진행합니다.
views.py에 추가
class ProfileUpdateView(UpdateView): model = Profile context_object_name = 'target_profile' form_class = ProfileCreationForm success_url = reverse_lazy('accountapp:hello_world') template_name = 'profileapp/update.html'
Python
복사
urls.py 도 추가
path('update/<int:pk>', ProfileUpdateView.as_view(), name='update'),
Python
복사
update.html 만들기
{% extends 'base.html' %} {% load bootstrap4 %} {% block content %} <div style="text-align:center; max-width: 500px; margin: 4rem auto"> <div class="mb-4"> <h4>Update Profile</h4> </div> <form action="{% url 'profileapp:update' pk=target_profile.pk %}" method="post" enctype="multipart/form-data"> {% csrf_token %} {% bootstrap_form form %} <input type="submit" class="btn btn-dark rounded-pill col-6 mt-3"> </form> </div> {% endblock %}
HTML
복사
detail.html에 추가
<img src="{{target_user.profile.image.url }}" alt="" style="height: 12rem; width 12rem; border-radius: 20rem; margin-bottom: 2rem;"> .... {{ target_user.profile.nickname }} <a href="{% url 'profileapp:update' pk=target_user.profile.pk %}"> edit </a> .... <h5 style="margin-bottom: 3rem;"> {{ target_user.profile.message }} </h5>
HTML
복사
pragmatic/urls.py 수정
from django.conf import settings from django.conf.urls.static import static from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('accountapp.urls')), path('profiles/', include('profileapp.urls')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Python
복사
profileapp/decorators.py 생성.
from django.http import HttpResponseForbidden from profileapp.models import Profile def profile_ownership_required(func): def decorated(request, *args, **kwargs): profile = Profile.objects.get(pk=kwargs['pk']) if not profile.user == request.user: return HttpResponseForbidden() return func(request, *args, **kwargs) return decorated
Python
복사
profileapp/view.py에 class ProfileUpdateView(UpdateView): 위에 추가.
@method_decorator(profile_ownership_required, 'get') @method_decorator(profile_ownership_required, 'post')
Python
복사
git add .
git commit -m “django course 32 commit”

33강. get_success_url 함수 그리고 리팩토링

지금까지 짠 코드들의 문제를 조금 해결하고, get_success_url을 통해 동적인 redirect url을 반환해 주도록 변경해봅니다.
profileapp/views.py 수정
class ProfileCreateView(CreateView): model = Profile context_object_name = 'target_profile' form_class = ProfileCreationForm template_name = 'profileapp/create.html' def form_valid(self, form): temp_profile = form.save(commit=False) temp_profile.user = self.request.user temp_profile.save() return super().form_valid(form) def get_success_url(self): return reverse('accountapp:detail', kwargs={'pk':self.object.user.pk}) @method_decorator(profile_ownership_required, 'get') @method_decorator(profile_ownership_required, 'post') class ProfileUpdateView(UpdateView): model = Profile context_object_name = 'target_profile' form_class = ProfileCreationForm template_name = 'profileapp/update.html' def get_success_url(self): return reverse('accountapp:detail', kwargs={'pk':self.object.user.pk})
Python
복사
detail 수정
{% extends 'base.html' %} {% block content %} <div> <div style="text-align: center; max-width: 500px; margin: 4rem auto;"> {% if target_user.profile %} <img src="{{target_user.profile.image.url }}" alt="" style="height: 12rem; width 12rem; border-radius: 20rem; margin-bottom: 2rem;"> <h2 style="font-family: 'NanumSquareB'"> {{ target_user.profile.nickname }} {% if target_user == user %} <a href="{% url 'profileapp:update' pk=target_user.profile.pk %}"> edit </a> {% endif %} </h2> <h5 style="margin-bottom: 3rem;"> {{ target_user.profile.message }} </h5> {% else %} <a href="{% url 'profileapp:create' %}"> <h2 style="font-family: 'NanumSquareB'"> Create Profile </h2> </a> {% else %} <h2> 닉네임 미설정 </h2> {% endif %} {% endif %} {% if target_user == user %} <a href="{% url 'accountapp:update' pk=user.pk %}"> <p> Change Info </p> </a> <a href="{% url 'accountapp:delete' pk=user.pk %}"> <p> Quit </p> </a> {% endif %} </div> </div> {% endblock %}
Python
복사
git add .
git commit -m “django course 33 commit”

Articleapp Implementation

34강. MagicGrid 소개 및 Articleapp 시작

핀터레스트의 카드형 레이아웃을 구현하기 위한 라이브러리, MagicGrid를 소개하고, 저희 사이트에 ArticleApp 내부에 간단히 구현해봅니다.
Lorem Picsum : https://picsum.photos/
python manage.py startapp articleapp
settings.py에 INSTALLED_APPS에 articleapp 추가
urls.py path('articles/', include('articleapp.urls')), 추가
articleapp에 urls 만들고 작성.
from django.urls import path from django.views.generic import TemplateView urlpatterns = [ path('list/', TemplateView.as_view(template_name='articleapp/list.html'), name='list'), ]
Python
복사
articleapp/templates/list.html 만들기.
밑에 내려서 on JSFIDDLE 링크에 html과 css 부분을 가져온다.
{% extends 'base.html' %} {% load static %} {% block content %} <style> .container div { width: 280px; height: 500px; background-color: antiquewhite; display: flex; justify-content: center; align-items: center; border-radius: 8px; } .container .item1 { height: 200px; } .container .item4 { height: 800px; } .container .item6 { height: 600px; } .container .item11 { height: 400px; } </style> <div class="container"> <div class="item1">1</div> <div class="item2">2</div> <div class="item3">3</div> <div class="item4">4</div> <div class="item5">5</div> <div class="item6">6</div> <div class="item7">7</div> <div class="item8">8</div> <div class="item9">9</div> <div class="item10">10</div> <div class="item11">11</div> <div class="item12">12</div> <div class="item13">13</div> </div> <script src="{% static 'js/magicgrid.js' %}"></script> {% endblock %}
HTML
복사
static/js/magicgrid.js 만들기.
코드는 매직그리드 깃헙에서 dist에 magic-grid.cjs.js 파일 코드를 끌어온다.
'use strict'; /** * @author emmanuelolaojo * @since 11/11/18 */ /** * Validates the configuration object. * * @param config - configuration object */ var checkParams = function (config) { var DEFAULT_GUTTER = 25; var booleanProps = ["useTransform", "center"]; if (!config) { throw new Error("No config object has been provided."); } for(var prop of booleanProps){ if(typeof config[prop] !== "boolean"){ config[prop] = true; } } if(typeof config.gutter !== "number"){ config.gutter = DEFAULT_GUTTER; } if (!config.container) { error("container"); } if (!config.items && !config.static) { error("items or static"); } }; /** * Handles invalid configuration object * errors. * * @param prop - a property with a missing value */ var error = function (prop) { throw new Error(("Missing property '" + prop + "' in MagicGrid config")); }; /** * Finds the shortest column in * a column list. * * @param cols - list of columns * * @return shortest column */ var getMin = function (cols) { var min = cols[0]; for (var col of cols) { if (col.height < min.height) { min = col; } } return min; }; /** * @author emmanuelolaojo * @since 11/10/18 * * The MagicGrid class is an * implementation of a flexible * grid layout. */ var MagicGrid = function MagicGrid (config) { checkParams(config); if (config.container instanceof HTMLElement) { this.container = config.container; this.containerClass = config.container.className; } else { this.containerClass = config.container; this.container = document.querySelector(config.container); } this.static = config.static || false; this.size = config.items; this.gutter = config.gutter; this.maxColumns = config.maxColumns || false; this.useMin = config.useMin || false; this.useTransform = config.useTransform; this.animate = config.animate || false; this.center = config.center; this.styledItems = new Set(); }; /** * Initializes styles * * @private */ MagicGrid.prototype.initStyles = function initStyles () { if (!this.ready()) { return; } this.container.style.position = "relative"; var items = this.items(); for (var i = 0; i < items.length; i++) { if (this.styledItems.has(items[i])) { continue; } var style = items[i].style; style.position = "absolute"; if (this.animate) { style.transition = (this.useTransform ? "transform" : "top, left") + " 0.2s ease"; } this.styledItems.add(items[i]); } }; /** * Gets a collection of all items in a grid. * * @return {HTMLCollection} * @private */ MagicGrid.prototype.items = function items () { return this.container.children; }; /** * Calculates the width of a column. * * @return width of a column in the grid * @private */ MagicGrid.prototype.colWidth = function colWidth () { return this.items()[0].getBoundingClientRect().width + this.gutter; }; /** * Initializes an array of empty columns * and calculates the leftover whitespace. * * @return {{cols: Array, wSpace: number}} * @private */ MagicGrid.prototype.setup = function setup () { var width = this.container.getBoundingClientRect().width; var colWidth = this.colWidth(); var numCols = Math.floor(width/colWidth) || 1; var cols = []; if (this.maxColumns && numCols > this.maxColumns) { numCols = this.maxColumns; } for (var i = 0; i < numCols; i++) { cols[i] = {height: 0, index: i}; } var wSpace = width - numCols * colWidth + this.gutter; return {cols: cols, wSpace: wSpace}; }; /** * Gets the next available column. * * @param cols list of columns * @param i index of dom element * * @return {*} next available column * @private */ MagicGrid.prototype.nextCol = function nextCol (cols, i) { if (this.useMin) { return getMin(cols); } return cols[i % cols.length]; }; /** * Positions each item in the grid, based * on their corresponding column's height * and index then stretches the container to * the height of the grid. */ MagicGrid.prototype.positionItems = function positionItems () { var ref = this.setup(); var cols = ref.cols; var wSpace = ref.wSpace; var maxHeight = 0; var colWidth = this.colWidth(); var items = this.items(); wSpace = this.center ? Math.floor(wSpace / 2) : 0; this.initStyles(); for (var i = 0; i < items.length; i++) { var col = this.nextCol(cols, i); var item = items[i]; var topGutter = col.height ? this.gutter : 0; var left = col.index * colWidth + wSpace + "px"; var top = col.height + topGutter + "px"; if(this.useTransform){ item.style.transform = "translate(" + left + ", " + top + ")"; } else{ item.style.top = top; item.style.left = left; } col.height += item.getBoundingClientRect().height + topGutter; if(col.height > maxHeight){ maxHeight = col.height; } } this.container.style.height = maxHeight + this.gutter + "px"; }; /** * Checks if every item has been loaded * in the dom. * * @return {Boolean} true if every item is present */ MagicGrid.prototype.ready = function ready () { if (this.static) { return true; } return this.items().length >= this.size; }; /** * Periodically checks that all items * have been loaded in the dom. Calls * this.listen() once all the items are * present. * * @private */ MagicGrid.prototype.getReady = function getReady () { var this$1 = this; var interval = setInterval(function () { this$1.container = document.querySelector(this$1.containerClass); if (this$1.ready()) { clearInterval(interval); this$1.listen(); } }, 100); }; /** * Positions all the items and * repositions them whenever the * window size changes. */ MagicGrid.prototype.listen = function listen () { var this$1 = this; if (this.ready()) { var timeout; window.addEventListener("resize", function () { if (!timeout){ timeout = setTimeout(function () { this$1.positionItems(); timeout = null; }, 200); } }); this.positionItems(); } else { this.getReady(); } }; // 이 부분은 밑에 내려서 on JSFIDDLE 링크에 자바스크립트 부분을 가져온다. let magicGrid = new MagicGrid({ container: '.container', animate: true, gutter: 30, static: true, useMin: true }); // 직접입력 var masonrys = document.getElementsByTagName("img"); for (let i = 0; i < masonrys.length; i++) { masonrys[i].addEventListener('load', function () { magicGrid.positionItems(); }, false); } magicGrid.listen();
JavaScript
복사
list.html 이미지 추가해주기
{% extends 'base.html' %} {% load static %} {% block content %} <style> .container div { width: 250px; background-color: antiquewhite; display: flex; justify-content: center; align-items: center; border-radius: 1rem; } .container img { width: 100%; border-radius: 1rem; } </style> <div class="container"> <div class="item1"> <img src="https://picsum.photos/200/300" alt=""> </div> <div> <img src="https://picsum.photos/200/340" alt=""> </div> <div> <img src="https://picsum.photos/200/260" alt=""> </div> <div> <img src="https://picsum.photos/200/400" alt=""> </div> <div> <img src="https://picsum.photos/200/510" alt=""> </div> <div> <img src="https://picsum.photos/200/300" alt=""> </div> <div> <img src="https://picsum.photos/200/258" alt=""> </div> <div> <img src="https://picsum.photos/200/288" alt=""> </div> <div> <img src="https://picsum.photos/200/448" alt=""> </div> <div> <img src="https://picsum.photos/200/320" alt=""> </div> <div> <img src="https://picsum.photos/200/298" alt=""> </div> <div> <img src="https://picsum.photos/200/298" alt=""> </div> <div> <img src="https://picsum.photos/200/428" alt=""> </div> </div> <script src="{% static 'js/magicgrid.js' %}"></script> {% endblock %}
HTML
복사
git add .
git commit -m “django course 34 commit”

35강. Article 모델 생성 오류 수정

created_at = models.DateField(auto_now_add=True, null=True)

35강. Articleapp 구현

models.py 작성
from django.contrib.auth.models import User from django.db import models # Create your models here. class Article(models.Model): writer = models.ForeignKey(User, on_delete=models.SET_NULL, related_name='article', null=True) title = models.CharField(max_length=200, null=True) image = models.ImageField(upload_to='article/', null=False) content = models.TextField(null=True) created_at = models.DateField(auto_now_add=True, null=True)
Python
복사
forms.py를 만들어준다.
from django.forms import ModelForm from articleapp.models import Article class ArticleCreationForm(ModelForm): class Meta: model = Article fields = ['title', 'image', 'content']
Python
복사
python manage.py makemigrations
views.py
from django.contrib.auth.decorators import login_required from django.shortcuts import render # Create your views here. from django.urls import reverse from django.utils.decorators import method_decorator from django.views.generic import CreateView, DetailView from articleapp.forms import ArticleCreationForm from articleapp.models import Article @method_decorator(login_required, 'get') @method_decorator(login_required, 'post') class ArticleCreateView(CreateView): model = Article form_class = ArticleCreationForm template_name = 'articleapp/create.html' def form_valid(self, form): temp_article = form.save(commit=False) temp_article.writer = self.request.user temp_article.save() return super().form_valid(form) def get_success_url(self): return reverse('articleapp:detail', kwargs={'pk': self.object.pk}) class ArticleDetailView(DetailView): model = Article context_object_name = 'target_article' template_name = 'articleapp/detail.html'
Python
복사
create.html 만들기
{% extends 'base.html' %} {% load bootstrap4 %} {% block content %} <div style="text-align:center; max-width: 500px; margin: 4rem auto"> <div class="mb-4"> <h4>Article Create</h4> </div> <form action="{% url 'articleapp:create' %}" method="post" enctype="multipart/form-data"> {% csrf_token %} {% bootstrap_form form %} <input type="submit" class="btn btn-dark rounded-pill col-6 mt-3"> </form> </div> {% endblock %}
HTML
복사
detail.html 만들기
{% extends 'base.html' %} {% block content %} <div> <div style="text-align: center; max-width: 500px; margin: 4rem auto;"> <h1> {{ target_article.title }} </h1> <img src="{{ target_article.image.url }}" alt=""> <p> {{ target_article.content }} </p> </div> </div> {% endblock %}
HTML
복사
header.html nav1수정
<a href="{% url 'articleapp:list' %}"> <span>Articles</span> </a>
HTML
복사
list.html 아래쪽에 추가
.... <div> <a href="{% url 'articleapp:create' %}" class="btn btn-dark rounded-pill col-3 mt-3 mb-3"> Create Article </a> </div> ....
HTML
복사
urls.py 수정
from django.urls import path from django.views.generic import TemplateView from articleapp.views import ArticleCreateView, ArticleDetailView app_name = 'articleapp' urlpatterns = [ path('list/', TemplateView.as_view(template_name='articleapp/list.html'), name='list'), path('create/', ArticleCreateView.as_view(), name='create'), path('detail/<int:pk>', ArticleDetailView.as_view(), name='detail'), ]
Python
복사
views.py ArticleUpdateView 만들기
@method_decorator(article_ownership_required, 'get') @method_decorator(article_ownership_required, 'post') class ArticleUpdateView(UpdateView): model = Article context_object_name = 'target_article' form_class = ArticleCreationForm template_name = 'articleapp/update.html' def get_success_url(self): return reverse('articleapp:detail', kwargs={'pk': self.object.pk})
Python
복사
articleapp폴더에 decorators 복붙.
from django.http import HttpResponseForbidden from articleapp.models import Article def article_ownership_required(func): def decorated(request, *args, **kwargs): article = Article.objects.get(pk=kwargs['pk']) if not article.writer == request.user: return HttpResponseForbidden() return func(request, *args, **kwargs) return decorated
Python
복사
detail.html 코드 추가
.... <a href="{% url 'articleapp:update' pk=target_article.pk %}"> <p>Update Article</p> </a> ....
HTML
복사
urls에 추가
path('update/<int:pk>', ArticleUpdateView.as_view(), name='detail'),
update.html 생성.
{% extends 'base.html' %} {% load bootstrap4 %} {% block content %} <div style="text-align:center; max-width: 500px; margin: 4rem auto"> <div class="mb-4"> <h4>Article Update</h4> </div> <form action="{% url 'article:update' pk=target_article.pk %}" method="post" enctype="multipart/form-data"> {% csrf_token %} {% bootstrap_form form %} <input type="submit" class="btn btn-dark rounded-pill col-6 mt-3"> </form> </div> {% endblock %}
HTML
복사
views.py ArticleDeleteView 만들기
@method_decorator(article_ownership_required, 'get') @method_decorator(article_ownership_required, 'post') class ArticleDeleteView(DeleteView): model = Article context_object_name = 'target_article' success_url = reverse_lazy('articleapp:list') template_name = 'articleapp/delete.html'
Python
복사
delete.html
{% extends 'base.html' %} {% block content %} <div style="text-align:center; max-width: 500px; margin: 4rem auto"> <div class="mb-4"> <h4>Delete Article : {{ target_article.title }}</h4> </div> <form action="{% url 'accountapp:delete' pk=target_article.pk %}" method="post"> {% csrf_token %} <input type="submit" class="btn btn-danger rounded-pill col-6 mt-3"> </form> </div> {% endblock %}
HTML
복사
detail.html에 코드 추가
<a href="{% url 'articleapp:delete' pk=target_article.pk %}"> <p>Delete Article</p> </a>
HTML
복사
urls에 추가
path('delete/<int:pk>', ArticleDeleteView.as_view(), name='delete'),
git add .
git commit -m “django course 35 commit”

36강. ListView, Pagination 소개 및 적용

magicgrid 내부에 들어갈 실제 게시물들을 리스트화해줄 View, ListView에 대해서 간단히 알아보고, 실제 코드에 ListView와 Pagination을 적용해봅니다. 그리고 CommentApp을 만들기 전에 Articleapp Detail 페이지의 세부 디자인을 약간 수정합니다.
views.py 에 추가
.... class ArticleListView(ListView): model = Article context_object_name = 'article_list' template_name = 'articleapp/list.html' paginate_by = 5
Python
복사
urls.py list 부분 수정
path('list/', ArticleListView.as_view(), name='list'),
templates/articleapp/snippets/card.html 만들기
<div> <img src="{{ article.image.url }}" alt=""> </div>
HTML
복사
스타일 아래부분 list.html 수정.
{% if article_list %} <div class="container"> {% for article in article_list %} <a href="{% url 'articleapp:dtail' pk=article.pk %}"> {% include 'snippets/card.html' with article=article %} </a> {% endfor %} </div> <script src="{% static 'js/magicgrid.js' %}"></script> {% else %} <div class="text-center"> <h1>No Articles YET!</h1> </div> {% endif %} {% include 'snippets/pagination.html' with page_obj=page_obj %} <div style="text-align: center"> <a href="{% url 'articleapp:create' %}" class="btn btn-dark rounded-pill col-3 mt-3 mb-3"> Create Article </a> </div>
HTML
복사
snippets 안에 pagination.html 만들기
<div style="text-align: center; margin: 1rem 0;"> {% if page_obj.has_previous %} <a href="{% url 'articleapp:list' %}?page={{ page_obj.previous_page_number }}" class="btn btn-secondary rounded-pill"> {{ page_obj.previous_page_number }} </a> {% endif %} <a href="{% url 'articleapp:list' %}?page={{ page_obj.number }}" class="btn btn-secondary rounded-pill active"> {{ page_obj.number }} </a> {% if page_obj.has_next %} <a href="{% url 'articleapp:list' %}?page={{ page_obj.next_page_number }}" class="btn btn-secondary rounded-pill"> {{ page_obj.next_page_number }} </a> {% endif %} </div>
HTML
복사
detail.html 수정
<div style="text-align: center; max-width: 700px; margin: 4rem auto"> <h1> {{ target_article.title }} </h1> <h5> {{ target_article.writer.profile.nickname }} </h5> <hr> <img style="width: 100%; border-radius: 1rem; margin: 2rem 0;" src="{{ target_article.image.url }}" alt=""> <p> {{ target_article.content }} </p> {% if target_article.writer == user %} <a href="{% url 'articleapp:update' pk=target_article.pk %}" class="btn btn-primary rounded-pill col-3"> Update </a> <a href="{% url 'articleapp:delete' pk=target_article.pk %}" class="btn btn-danger rounded-pill col-3"> Delete </a> {% endif %} <hr> </div>
HTML
복사
git add .
git commit -m “django course 36 commit”

Commentapp Implementation

37강. Mixin 소개 및 Commentapp 구현

django에서 제공하는 Mixin을 다중 상속 받아 DetailView 안에 form을 포함, 댓글시스템을 구현해봅니다.
python manage.py startapp commentapp
pragmatic/settings.py INSTALLED_APPS'commentapp', 추가
pragmatic/urls.py urlpatternspath('comments/', include('commentapp.urls')), 추가
commentapp/urls.py 생성.
from django.urls import path app_name = 'commentapp' urlpatterns = [ ]
Python
복사
views.py 작성.
class CommentCreateView(CreateView): model = Comment form_class = CommentCreationForm template_name = 'commentapp/create.html' def form_valid(self, form): temp_comment = form.save(commit=False) temp_comment.article = Article.objects.get(pk=self.request.POST['article_pk']) temp_comment.writer = self.requset.user temp_comment.save() return super().form_valid(form) def get_success_url(self): return reverse('articleapp:detail', kwargs={'pk': self.object.article.pk})
Python
복사
models.py 작성.
class Comment(models.Model): article = models.ForeignKey(Article, on_delete=models.SET_NULL, null=True, related_name='comment') writer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='comment') content = models.TextField(null=False) created_at = models.DateTimeField(auto_now=True)
Python
복사
commentapp/forms.py 생성.
from django.forms import ModelForm from commentapp.models import Comment class CommentCreationForm(ModelForm): class Meta: model = Comment fields = ['content']
Python
복사
python manage.py makemigrations
python manage.py migrate
commentapp/urls.py path('create/', CommentCreateView.as_view(), name='create'), 추가
commentapp/templates/commentapp/create.html 생성
{% load bootstrap4 %} {% block content %} <div style="text-align: center; max-width: 500px; margin: 4rem auto"> <div class="mb-4"> <h4>Comment Create</h4> </div> <form action="{% url 'commentapp:create' %}" method="post"> {% csrf_token %} {% bootstrap_form form %} {% if user.is_authenticated %} <input type="submit" class="btn btn-dark rounded-pill col-6 mt-3"> {% else %} <a href="{% url 'accountapp:login' %}?next={{ request.path }}" class="btn btn-dark rounded-pill col-6 mt-3"> Login </a> {% endif %} <input type="hidden" name="article_pk" value="{{ article.pk }}"> </form> </div> {% endblock %}
HTML
복사
articleapp/detail.html <hr> 밑에 추가
{% include 'commentapp/create.html' with article=target_article %}
articleapp/views.py ArticleDetailView 수정
class ArticleDetailView(DetailView, FormMixin): model = Article form_class = CommentCreationForm context_object_name = 'target_article' template_name = 'articleapp/detail.html'
Python
복사
git add .
git commit -m “django course 37 commit”

38강. Commentapp 마무리

View 파트를 수정하지 않고 template 내에서 for 문을 통해 댓글을 시각화 하는 작업 및 CommentApp 나머지 부분을 마무리합니다.
articleapp/detail.html <hr>과 ‘댓글 작성칸’ 사이 코드 추가
{% for comment in target_article.comment.all %} {% include 'commentapp/detail.html' with comment=comment %} {% endfor %}
HTML
복사
commentapp/detail.html 생성
<div style="border: 1px solid; text-align: left; padding: 4%; margin: 1rem 0; border-radius: 1rem; border-color: #bbb;"> <div> <strong> {{ comment.writer.profile.nickname }} </strong> &nbsp&nbsp&nbsp {{ comment.created_at }} </div> <div style="margin: 1rem 0;"> {{ comment.content }} </div> {% if comment.writer == user %} <div style="text-align: right"> <a href="{% url 'commentapp:delete' pk=comment.pk%}" class="btn btn-danger rounded-pill"> Delete </a> </div> {% endif %} </div>
HTML
복사
commentapp/views.py 마지막에 추가
class CommentDeleteView(DeleteView): model = Comment context_object_name = 'target_comment' template_name = 'commentapp/delete.html' def get_success_url(self): return reverse('articleapp:detail', kwargs={'pk': self.object.article.pk})
Python
복사
commentapp/urls.py path('delete/<int:pk>', CommentDeleteView.as_view(), name='delete'), 추가.
commentapp/delete.html 생성.
{% extends 'base.html' %} {% block content %} <div style="text-align:center; max-width: 500px; margin: 4rem auto"> <div class="mb-4"> <h4>Delete Comment : {{ target_comment.content }}</h4> </div> <form action="{% url 'commentapp:delete' pk=target_comment.pk %}" method="post"> {% csrf_token %} <input type="submit" class="btn btn-danger rounded-pill col-6 mt-3"> </form> </div> {% endblock %}
HTML
복사
from django.http import HttpResponseForbidden from commentapp.models import Comment def comment_ownership_required(func): def decorated(request, *args, **kwargs): comment = Comment.objects.get(pk=kwargs['pk']) if not comment.writer == request.user: return HttpResponseForbidden() return func(request, *args, **kwargs) return decorated
Python
복사
commentapp/views.py class CommentDeleteView(DeleteView): 윗 부분에 작성.
@method_decorator(comment_ownership_required, 'get') @method_decorator(comment_ownership_required, 'post')
Python
복사
git add .
git commit -m “django course 38 commit”

Mobile Responsive Layout

39강. 모바일 디버깅, 반응형 레이아웃

모바일 기기로 직접 우리가 만든 사이트를 접속하여 모바일에서 실제로 보여지는 화면이 어떤지 확인해보고, 반응형 레이아웃을 위한 설정을 다룹니다.
python manage.py runserver 0.0.0.0:8000 다른 컴퓨터에서도 접속 가능.
pragmatic/settings.py ALLOWED_HOSTS = [] -> ALLOWED_HOSTS = ['*'] 수정
templates/head.html <meta charset="UTF-8"> 밑에
<meta name="viewpoint" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 추가
articleapp/list.html 스타일 태그 변경.
.container { padding: 0; margin: 0, auto; } .container a { width: 45%; max-width: 250px; } .container div { display: flex; justify-content: center; align-items: center; border-radius: 1rem; } .container img { width: 100%; border-radius: 1rem; }
HTML
복사
static/base.css 밑에 추가
@mdeia screen and (max-width: 500px) { html { font-size: 13px; } }
CSS
복사
static/js/magicgrid.js 수정
let magicGrid = new MagicGrid({ container: '.container', animate: true, gutter: 12, static: true, useMin: true });
JavaScript
복사
git add .
git commit -m “django course 39 commit”

Projectapp Implementation

40강. ProjectApp 구현

게시판에 해당하는 projectapp 을 구현해봅니다.
1.
Create, Detail, List View
2.
Success_url to realated Project
3.
Login_required to CreateView
4.
Model (: title / description / image / created_at)
python manage.py startapp projectapp
settings INSTALLED_APP에 추가
'projectapp',
pragmatic/urls.py 에 추가
path('projects/', include('projectapp.urls')),
projectapp/urls.py 만들기
from django.urls import path from projectapp.views import ProjectListView, ProjectCreateView, ProjectDetailView app_name = 'projectapp' urlpatterns = [ path('list/', ProjectListView.as_view(), name='list'), path('create/', ProjectCreateView.as_view(), name='create'), path('detail/<int:pk>', ProjectDetailView.as_view(), name='detail'), ]
Python
복사
views.py 작성
from django.contrib.auth.decorators import login_required from django.shortcuts import render # Create your views here. from django.urls import reverse from django.utils.decorators import method_decorator from django.views.generic import CreateView, DetailView, ListView from projectapp.forms import ProjectCreationForm from projectapp.models import Project @method_decorator(login_required, 'get') @method_decorator(login_required, 'post') class ProjectCreateView(CreateView): model = Project form_class = ProjectCreationForm template_name = 'projectapp/create.html' def get_success_url(self): return reverse('projectapp:detail', kwargs={'pk': self.object.pk}) class ProjectDetailView(DetailView): model = Project context_object_name = 'target_project' template_name = 'projectapp/detail.html' class ProjectListView(ListView): model = Project context_object_name = 'project_list' template_name = 'projectapp/list.html' paginate_by = 25
Python
복사
models.py 만들기
from django.db import models # Create your models here. class Project(models.Model): image = models.ImageField(upload_to='project/', null=False) title = models.CharField(max_length=20, null=False) description = models.CharField(max_length=200, null=True) created_at = models.DateTimeField(auto_now=True)
Python
복사
forms.py 만들기
from django.forms import ModelForm from projectapp.models import Project class ProjectCreationForm(ModelForm): class Meta: model = Project fields = ['image', 'title', 'description']
Python
복사
python manage.py makemigrations
python manage.py migrate
create.html
{% extends 'base.html' %} {% load bootstrap4 %} {% block content %} <div style="text-align:center; max-width: 500px; margin: 4rem auto"> <div class="mb-4"> <h4>Create Project</h4> </div> <form action="{% url 'projectapp:create' %}" method="post" enctype="multipart/form-data"> {% csrf_token %} {% bootstrap_form form %} <input type="submit" class="btn btn-dark rounded-pill col-6 mt-3"> </form> </div> {% endblock %}
HTML
복사
detail.html
{% extends 'base.html' %} {% block content %} <div> <div style="text-align: center; max-width: 500px; margin: 4rem auto;"> <img src="{{target_user.profile.image.url }}" alt="" style="height: 12rem; width: 12rem; border-radius: 20rem; margin-bottom: 2rem; object-fit: cover;"> <h2 style="font-family: 'NanumSquareB'"> {{ target_project.title }} </h2> <h5 style="margin-bottom: 3rem;"> {{ target_project.description }} </h5> </div> </div> {% endblock %}
HTML
복사
list.html
{% extends 'base.html' %} {% load static %} {% block content %} <style> .container { padding: 0; margin: 0, auto; } .container a { width: 45%; max-width: 250px; } .container div { display: flex; justify-content: center; align-items: center; border-radius: 1rem; } .container img { width: 100%; border-radius: 1rem; } </style> {% if project_list %} <div class="container"> {% for project in project_list %} <a href="{% url 'projectapp:dtail' pk=project.pk %}"> {% include 'snippets/card_project.html' with project=project %} </a> {% endfor %} </div> <script src="{% static 'js/magicgrid.js' %}"></script> {% else %} <div class="text-center"> <h1>No Projects YET!</h1> </div> {% endif %} {% include 'snippets/pagination.html' with page_obj=page_obj %} <div style="text-align: center"> <a href="{% url 'projectapp:create' %}" class="btn btn-dark rounded-pill mt-3 mb-3 px-3"> Create Project </a> </div> {% endblock %}
HTML
복사
templates/snippets/card_project.html
<div style="display: block; text-align: center"> <img src="{{ project.image.url }}" alt=""> <h5 class="mt-2" style="font-family: 'NanumSquareB';"> {{ project.title | truncatechars:8 }} </h5> </div>
HTML
복사
base.css 에 코드 추가
a { color: black; } a:hover{ color: black; text-decoration: none; } .pragmatic_header_nav { margin: 0 0.5rem; } .pragmatic_header_navbar { margin: 1rem 0; }
CSS
복사
header.html 수정
<div class="pragmatic_header"> <div> <h1 class="pragmatic_logo">Pragmatic</h1> </div> <div class="pragmatic_header_navbar"> <a href="{% url 'articleapp:list' %}" class="pragmatic_header_nav"> <span>Articles</span> </a> <a href="{% url 'projectapp:list' %}" class="pragmatic_header_nav"> <span>Projects</span> </a> {% if not user.is_authenticated %} <a href="{% url 'accountapp:login' %}?next={{ request.path }}" class="pragmatic_header_nav"> <span>login</span> </a> <a href="{% url 'accountapp:create' %}" class="pragmatic_header_nav"> <span>SignUp</span> </a> {% else %} <a href="{% url 'accountapp:detail' pk=user.pk %}" class="pragmatic_header_nav"> <span>MyPage</span> </a> <a href="{% url 'accountapp:logout' %}?next={{ request.path }}" class="pragmatic_header_nav"> <span>logout</span> </a> {% endif %} </div> </div>
HTML
복사
git add .
git commit -m “django course 40 commit”