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
어플리케이션 생성후 디렉토리 구조입니다.
그림6-4-1 디렉토리 구조입니다.
# 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 동일함.
]
그림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
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) => 데이터베이스에 모델의 내용을 반영(테이블 생성)
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
(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
그림6-4-3 테이블 ImageField 필드 사용으로 pillow 모듈 필요함.
그림6-4-3 pillow 설치
그림6-4-3 데이터베이스 변경사항 생성
그림6-4-3 데이터베이스 변경사항 적용 및 프로젝트 실행
그림6-4-3 관리자 화면 확인
그림6-4-3 photo 테이블 추가
그림6-4-3 photo 테이블 리스트
그림6-4-3 photo 테이블 수정
그림6-4-3 photo 파일 upload 확인
6-4-4. 업로드 폴더 변경
그림6-4-3
그림6-4-3
6-4-5. 관리자 페이지 수정
- photo/admin.py 변경(목록을 보기 좋게 확인 가능)
다음과 같은 관리자 화면의 Photo 테이블리스트에서 그림6-4-5
아래와 같이 Photo 테이블 컬럼을 추가해서 보여 주려고 합니다. 그림6-4-5
아래의 photo/admin.py 파일을 다음과 같이 수정합니다. PhotoAdmin Class를 만들어서 admin.site 에 등록해 줍니다.
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
@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})
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})
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'
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'
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
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'),
]
"""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
- config/setting.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',
],
},
},
]
- templates/base.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>© 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>© 2020 Baepeu. Powered By Django 3</p>
</footer>
</div>
</body>
</html>
- photo/templates/photo/list.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 %}
- photo/templates/photo/upload.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 %}
- photo/templates/photo/detail.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 %}
- photo/templates/photo/update.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 %}
- photo/templates/photo/delete.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
# 아래의 부분을 추가 합니다.
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# 아래의 부분을 추가 합니다.
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
[참고] 신규로 파일을 추가할때 Git에 Add 할 것인지를 확인하는 창이 오픈됩니다. 추가하기 위해서 'Add' 버튼을 클릭합니다. 그림6-5-3
6-4-10. 실행 화면
지금까지 내용을 바탕으로 실행하면 다음과 같은 실행화면을 볼 수 있습니다.
URL: http://127.0.0.1:8000/
그림6-5-3
다음장에서는 Account: 회원가입, 로그인, 로그아웃 기능을 추가해 보도록 하겠습니다.