Django Crash Course-1

Toku
仮想環境の設定(Virtual Environment)
参考:Udemyの Django 3.0 MasterClass - Learn How To Create Django Apps
pythonのバージョン確認
user@mbp Django-tutorial-blog % python -m django --version
4.0.4
Django Girls のチュートリアルを参考
仮想環境:https://tutorial.djangogirls.org/ja/django_installation/
$ python3 -m venv myvenv
クラスベースビュー:https://super-vitality.com/django-class-based-views/
user@mbp Django-tutorial-blog % python3 -m venv venv
user@mbp Django-tutorial-blog % source venv/bin/activate
(venv) user@mbp Django-tutorial-blog % pip install Django
(venv) user@mbp Django-tutorial-blog %code .
(venv) user@mbp Django-tutorial-blog % pip freeze
asgiref==3.5.2
Django==4.1.4
sqlparse==0.4.3
(venv) user@mbp Django-tutorial-blog % django-admin startproject blog .
(venv) user@mbp Django-tutorial-blog % python manage.py runserver
user@mbp Django-tutorial-blog % git init
(venv) user@mbp Django-tutorial-blog % python manage.py runserver 7000
Starting development server at http://127.0.0.1:7000/
データベース sqlite3
(venv) user@mbp Django-tutorial-blog % python manage.py migrate
db.qlite3を右クリック→Open Database→SQLITE EXPLORER
Django Appの作成
(venv) user@mbp Django-tutorial-blog % python manage.py startapp posts
SuperUserの作成
(venv) user@mbp Django-tutorial-blog % python manage.py createsuperuser
Username (leave blank to use 'user'): user
Email address:
Password: 123
Templates Directoryの作成
// settings.py
import os
. . .
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], // 追加
'APP_DIRS': True,
ルートにtemplatesフォルダを作成し配下にblogフォルダを作成する
Htmlファイルの作成
blogのurls.py
from django.contrib import admin
from django.urls import path
from posts.views import index // added
urlpatterns = [
path('admin/', admin.site.urls),
path('', index) // added
]
# posts/views.py
from django.shortcuts import render
def index(request):
context = {
'message': 'first message',
'message2': 'second message'
}
return render(request, 'post/index.html', context)
# templates/post/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Posts</title>
</head>
<body>
<div>{{message}}</div>
<div>{{message2}}</div>
</body>
</html>
Modelの作成
# posts/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=50)
content = models.TextField()
date=models.DateField(auto_now_add=True)
date=models.DateField(auto_now_add=True)にすると自動的に日付が適用される
appをsetting.pyに登録する
settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'posts.apps.PostsConfig' // added
]
テーブルを作成する
(venv) user@mbp Django-tutorial-blog % python manage.py makemigrations
DBに登録する
(venv) user@mbp Django-tutorial-blog % python manage.py migrate
admin画面で登録したPostテーブルが見れるようにする
# posts/admin.py
from django.contrib import admin
from .models import Post
admin.site.register(Post)
Models Admin Pageの編集
Django administrationにtitleを表示させる
# posts/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=50)
content = models.TextField()
date=models.DateField(auto_now_add=True)
def __str__(self): # 追加
return self.title
タイトル以外に日付やリンクを追加する
# posts/admin.py
from django.contrib import admin
from .models import Post
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'date']
list_display_links = ['title', 'date']
admin.site.register(Post, PostAdmin)
さらにDjango administrationにサーチフィールドやフィルターを追加する
# posts/admin.py
from django.contrib import admin
from .models import Post
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'date']
list_display_links = ['title', 'date']
search_fields = ['title', 'content']
list_filter = ['date']
admin.site.register(Post, PostAdmin)
Static Files Directory の作成
https://docs.djangoproject.com/en/4.1/howto/static-files/
# settings.py
STATIC_URL = 'static/'
STATICFILES_DIRS = [ # add
BASE_DIR / "static",
]
画像を載せる時に使うディレクりは以下を使って設定する
{% load static %}
<img src="{% static 'my_app/example.jpg' %}" alt="My image">
ルートにstaticフォルダを作成し直下にimg,css,jsフォルダを作成する
# templates/post/index.html
{% load static %} # added
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Posts</title>
</head>
<body>
<h3>This is main page</h3>
<div>{{message}}</div>
<div>{{message2}}</div>
<img src="{% static 'img/e.jpg' %}" alt="My image"> # added
</body>
</html>
アップロードした時に使うmediaディレクトリをsettings.pyに設定する
# settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
main(blog)のurls.pyに以下を追加
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# ... the rest of your URLconf goes here ...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# blog/blog/urls.py
from django.contrib import admin
from django.urls import path
from posts.views import index
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', index)
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Modelにimagesをアップロードする
(venv) user@mbp Django-tutorial-blog % pip install Pillow
posts/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(verbose_name='タイトル',max_length=50)
content = models.TextField(verbose_name='内容')
date=models.DateField(verbose_name='投稿日', auto_now_add=True)
image = models.ImageField(verbose_name='画像',null=True, blank=True, upload_to='images') # added
# ’images'は画像を格納するフォルダ名
def __str__(self):
return self.title
(venv) user@mbp Django-tutorial-blog % python manage.py makemigrations
(venv) user@mbp Django-tutorial-blog % python manage.py migrate
http://127.0.0.1:8000/adminを開いて画像をアップロードするとmedia/imagesフォルダが作成されている
テンプレートファイルにPostsを表示する(2種類の方法)
その1:SQL Query文を使う
from django.shortcuts import render
import sqlite3
con = sqlite3.connect("db.sqlite3", check_same_thread=False)
cur = con.cursor()
def index(request):
query = "SELECT * FROM posts_post"
posts = cur.execute(query).fetchall()
for post in posts:
# print(post)
print(post[1])
context = {
'message': 'first message',
'message2': 'second message'
}
return render(request, 'post/index.html', context)
その2:QuerySet API リファレンス https://man.plustar.jp/django/ref/models/querysets.html
list() を呼び出すことで、 QuerySet の評価を矯正する
entry_list = list(Entry.objects.all())
from django.shortcuts import render
from .models import Post
# from posts.models import Post
def index(request):
context = {
'posts': Post.objects.all()
}
return render(request, 'post/index.html', context)
For文を使ってPOSTを全部表示する
# templates/post/index.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Posts</title>
</head>
<body>
<h3>This is main page</h3>
{% for post in posts %}
<img src="{{ post.image.url }}" alt="Post image">
<h3>{{post.title}}</h3>
<small>{{ post.date }}</small>
<p>{{ post.content }}</p>
{% endfor %}
</body>
</html>
Bootstrapを使う
コンパイルされた CSS と JSダウンロード:https://getbootstrap.jp/docs/5.0/getting-started/download/
static/cssにbootstrap.min.cssを格納し、static/jsにbootstrap.min.jsを格納する、
# templates/post/index.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Posts</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css'%}"> # added
</head>
<body>
<h3>This is main page</h3>
{% for post in posts %}
<img src="{{ post.image.url }}" alt="Post image">
<h3>{{post.title}}</h3>
<small>{{ post.date }}</small>
<p>{{ post.content }}</p>
{% endfor %}
<script src="{% static 'js/bootstrap.min.js' %}"></script> # added
</body>
</html>
Bootstrapコンポーネントを使う
https://getbootstrap.jp/docs/5.0/components/card/
カードのサンプル
<div class="card" style="width: 18rem;">
<img src="..." class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<a href="#" class="btn btn-primary">Go somewhere</a>
</div>
</div>
# templates/post/index.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Posts</title>
<link rel="stylesheet" href="{% static "css/bootstrap.min.css" %}">
</head>
<body>
<div class="container" style="background-color:pink; padding-top:45px; padding-bottom:45px">
<h3>This is main page</h3>
{% for post in posts %}
<div class="card" style="width: 48rem; margin-bottom:30px">
<img src="{{ post.image.url }}" class="card-img-top" alt="Post image">
<div class="card-body">
<h5 class="card-title">{{post.title}}</h5>
<p class="card-text">{{ post.content|truncatechars:150 }}</p>
<a href="#" class="btn btn-primary">More</a>
</div>
</div>
{% endfor %}
</div>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
</body>
</html>
Post Detail Pageを作成する
urls.pyにpath を追加する
'detail/int:id'のidはdetail_viewの引数のidと同じ
またname="detail"をつけることで遷移先のURLを指定できる
# blog/settings.py
from django.contrib import admin
from django.urls import path
# from posts.views import index
from posts.views import *
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', index, name="index"), # nameを追加
path('detail/<int:id>', detail_view, name="detail") # added
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
posts/views.pyにdetail_viewを追加する
# posts/views.py
from django.shortcuts import render
from .models import Post
# from posts.models import Post
def index(request):
context = {
'posts': Post.objects.all()
}
return render(request, 'posts/index.html', context)
def detail_view(request, id):
post = Post.objects.get(id=id)
context = {
'post':post
}
return render(request, 'posts/detail.html', context)
# index.html
<div class="container" style="padding-top:45px; padding-bottom:45px">
<h3>This is main page</h3>
{% for post in posts %}
<div class="card" style="width: 48rem; margin-bottom:30px">
<img src="{{ post.image.url }}" class="card-img-top" alt="Post image">
<div class="card-body">
<h5 class="card-title">{{post.title}}</h5>
<small>{{ post.date }}</small>
<p class="card-text">{{ post.content|truncatechars:150 }}</p>
<a href="/detail/{{post.id}}" class="btn btn-primary">More</a>
</div>
</div>
{% endfor %}
</div>
上のindex.htmlで、
<a href="/detail/{{post.id}}" class="btn btn-primary">More</a>
の代わりに、
<a href="{% url 'detail' post.id %}" class="btn btn-primary">More</a>
を使う
detailページでidに存在しないページを指定したときエラーになるので、get_object_or_404を使うと「Page not found (404)」を表示する
# posts/views.py
from django.shortcuts import render, get_object_or_404
from .models import Post
# from posts.models import Post
def index(request):
context = {
'posts': Post.objects.all()
}
return render(request, 'posts/index.html', context)
def detail_view(request, id):
# post = Post.objects.get(id=id)
post = get_object_or_404(Post, id=id) # change
context = {
'post':post
}
return render(request, 'posts/detail.html', context)
# template/posts/detail.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>詳細ページ</title>
<link rel="stylesheet" href="{% static "css/bootstrap.min.css" %}">
</head>
<body>
<div class="container" style="padding-top:45px; padding-bottom:45px">
<h3>詳細ページ</h3>
<div class="card" style="width: 48rem; margin-bottom:30px">
<img src="{{ post.image.url }}" class="card-img-top" alt="Post image">
<div class="card-body">
<h5 class="card-title">{{post.title}}</h5>
<small>{{ post.date }}</small>
<p class="card-text">{{ post.content}}</p>
<a href="{% url 'index' %}" class="btn btn-primary">戻る</a>
</div>
</div>
</div>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
</body>
</html>
Navbarを作成する
https://getbootstrap.jp/docs/5.0/components/navbar/
dropdownが効かない時はjQueryをstatic/js に入れる:
https://jquery.com/download/ のDownload the compressed, production jQuery 3.6.3
# index.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ブログページ</title>
<link rel="stylesheet" href="{% static "css/bootstrap.min.css" %}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
</li>
</ul>
<form class="d-flex">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
<div class="container" style="padding-top:45px; padding-bottom:45px">
<h3>This is main page</h3>
{% for post in posts %}
<div class="card" style="width: 48rem; margin-bottom:30px">
<img src="{{ post.image.url }}" class="card-img-top" alt="Post image">
<div class="card-body">
<h5 class="card-title">{{post.title}}</h5>
<small>{{ post.date }}</small>
<p class="card-text">{{ post.content|truncatechars:150 }}</p>
{% comment %} <a href="/detail/{{post.id}}" class="btn btn-primary">More</a> {% endcomment %}
<a href="{% url 'detail' post.id %}" class="btn btn-primary">More</a>
</div>
</div>
{% endfor %}
</div>
<script src="{% static 'js/jquery-3.6.3.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
</body>
</html>
Template Extendingを利用する
# tenplates/base.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ブログページ</title>
<link rel="stylesheet" href="{% static "css/bootstrap.min.css" %}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
</li>
</ul>
<form class="d-flex">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
{% block body %}
{% endblock %}
<script src="{% static 'js/jquery-3.6.3.min.js' %}"></script> # added
<script src="{% static 'js/bootstrap.min.js' %}"></script>
</body>
</html>
# templates/posts/index.html
{% extends 'base.html' %}
{% block body %}
<div class="container" style="padding-top:45px; padding-bottom:45px">
<h3>This is main page</h3>
{% for post in posts %}
<div class="card" style="width: 48rem; margin-bottom:30px">
<img src="{{ post.image.url }}" class="card-img-top" alt="Post image">
<div class="card-body">
<h5 class="card-title">{{post.title}}</h5>
<small>{{ post.date }}</small>
<p class="card-text">{{ post.content|truncatechars:150 }}</p>
{% comment %} <a href="/detail/{{post.id}}" class="btn btn-primary">More</a> {% endcomment %}
<a href="{% url 'detail' post.id %}" class="btn btn-primary">More</a>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
フォームを作成する
modelForm用いたforms.pyのExample
class ReviewForm(ModelForm):
class Meta:
model = Review
fields = ['value', 'body']
labels = {
'value':'評価してください',
'body': 'コメントお願いします'
}
def __init__(self, *args, **kwargs):
super(ReviewForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs.update({'class': 'form-input'})
class CategoryForm(ModelForm):
class Meta:
model = Category
fields = ['name']
def __init__(self, *args, **kwargs):
super(CategoryForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update(
{ 'class': 'form-input'})
class TagForm(ModelForm):
class Meta:
model = Tag
fields = ['name']
def __init__(self, *args, **kwargs):
super(TagForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update(
{ 'class': 'form-input'})
form.pyの実装Example
<form action="" method="POST">
{% csrf_token %}
{% for field in form %}
</label>{{field.label}}</label>
<div class='form-field'>{{field}}</div>
{% endfor %}
<a class="btn btn-secondary" href="{% url 'urls' %}">戻る</a>
<input class="btn btn-primary" type="submit" value="送信">
</div>
</form>
# posts/forms.py
from django import forms
from .models import Post
class Postform(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'image']
# posts/views.py
from django.shortcuts import render, get_object_or_404
from .models import Post # from posts.models import Post
from .forms import PostForm # added
def index(request):
context = {
'posts': Post.objects.all()
}
return render(request, 'posts/index.html', context)
def detail_view(request, id):
# post = Post.objects.get(id=id)
post = get_object_or_404(Post, id=id)
context = {
'post':post
}
return render(request, 'posts/detail.html', context)
def create_view(request): # added
form = PostForm(request.POST or None, request.FILES or None)
context = {
'form': form
}
return render (request, 'posts/create.html', context)
# templates/posts/create.html
{% extends 'base.html' %}
{% block body %}
<form method="post" enctype="multipart/form-data">
{{ form.as_p }}
</form>
{% endblock %}
templates/base.htmlの一部を追加修正(styleの追加)
# templates/base.html
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"
style="margin-bottom: 30px"> # added
新しいPostを作成する
フォームにボタンを追加する csrf_tokenも追加
templates/posts/create.html
{% extends 'base.html' %}
{% block body %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="送信" />
</form>
{% endblock %}
# posts/views.pyのcreate_view
from django.shortcuts import render, get_object_or_404, HttpResponseRedirect
def create_view(request):
form = PostForm(request.POST or None, request.FILES or None)
if form.is_valid():
post = form.save()
return HttpResponseRedirect('/')
context = {
'form': form
}
return render (request, 'posts/create.html', context)
Postを作成後、Postの詳細ページに遷移するようにget_absolute_url()を使う success_url=reverse_lazy()でもできる?
参考:https://djangobrothers.com/blogs/get_absolute_url/
get_absolute_urlはPostなどモデルに実装して各Postインスタンスに対応するURLを返す
仮にURLパスや、パスにつけたnameが変更された時でもget_absolute_url内の処理だけを修正すればよくHTML側での修正は不要
# posts/models.py
from django.db import models
from django.urls import reverse # added
class Post(models.Model):
title = models.CharField(verbose_name='タイトル',max_length=50)
content = models.TextField(verbose_name='内容')
date=models.DateField(verbose_name='投稿日', auto_now_add=True)
image = models.ImageField(verbose_name='画像',null=True, blank=True, upload_to='images')
def __str__(self):
return self.title
def get_absolute_url(self): # added
return reverse("detail", kwargs={"id": self.id})
# posts/views.pyのcreate_view
def create_view(request):
form = PostForm(request.POST or None, request.FILES or None)
if form.is_valid():
post = form.save()
return HttpResponseRedirect(post.get_absolute_url())
context = {
'form': form
}
return render (request, 'posts/create.html', context)