goodthings4me.tistory.com
장고 웹 프레임워크 프로젝트 예제로 알아보는 파이썬 웹 프로그래밍 연습
Python 장고(django) 웹 프레임워크를 이용한 프로젝트 만들기 영상을 보고 개인적으로 정리한 내용이며, 나중에 django 웹사이트를 만들거나 파이썬 웹 프로그래밍 코딩 연습 시에 참고하고자 포스팅 함.
[출처] 오지랖 파이썬 웹 프로그래밍 - Dstagram 프로젝트 만들기
출처에 있는 강의 영상의 주요 내용은 다음과 같다. (AWS S3 등과 배포 관련 영상 내용은 제외)
장고(django) Dstagram 프로젝트 작성 절차
|
■ 파이참(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;">© 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'
'코딩 연습 > 코딩배우기' 카테고리의 다른 글
NoReverseMatch ~ 파이썬 장고(django) 에러 (0) | 2021.08.02 |
---|---|
가비아 파이썬 웹 호스팅 사이트 운영을 위한 프로젝트 개발용 Anaconda 가상환경 생성과 PyCharm 설정 연습 (0) | 2021.07.30 |
파이썬 장고(Django) 웹 프레임워크로 웹사이트 만들기 예제 (프로젝트 생성 후 배포까지 해보기) (0) | 2021.07.28 |
[Python] 파이썬 기본기 UP_2 - 참조변수, 컴프리헨션(comprehension), 반복자(iterator), 컨텍스트 매니저(context manager), 제너레이터(generator)와 yield (0) | 2021.07.22 |
[Python] 파이썬 기본기 UP - 함수, 클래스, DB 다루기 (0) | 2021.07.20 |
댓글