Skip to content

6-4. Photo 어플리케이션 작성

INFO

Dstagram 앱의 중심인 기능은 바로 사진 관리입니다. 사진 관리를 위해서 photo 앱을 만들어 보겠습니다.

6-4-1. URL 설계

구분URL 패턴뷰 이름뷰가 처리하는 내용
photo/photo_list()photo 리스트 템플릿을 보여 줍니다.
photo/photo/upload/PhotoUploadView()photo_upload
photo/photo/detail/1/DetailView()photo_detail
photo/photo/update/1/PhotoUpdateView()photo_update
photo/photo/delete/1/PhotoDeleteView()photo_delete
accounts/login/LoginView()login
accounts/logout/LogoutView()logout
accounts/register/register()register
admin/admin/(기본제공)관리자 사이트를 보여 줍니다. (장고제공)

6-4-2. 어플리케이션 생성

다음은 파이썬 startapp 명령어로 어플리케이션을 생성하도록 하겠습니다. photo 라는 어플리케이션 명을 입력합니다.

windows command 
C:\hyungsik74\pycharm\> cd dstagram-project
C:\hyungsik74\pycharm\dstagram-project> cd 
C:\hyungsik74\pycharm\dstagram-project
C:\hyungsik74\pycharm\dstagram-project> python manage.py startapp photo
linux command 
$/home/hyungsik74/pycharm> cd dstagram-project
$/home/hyungsik74/pycharm/dstagram-project> pwd
/home/hyungsik74/pycharm/dstagram-project
$/home/hyungsik74/pycharm/dstagram-project> python manage.py startapp photo 

어플리케이션 생성후 디렉토리 구조입니다.

ch06-04-02_01.png 그림6-4-1 디렉토리 구조입니다.

py
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'photo',            # 추가  photo.apps.photoConfig 동일함.    
]
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'photo',            # 추가  photo.apps.photoConfig 동일함.    
]

ch06-04-02_02.png 그림6-4-1 startapp photo

6-4-3. Photo 데이터모델 생성

데이터모델 생성은 장고 프레임웍에서 데이터베이스 관련 테이블 등을 생성을 하는 단계입니다. 다음의 순서대로 생성합니다.

windows command 
C:\hyungsik74\pycharm\photo-project> cd photo
C:\hyungsik74\pycharm\photo-project\photo> cd 
C:\hyungsik74\pycharm\photo-project\photo
// 테이블을 정의함
C:\hyungsik74\pycharm\photo-project\photo> notepad models.py  
// 관리자화면에 표시 
C:\hyungsik74\pycharm\photo-project\photo> notepad admin.py   
linux command
$/home/hyungsik74/pycharm/photo-project> cd photo
$/home/hyungsik74/pycharm/photo-project/photo> pwd
/home/hyungsik74/pycharm/photo-project/photo
$/home/hyungsik74/pycharm/photo-project/photo> vi models.py
$/home/hyungsik74/pycharm/photo-project/photo> vi admin.py
py
from django.db import models

# Create your models here.

# 1. 모델 : 데이터베이스 저장될 데이터가 있다면 해당 데이터를 묘사한다.
# 2. 뷰(기능) : 계산, 처리 - 실제 기능, 화면
# 3. URL 맵핑 : 라우팅 테이블에 기록한다. urls.py에 기록 - 주소를 지정
# 4. 화면에 보여줄 것이있다 : 템플릿작성(html)

# 장고의 기본 유저 모델
from django.contrib.auth.models import User
from django.urls import reverse
# 외래키(ForeignKey) - User 테이블에서 해당 유저를 찾을 수 있는 주키
# 주키(PrimaryKey) - User 테이블에 1 admin x x x x
class Photo(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_photos')
    photo = models.ImageField(upload_to='photos/%Y/%m/%d', default='photos/no_image.png')
    text = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    # makemigrations -> migrate
    class Meta:
        ordering = ['-updated']

    def __str__(self):
        return self.author.username + " " + self.created.strftime("%Y-%m-%d %H:%M:%S")

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

# 모델을 만들었다 => 데이터베이스에 어떤 데이터들을 어떤 형태로 넣을지 결정!
# makemigrations => 모델의 변경사항을 추적해서 기록
# 마이그레이션(migrate) => 데이터베이스에 모델의 내용을 반영(테이블 생성)
from django.db import models

# Create your models here.

# 1. 모델 : 데이터베이스 저장될 데이터가 있다면 해당 데이터를 묘사한다.
# 2. 뷰(기능) : 계산, 처리 - 실제 기능, 화면
# 3. URL 맵핑 : 라우팅 테이블에 기록한다. urls.py에 기록 - 주소를 지정
# 4. 화면에 보여줄 것이있다 : 템플릿작성(html)

# 장고의 기본 유저 모델
from django.contrib.auth.models import User
from django.urls import reverse
# 외래키(ForeignKey) - User 테이블에서 해당 유저를 찾을 수 있는 주키
# 주키(PrimaryKey) - User 테이블에 1 admin x x x x
class Photo(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_photos')
    photo = models.ImageField(upload_to='photos/%Y/%m/%d', default='photos/no_image.png')
    text = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    # makemigrations -> migrate
    class Meta:
        ordering = ['-updated']

    def __str__(self):
        return self.author.username + " " + self.created.strftime("%Y-%m-%d %H:%M:%S")

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

# 모델을 만들었다 => 데이터베이스에 어떤 데이터들을 어떤 형태로 넣을지 결정!
# makemigrations => 모델의 변경사항을 추적해서 기록
# 마이그레이션(migrate) => 데이터베이스에 모델의 내용을 반영(테이블 생성)
py
from django.contrib import admin
# Register your models here.
# 내가 만든 모델을 관리자 페이지에서 관리할 수 있도록 등록
from .models import Photo

admin.site.register(Photo)
from django.contrib import admin
# Register your models here.
# 내가 만든 모델을 관리자 페이지에서 관리할 수 있도록 등록
from .models import Photo

admin.site.register(Photo)
windows command 
// DB 변경사항
C:\hyungsik74\pycharm\photo-project> python manage.py makemigrations photo  

// install pillow C:\hyungsik74\pycharm\photo-project> python -m pip install Pillow

// DB 변경사항 반영 C:\hyungsik74\pycharm\photo-project> python manage.py makemigrations photo C:\hyungsik74\pycharm\photo-project> python manage.py migrate photo 0001

// 실행 //C:\hyungsik74\pycharm\photo-project> python manage.py runserver

linux command
$/home/hyungsik74/pycharm/photo-project> pwd
/home/hyungsik74/pycharm/photo-project
$/home/hyungsik74/pycharm/photo-project> python manage.py makemigrations photo

#pillow not install $/home/hyungsik74/pycharm/photo-project> python -m pip install Pillow

$/home/hyungsik74/pycharm/photo-project> python manage.py makemigrations photo $/home/hyungsik74/pycharm/photo-project> python manage.py migrate photo 0001

$/home/hyungsik74/pycharm/photo-project> python manage.py runserver

log
(venv) PS C:\hyungsik74\pycharm\photo-project> python manage.py startapp photo
(venv) PS C:\hyungsik74\pycharm\photo-project> py manage.py makemigrations photo
Migrations for 'photo':
  photo\migrations\0001_initial.py
    - Create model photo          
(venv) PS C:\hyungsik74\pycharm\photo-project> py manage.py sqlmigrate photo 0001
BEGIN;
--                                                                                                                                                 
-- Create model photo                                                                                                                           
--                                                                                                                                                 
CREATE TABLE "photo_photo" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 
    "site_name" varchar(100) NOT NULL, 
    "url" varchar(200) NOT NULL);
COMMIT;                                                                                                                                            
(venv) PS C:\hyungsik74\pycharm\photo-project> 

(venv) PS C:\hyungsik74\pycharm\photo-project> py manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, photo, contenttypes, sessions
Running migrations:
  Applying photo.0001_initial... OK
(venv) PS C:\hyungsik74\pycharm\photo-project> py manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
September 24, 2023 - 12:55:42                         
Django version 4.2.5, using settings 'config.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
(venv) PS C:\hyungsik74\pycharm\photo-project> python manage.py startapp photo
(venv) PS C:\hyungsik74\pycharm\photo-project> py manage.py makemigrations photo
Migrations for 'photo':
  photo\migrations\0001_initial.py
    - Create model photo          
(venv) PS C:\hyungsik74\pycharm\photo-project> py manage.py sqlmigrate photo 0001
BEGIN;
--                                                                                                                                                 
-- Create model photo                                                                                                                           
--                                                                                                                                                 
CREATE TABLE "photo_photo" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 
    "site_name" varchar(100) NOT NULL, 
    "url" varchar(200) NOT NULL);
COMMIT;                                                                                                                                            
(venv) PS C:\hyungsik74\pycharm\photo-project> 

(venv) PS C:\hyungsik74\pycharm\photo-project> py manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, photo, contenttypes, sessions
Running migrations:
  Applying photo.0001_initial... OK
(venv) PS C:\hyungsik74\pycharm\photo-project> py manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
September 24, 2023 - 12:55:42                         
Django version 4.2.5, using settings 'config.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
  • pillow install
windows command 

C:\hyungsik74\pycharm\photo-project> python -m pip install pillow C:\hyungsik74\pycharm\photo-project> python manage.py makemigrations photo C:\hyungsik74\pycharm\photo-project> python manage.py migrate photo 0001
// 실행 //C:\hyungsik74\pycharm\photo-project> py manage.py runserver

linux command
$/home/hyungsik74/pycharm/photo-project> pwd
/home/hyungsik74/pycharm/photo-project
$/home/hyungsik74/pycharm/photo-project> python -m pip install pillow
$/home/hyungsik74/pycharm/photo-project> python manage.py makemigrations photo
$/home/hyungsik74/pycharm/photo-project> python manage.py migrate photo 0001   
$/home/hyungsik74/pycharm/photo-project> python manage.py runserver

ch06-04-02_03.png 그림6-4-3 테이블 ImageField 필드 사용으로 pillow 모듈 필요함.

ch06-04-02_04.png 그림6-4-3 pillow 설치

ch06-04-02_05.png 그림6-4-3 데이터베이스 변경사항 생성

ch06-04-02_06.png 그림6-4-3 데이터베이스 변경사항 적용 및 프로젝트 실행

ch06-04-02_07.png 그림6-4-3 관리자 화면 확인

ch06-04-02_08.png 그림6-4-3 photo 테이블 추가

ch06-04-02_09.png 그림6-4-3 photo 테이블 리스트

ch06-04-02_10.png 그림6-4-3 photo 테이블 수정

ch06-04-02_11.png 그림6-4-3 photo 파일 upload 확인

6-4-4. 업로드 폴더 변경

ch06-04-04_01.png 그림6-4-3

ch06-04-04_02.png 그림6-4-3

6-4-5. 관리자 페이지 수정

  • photo/admin.py 변경(목록을 보기 좋게 확인 가능)

다음과 같은 관리자 화면의 Photo 테이블리스트에서 ch06-04-05_01.png 그림6-4-5

아래와 같이 Photo 테이블 컬럼을 추가해서 보여 주려고 합니다. ch06-04-05_02.png 그림6-4-5

아래의 photo/admin.py 파일을 다음과 같이 수정합니다. PhotoAdmin Class를 만들어서 admin.site 에 등록해 줍니다.

py
from django.contrib import admin

# Register your models here.
from .models import Photo
class PhotoAdmin(admin.ModelAdmin):
    list_display = ['id','author','created','updated']
    raw_id_fields = ['author']
    list_filter = ['created','updated','author']
    search_fields = ['text','created','author__username']
    ordering = ['-updated','-created']

admin.site.register(Photo, PhotoAdmin)
from django.contrib import admin

# Register your models here.
from .models import Photo
class PhotoAdmin(admin.ModelAdmin):
    list_display = ['id','author','created','updated']
    raw_id_fields = ['author']
    list_filter = ['created','updated','author']
    search_fields = ['text','created','author__username']
    ordering = ['-updated','-created']

admin.site.register(Photo, PhotoAdmin)

6-4-6. 뷰 만들기

사진목록, 업로드, 확인, 수정, 삭제 기능

  • photo/views.py
py
@login_required
def photo_list(request):
    # 보여줄 사진 데이터
    photos = Photo.objects.all()
    return render(request, 'photo/list.html', {'photos':photos})
@login_required
def photo_list(request):
    # 보여줄 사진 데이터
    photos = Photo.objects.all()
    return render(request, 'photo/list.html', {'photos':photos})
py
from django.views.generic.edit import CreateView,DeleteView, UpdateView
from django.shortcuts import redirect

class PhotoUploadView(LoginRequiredMixin, CreateView):
    model = Photo
    fields = ['photo', 'text'] # 작성자(author), 작성시간(created)
    template_name = 'photo/upload.html'

    def form_valid(self, form):
        form.instance.author_id = self.request.user.id
        if form.is_valid():
            # 데이터가 올바르다면
            form.instance.save()
            return redirect('/')
        else:
            return self.render_to_response({'form':form})
from django.views.generic.edit import CreateView,DeleteView, UpdateView
from django.shortcuts import redirect

class PhotoUploadView(LoginRequiredMixin, CreateView):
    model = Photo
    fields = ['photo', 'text'] # 작성자(author), 작성시간(created)
    template_name = 'photo/upload.html'

    def form_valid(self, form):
        form.instance.author_id = self.request.user.id
        if form.is_valid():
            # 데이터가 올바르다면
            form.instance.save()
            return redirect('/')
        else:
            return self.render_to_response({'form':form})
py
class PhotoDeleteView(LoginRequiredMixin, DeleteView):
    model = Photo
    success_url = '/'
    template_name = 'photo/delete.html'
class PhotoDeleteView(LoginRequiredMixin, DeleteView):
    model = Photo
    success_url = '/'
    template_name = 'photo/delete.html'
py
class PhotoUpdateView(LoginRequiredMixin, UpdateView):
    model = Photo
    fields = ['photo','text']
    template_name = 'photo/update.html'
class PhotoUpdateView(LoginRequiredMixin, UpdateView):
    model = Photo
    fields = ['photo','text']
    template_name = 'photo/update.html'
py
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
# Create your views here.
from .models import Photo

@login_required
def photo_list(request):
    # 보여줄 사진 데이터
    photos = Photo.objects.all()
    return render(request, 'photo/list.html', {'photos':photos})

from django.views.generic.edit import CreateView,DeleteView, UpdateView
from django.shortcuts import redirect

class PhotoUploadView(LoginRequiredMixin, CreateView):
    model = Photo
    fields = ['photo', 'text'] # 작성자(author), 작성시간(created)
    template_name = 'photo/upload.html'

    def form_valid(self, form):
        form.instance.author_id = self.request.user.id
        if form.is_valid():
            # 데이터가 올바르다면
            form.instance.save()
            return redirect('/')
        else:
            return self.render_to_response({'form':form})

class PhotoDeleteView(LoginRequiredMixin, DeleteView):
    model = Photo
    success_url = '/'
    template_name = 'photo/delete.html'

class PhotoUpdateView(LoginRequiredMixin, UpdateView):
    model = Photo
    fields = ['photo','text']
    template_name = 'photo/update.html'

"""
서버에 이미지 파일을 업로드하거나 수정하거나 지운다.
장고 웹 앱이 해당 작업을 수행한다.
장고 웹 앱은 특정 서버에 업로드 되어 있다.
* 그럼 해당 기능은 특정 서버 안에서만 영향을 끼칠 수 있다.
-> 실 서비스를 배포하면 서버 컴퓨터는 1대가 아니다.
-> 사용자가 늘어날 때마다 서버 컴퓨터도 늘어난다.
-> 장고 웹 앱이 업로드 되어있는 서버 컴퓨터가 늘어난다.
-> 이미지 파일이 업로드 되는 컴퓨터의 댓수도 늘어나야 한다.
-> 업로드 받은 후에 다른 서버에도 공유해줘야 한다.
-> 공유해주는데 사용되는 자원(돈이나 시간)이 아깝다.
-> 어떻게하면 공유하는데 들어가는 돈이나 시간을 절약할 수 있을까?
-> 이미지는 한 곳의 서버에다만 올려놓고, 거기에 접속해서 사용하자.
"""
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
# Create your views here.
from .models import Photo

@login_required
def photo_list(request):
    # 보여줄 사진 데이터
    photos = Photo.objects.all()
    return render(request, 'photo/list.html', {'photos':photos})

from django.views.generic.edit import CreateView,DeleteView, UpdateView
from django.shortcuts import redirect

class PhotoUploadView(LoginRequiredMixin, CreateView):
    model = Photo
    fields = ['photo', 'text'] # 작성자(author), 작성시간(created)
    template_name = 'photo/upload.html'

    def form_valid(self, form):
        form.instance.author_id = self.request.user.id
        if form.is_valid():
            # 데이터가 올바르다면
            form.instance.save()
            return redirect('/')
        else:
            return self.render_to_response({'form':form})

class PhotoDeleteView(LoginRequiredMixin, DeleteView):
    model = Photo
    success_url = '/'
    template_name = 'photo/delete.html'

class PhotoUpdateView(LoginRequiredMixin, UpdateView):
    model = Photo
    fields = ['photo','text']
    template_name = 'photo/update.html'

"""
서버에 이미지 파일을 업로드하거나 수정하거나 지운다.
장고 웹 앱이 해당 작업을 수행한다.
장고 웹 앱은 특정 서버에 업로드 되어 있다.
* 그럼 해당 기능은 특정 서버 안에서만 영향을 끼칠 수 있다.
-> 실 서비스를 배포하면 서버 컴퓨터는 1대가 아니다.
-> 사용자가 늘어날 때마다 서버 컴퓨터도 늘어난다.
-> 장고 웹 앱이 업로드 되어있는 서버 컴퓨터가 늘어난다.
-> 이미지 파일이 업로드 되는 컴퓨터의 댓수도 늘어나야 한다.
-> 업로드 받은 후에 다른 서버에도 공유해줘야 한다.
-> 공유해주는데 사용되는 자원(돈이나 시간)이 아깝다.
-> 어떻게하면 공유하는데 들어가는 돈이나 시간을 절약할 수 있을까?
-> 이미지는 한 곳의 서버에다만 올려놓고, 거기에 접속해서 사용하자.
"""

6-4-7. URL 연결

  • photo/urls.py
  • config/urls.py
py
from django.urls import path
from django.views.generic.detail import DetailView
from .views import *
from .models import Photo
# 2차 URL 파일
app_name = 'photo'

urlpatterns = [
    path('', photo_list, name='photo_list'),
    path('detail/<int:pk>/', DetailView.as_view(model=Photo, template_name='photo/detail.html'), name='photo_detail'),
    path('upload/', PhotoUploadView.as_view(), name='photo_upload'),
    path('delete/<int:pk>/', PhotoDeleteView.as_view(), name='photo_delete'),
    path('update/<int:pk>/', PhotoUpdateView.as_view(), name='photo_update'),
]
from django.urls import path
from django.views.generic.detail import DetailView
from .views import *
from .models import Photo
# 2차 URL 파일
app_name = 'photo'

urlpatterns = [
    path('', photo_list, name='photo_list'),
    path('detail/<int:pk>/', DetailView.as_view(model=Photo, template_name='photo/detail.html'), name='photo_detail'),
    path('upload/', PhotoUploadView.as_view(), name='photo_upload'),
    path('delete/<int:pk>/', PhotoDeleteView.as_view(), name='photo_delete'),
    path('update/<int:pk>/', PhotoUpdateView.as_view(), name='photo_update'),
]
py
"""config URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include

from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    # 127.0.0.1:8000/abcd/
    path('', include('photo.urls')),
    # path('accounts/', include('accounts.urls')),
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# DEBUG 모드일 때 - static 기능을 사용한다.
# 1. 미디어 파일 서버를 별도로 두고 사용한다.
# 2. 웹서버에서 별도로 서빙 설정을 한다.
"""config URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include

from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    # 127.0.0.1:8000/abcd/
    path('', include('photo.urls')),
    # path('accounts/', include('accounts.urls')),
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# DEBUG 모드일 때 - static 기능을 사용한다.
# 1. 미디어 파일 서버를 별도로 두고 사용한다.
# 2. 웹서버에서 별도로 서빙 설정을 한다.

6-4-8. 템플릿 분리와 확장

  • config/setting.py TEMPLATES = ....

  • templates/base.html

  • photo/templates/photo/list.html

  • photo/templates/photo/upload.html

  • photo/templates/photo/detail.html

  • photo/templates/photo/update.html

  • photo/templates/photo/delete.html

  1. config/setting.py
py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        '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',
            ],
        },
    },
]
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        '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',
            ],
        },
    },
]
  1. templates/base.html
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <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.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>

    <title>Dstagram {% block title %}{% endblock %}</title>
</head>
<body>

<div class="container">
    <header class="header clearfix">
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <a class="navbar-brand" href="/">Dstagram</a>
            <ul class="nav">
                <li class="nav-item"><a href="/" class="active nav-link ">Home</a></li>
                {% if user.is_authenticated %}
                <li class="nav-item"><a href="#" class="nav-link">Welcome, {{user.get_username}}</a></li>
                <li class="nav-item"><a href="{% url 'photo:photo_upload' %}" class="nav-link">Upload</a></li>
                <li class="nav-item"><a href="{#% url 'logout' %}" class="nav-link">Logout</a></li>
                {% else %}
                <li class="nav-item"><a href="{#% url 'login' %}" class="nav-link">Login</a></li>
                <li class="nav-item"><a href="{#% url 'register' %}" class="nav-link">Signup</a></li>
                {% endif %}
            </ul>
        </nav>
    </header>
    {% block content %}
    {% endblock %}

    <footer class="footer">
        <p>&copy; 2020 Baepeu. Powered By Django 3</p>
    </footer>
</div>


</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <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.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>

    <title>Dstagram {% block title %}{% endblock %}</title>
</head>
<body>

<div class="container">
    <header class="header clearfix">
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <a class="navbar-brand" href="/">Dstagram</a>
            <ul class="nav">
                <li class="nav-item"><a href="/" class="active nav-link ">Home</a></li>
                {% if user.is_authenticated %}
                <li class="nav-item"><a href="#" class="nav-link">Welcome, {{user.get_username}}</a></li>
                <li class="nav-item"><a href="{% url 'photo:photo_upload' %}" class="nav-link">Upload</a></li>
                <li class="nav-item"><a href="{#% url 'logout' %}" class="nav-link">Logout</a></li>
                {% else %}
                <li class="nav-item"><a href="{#% url 'login' %}" class="nav-link">Login</a></li>
                <li class="nav-item"><a href="{#% url 'register' %}" class="nav-link">Signup</a></li>
                {% endif %}
            </ul>
        </nav>
    </header>
    {% block content %}
    {% endblock %}

    <footer class="footer">
        <p>&copy; 2020 Baepeu. Powered By Django 3</p>
    </footer>
</div>


</body>
</html>
  1. photo/templates/photo/list.html
html
{% extends 'base.html' %}
{% block title %}- List{% endblock %}

{% block content %}
    {% for post in photos %}
        <div class="row">
            <div class="col-md-2"></div>
            <div class="col-md-8 panel panel-default">
                <p><img src="{{post.photo.url}}" style="width:100%;"></p>
                <button type="button" class="btn btn-xs btn-info">
                    {{post.author.username}}
                </button>
                <p>{{post.text|linebreaksbr}}</p>
                <p class="text-right">
                    <a href="{% url 'photo:photo_detail' pk=post.id %}" class="btn btn-xs btn-success">댓글달기</a>
                </p>
            </div>
            <div class="col-md-2"></div>
        </div>
    {% endfor %}
{% endblock %}
{% extends 'base.html' %}
{% block title %}- List{% endblock %}

{% block content %}
    {% for post in photos %}
        <div class="row">
            <div class="col-md-2"></div>
            <div class="col-md-8 panel panel-default">
                <p><img src="{{post.photo.url}}" style="width:100%;"></p>
                <button type="button" class="btn btn-xs btn-info">
                    {{post.author.username}}
                </button>
                <p>{{post.text|linebreaksbr}}</p>
                <p class="text-right">
                    <a href="{% url 'photo:photo_detail' pk=post.id %}" class="btn btn-xs btn-success">댓글달기</a>
                </p>
            </div>
            <div class="col-md-2"></div>
        </div>
    {% endfor %}
{% endblock %}
  1. photo/templates/photo/upload.html
html
{% extends 'base.html' %}
{% block title %}- Upload{% endblock %}

{% block content %}
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8">
            <form action="" method="post" enctype="multipart/form-data">
                {{form.as_p}}
                {%csrf_token%}
                <input type="submit" class="btn btn-primary" value="Upload">
            </form>
        </div>
        <div class="col-md-2"></div>
    </div>
{% endblock %}
{% extends 'base.html' %}
{% block title %}- Upload{% endblock %}

{% block content %}
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8">
            <form action="" method="post" enctype="multipart/form-data">
                {{form.as_p}}
                {%csrf_token%}
                <input type="submit" class="btn btn-primary" value="Upload">
            </form>
        </div>
        <div class="col-md-2"></div>
    </div>
{% endblock %}
  1. photo/templates/photo/detail.html
html
{% extends 'base.html' %}
{% block title %}
    {{object.text|truncatechars:10}}
{% endblock %}

{% block content %}
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8 panel panel-default">
            <p><img src="{{object.photo.url}}" style="width:100%;"></p>
            <button type="button" class="btn btn-xs btn-info">
                {{object.author.username}}
            </button>
            <p>{{object.text|linebreaksbr}}</p>
            <a href="{% url 'photo:photo_delete' pk=object.id %}" class="btn btn-outline-danger btn-sm float-right">Delete</a>
            <a href="{% url 'photo:photo_update' pk=object.id %}" class="btn btn-outline-success btn-sm float-right">Update</a>
        </div>
        <div class="col-md-2"></div>
    </div>
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8">
            {% load disqus_tags %}
            {% disqus_show_comments %}
        </div>
        <div class="col-md-2"></div>
    </div>
{% endblock %}
{% extends 'base.html' %}
{% block title %}
    {{object.text|truncatechars:10}}
{% endblock %}

{% block content %}
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8 panel panel-default">
            <p><img src="{{object.photo.url}}" style="width:100%;"></p>
            <button type="button" class="btn btn-xs btn-info">
                {{object.author.username}}
            </button>
            <p>{{object.text|linebreaksbr}}</p>
            <a href="{% url 'photo:photo_delete' pk=object.id %}" class="btn btn-outline-danger btn-sm float-right">Delete</a>
            <a href="{% url 'photo:photo_update' pk=object.id %}" class="btn btn-outline-success btn-sm float-right">Update</a>
        </div>
        <div class="col-md-2"></div>
    </div>
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8">
            {% load disqus_tags %}
            {% disqus_show_comments %}
        </div>
        <div class="col-md-2"></div>
    </div>
{% endblock %}
  1. photo/templates/photo/update.html
html
{% extends 'base.html' %}
{% block title %}- Update{% endblock %}

{% block content %}
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8">
            <form action="" method="post" enctype="multipart/form-data">
                {{form.as_p}}
                {%csrf_token%}
                <input type="submit" class="btn btn-primary" value="Update">
            </form>
        </div>
        <div class="col-md-2"></div>
    </div>
{% endblock %}
{% extends 'base.html' %}
{% block title %}- Update{% endblock %}

{% block content %}
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8">
            <form action="" method="post" enctype="multipart/form-data">
                {{form.as_p}}
                {%csrf_token%}
                <input type="submit" class="btn btn-primary" value="Update">
            </form>
        </div>
        <div class="col-md-2"></div>
    </div>
{% endblock %}
  1. photo/templates/photo/delete.html
html
{% extends 'base.html' %}
{% block title %}- Delete{% endblock %}

{% block content %}
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8">
            <div class="alert alert-info">
                Do you want to delete {{object}}?
            </div>
            <form action="" method="post">
                {{form.as_p}}
                {%csrf_token%}
                <input type="submit" class="btn btn-primary" value="Confirm">
            </form>
        </div>
        <div class="col-md-2"></div>
    </div>
{% endblock %}
{% extends 'base.html' %}
{% block title %}- Delete{% endblock %}

{% block content %}
    <div class="row">
        <div class="col-md-2"></div>
        <div class="col-md-8">
            <div class="alert alert-info">
                Do you want to delete {{object}}?
            </div>
            <form action="" method="post">
                {{form.as_p}}
                {%csrf_token%}
                <input type="submit" class="btn btn-primary" value="Confirm">
            </form>
        </div>
        <div class="col-md-2"></div>
    </div>
{% endblock %}

6-4-9. 사진 표시하기

  • config/urls.py
py
# 아래의 부분을 추가 합니다.
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# 아래의 부분을 추가 합니다.
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

[참고] 신규로 파일을 추가할때 Git에 Add 할 것인지를 확인하는 창이 오픈됩니다. 추가하기 위해서 'Add' 버튼을 클릭합니다. ch06-04-05_03.png 그림6-5-3

6-4-10. 실행 화면

지금까지 내용을 바탕으로 실행하면 다음과 같은 실행화면을 볼 수 있습니다.
URL: http://127.0.0.1:8000/

ch06-04-05_10.png 그림6-5-3

다음장에서는 Account: 회원가입, 로그인, 로그아웃 기능을 추가해 보도록 하겠습니다.

Released under the MIT License.