Django自分用メモになります
今回はクラスベースビューについてまとめます
開発環境
OS:mac
エディタ:vscode
python:3.10.9
django:4.1.0
公式ページ
View
GET,POSTなどリクエストに応じて実行する処理を変えたい場合にはget(),post()をメゾットとして定義する
class MyView(View):
def get(self,request,**kwargs):
pass #GETの場合の処理
def post(self,request,**kwargs):
pass #POSTの場合の処理
クラスベースビューに触れてみる
Viewを継承したクラスの中でgetメゾットを使ってページを表示する
from django.shortcuts import render
from django.views.generic.base import(
View,
)
class IndexView(View):
#getメゾットが呼び出されてページを呼び出す
def get(self,request,*args,**kwargs):
return render(request,'index.html')
from django.urls import path
from .views import IndexView
app_name = 'store'
urlpatterns = [
path('index/',IndexView.as_view(),name='index')
]
IndexView.as_view()とすることで呼び出し可
get,postメゾットを定義してそれぞれで処理を変える
1、単純にそのフォームのページを呼び出す場合。(get)
2、フォームで送信した場合。(post)
で処理を変える。
まずモデルの作成と、ModelFormを継承したフォームの作成とsaveメゾットのカスタマイズを復習
from django.db import models
class BaseModel(models.Model):
create_at = models.DateTimeField()
update_at = models.DateTimeField()
class Meta:
abstract = True #抽象的なモデルとして作成
class Books(BaseModel): #create_atとupdate_atを作成しなくても引き継ぐことが可能
name = models.CharField(max_length=255)
description = models.CharField(max_length=1000)
price = models.IntegerField()
class Meta:
db_table = 'books'
abstract=Trueと抽象クラスで設定する。
それを継承することで複数のモデルで共通したカラムを持たせることができる
from django import forms
from .models import Books
from datetime import datetime
class BookForm(forms.ModelForm):
class Meta:
model = Books
fields = ['name','description','price']
#saveメゾットのカスタマイズ(create_atとupdate_atを保存する処理を加えたい。Formの記事でも書いてあるので要チェック)
def save(self,*args,**kwargs):
obj = super(BookForm,self).save(commit=False) #saveを実行して、Booksクラスのインスタンスがobjに入る
obj.create_at = datetime.now()
obj.update_at = datetime.now()
obj.save()
return obj
saveメゾットのカスタマイズはformの記事でも書いたようにかなりつかう記述
getの場合は単純にforms.pyのフォームをコンテキストとして渡してテンプレートで表示
postの場合はフォームの値を受け取って、バリデーションをして保存するという処理にする
from django.shortcuts import render
from django.views.generic.base import(
View,
)
from . import forms
class IndexView(View):
#getメゾットが呼び出されてページを呼び出す
def get(self,request,*args,**kwargs):
#フォームをコンテキストでページに渡してあげる
book_form = forms.BookForm()
return render(request,'index.html',context={
'book_form':book_form,
})
#ここでpostメゾットが定義されていないとフォームで送られてきた際にどういった処理をするのかがわからないから定義してあげる必要がある
def post(self,request,*args,**kwargs):
book_form = forms.BookForm(request.POST or None)
if book_form.is_valid():
book_form.save()
return render(request,'index.html',context={
'book_form':book_form,
})
TemplateView
TemplateViewではこれまでcontextで値を渡していたやり方を
get_context_dataで渡すようにする
TemplateViewの呼び出し方(urls.pyからとviews.py)
urls.pyからとviews.pyから呼び出す2種類がある
from django.urls import path
from django.views.generic.base import TemplateView
app_name = 'store'
urlpatterns = [
#urls.pyからTemplateViewを呼び出す方法
path('home/',TemplateView.as_view(template_name='home.html'),name='home'),
]
またはviews.pyから呼び出す
from django.views.generic.base import(
TemplateView
)
class HomeView(TemplateView):
template_name = 'home.html'
from .views import HomeView
urlpatterns = [
path('home/',HomeView.as_view(),name='home'),
]
get_context_dataで値を渡す
class HomeView(TemplateView):
template_name = 'home.html'
def get_context_data(self,**kwargs): #kwargsとは何を受け取るのかはのちに解説
context = super().get_context_data(**kwargs)#get_context_dataをオーバライドする
context['time'] = datetime.now()
return context
渡したコンテキストは{{time}}で表示可能
<h1>Home画面</h1>
<p>現在時刻は{{time}}です。</p>
get_context_dataで値を渡す その2(urlパラメータを受け取って動的にページを変更)
urls.pyでパラメータを受け取るように設定
urlpatterns = [
path('home/<name>',HomeView.as_view(),name='home'),
]
get_context_dataの引数の**kwargsはパラメータのnameを受け取る
class HomeView(TemplateView):
template_name = 'home.html'
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
print(kwargs)
context['name']=kwargs.get('name') #nameはkwargsに入ってくるのでパラメータのnameを取得してコンテキストに渡す
context['time'] = datetime.now()
return context
表示する
<p>{{name}}さん。現在時刻は{{time}}です。</p>
get_context_dataで値を渡す その3(フォームをつくって渡す)演習
#フォームをつくってわたす
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
picture_form = forms.PictureUploadForm
#8.本の一覧を取得する処理を書くが、models.pyでマネージャーを用意してそれを使って取得する
pictures = Pictures.objects.filter_by_book(book=self.object)
context['pictures']=pictures
context['picture_form'] =picture_form
return context
#ファイルが上げられた際の保存処理をする
def post(self,request,*args,**kwargs):
#2.こちらで画像のアップロードする処理を書く
picture_form = forms.PictureUploadForm(request.POST or None,request.FILES or None)
if picture_form.is_valid() and request.FILES:
#3.いまBookUpdateViewで更新している本がどの本なのかをおしえてあげる
book = self.get_object()
picture_form.save(book=book) #4.Pictureモデルで定義したbookというカラムにその本を渡す
#5.forms.pyでsaveメゾットをカスマイズする必要がある
#1.本の名前や説明、値段などの更新処理はこちらで行うので
return super(BookUpdateView,self).post(request,*args,**kwargs)
forms.pyでフォームを定義
class PictureUploadForm(forms.ModelForm):
picture =forms.FileField(required=False)
class Meta:
model=Pictures
fields = ['picture',]
#6.saveメゾットのカスタマイズ
def save(self,*args,**kwargs):
obj = super(PictureUploadForm,self).save(commit=False)
obj.create_at = datetime.now()
obj.update_at = datetime.now()
obj.book = kwargs['book']
obj.save()
return obj
コンテキストで渡しているのでhtml側では {{picture_form.as_p}}で表示ができるが、
画像を処理するので
form のenctype="multipart/form-data"とすることを忘れずに
コンテキストでpicturesと画像一覧を渡しているのでfor文で回して
picture.picture.urlで取り出せる
{%for picture in pictures %}
<img width='100px' height = '100px' src="{{picture.picture.url}}">
{% endfor %}
画像を表示するならプロジェクトのurls.pyにその設定をすることを忘れない
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('store/',include('store.urls')),
]
if settings.DEBUG:
urlpatterns +=static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
インスタンス名.モデル名_set.all(インスタンスから逆参照する)
{%for object in object_list%}
{# object.pictures_set.allとはインスタンス名.モデル名_set.all()とすることでインスタンスの方からモデルのデータに逆参照できる #}
{% for picture in object.pictures_set.all %}
<img width="20px" height="20px" src={{picture.picture.url}}>
{% endfor %}
{% endfor %}
object.pictures_set.allでインスタンスからモデルに含まれる画像をすべてとりだしfor文でループさせて表示するといったことができる
参考にさせてもらったページ
DB上からも画像データを削除(シグナル復習)
やりかたは2通り。views.pyで関数として定義するか、シグナルのpost_deleteでモデルが削除された時に起動するコードを書くか。
1.views.pyの場合
#画像削除用の関数
def delete_picture(request,pk):
picture = get_object_or_404(Pictures,pk=pk)
picture.delete()
#上記だとテーブルから削除しただけでDBのフォルダ上には残るのでそちらも削除したい場合に下記を記述
#もしくはモデルに操作が加わった場合に操作を実行したいのでシグナルを使う。
import os
if os.path.isfile(picture.picture.path):
os.remove(picture.picture.path)
messages.success(request,'画像を削除しました')
return redirect('store:edit_book',pk=picture.book.id)
#delete_picture関数はhtml側でimgのa href='{%url 'store:delete_picture' pk=picture.id%}'として設定する
2.models.pyでシグナルを使う場合
from django.db.models.signals import post_delete
from django.dispatch import receiver
import os
#画像の削除が行われたらDBからも削除する
@receiver(post_delete,sender=Pictures)
def delete_picture(sender,instance,**kwargs):
#instanceの中に削除されたpictureのデータが入ってるので
if instance.picture:
if os.path.isfile(instance.picture.path):
os.remove(instance.picture.path)
models.pyはこんなかんじ
class BaseModel(models.Model):
create_at = models.DateTimeField()
update_at = models.DateTimeField()
class Meta:
abstract = True #抽象的なモデルとして作成
class Books(BaseModel): #create_atとupdate_atを作成しなくても引き継ぐことが可能
name = models.CharField(max_length=255)
description = models.CharField(max_length=1000)
price = models.IntegerField()
class Meta:
db_table = 'books'
#BookCreateViewで作成した際に変移先を設定する
def get_absolute_url(self):
return reverse_lazy('store:detail_book',kwargs={'pk':self.pk})
#7.画像を表示するためにPicturesモデルのマネージャーを用意
class PicturesManager(models.Manager):
def filter_by_book(self,book):
return self.filter(book=book).all()
class Pictures(BaseModel):
picture = models.FileField(upload_to='picture/',blank=True)
book = models.ForeignKey(
'Books',on_delete=models.CASCADE
)
objects = PicturesManager()
class Meta:
db_table='pictures'
DetailView
データの詳細を表示するのに使う
テンプレート側はobjectにデータが格納されているのでそれを使う
from django.views.generic.detail import DetailView
from .models import Books
class BookDetailView(DetailView):
model = Books
template_name = 'book.html'
#おまけ
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
print(context)
#詳細を表示すると共にフォームを渡してpostメゾットを記述して更新するなんかもできる
context['form'] = forms.BookForm()
return context
int:pkとしてそのプライマリキーを持つ要素を取得する
DetailViewの便利なメゾットself.get_object()
class ItemDetailView(DetailView):
model = Items
template_name = 'item_detail.html'
def get_context_data(self,**kwargs):
context =super().get_context_data(**kwargs)
#その品物のお気に入り数を取得
item = self.get_object() #ここ
favorite_count = item.favorited_by.count()
context['favorite_count']=favorite_count
return context
URLパラメータで取得してきたインスタンスを簡単に取得できて操作できるのがself.get_object()
from .views import BookDetailView
app_name = 'store'
urlpatterns = [
path('detail_book/<int:pk>',BookDetailView.as_view(),name='detail_book'),
]
テンプレート側の表示はobjectが全て持っているのでそれを使う
{{object.name}}<br>
{{object.description}}<br>
{{object.price}}円<br>
ListViewのあるリンクからDetailViewに移動させたいときのurl記法(例)
#ListViewはobject_listにはいってくるのでそれをobjectという変数にいれて<int:pk>に渡す
#ここでのitem_idはプライマリキーに設定したカラム
{% for object in object_list%}
<td>商品名</td>
<td><a href="{% url 'boards:item_detail' object.item_id %}">{{object.name}}</a></td>
{% endfor %}
ListView
挿入したいデータの一覧を表示したい場合に用いる
def get_queryset(self)でデータを取得する際のSQLを定義できるので
並び替えたりフィルターをかけて取り出す、なんかもできる
テンプレート側はobject_listにデータが格納されているのでそれを使う
from django.views.generic.list import ListView
class BookListView(ListView):
model = Books
template_name='book_list.html'
#SQLを発行して表示項目を並び替えたり絞り込む
def get_queryset(self):
qs = super(BookListView,self).get_queryset()#オーバーライド
qs = qs.filter(name__startswith='Book') #nameがBookで始まるもの
qs = qs.order_by('-id') #idを逆順に
print(qs)
return qs
#例 演習 inputに入力された内容でProductsモデルからフィルターする inputのname属性はproduct_type_name
def get_queryset(self):
qs=super(ProductListView,self).get_queryset()
product_type_name=self.request.GET.get('product_type_name',None) #inputタグから取り出す。なにもなければNoneをいれる
product_name=self.request.GET.get('product_name',None) #inputタグから取り出す。なにもなければNoneをいれる
if product_type_name:
#Productsモデルのproduct_typeの外部キーでnameにアクセス
qs = qs.filter(product_type__name=product_type_name)
if product_name:
#Productsモデルのnameにアクセス
qs = qs.filter(name=product_name)
#昇順降順で並び替える(選択されていない時は0をいれる)
order_by_price=self.request.GET.get('order_by_price',0)
if order_by_price =='1':
qs = qs.order_by('price')
elif order_by_price =='2':
qs = qs.order_by('-price')
return qs
#フィルターしたときにテキストボックスの中にフィルターした文字列を渡して表示する
def get_context_data(self,**kwargs):
context=super().get_context_data(**kwargs)
#contextの['product_type_name']という場所にinputの'product_type_name'属性の値を渡し、なければ空白を渡す
context['product_type_name']=self.request.GET.get('product_type_name','')
context['product_name']=self.request.GET.get('product_name','')
#昇順降順で並び替えた時にチェックを維持する
order_by_price=self.request.GET.get('order_by_price',0)
if order_by_price == '1':
context['ascending']=True
elif order_by_price =='2':
context['descending']=True
return context
#htmlのinputのvalue属性に{{product_type_name}}と{{product_name}}を渡すことを忘れずに
#<p>値段で並び替える:
#昇順<input type ="radio" name="order_by_price" value="1" {%if ascending %}checked{%endif%}>
#降順<input type ="radio" name="order_by_price" value="2" {%if descending %}checked{%endif%}>
#</p>
urls.pyも記述する
path('list_books/',BookListView.as_view(),name='list_books'),
#urlにパラメータを指定してkwargsで受け取ってそれをフィルターにかけることもできる
path('list_books/<name>',BookListView.as_view(),name='list_books'),
パラメータからフィルターする
def get_queryset(self):
qs = super(BookListView,self).get_queryset()
if 'name' in self.kwargs:
qs = qs.filter(name__startswith=self.kwargs['name'])
return qs
for文で一つ一つ取り出して表示
<ul>
{%for object in object_list%}
<li>名前:{{object.name}}</li>
<li>説明:{{object.description}}</li>
<hr>
{%endfor%}
</ul>
CreateView
テーブルにデータを挿入したい時に用いる
from django.views.generic.edit import CreateView
入力するデータの指定、成功時の変移先の指定、送信時に処理を加える、初期値を設定できるなど
いろんな設定ができる
CreateViewのメゾット一覧
【Django】CreateViewの使い方(クラスベースビューでの新規登録ページの実現)
from django.urls import reverse_lazy
class MyCreateView(CreateView):
template_name=''
model = モデル
field = ['',''] #入力するデータ
#もしくは
form_class = ''#利用したいForm
success_url =reverse_lazy('app_name:name') #Create成功時の変移先を定義 ないとエラーに
#もしくは
get_absolute_url = '' #対象のモデルにこちらを定義
def get_success_url() #成功時の変移先を定義
def form_valid(self,form) #formの送信時の処理をカスタマイズする
def get_initial(self,**kwargs): #初期値の設定
実際に書く
class BookCreateView(CreateView):
model = Books
fields = ['name','description','price']
template_name = 'add_book.html'
success_url =reverse_lazy('store:list_books')
#またはmodels.pyにdef get_absolute_urlを定義する(両方に書いてある場合はこちらが優先される)
def form_valid(self,form): #送信時の処理をカスタマイズする(create_atがnullだと保存できないのでそれを追加する)
form.instance.create_at = datetime.now()
form.instance.update_at = datetime.now()
return super(BookCreateView,self).form_valid(form)
#初期値の設定
def get_initial(self,**kwargs):
initial = super(BookCreateView,self).get_initial(**kwargs)
initial['name'] = 'sample'
return initial
models.pyに変移先を設定もできる
#class Books内
#BookCreateViewで作成した際に変移先を設定する
def get_absolute_url(self):
return reverse_lazy('store:detail_book',kwargs={'pk':self.pk})
UpdateView(CreateViewと似ている)
from django.views.generic.edit import CreateView,UpdateView,DeleteView #全て同じところからimportできる
class BookUpdateView(UpdateView):
template_name='update_book.html'
model=Books
form_class = forms.BookUpdateForm
#models.pyでdef get_absolute_urlを設定しているので自動的にページ遷移する
#こちらを設定すればこちらが優先される
def get_success_url(self):
print(self.object)
return reverse_lazy('store:edit_book',kwargs={'pk':self.object.id})
forms.pyでupdateで使うフォームを作成する
IndexViewでつくったフォームをコピーして一部編集。
変更箇所はsuperのあとをBookUpdateFormにすることと、更新時に使うフォームなのでcreate_atは削除する
class BookUpdateForm(forms.ModelForm):
class Meta:
model = Books
fields = ['name','description','price']
labels={
'〇〇':'◻️◻️',
'△△':'⚪︎⚪︎',
} #labelを一括で指定することもできる
#saveメゾットのカスタマイズ(create_atとupdate_atを保存する処理を加えたい。Formの記事でも書いてあるので要チェック)
def save(self,*args,**kwargs):
obj = super(BookUpdateForm,self).save(commit=False) #saveを実行して、Booksクラスのインスタンスがobjに入る
obj.update_at = datetime.now()
obj.save()
return obj
パスの設定。pkを受け取る
path('edit_book/<int:pk>',BookUpdateView.as_view(),name='edit_book'),
DeleteView
class BookDeleteView(DeleteView):
model = Books
template_name='delete_book.html'
success_url =reverse_lazy('store:list_books') #削除後一覧ページに遷移
urls.pyはpkを受け取る
path('delete_book/<int:pk>',BookDeleteView.as_view(),name='delete_book'),
一覧画面から各画面へ遷移させる
object_listをfor文で一つ一つをobjectでとりだすと{%url ""%}のあとにobject.idをつけるだけで
パラメータにわたすことができる
a href="{{object.get_absolute_url}}"としてもurlを渡すことができる
<p><a href="{% url 'store:add_book'%}">データ追加</a></p>
<table>
<tbody>
{%for object in object_list%}
<tr>
<td>名前:<a href="{{object.get_absolute_url}}">{{object.name}}</a></td>
#<td>名前:<a href="{%url 'store:detail_book' object.id%}">{{object.name}}</a></td> こちらでも可
<td>説明:{{object.description}}</td>
<td><a href = "{% url 'store:edit_book' object.id%}">編集</a></td>
<td><a href = "{% url 'store:delete_book' object.id%}">削除</a></td>
</tr>
{%endfor%}
<tbody>
</table>
FormView
フォームを使う際はこちらが便利らしい
from django.views.generic.edit import CreateView,UpdateView,DeleteView,FormView
class BookFormView(FormView):
template_name='form_book.html'
form_class = forms.BookForm #利用するフォームを定義
success_url = reverse_lazy('store:list_books')
#POSTリクエストを投げた場合に保存処理する記述をする(これがなければ送信しても何も実行されない)
def form_valid(self,form):
if form.is_valid():
form.save()
return super(BookFormView,self).form_valid(form)
#フォームの初期値を設定
def get_initial(self):
initial = super(BookFormView,self).get_initial()
initial['name']='form sample'
return initial
RedirectView(別のView,別のページにリダイレクトしたい時に用いる)
やりかたは2種類
urls.pyに直接定義する
from django.views.generic.base import RedirectView
from .views import BookRedirectView
urlpatterns=[
path('/search/<term>/',RedirectView.as_view(url='http://google.co.jp/?q=%(term)s')),
#例
path('google/',RedirectView.as_view(url='https://google.co.jp')),
path('book_redirect_view',BookRedirectView.as_view(),name='book_redirect_view'),
path('book_redirect_view/<int:pk>',BookRedirectView.as_view(),name='book_redirect_view'),
]
RedirectViewを継承してクラスを作成する
class SearchRedirectView(RedirectView):
url = 'https://google.co.jp' #静的に定義
class SearchRedirectView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
return redirect(〇〇) #動的に定義
#例(静的)
class BookRedirectView(RedirectView):
url = 'https://google.co.jp'
#例(動的)
class BookRedirectView(RedirectView):
#DBからデータを取得してそのpkをつかって編集画面へ遷移するといった動的に動かすこともできる
def get_redirect_url(self,*args,**kwargs):
book = Books.objects.first()
if 'pk' in kwargs: #urlパラメータがある場合はこちら
return reverse_lazy('store:detail_book',kwargs={'pk':kwargs['pk']})
return reverse_lazy('store:edit_book',kwargs={'pk':book.pk})
更新時削除時にメッセージを表示する(SuccessMessageMixin)
SuccessMessageMixinをUpdateViewなんかと一緒に多重継承させる
from django.contrib.messages.views import SuccessMessageMixin
class BookUpdateView(SuccessMessageMixin,UpdateView):
model=Books
success_message='成功しました' #成功した場合のメッセージを定義(静的)
def get_success_message(self,cleaned_data): #成功した場合のメッセージを定義(動的)
html側ではmessagesから表示させる
{% if messages %}
{% for message in messages %}
{{ message.message }}
{% endfor %}
{% endif %}
def get_success_messageの例
print(cleaned_data)をみてみると
{'name': 'Book3', 'description': 'Book3の説明メッセージ再更新', 'price': 3000}
このようになっているのでcleaned_data.get('name')でBook3をとりだすことができる
#成功時のメッセージを動的に
def get_success_message(self, cleaned_data):
print(cleaned_data)
return cleaned_data.get('name') + 'を更新しました'