1:N 관계를 ORM에서 어떻게 표현하며 일반 DB와 어떻게 연결되는지 살펴보자.
새로운 프로젝트 생성
- 프로젝트 생성
python manage.py startapp blog #플젝생성
- settings.py 에 blog 라는 앱을 설치 (평소엔 꼭 설치할 필요는 없지만, DB와 연동하려면 설치 필요)
INSTALLED_APPS = [..., 'blog',]
- blog/models.py - 테이블 생성
from django.db import models
from django.utils import timezone
class Post(models.Model):
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField() # 글자수에 제한 없는 텍스트
created_date = models.DateTimeField(
default=timezone.now) # 날짜와 시간
published_date = models.DateTimeField(
blank=True, null=True) # 필드가 폼에서 빈 채로 저장되는 것을 허용
def publish(self):
self.published_date = timezone.now() #수정된 날짜
self.save()
def __str__(self): #print ftn에 적용할 str ftn
return self.title
auth.User
: system table. 시스템이 기본으로 생성한 table
on_delete=models.CASCADE
: cascade:종속성. user 지울 때 관련된 post 자동으로 지워라.
blank=True
: form에서 빈 데이터 허용 (application level)
null=True
: 필드값 optional (database level)
blank=False, null=True
: application에서는 값 필수, db차원에서는 값 optional
실제로 blog_post에 생성된 테이블을 보면, id, title, text, created_date, published_date, author_id
기본적으로 class 만들면 기본적으로 id field는 자동으로 만들어진다. (django 규칙)
보통 id값은 auto-increment type 으로 생성된다.
author
로 테이블을 만들었는데 author_id
로 만들어졌다. 기본적으로 author에도 id field가 있는 것이다.
즉 외래키로 잡힌 것은 underbar 붙여 id 생성된다.
- blog/admin.py - models에서 만든 table 등록
from blog.models import Post #루트폴더/blog/models.py 안의 Post Class를 참조
admin.site.register(Post)
- migration
python manage.py makemigrations #변경사항 기록
python manage.py migrate #실제 데이터베이스 변경사항 반영
Applying ... OK 떠야 반영 성공된 것이다.
localhost:8000/admin
에서 확인해보자.
auth.User
: 시스템 테이블로, admin 화면에서 AUTHENTICATION AND AUTHORIZATION > Users
테이블을 가리킨다.
parameter 전달하는 방법에 차이가 있는 것일 뿐
GET method : 전통적인 방식
정적 URL
동적 URL pattern : 프로그램 전달 방식???
DB Browser에서 blog_post 테이블의 스키마를 보면 REFERENCES "auth_user" ("id")
로 되어있고,
시스템 테이블인 auth_user 테이블 밑의 id는 integer이다.
DB Browser에서는 신경 안 쓰고 작업해도 되지만, 코딩 시에는 auth_user의 'id'를 참고한다는 것을 고려해야 한다.
in Jupyter Notebook for unit test
Post Record 생성
from blog.models import Post
p = Post(title="오늘 점심 메뉴", text="뭐지?")
p.save()
#result
#IntegrityError: NOT NULL constraint failed: blog_post.author_id
로 하면, NOT NULL field에 값을 채워주지 않았기 때문에 오류난다.
from blog.models import Post #객체 참조
from django.contrib.auth.models import User
u = User.objects.all().get(username='lee') #system에서 만든 User table
#객체생성방법: 하나는 객체 만들어서 save, 두번째는 그냥 함수 사용
p = Post(title="오늘 점심 메뉴", text="뭐지?", author=u)
p.save()
p.title = "오늘 저엄심 메뉴"
p.save() #p는 아직 기존 레코드를 가리키고 있으므로 update된다.
in APP
동적 URL 경로 구성
- mysite/urls.py
urlpatterns = [
path('blog/', include('blog.urls')),
...
]
blog폴더 내의 urls.py를 불러오고, 기본 경로는 blog/
로 시작한다.
- blog/urls.py
urlpatterns = [
path('<name>/', views.index2), #<name> parameter에 대해 동적으로 mapping
path('<int:pk>/detail', views.index3),
]
으로 사용하여, 동적으로 URL을 mappping할 수 있다.
pk라는 파라미터를 앞에 int:
를 붙여서 변수 형태를 integer로 한정시킬 수 있다.
- blog/views.py
def index2(request, name):
return HttpResponse("INDEX2 OK" + name)
def index3(request, name):
return HttpResponse("INDEX3 OK" + str(pk))
404 Error Exception
- blog/views.py
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from blog.models import Post
def index3(request, pk):
#p = Post.objects.get(pk=pk)
p = get_object_or_404(Post, pk=pk) #pagenotfound(404) exception return
return HttpResponse("INDEX3 OK" + p.title)
Post.objects.get(pk=pk)
에서 왼쪽의 pk는 parameter name, 오른쪽의 pk는 variable이다.
get 함수는 pk=pk인 것을 찾아주는 함수이다.
get_object_or_404(Post, pk=pk)
: pk=pk인 것을 찾고, 에러 시 아랫줄 return이 아니라 PageNotFound(404) error로 리턴시키도록 exception을 발생시킨다.
List template
- blog/urls.py
urlpatterns = [
...
path('list', views.list),
]
- blog/views.py
def list(request):
data = Post.objects.all()
return render(request, "blog/list.html", {"data":data})
header/footer contents
- templates/blog/base.html
{% block content %}
{% endblock %}
부모에게 상속을 받고, 내가 렌더링한 부분은 여기에 넣겠다.
- templates/blog/list.html
{% extends 'blog/base.html' %}
{% block content %}
내용
{% endblock %}
{% extends 'blog/base.html' %}
를 base template으로 가져올 건데,
{% block content %}
부터 {% endblock %}
까지 렌더링한다.
LAST CODE
- blog/urls.py
from django.urls import path
from . import views #from 다음에는 폴더명. import 다음에는 함수명이나
urlpatterns = [
path('', views.index),
path('<name>/', views.index2), #<name> parameter에 대해 동적으로 mapping
path('list', views.list),
path('<int:pk>/detail', views.detail),
]
- blog/views.py
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from blog.models import Post
# Create your views here.
def index(request):
return HttpResponse("INDEX. OKOKOK")
def index2(request, name):
return HttpResponse("INDEX2 OK" + name)
def list(request):
data = Post.objects.all()
return render(request, "blog/list.html", {"data":data})
def detail(request, pk):
p = get_object_or_404(Post, pk=pk) #pagenotfound(404) exception return
return render(request, "blog/detail.html", {"d":p})
- templates/blog/base.html
<h1><font color="red">My Blog</font></h1>
<!--부모에게 상속을 받고, 내가 렌더링한 부분은 여기에 넣겠다.-->
{% block content %}
{% endblock %}
<br><br><br>
copy right........<br>
서울특별시........
- templates/blog/list.html
{% extends 'blog/base.html' %}
{% block content %}
{% for d in data %}
<a href="{{d.pk}}/detail">{{d.title}}</a>
<hr>
{% endfor %}
{% endblock %}
- templates/blog/detail.html
{% extends 'blog/base.html' %}
{% block content %}
<h2>게시물 보기</h2>
title >> {{d.title}}<hr>
{{d.text|linebreaks}}
<br><a href="/blog/list">뒤로가기</a>
{% endblock %}
{{변수|linebreaks}}
옵션을 주면 엔터 반영되어 렌더링된다.
in APP - Class type View 적용
class와 객체 이용한 일반적인 로그인 폼
클래스형 뷰 이론
목적: GET method와 POST method를 다른 페이지로 적용할 때, 편리를 위해 Class type View를 사용한다.
- 클래스로 작성되어 있는 뷰 객체를 말한다.
- 상속과 믹스인 기능 사용으로 코드의 재사용이 가능
- 뷰의 체계적 관리
- 제네릭 뷰 작성
#urls.py
urlpatterns = [
path('login/', views.LoginView.as_view())
]
클래스형 뷰는 클래스로 진입하기 위한 진입 메소드를 제공하는데, 이것이 위의 as_view()
메소드이며, 아래의 순서로 요청을 처리한다.
as_view()
메소드에서 클래스의 인스턴스를 생성한다.- 생성된 인스턴스의
dispatch()
메소드를 호출한다. dispatch()
메소드는 요청을 검사해서 HTTP의 메소드(GET, POST)를 알아낸다.- 인스턴스 내에 해당 이름을 갖는 메소드로 요청을 중계한다.
- 해당 메소드가 정의되어 있지 않으면, HttpResponseNotAllowd 예외를 발생시킨다.
#views.py
from django.views.generic import View
class PostView(View):
def get(self, request):
return HttpResponse("get OK")
def post(self, request):
return HttpResponse("post OK")
- PostView 클래스는 View 클래스를 상속받는다.
- View 클래스에는
as_view()
메소드와dispatch()
메소드가 정의되어 있다.
함수형 뷰와 비교했을 때 클래스형 뷰가 가지는 장점
- GET, POST 등의 HTTP 메소드에 따른 처리를 메소드명으로 구분 할 수 있어, 좀 더 깔끔한 구조의(IF 문이 없는) 코드를 생산할 수 있다.
- 다중 상속과 같은 객체 지향 기술이 가능하여 코드의 재사용성이나 개발 생산성을 높여준다.
#함수형 뷰에서의 메소드 구분
def my_view(request):
if request.method == 'GET':
# 뷰 로직 작성
return HttpResponse('result')
함수형 뷰에서는 예제에서 볼 수 있듯 HTTP 메소드별 다른 처리가 필요할 때 if 문을 이용해야 한다. 하지만, 클래스형 뷰는 다음과 같이 코드의 구조가 훨씬 깔끔해진다.
#클래스형 뷰에서의 메소드 구분
from django.views.generic import View
class PostView(View):
def get(self, request):
return HttpResponse("get OK")
def post(self, request):
return HttpResponse("post OK")
클래스형 뷰에서는 HTTP 메소드 이름으로 클래스 내의 메소드를 정의하면 된다.
단, 메소드명은 소문자로~ 이러한 처리가 가능한 것은 내부적으로 dispatch()
메소드가 어떤 HTTP 메소드로 요청되었는지 알아내고, 이를 처리해주기 때문이다.
상속 기능 가능
개발자가 작성하는 대부분의 클래스형 뷰는 장고가 제공해주는 제네릭 뷰를 상속받아 작성한다.
제네릭 뷰: 뷰 개발 과정에서 공통적으로 사용할 수 있는 기능들을 추상화하고, 장고에서 기본적으로 제공해주는 클래스형 뷰
위에서의 View
는 특별한 로직이 없고, URL 맞춰 해당 템플릿 파일의 내용만 보여줄 때 사용하는 제네릭 뷰이기 때문에 위처럼 상속받아 불러오는 것만으로도 사용할 수 있는 것이다.
Django 의 제네릭 뷰
Django 에서 제공하는 제네릭 뷰는 다음과 같이 4가지로 분류할 수 있다.
- Base View: 뷰 클래스를 생성하고, 다른 제네릭 뷰의 부모 클래스를 제공하는 기본 제네릭 뷰
- Generic Display View: 객체의 리스트를 보여주거나, 특정 객체의 상세 정보를 보여준다.
- Generic Edit View: 폼을 통해 객체를 생성, 수정, 삭제하는 기능을 제공한다.
- Generic Date View: 날짜 기반 객체의 년/월/일 페이지로 구분해서 보여준다.
아래는 위 4가지 분류에 따른 구체 뷰 클래스에 대한 설명이다.
- Base View
- View: 가장 기본이 되는 최상위 제네릭 뷰
- TemplateView: 템플릿이 주어지면 해당 템플릿을 렌더링한다.
- RedirectView: URL이 주어지면 해당 URL로 리다이렉트 시켜준다.
- Generic Display View
- DetailView: 객체 하나에 대한 상세한 정보를 보여준다.
- ListView: 조건에 맞는 여러 개의 객체를 보여준다.
- Generic Edit View
- FormView: 폼이 주어지면 해당 폼을 보여준다.
- CreateView: 객체를 생성하는 폼을 보여준다.
- UpdateView: 기존 객체를 수정하는 폼을 보여준다.
- DeleteView: 기존 객체를 삭제하는 폼을 보여준다.
- Generic Date View
- YearArchiveView: 년도가 주어지면 그 년도에 해당하는 객체를 보여준다.
- MonthArchiveView: 월이 주어지면 그 월에 해당하는 객체를 보여준다.
- DayArchiveView: 날짜가 주어지면 그 날짜에 해당하는 객체를 보여준다.
제네릭 뷰의 전체 리스트는 여기에서 확인 가능하다.
클래스형 뷰에서의 폼 처리
- blog/views.py
from django.forms import Form, CharField, Textarea
class PostForm(Form):
title = CharField(label='제목', max_length=20)
text = CharField(label='내용', widget=Textarea)
class PostEditView(View):
def get(self, request, pk): #특정 포스트를 수정하므로 pk parameter를 받아와야 한다.
#초기값 지정
post = get_object_or_404(Post, pk=pk)
form = PostForm(initial={'title':post.title, 'text':post.text})
return render(request, "blog/edit.html", {'form':form})
def post(self, request, pk):
form = PostForm(request.POST)
post = get_object_or_404(Post, pk=pk)
post.title = form['title'].value()
post.text = form['text'].value()
post.publish()
return redirect('list')
initial={dictionary}
- templates/blog/edit.html
{{ form.as_p }}
- form_class: 사용자에 보여줄 폼을 정의한 forms.py 파일 내의 클래스명
- template_name: 폼을 포함하여 렌더링할 템플릿 파일 이름
- success_url: MyFormView 처리가 정상적으로 완료되었을 때 리다이렉트 될 URL
- form_valid() 함수: 유효한 폼 데이터로 처리할 로직 코딩. 반드시 super() 함수를 호출해야 함.
참고: http://ruaa.me/django-view/
- 폼 기능을 추가한 blog/views.py
from django.forms import Form, CharField, Textarea, ValidationError
def validator(value):
if len(value) < 5 : raise ValidationError("길이가 너무 짧아요");
class PostForm(Form):
title = CharField(label='제목', max_length=20, validators=[validator])
text = CharField(label='내용', widget=Textarea)
class PostEditView(View):
def get(self, request, pk): #특정 포스트를 수정하므로 pk parameter를 받아와야 한다.
#초기값 지정
post = get_object_or_404(Post, pk=pk)
form = PostForm(initial={'title':post.title, 'text':post.text})
return render(request, "blog/edit.html", {'form':form, 'pk':pk})
def post(self, request, pk):
form = PostForm(request.POST)
if form.is_valid():
post = get_object_or_404(Post, pk=pk)
post.title = form['title'].value()
post.text = form['text'].value()
post.publish()
return redirect('list')
return render(request, 'blog/edit.html', {'form':form, 'pk':pk})
Authentication
global settings에 정의된 인증관련 기본 설정에 AUTH_USER_MODEL = 'auth.User'
로 정의되어있다.
#in Jupyter Notebook for Unit Test
from django.contrib.auth import authenticate
user = authenticate(username = 'home', password='choikt1234')
if user == None : print(user)
from django.contrib.auth import authenticate
user = authenticate(username = 'home', password='choikt1234')
if user == None : print(user) #None
User 모델 클래스 획득 방법
- 직접 User 모델 import (비추)
from django.contrib.auth.models import User
User.objects.all()
global settings 오버라이딩을 통해서 인증 User 모델을 다른 모델로 변경할 수 있음
get_user_model
helper 함수를 통해 모델 클래스 참조 (추천)
from django.contrib.auth import get_user_model
User = get_user_model()
User.objects.all()
settings.AUTH_USER_MODEL
을 통한 모델클래스 참조 (추천)
from django.conf import settings # 추천!
from django.conf.auth.models import User # 비추
from django.db import models
class Post(models.Model):
author = models.ForeignKey(User) # 비추
author = models.ForeignKey('auth.User') # 비추
author = models.ForeignKey(settings.AUTH_USER_MODEL) # 추천!
view에서 현재 로그인 유저 획득하는 방법
- FBV : request.user
- CBV : self.request.user
- 로그인 상태 :settings.AUTH_USER_MODEL 클래스 인스턴스
- 로그아웃 상태 :django.contrib.auth.models.AnonymousUser 클래스 (모델 인스턴스가 아님, 다른 모델과 관계 불가능)
- context_processor를 통해서 user가 모든 view에 context로 기본 제공 됨
출처: https://wayhome25.github.io/django/2017/05/18/django-auth/
경로 찾아주는 template 명령어
views 사용때문에 생기는 상대경로 문제를 해결해주는 방법이다.
html에 다음과 같은 함수로 상대경로 문제를 해결할 수 있다.
{% url 'url_name' param1 param2 param3 %}
- blog/urls.py
urlpatterns = [
path('', views.index),
path('list', views.list, name='list'),
path('<int:pk>/detail', views.detail, name='detail'), #function base
path('list2', views.PostView.as_view()), #class base
path('login/', views.LoginView.as_view(), name='login'),
]
path에 name을 정의하게되면, name으로 경로명을 자동으로 알아낼 수 있다.
- blog/views.py
...
return redirect('login') #urls.py에서 지정한 name. NOT 경로명.
...
return redirect('list')
urls.py에서 login
이라는 name을 지정했으므로, 경로명이 아니라 name을 불러온 것이다.
- templates/blog/login.html
<form action="{% url 'login' %}" method="post">
{% url 'login' %}
: url 명령어 뒤 name을 알아서 절대경로를 찾아 지정해준다.
- templates/blog/list.html
{% for d in data %}
<a href="{% url 'detail' d.pk %}">{{d.title}}</a>
<hr>
{% endfor %}
{% url 'detail' d.pk %}
: url 명령어 뒤 name=detail, parameter=d.pk 를 가져와 절대경로를 찾아 지정해준다.
cf) in templates,
명령어 사용할 때에는 {% 명령어%}
변수값을 가져올 때에는 {{변수명}}
여러 가지 경로 지정 방법 비교
- CODE of list.html
{% extends 'blog/base.html' %}
{% block content %}
{% for d in data %}
<a href="{% url 'detail' d.pk %}">{{d.title}}</a><br>
<a href="/blog/{{d.pk}}/detail">{{d.title}}</a><br>
<a href="/blog/detail?id={{d.pk}}">{{d.title}}</a><br>
<a href="detail?id={{d.pk}}">{{d.title}}</a><br>
<hr>
{% endfor %}
{% endblock %}
첫번째 방법: 경로 찾아주는 template 명령어
두번째 방법: 절대경로
세번째: get parameter로 받아올 경우, 절대경로
네번째: get parameter로 받아올 경우, 상대경로
<a href="/blog/1/detail">오늘 날씨가 추워요</a><br>
<a href="/blog/1/detail">오늘 날씨가 추워요</a><br>
<a href="/blog/detail?id=1">오늘 날씨가 추워요</a><br>
<a href="detail?id=1">오늘 날씨가 추워요</a><br>
LAST CODE
blog/models.py
from django.db import models
class User(models.Model) :
userid = models.CharField(max_length=10, primary_key=True)
name = models.CharField(max_length=10)
age = models.IntegerField()
hobby = models.CharField(max_length=20)
def __str__(self): #print 적용할 때 자동으로 적용되는 함수
return f"{self.userid} / {self.name} / {self.age}"
blog/urls.py
from django.urls import path
from . import views #from 다음에는 폴더명. import 다음에는 함수명이나
urlpatterns = [
path('', views.index),
# path('<name>/', views.index2), #<name> parameter에 대해 동적으로 mapping
# path('<int:pk>/detail', views.index3),
path('login/', views.LoginView.as_view(), name='login'), # class base
path('list/', views.list, name='list'),
path('<int:pk>/detail/', views.detail, name='detail'), #function base
path('add/', views.PostView.as_view(), name='add'),
path('<int:pk>/edit/', views.PostEditView.as_view(), name='edit'),
]
login
- blog/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponse
from blog.models import Post
from django.views.generic import View
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.forms import Form, CharField, Textarea, ValidationError
class LoginView(View):
def get(self, request):
return render(request, "blog/login.html")
def post(self, request):
#Loging 처리
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username, password=password)
if user == None :
return redirect('login') #urls.py에서 지정한 name. NOT 경로명.
#로그인 성공한 경우
request.session['username'] = username
return redirect('list')
- templates/blog/login.html
<form action="{% url 'login' %}" method="post"> <!--action 빼면 자기 자신, url이라는 명령어 뒤 name-->
{% csrf_token %}
username <input type="text" name="username"><br>
password <input type="password" name="password"><br>
<input type="submit" value="로그인">
</form>
base.html
<h1><font color="red">My Blog</font></h1>
로그인 사용자: {{username}} 님<br>
<!--부모에게 상속을 받고, 내가 렌더링한 부분은 여기에 넣겠다.-->
{% block content %}
{% endblock %}
<br><br><br>
copy right........<br>
서울특별시........
list
- blog/views.py
def list(request):
username = request.session['username'] #text
user = User.objects.get(username=username) #object
data = Post.objects.all().filter(author=user)
context = {"data":data, 'username':username}
return render(request, "blog/list.html", context)
- templates/blog/list.html
{% extends 'blog/base.html' %}
{% block content %}
<a href="{% url 'add' %}">글쓰기</a><br>
<h2>글 리스트</h2>
{% for d in data %}
<a href="{% url 'detail' d.pk %}">{{d.title}}</a><br>
<hr>
{% endfor %}
{% endblock %}
detail
- blog/views.py
def detail(request, pk):
p = get_object_or_404(Post, pk=pk) #에러나면 아래 return이 아닌, pagenotfound(404) exception로 리턴시킨다.
return render(request, "blog/detail.html", {"d":p})
- templates/blog/detail.html
{% extends 'blog/base.html' %}
{% block content %}
<h2>게시물 보기</h2>
{{d.title}}<hr>
{{d.text|linebreaks}}
<br><a href="{% url 'edit' d.pk %}">수정</a>
<br><a href="/blog/list">목록 보기</a>
{% endblock %}
add
- blog/views.py
class PostView(View):
def get(self, request):
username = request.session['username']
return render(request, "blog/add.html", {'username':username})
def post(self, request):
title = request.POST.get('title')
text = request.POST.get('text')
username = request.session['username']
user = User.objects.get(username=username)
Post.objects.create(title=title, text=text, author=user) #create:생성과 동시에 save
return redirect('list')
- templates/blog/add.html
{% extends 'blog/base.html' %}
{% block content %}
<form action="{% url 'add' %}" method="post">
{% csrf_token %}
제목 <input type="text" name="title" /><br>
내용 <textarea rows="10" cols="30" name="text"></textarea>
<input type="submit" value="작성">
</form>
{% endblock %}
edit
데이터를 읽어와서 default value로 넣어줘야 한다.
- blog/views.py
def validator(value):
if len(value) < 5 : raise ValidationError("길이가 너무 짧아요");
class PostForm(Form):
title = CharField(label='제목', max_length=20, validators=[validator])
text = CharField(label='내용', widget=Textarea)
class PostEditView(View):
def get(self, request, pk): #특정 포스트를 수정하므로 pk parameter를 받아와야 한다.
#초기값 지정
post = get_object_or_404(Post, pk=pk)
form = PostForm(initial={'title':post.title, 'text':post.text})
return render(request, "blog/edit.html", {'form':form, 'pk':pk})
def post(self, request, pk):
form = PostForm(request.POST)
if form.is_valid():
post = get_object_or_404(Post, pk=pk)
post.title = form['title'].value()
post.text = form['text'].value()
post.publish()
return redirect('list')
return render(request, 'blog/edit.html', {'form':form, 'pk':pk})
- templates/blog/edit.html
{% extends 'blog/base.html' %}
{% block content %}
<form action="{% url 'edit' pk %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="작성">
</form>
{% endblock %}
'Python > Django' 카테고리의 다른 글
장고(django)에서 이미지 업로드 필드 추가 없이 (0) | 2020.04.24 |
---|---|
Django에 Database 사용하기 (1) | 2020.02.19 |
HTML과 python web server 구축 (0) | 2020.02.18 |
Python Socket programming (0) | 2020.02.18 |
OSI 참조 모델 이론 (0) | 2020.02.18 |