LoginSignup
1
1

クラスベースビューについて(自分用メモ)

Last updated at Posted at 2023-08-20

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メゾットを使ってページを表示する

views.py
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')
urls.py
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メゾットのカスタマイズを復習

models.py
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と抽象クラスで設定する。
それを継承することで複数のモデルで共通したカラムを持たせることができる

forms.py
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の場合はフォームの値を受け取って、バリデーションをして保存するという処理にする

views.py
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種類がある

urls.py
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から呼び出す

views.py
from django.views.generic.base import(
    TemplateView
)
class HomeView(TemplateView):
    template_name = 'home.html'
urls.py
from .views import HomeView
urlpatterns = [
    path('home/',HomeView.as_view(),name='home'),
]

get_context_dataで値を渡す

views.py
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}}で表示可能

home.html
<h1>Home画面</h1>
<p>現在時刻は{{time}}です。</p>

get_context_dataで値を渡す その2(urlパラメータを受け取って動的にページを変更)

urls.pyでパラメータを受け取るように設定

urls.py
urlpatterns = [
    path('home/<name>',HomeView.as_view(),name='home'),
]

get_context_dataの引数の**kwargsはパラメータのnameを受け取る

views.py
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

表示する

home.html
<p>{{name}}さん。現在時刻は{{time}}です。</p>

get_context_dataで値を渡す その3(フォームをつくって渡す)演習

views.py
#フォームをつくってわたす
    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でフォームを定義

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で取り出せる

html
{%for picture in pictures %}
    <img width='100px' height = '100px' src="{{picture.picture.url}}">
{% endfor %}

画像を表示するならプロジェクトのurls.pyにその設定をすることを忘れない

プロジェクトの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(インスタンスから逆参照する)

book_list.html
{%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の場合

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でシグナルを使う場合

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はこんなかんじ

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にデータが格納されているのでそれを使う

views.py
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()

urls.py
from .views import BookDetailView
app_name = 'store'

urlpatterns = [
    path('detail_book/<int:pk>',BookDetailView.as_view(),name='detail_book'),
]

テンプレート側の表示はobjectが全て持っているのでそれを使う

book.html
{{object.name}}<br>
{{object.description}}<br>
{{object.price}}円<br>

ListViewのあるリンクからDetailViewに移動させたいときのurl記法(例)

html
#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にデータが格納されているのでそれを使う

views.py
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も記述する

urls.py
path('list_books/',BookListView.as_view(),name='list_books'),
#urlにパラメータを指定してkwargsで受け取ってそれをフィルターにかけることもできる
path('list_books/<name>',BookListView.as_view(),name='list_books'),

パラメータからフィルターする

views.py
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文で一つ一つ取り出して表示

book_list.html
<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
入力するデータの指定、成功時の変移先の指定、送信時に処理を加える、初期値を設定できるなど
いろんな設定ができる

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): #初期値の設定

実際に書く

views.py
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に変移先を設定もできる

models.py
#class Books内
#BookCreateViewで作成した際に変移先を設定する  
    def get_absolute_url(self):
        return reverse_lazy('store:detail_book',kwargs={'pk':self.pk})

UpdateView(CreateViewと似ている)

views.py
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は削除する

forms.py
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を受け取る

urls.py
    path('edit_book/<int:pk>',BookUpdateView.as_view(),name='edit_book'),

DeleteView

views.py
class BookDeleteView(DeleteView):
    model = Books
    template_name='delete_book.html'
    success_url =reverse_lazy('store:list_books') #削除後一覧ページに遷移

urls.pyはpkを受け取る

urls.py
    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を渡すことができる

book_list.html
<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

フォームを使う際はこちらが便利らしい

views.py
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に直接定義する

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を継承してクラスを作成する

views.py
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から表示させる

update_book.html
{% 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をとりだすことができる

views.py
#成功時のメッセージを動的に
    def get_success_message(self, cleaned_data):
        print(cleaned_data)
        return cleaned_data.get('name') + 'を更新しました'
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1