본문 바로가기
코딩 연습/코딩배우기

장고(Django) 웹 프레임워크 프로젝트 예제로 알아보는 파이썬 웹 프로그래밍 연습

by good4me 2021. 7. 29.

goodthings4me.tistory.com

 

장고 웹 프레임워크 프로젝트 예제로 알아보는 파이썬 웹 프로그래밍 연습

Python 장고(django) 웹 프레임워크를 이용한 프로젝트 만들기 영상을 보고 개인적으로 정리한 내용이며, 나중에 django 웹사이트를 만들거나 파이썬 웹 프로그래밍 코딩 연습 시에 참고하고자 포스팅 함.

장고(Django) 웹 프레임워크 프로젝트

 

[출처] 오지랖 파이썬 웹 프로그래밍 - Dstagram 프로젝트 만들기

 

출처에 있는 강의 영상의 주요 내용은 다음과 같다. (AWS S3 등과 배포 관련 영상 내용은 제외)

장고(django) Dstagram 프로젝트 작성 절차

  1. 장고 설치 및 프로젝트 생성

  2. 앱 생성
       1) photo 앱 (이미지 CURD)
       2) accounts 앱 (계정-회원관리)

  3. 모델(Photo) 정의

  4. 관리자 페이지 커스터 마이징

  5. 이미지 보이기 (개발 환경에서 이미지 표시)

  6. accounts 로그인/로그아웃 구현

  7. 회원가입 기능 (forms 사용)

  8. Decorator와 Mixin 이용한 접근제한

 

good4me.co.kr

 

 

■ 파이참(PyCharm)에 장고 설치하고 새 프로젝트 만들기 (PyCharm Terminal 이용)

(venv) C:\Users\haemi\OneDrive\myDev\PyCharm\ogLap2_dstagram>pip install django
(venv) C:\Users\haemi\OneDrive\myDev\PyCharm\ogLap2_dstagram>django-admin startproject config .

(venv) C:\Users\haemi\OneDrive\myDev\PyCharm\ogLap2_dstagram>python manage.py migrate

(venv) C:\Users\haemi\OneDrive\myDev\PyCharm\ogLap2_dstagram>python manage.py createsuperuser
# user & password 입력

 

■ 앱(photo) 생성 및 등록

venv) C:\Users\haemi\OneDrive\myDev\PyCharm\ogLap2_dstagram>python manage.py startapp photo

# settings.py 에 앱 등록

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'photo',  # photo.apps.PhotoConfig
]

 

▷ models

from django.db import models
from django.contrib.auth.models import User  # 장고의 기본 유저 모델
from django.urls import reverse


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)

    class Meta:
        ordering = ['-updated',]  # '-'이면 내림차순

    def __str__(self):
        return self.author.username + ' ' + self.created.strftime('%Y-%m-%d %H:%M:%S')  # User 테이블의 username

    def get_absolute_url(self):
        return reverse('photo:photo_detail', args=[self.id])
  • models.Model에 장고의 ORM 기능들이 들어있다.
  • related_name='user_photos' : User의 인스턴스 측면에서 Photo의 author와 관련하여 user_instance.user_photos 하면 해당 user가 작성한 photo 목록을 가져올 수 있다.
  • upload_to= 는 저장 위치 지정, photos 폴더 밑으로 %Y/%m/%d 폴더 생성
  • default= 값이 없으면 필수 필드가 됨. ImageField()는 pip intall Pillow 모듈 설치 요함
  • auto_now_add=True 는 db에 row 등록될 때 현재 시간 자동 설정,  auto_now=True 는 등록, 수정될 때마다 새로 시간 설정
  • 메타 클래스(class Meta) : 모델의 옵션 정의
    - id 필드는 장고에서 자동으로 만들어지고 정렬기준이 되나, 다른 기준을 적용할 경우 ordering을 정의함
  • __str__(self)는 모델 인스턴스의 print, 관리자 페이지에서 인스턴스 정보를 보여야 할 때 출력될 내용을 만들어 냄
  • get_absolute_url()는 reverse 함수를 이용하여 모델 인스턴스 url을 문자열로 반환. 즉, 모델의 인스턴스의 이동 페이지를 photo 앱의 detail 페이지로 지정

 

▷ 모델을 관리자 페이지에 등록

from django.contrib import admin
from .models import Photo

admin.site.register(Photo)
  • 이미지 업로드 시, 현재의 Photo models에서 'photos/%Y/%m/%d' 로 되어 있어서 root에 Photo 폴더가 생긴다.(잘 못된 설정)
  • 이를 위해 settings.py에서 MEDIA_URL = '/media/' 설정 필요
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')   # 특정 폴더에 미디어 파일을 모아놓도록 함

 

▷ 관리자 페이지 커스터마이징

from django.contrib import admin
from .models import Photo

# 클래스명은 다른 이름도 가능하나 모델명Admin 형태가 일반적임
class PhotoAdmin(admin.ModelAdmin):
    list_display = ['id', 'author', 'created', 'updated']
    
    # 회원이 많을 경우, author_username 으로 찾는 기능이 됨
    raw_id_fields = ['author']  
    
    #  filter는 '기간'에 많이 사용
    list_filter = ['created', 'updated', 'author']  
    
    # 리스트 요소가 검색 대상이 됨.
    search_fields = ['text', 'created', 'author__username']  
    # author은 ForeignKey 객체라서 icontains ORM으로 찾아야 함
    
    ordering = ['-updated', '-created']  
    # 관리자 페이지 전용, models의 ordering(db의 기본 정렬값)과는 다름

admin.site.register(Photo, PhotoAdmin)

 

 

 

▷ 이미지(사진) 보이기

# config/urls.py

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),
    path('', include('photo.urls')),  
    # '' 는 admin/을 제외한 모든 접속을 넘겨줌(127.0.0.1:8000/abcd 도 넘김)
]

# 이미지 보이기(디버깅 모드에서만)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

# 개발 시 이미지 보이기는 디버깅 모드에서만 가능, 실 서버에서는 불필요한 작업임
# 실 서버에서는 1)미디어 파일 서버를 두고 사용하거나, 2) 웹서버에서 별도로 서빙 설정 등의 방법을 사용해야 함

 

 

 URL Conf (urls) 작업

from django.urls import path
from django.views.generic.detail import DetailView  # 클래스형 뷰(제네릭 뷰)를 urls에서 직접 지정 시
from .views import *
from .models import Photo  # DetailView.as_view()

app_name = 'photo'  # namespace

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'),
]

 

 

 뷰(views) 작업

from django.shortcuts import render, redirect
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.detail import DetailView
from .models import Photo


def photo_list(request):  # 함수형 뷰, request는 요청 객체
    # 보여줄 사진 데이터
    photos = Photo.objects.all()  # all()이 쿼리 부분임
    # Photo 객체의 ORM 관련 매니저 이름이 기본으로 objects로 되어있음 (Photo객체의 ORM 매니저에게 전체를 넘겨주라는 의미)
    
    return render(request, 'photo/list.html', {'photos': photos})
    # render()는 화면에 표시해주는 메서드. render(request, template_name, context=None, content_type=None, status=None, using=None)
    # {}는 템플릿 변수들 할당의 의미. {'템플릿 사용 키(변수)': 값(로컬 변수)} 키의 기본은 'object_list' 이지만 변경 사용 가능


class PhotoUploadView(CreateView):
    model = Photo
    
    # 등록(업로드)할 항목들. 
    # 단, 작성자(author)는 로그인 했기 때문에, 작성시간(created)은 자동으로 입력
    fields = ['photo', 'text']  
    
    template_name = 'photo/upload.html'  # 전체 지정. 파일명만 변경 시에는 template_name_suffix =

    # 글 쓰기 완료 시 작성자(author)는 db 저장하기 전에 유효한지 체크한 후 유효하면 저장, 아니면 그대로 반환 시킴
    # 장고에서도 데이터를 form 객체로 받아서 저장 처리하며, 데이터 유효성도 체크함 (form_valid() or form_invalid())
    
    def form_valid(self, form):  # 매개변수 form --> Photo 모델의 값을 입력받는 form객체
        form.instance.author_id = self.request.user.id  # 로그인 사용자
        if form.is_valid():
            # 데이터가 올바르다면
            form.instance.save()  # Photo 모델의 instance. db에 저장
            return redirect('/')  # root로 가기. success_url= 처리와 같음
        else:
            return self.render_to_response({'form': form})  
            # 이 뷰 객체에서 입력받은 form을 그대로 돌려줌
            # render_to_response(template_name, context=None, content_type=None, status=None, using=None)
            # render_to_response('myapp/index.html', {'foo': 'bar'}, context_instance=RequestContext(request))


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


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

 

 

 

템플릿(Templates) 작업

 

o 템플릿 확장 - templates/base.html

<!--{% load static %}-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dstagram {% block title %} {% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<!--    <link rel="stylesheet" href="{% static 'style.css' %}">-->
</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 class="active nav-link" href="/">Home</a></li>
                        {% if user.is_authenticated %}
                        <li class="nav-item"><a class="nav-link" href="#">Welcome, {{ user.get_username }}</a></li>
                        <li class="nav-item"><a class="nav-link" href="{% url 'photo:photo_upload' %}">Upload</a></li>
                        <li class="nav-item"><a class="nav-link" href="{% url 'logout' %}">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>
        <p></p>
        <div class="row">
            <div class="col">
                {% block content %}
                {% endblock %}
            </div>
        </div>
        <p></p>
        <footer class="footer">
          <p style="text-align:center;">&copy; 2021 Dstagram. Powered By Django 3</p>
        </footer>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"
        integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p"
        crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js"
        integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF"
        crossorigin="anonymous"></script>
</body>
</html>

※ settings.py TEMPLATES = 에 'DIRS': [os.path.join(BASE_DIR, 'templates')], 추가

 

 o 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>
                <p>{{ post.text|linebreaksbr}}</p>
                <p class="text-right">
                <button type="button" class="btn btn-xs btn-info">
                    {{ post.author.username }}
                </button>
                    <a href="{% url 'photo:photo_detail' pk=post.id %}" class="btn btn-xs btn-sm btn-success">댓글 달기</a>
                </p>
            </div>
            <div class="col-md-2"></div>
        </div>
    {% endfor %}

{% endblock %}

 

 o 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 %}

 

 o 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 %}

 

 o 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 delte "{{ 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 %}

 

 o photo/templates/photo/detail.html

{% extends 'base.html' %}
{% block title %}- Detail {% 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>

{% endblock %}

 

 

 

■ account 앱(계정 및 회원 관리) 생성 및 등록

장고는 기본적인 회원관리 기능이 있음
User 모델을 사용해서 별도의 Account 앱 만들고 로그인/로그아웃 기능을 만들어본다

 

accounts 앱 생성 및 login/logout 기능 실행

(venv) C:\Users\haemi\OneDrive\myDev\PyCharm\ogLap2_dstagram>python manage.py startapp accounts

settings.py INSTALLED_APPS = 에 'accounts' 추가

 

※ accounts 관련 모델은 만들지 않음 (User 모델 사용) --> views.py 도 작성내용 없이 동작 가능

# accounts/urls.py

from django.urls import path
# login, logout 뷰가 이미 있음 from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.auth import views as auth_view

urlpatterns = [
    path('login/', auth_view.LoginView.as_view(), name='login'),
    path('logout/', auth_view.LogoutView.as_view(), name='logout'),  # LogoutView에는 template_name 있음
]

http://localhost:8000/accounts/login/ 시 에러 발생 --> 로그인 페이지는 별도 작성하면 해결됨

TemplateDoesNotExist at /accounts/login/
registration/login.html

 

http://localhost:8000/accounts/logout/ 시 에러 발생 --> 관리자 페이지가 로그아웃 되는 문제가 발생함. 즉, 별도의 템플릿 파일 작업이 필요하다는 의미임

# 관리자(admin) 페이지
----------------------------------
Logged out
Thanks for spending some quality time with the Web site today.

Log in again
---------------------------------------------------------------

# accounts/templates/registration/login.html, logout.html 2개의 템플릿 파일 작업 필요

 

 

 

 템플릿(Templates) 작업

 

 o templates/registration/login.html

{% extends 'base.html' %}
{% block title %}- Login{% endblock %}

{% block content %}

  <div class="row">
    <div class="col-md-2"></div>
    <div class="col-md-8 panel panel-default">
        <div class="alert alert-info">Please enter your login information.</div>
        <form action="" method="post">
            {{ form.as_p }}
            {% csrf_token %}
          <input class="btn btn-primary" type="submit" value="Login">
        </form>

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

 

 o templates/registration/logout.html

{% extends 'base.html' %}
{% block title %}- Logout{% endblock %}

{% block content %}

  <div class="row">
    <div class="col-md-2"></div>
    <div class="col-md-8 panel panel-default">
        <div class="alert alert-info">You have been successfully logged out.</div>
        <a class="btn btn-primary" href="{% url 'login' %}">Click to Login</a>
    </div>
    <div class="col-md-2"></div>
  </div>
{% endblock %}

 

※ 로그인 후, 자동으로 이동하는 profile은 별도로 작성해야 함  http://localhost:8000/accounts/profile

Page not found (404)
Request Method:	GET
Request URL:	http://localhost:8000/accounts/profile/
  • 장고에서는 LoginView를 사용할 경우, 기본적으로 http://localhost:8000/accounts/profile/ 페이지로 자동 이동시킴
  • 이 에러를 해결하는 방법은
    1) profile 페이지 만들기
    2) 다른 페이지로 보내기
        a. 장고 설정 변경 : settings.py --> LOGIN_REDIRECT_URL = '/'
        b. 웹 서버에서 설정(redirect)
  • 장고를 사용하기 때문에 장고 설정 변경으로 해결하고, profile 파일은 회원의 마이페이지 기능으로 사용할 수 있다.
  • settings.py 에서는 항상 lazy를 사용해야 함. 그렇지 않으면 url 라우팅 테이블에서 불러오기전에 이동되어 오류 발생 가능함

 

 

회원 가입 기능(with accounts 앱)

  • urls에서 auth.views.LoginView / LogoutView를 사용했기 때문에 models나 views는 아무 내용이 없었음
  • 회원가입 기능을 만들기 위해서는 views.py 이용하는데, 회원가입 폼을 제네릭뷰를 상속받아서 자동으로 폼 생성(모델 폼 팩토리)해 사용토록 작성할 수도 있지만, 
  • 별도로 폼을 만들어서 회원가입 받을 수도 있으며, 이 경우 /accounts/forms.py 생성하여 사용한다.
  • 회원가입 기능 별도 작성 시, CRUD 고려함
# accounts/forms.py

from django import forms
from django.contrib.auth.models import User

class RegisterForm(forms.ModelForm):
    # 비밀번호 입력 --> Meta fields list에 추가됨
    password = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Repeat Password', widget=forms.PasswordInput)

    class Meta:
        model = User
        fields = ['username', 'first_name', 'last_name', 'email']

    def clean_password2(self):
        cd = self.cleaned_data  # 폼의 전체 데이터가 들어있음
        if cd['password'] != cd['password2']:
            raise forms.ValidationError('Password not matched!')
        return cd['password2']

forms.py에서 만들어진 forms의 class 인스턴스는 보통 views.py에서 사용함
class로 만든 회원가입 폼은 forms.ModelForm을 상속받아 작성한다. 그래서 models의 모델이 필요하고, 회원관리 폼에서는 보통 User 모델 사용

웹 프로그램에서
Form(HTML의 폼 태그)은 Frontend 단에서 사용자의 입력을 받는 인터페이스인데,

장고의 폼은
1) HTML의 폼 역할과
2) 데이터 베이스에 저장할 내용에 대해 어떤 형식이나 제약조건을 결정하여 입력받게 하는 역할을 한다. (즉, 미리 잘 갖춰진 Form) 

 

장고 공식 매뉴얼에서도 사용자의 입력을 받을 경우 폼(forms 모듈) 사용을 권장함. (보안 관련 기능 등 많은 기능이 포함되어 있기 때문)

장고 폼을 통해 사용자 입력을 받을 경우, 데이터의 유효성을 검증하기 위해 폼 데이터에 대한 validation을 체크함

# clean_필드명 : 해당 필드에 대한 validation을 어떻게 할지 하는 의미함
# self.cleaned_data : 입력을 받은 후에 sql_injection 등을 방지하기 위해서 어느정도 처리를 거친 데이터(안전 데이터) 의미
# 그래서, 장고에서는 폼에 넣고 cleaned_data를 꺼내 쓰도록 권장함

 

 

accounts URL Conf(urls.py) 작업

# config/urls.py

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),
    path('', include('photo.urls')),  # admin/을 제외한 모든 접속을 넘겨줌(127.0.0.1:8000/abcd 도 넘김)
    path('accounts/', include('accounts.urls')),
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)



# accounts/urls.py

from django.urls import path
# login, logout 뷰가 이미 있음 from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.auth import views as auth_view
from .views import *

urlpatterns = [
    path('login/', auth_view.LoginView.as_view(), name='login'),
    path('logout/', auth_view.LogoutView.as_view(template_name='registration/logout.html'), name='logout'),
    path('register/', register, name='register'),  # 회원가입 함수형 뷰
]

 

 

accounts 뷰(views) 작업

from django.shortcuts import render
from .forms import RegisterForm

def register(request):
    if request.method == 'POST':
        # 회원 가입 데이터 입력 완료
        user_form = RegisterForm(request.POST)  # 해당 폼 모델의 인스턴스
        # 회원가입 입력 값(ResisterForm 인스턴스의 입력 값)들의 유효성 체크(데이터가 정확하게 들어있는지)
        if user_form.is_valid():
            new_user = user_form.save(commit=False)  # 해당 폼 모델의 인스턴스의 값 저장, commit은 안함
            new_user.set_password(user_form.cleaned_data['password'])  # cleaned_data 중 password 값을 password 컬럼에 저장(set)
            new_user.save()
            return render(request, 'registration/register_done.html', {'new_user':new_user})
    else:
        # 회원가입 클릭 또는 회원가입 내용을 입력(해야)하는 상황
        user_form = RegisterForm()  # 빈 폼
    return render(request, 'registration/register.html', {'form':user_form})

 

 

accounts 템플릿(Templates) 작업

 

 o accounts/templates/registration/register.html

{% extends 'base.html' %}
{% block title %}- Registration{% endblock %}

{% block content %}

  <div class="row">
    <div class="col-md-2"></div>
    <div class="col-md-8 panel panel-default">
        <div class="alert alert-info">Please enter your account information.</div>
        <form action="" method="post">
            {{ form.as_p }}
            {% csrf_token %}
          <input class="btn btn-primary" type="submit" value="Register">
        </form>

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

 

 o accounts/templates/registration/register_done.html

{% extends 'base.html' %}
{% block title %}- Registration Done{% endblock %}

{% block content %}

  <div class="row">
    <div class="col-md-2"></div>
    <div class="col-md-8 panel panel-default">
        <div class="alert alert-info">Registration Success. Welcome, {{ new_user.username }}</div>
        <!-- views register() 함수의 반환 값 - context의 new_user 의 템플릿 변수 사용 -->
        <a class="btn btn-info" href="/">Move to main</a>
        <a class="btn btn-info" href="{% url 'login' %}">Login</a>
    </div>
    <div class="col-md-2"></div>
  </div>
{% endblock %}

 

 

■ Decorator와 Mixin 이용한 접근제한

로그인 한 사람만 글을 쓰게 하는 등의 제한 두기

  • Mixin - 클래스 형 뷰에 쓰는 방법
  • Decorator - 함수형 뷰에 쓰는 방법

 

URL Conf(urls) 수정

from django.urls import path
from django.views.generic.detail import DetailView  # 클래스형 뷰(제네릭 뷰)를 urls에서 직접 지정
from .views import *
from .models import Photo  # DetailView.as_view()

app_name = 'photo'  # namespace

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('detail/<int:pk>/', PhotoDetailView.as_view(), 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'),
]

 

 

 뷰(views) 수정

from django.shortcuts import render, redirect
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.detail import DetailView
from .models import Photo

from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin

#@login_required()  # 주석 제거 시 리스트까지 로그인해야 보임
def photo_list(request):
    # 보여줄 사진 데이터
    photos = Photo.objects.all()
    return render(request, 'photo/list.html', {'photos': photos})

# 데코레이터는 어떤 함수가 실행되기 전에 이 실행 부위를 감싼 준 하나의 함수를 또 만들어 내는 방식


class PhotoUploadView(LoginRequiredMixin, CreateView):  # 매개변수 순서 지킴 (권한 체크)
    model = Photo
    fields = ['photo', 'text']
    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()  # Photo 모델의 instance. db에 저장
            return redirect('/')  # root로 가기. success_url = 처리와 같음
        else:
            return self.render_to_response({'form': 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'


class PhotoDetailView(LoginRequiredMixin, DeleteView):
    model = Photo
    template_name = 'photo/detail.html'

 

댓글