見出し画像

djangoで作る本格的なSNSアプリケーション Part2

djangoで簡単なTodoアプリ作成等は作成できたけれども、オリジナルWebアプリを作る前にもう少しだけ本格的なアプリをチュートリアル形式で取り組みたい方を対象としています。

Part2では、目次の③ログイン、ログアウト機能と④ユーザー情報の更新と一覧表示について説明していきます。Part1をまだの方は、環境構築なども説明していますのでご確認ください。

開発の手順のおさらい

①SNSプロジェクトの開始とアプリの作成
②ユーザーモデルとユーザー登録機能
③ログイン、ログアウト機能
④ユーザー情報の更新と一覧表示
⑤ポストモデルとCRUD操作
⑥ユーザのFollow/Unfollow機能の追加
⑦ポストのお気に入り機能追加

 Part1で①SNSプロジェクトの開始とアプリの作成と②ユーザーモデルとユーザー登録機能までが出来ましたので、クラスベースビューを用いたログイン、ログアウト機能を実装していきます。

③ログイン、ログアウト機能

 まずは、出来上がりのログイン画面を以下の画像で確認して下さい。ログアウトは画面が特に不要ですので、作成するtemplateは1つだけになります。

画像1

 最終的なaccounts/views.pyは以下のようになります。

from django.shortcuts import render
from django.views.generic.edit import CreateView
from django.views.generic.base import TemplateView
from .forms import RegistForm, LoginForm  # 追加
from django.contrib.auth.views import LoginView, LogoutView #追加

class HomeView(TemplateView):
   template_name = 'accounts/home.html'
   
class RegistUserView(CreateView):
   template_name = 'accounts/regist.html'
   form_class = RegistForm
   
class UserLoginView(LoginView):  # 追加
   template_name = 'accounts/login.html'
   authentication_form = LoginForm
       
class UserLogoutView(LogoutView): # 追加
   pass

 ログイン、ログアウト機能をLoginViewLogoutViewを用いて実装しますのでインポートします。from django.contrib.auth.views からインポートします。説明が前後してしましますが、後ほど作成するLoginFormを .forms からインポートしています。

from .forms import RegistForm, UserLoginForm  # 追加
from django.contrib.auth.views import LoginView, LogoutView #追加

 ログイン画面ですが、UserLoginViewクラスとしてLoginViewを継承して作成します。ログイン画面をlogin.htmlとして作成しますので、template_nameは、'accounts/login.html'となります。
 ログイン認証に使用されるフォームをauthentication_formに指定します。今回は、LoginFormを後ほど作成する事にします。LoginViewを継承する事で、たった2行で基本的なLoginViewが完成します。
 ログアウトについては、UserLoOutViewクラスとしてLogoutViewを継承して作成します。ログアウト画面は不要ですのでtemplate_nameを設定する必要なく、認証フォームも不要ですのでpassとだけ記載ください。

template_name = 'accounts/login.html'
authentication_form = UserLoginForm

 次にLoginFormaccounts/forms.pyに定義していきます。まずは、認証用フォームであるAuthenticationFormをインポートします。次に、LoginFormクラスをAuthenticationFormを継承し作成します。
 ログイン時は、ユーザーを一意に識別出来る情報と対応するパスワードが必要になります。Userモデルには、usernameemailにユニーク制約を付けていましたので、どちらか一方もしくは両方をログイン認証に使う事が出来ます。今回は、usernamepasswordだけでログイン認証をさせる予定ですので、その処理を記載していきます。それぞれのlabelは画面上に表示されるフィールド名になりますので、カタカナでもアルファベットどちらでも構いません。
 passwordのところは、widget=forms.PasswordInputとして設定します。 こうすることで、パスワード入力欄をマスク(隠す)ことができます。現在のほとんどのパスワード入力欄の標準機能ですので忘れないように設定しましょう。

from django import forms
from .models import User
from django.contrib.auth.password_validation import validate_password
from django.contrib.auth.forms import AuthenticationForm # 追加

class RegistForm(forms.ModelForm):
   username = forms.CharField(label='ユーザーネーム')
   email = forms.EmailField(label='E-mail アドレス')
   password = forms.CharField(label='パスワード', widget=forms.PasswordInput())
   class Meta:
       model = User
       fields = ['username', 'email', 'password']
       
   def save(self, commit=False):
       user = super().save(commit=False)
       validate_password(self.cleaned_data['password'], user)
       user.set_password(self.cleaned_data['password'])
       user.save()
       return user
       
class LoginForm(AuthenticationForm):  # 追加
   username = forms.CharField(label='ユーザーネーム') 
   password = forms.CharField(label='パスワード', widget=forms.PasswordInput)   

 accounts/urls.pyLoginViewと後ほど作るLogoutViewのパスを通します。accounts/urls.pyを開きUserLoginViewUserLogoutViewをインポートします。urlpatternsUserLoginViewのurlとして'login/'を設定し、名前空間を'login'とします。これで、accounts/loginにアクセするとログインビューで指定されたlogin.htmlを画面に表示させることが出来るようになります。
 UserLogout Viewも同様にurlpatternsUser LogoutViewのurlとして'logout/'を設定し、名前空間を'logout'とします。

from django.urls import path
from .views import (
   RegistUserView,HomeView, 
   UserLoginView, UserLogoutView     #追加
)
app_name = 'accounts'
urlpatterns = [
   path('home/', HomeView.as_view(), name='home'),
   path('regist/', RegistUserView.as_view(), name='regist'),
   path('login/', UserLoginView.as_view(), name='login'), #追加
   path('logout/', UserLogoutView.as_view(), name='logout'),   #追加  
]

 ログイン/ログアウトのパスを通しましたので、対応するtemplateを作成します。ここでは、ログイン画面はlogin.htmlとして作成していきます。ログアウト画面は不要です。login.htmlのコードは以下になります。regist.htmlを全てコピーし、# 追加と記載されている部分をログインとして変更します。 最終的なlogin.htmは以下となります。

{% extends 'base.html' %}
{% block content %}
   <div class="row">
       <div class="col-sm-6 offset-sm-3">
           <h1 >ログイン</h1> # 変更
           <form method="POST">
               {% csrf_token %}
               {% bootstrap_form form %}             
               <button type="submit" class="btn btn-primary" name="button">
                   ログイン # 変更
               </button> 
           </form>
       </div>
   </div>
{% endblock %} 

 このままサーバーを立ち上げてログインをしてみたくなりますが、ログイン時、ログイン後、ログアウト後のurlをまだ指定していません。sns/settings.pyに以下のコードを追記します。Login_URL'accounts/login'で指定します。LOGIN _REDIRECT_URLはログイン成功後に遷移するユーザー一覧を表示予定のuserlistへ遷移させたいので'/accounts/userlist'とを指定します(この後作成します)。LOGOUT _REDIRECT_URLにはl、ログアウト後はloginページに再度遷移させたいので、'accounts/login'を指定します。

# login,logoutした時のURLを記述
LOGIN_URL = '/accounts/login'
LOGIN_REDIRECT_URL = '/accounts/userlist'
LOGOUT_REDIRECT_URL = '/accounts/login'

 ログイン、ログアウト、サインインへのリンクをbase.htmlのナビバーに設定していきましょう。base.htmlを開き46行目〜59行目のナビバーリンクのhrefに以下のように設定します。設定の仕方は、app_name=accountsであり、名前空間がLogoutへのリンクはhref="{% url 'accounts:logout' %}として設定しました。 LoginSign inへのリンクも同様にhref="{% url 'accounts:login' %}href="{% url 'accounts:regist' %}となります。

<ul class="navbar-nav navbar-right">
{% if user.is_authenticated %}
    <li class="nav-item">
        <a class="nav-link" href="{% url 'accounts:logout' %}">Logout</a>
    </li>
{% else %}
    <li class="nav-item">
        <a class="nav-link" href="{% url 'accounts:login' %}">Login</a>
    </li>
    <li class="nav-item">
        <a class="nav-link" href="{% url 'accounts:regist' %}">Sign in</a>
    </li>
{% endif %}
</ul>

 それでは実際にログインとログアウト機能が動作するかを確認していきます。
ターミナルからサーバーを立ち上げhttp://127.0.0.1:8000/accounts/login/へアクセスします。ご自分で登録済みのユーザーネームとパスワードでログインしてみて下さい。ログイン後は、accounts/homeへリダイレクトする設定としていましたのでhome.htmlが表示されているはずです。また、ナビゲーションバーもログイン前後で表示を変えるようにbase.htmlへ設定していました。下の画像のようにナビバーにAll Posts , All Users, New post , ichiro's detail , Logoutが並んでいるでしょうか?今回は、ichiroというユーザーでログインしています。

画像2
login後

 ナビゲーションバーのLogoutボタンを押して、ログアウトが実行されてログイン画面にリダイレクトされる事も確認して下さい。UserLogoutViewpassだけで他は何も記載していませんが正しく動作することが確認できました。

画像4

 templates/base.htmlを再確認して下さい。{% if user.is_authenticated %} 〜 {% endif %}でログインしている場合のナビバーの表示を記述しています。
 {% if user.is_authenticated %} 〜 {% else %} 〜 {% endif %}とする事で、ログインしている場合とログインしていない場合の処理を記述出来ます。

            <ul class="navbar-nav navbar-left">
           {% if user.is_authenticated %}
               <li class="nav-item">
                   <a class="nav-link" href="#">All Posts</a>
               </li>
               <li class="nav-item">
                   <a class="nav-link" href="#">All Users</a>
               </li>
               <li class="nav-item">
                   <a class="nav-link" href="#">New post</a>
               </li>
               <li class="nav-item dropdown">
                   <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" 
                   data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                       {{ user.username }}'s detail
                   </a>
                   <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
                       <a class="dropdown-item" href="#">My posts</a>
                       <a class="dropdown-item" href="#">Following</a>
                       <a class="dropdown-item" href="#">Follower</a>
                   </div>
               </li>
           {% endif %}
           </ul>
           <ul class="navbar-nav navbar-right">
           {% if user.is_authenticated %}
               <li class="nav-item">
                   <a class="nav-link" href="{% url 'accounts:logout' %}">Logout</a>
               </li>
           {% else %}
               <li class="nav-item">
                   <a class="nav-link" href="{% url 'accounts:login' %}">Login</a>
               </li>
               <li class="nav-item">
                   <a class="nav-link" href="{% url 'accounts:regist' %}">Sign in</a>
               </li>
           {% endif %}
           </ul>

④ユーザー情報の更新と一覧表示

 ユーザー情報の更新

 ログイン後のユーザー情報(プロフィール)更新画面と一覧表示画面を作成していきましょう。まずは、ユーザー情報更新画面を作ります。Userモデルには、username, Email, Passowrd, Avatarフィールドがありました。ユーザー登録時にAvatar画像を登録していませんでしたので、画像もフォームから登録できるようにします。

プロフィール更新

 最終的なaccounts/views.pyは以下のようになります。追加した部分を順に説明していきます。

from django.shortcuts import render
from django.views.generic.edit import CreateView, UpdateView # 追加
from django.views.generic.base import TemplateView
from .forms import RegistForm, LoginForm , ProfileForm  # 追加
from django.contrib.auth.views import LoginView, LogoutView
from django.views.generic import ListView # 追加
from django.contrib.auth.mixins import LoginRequiredMixin # 追加
from .models import User # 追加

class HomeView(TemplateView):
   template_name = 'accounts/home.html'
   
class RegistUserView(CreateView):
   template_name = 'accounts/regist.html'
   form_class = RegistForm
   
class UserLoginView(LoginView):  
   template_name = 'accounts/login.html'
   authentication_form = LoginForm
   def form_valid(self, form):
       remember = form.cleaned_data['remember']
       if remember:
           self.request.session.set_expiry(120000)
       return super().form_valid(form)
       
class UserLogoutView(LogoutView): 
   pass
   
class ProfileEditView(LoginRequiredMixin, UpdateView): # 追加
   template_name = 'accounts/edit_profile.html'
   model = User
   form_class = ProfileForm
   success_url = '/accounts/edit_profile/'
   def get_object(self):
       return self.request.user

class UserListView(LoginRequiredMixin, ListView): # 追加
   template_name = 'accounts/userlist.html'
   model = User
      
   def get_queryset(self):       
       return User.objects.all()
       
 

 インポートの部分ですが、プロフィール更新画面と一覧表示画では、それぞれクラスベースビューのUpdateViewListViewを使っていきます。また、プロフィール画面などログインしていないユーザーに変更されないようにするため、LoginRequiredMixinをインポートし制約をかけます。プロフィールを更新するためには、フォームも必要になりますので後で作成予定のProfileFormもここでインポートします。また、各ビューではUserモデルを使用していきますので、インポートしておきます。

from django.shortcuts import render
from django.views.generic.edit import CreateView, UpdateView # 追加
from django.views.generic.base import TemplateView
from .forms import RegistForm, LoginForm  
from django.contrib.auth.views import LoginView, LogoutView
from django.views.generic import ListView # 追加
from django.contrib.auth.mixins import LoginRequiredMixin # 追加
from .models import User # 追加

 次にプロフィール更新をするProfileEditViewクラスを作成していきます。ログイン制約を付けるLoginRequiredMixinと更新するためのUpdateViewを継承します。 template_nameは、'accounts/edit_profile.html'としますので、accountsフォルダにedit_profile.htmlを作成します。modelには、Userモデルを指定します。更新するためのフォームも必要になりますので、先ほどインポートしたProfileFormform_classとして指定します。
 success_urlは'/accounts/edit_profile/'として同じページを再読み込みします。 def get_object(self):のところでは、returnとしてuserを返していますのでページにアクセスした時にログインしているユーザーが返されます。

class ProfileEditView(LoginRequiredMixin, UpdateView): # 追加
   template_name = 'accounts/edit_profile.html'
   model = User
   form_class = ProfileForm
   success_url = '/accounts/edit_profile/'
   
   def get_object(self):
       return self.request.user

 accounts/forms.pyを開いてProfileFormを作成しforms.ModelFormを継承します。class Meta:では使用するモデル、フィールドを指定しています。help_textsではusernameフィールドとemailフィールドの説明文を削除しています。フィールドに指定している'avatar'はプロフィール画像でした。画像も特別な指定をしなくても、フィールドを指定するだけで登録/更新できます。

class ProfileForm(forms.ModelForm):
   def __init__(self, *args, **kwargs):
       super(ProfileForm, self).__init__(*args, **kwargs)
       
       for field in self.fields.values():  # bootstrapで使用するform-controlクラス
           field.widget.attrs['class'] = 'form-control'
           
   class Meta:
       model = User
       fields = ('username', 'email', 'avatar')
       help_texts = {
           'username': None,
           'email':None,
       }

 accounts/urls.pyProfileEditViewと後ほど作るUserListViewのパスを通します。accounts/urls.pyfrom .views import のところにUserLoginViewUserLogoutViewを追加します。urlpatternsProfileEditViewのurlとして'edit_profile/',を設定し、名前空間を'edit_profile'とします。
 UserListViewurl'userlist/'として設定し、名前空間を'userlist'とします。

from django.urls import path
from .views import (
   RegistUserView,HomeView, 
   UserLoginView, UserLogoutView,
   ProfileEditView,UserListView, # 追加
)
app_name = 'accounts'
urlpatterns = [
   path('home/', HomeView.as_view(), name='home'),
   path('regist/', RegistUserView.as_view(), name='regist'),
   path('login/', UserLoginView.as_view(), name='login'),
   path('logout/', UserLogoutView.as_view(), name='logout'),  
   path('edit_profile/', ProfileEditView.as_view(), name='edit_profile'), # 追加
   path('userlist/', UserListView.as_view(), name='userlist'), # 追加
]

 パスを通しましたので、対応するtemplateを作成します。ここでは、プロフィール更新画面はedit_profile.html、ユーザー一覧画面はuserlist.html として作成していきます。edit_profile.htmlのコードは以下になります。

{% extends 'base.html' %}
{% block content %}
   <div class="content-wrapper">
       <div class="container-fluid">
           <div class="row">
               <div class="col-sm-6 offset-sm-3">
                   <div class="card">
                       <div class="card-header">
                           <h4><b>プロフィール更新</b></h4>
                       </div>
                       <div class="card-body">
                           {# 画像ファイルをアップロードするためenctype="multipart/form-data"を指定#}
                           <form action="{% url 'accounts:edit_profile' %}" method="post" 
                               enctype="multipart/form-data">
                               {% csrf_token %}
                               {% bootstrap_form form %}
                               <button class='btn btn-outline-success btn-block' type="submit">更新</button>
                           </form>
                       </div>
                   </div>
               </div>
           </div>
       </div>
   </div>
{% endblock %}

 {% block content %}  〜  {% endblock %}内にプロフィール更新のためのコードを記載していきます。<div class="content-wrapper">  〜 </div>はbootstrap4を使っていますので、詳細はオフィシャルページを参考にして下さい。

   <div class="content-wrapper">
       <div class="container-fluid">
           <div class="row">
               <div class="col-sm-6 offset-sm-3">
                   ・・・省略・・・
               </div>
           </div>
       </div>
   </div>

 <div class="card">〜</div>でbootstrap4から採用されたCardsコンポーネントを使っています。<div class="card-header">〜</div>内にカードのヘッダーに入れる文字列を指定します。今回は、プロフィール更新とします。
 <div class="card-body">〜</div>にはカードのボディー内を記載していきます。今回は、プロフィール更新用のフォームをボディーの中に入れていきます。
 画像ファイルをアップロードするためには、form actionのところに
enctype="multipart/form-data"を指定します。当然、アップロードをするのでmetnod="post"となります。
 {% csrf_token %}と{% bootstrap_form form %}はdjango-bootstrapでフォームを生成し送信するときのお決まりです。更新ボタンも以下のように付けておきます。<button class='btn btn-outline-success btn-block' type="submit">更新</button>これでedit_profile.htmlは完成です。 

<div class="card">
    <div class="card-header">
         <h4><b>プロフィール更新</b></h4>
    </div>
    <div class="card-body">
          {# 画像ファイルをアップロードするためenctype="multipart/form-data"を指定#}
          <form action="{% url 'accounts:edit_profile' %}" method="post" 
              enctype="multipart/form-data">
          {% csrf_token %}
          {% bootstrap_form form %}
          <button class='btn btn-outline-success btn-block' type="submit">更新</button>
          </form>
    </div>
</div>

 ターミナルからサーバーを立ち上げて確認していきましょう。ログイン後に、http://127.0.0.1:8000/accounts/edit_profile/へアクセスして下さい。今回は、username = ichiroでログインしています。
 先ほどProfileFormで指定したフィールドのusername, EmailそしてAvatarが正しく表示されている事を確認して下さい。まだ、画像を登録していませんのでファイルを選択というボタンがAvatarのフィールドに表示されています。

画像6

 プロフィール画像(Avatar)を実際に登録してみましょう。ファイルを選択ボタンを押して、ご自分の好きな画像を選んで下さい。今回は、いらすとやさんのanimal_panda.pngを使用します。更新ボタンを押し、正しくアップロードされていれば 以下のようにAvatar現在としてanimal_panda.pngが表示されています。ここで、 クリアボタンを押せばアップロードされた画像がAvatarフィールドから消去されますが、画像は消去されません。それでは、今アップロードした画像が どこに保存されたか確認していきましょう。Visual Studio Codeのエクスプローラーを確認してください。 mediaフォルダ中にアップロードされた画像が確認できます。
 

avatarアップロード後
エクスプローラ4

 クリアボタンで更新しても画像が消去されないという意味が分かりにくい と思いますので、実際に試してみましょう。クリア を 選択して更新ボタンを押してください。その後、再度同じ画像をアップロードしてみましょう。
 画像は animal_panda_zKFhhXR.png として保存されました。これは、元の画像がmediaフォルダから削除されずファイル名が重複するため、django側でファイル名に追加文字列を入力しています。

Avatarアップロード後2

 ついでにUserテーブルにどのように画像が保存されているのか確認していきます。以下の画像を確認してください。テーブルにはアップロードしたファイル名のみが保存されています。このようにユーザテーブルには実際の画像が 保存されるわけではなく、mediaフォルダーに保存したファイルの名前が入力されます。

Userテーブル

 ここまででユーザー情報を更新する処理は完了です。

ユーザ情報の一覧表示

 次は、 ユーザ情報の一覧表示画面を作成していきます。まずは、出来上がりの画面を以下の画像で確認して下さい。 現時点で登録されているユーザが表形式で一覧で表示できるようにしていきます。

ユーザー一覧CSS

 最終的なaccounts/views.pyは以下のようになります。

from django.shortcuts import render
from django.views.generic.edit import CreateView, UpdateView 
from django.views.generic.base import TemplateView
from .forms import RegistForm, LoginForm, ProfileForm 
from django.contrib.auth.views import LoginView, LogoutView
from django.views.generic import ListView 
from django.contrib.auth.mixins import LoginRequiredMixin 
from .models import User # 追加class HomeView(TemplateView):
   template_name = 'accounts/home.html'
class RegistUserView(CreateView):
   template_name = 'accounts/regist.html'
   form_class = RegistForm
class UserLoginView(LoginView):  
   template_name = 'accounts/login.html'
   authentication_form = LoginForm
class UserLogoutView(LogoutView): 
   pass
class ProfileEditView(LoginRequiredMixin, UpdateView): 
   template_name = 'accounts/edit_profile.html'
   model = User
   form_class = ProfileForm
   success_url = '/accounts/edit_profile/'
   def get_object(self):
       return self.request.user

class UserListView(LoginRequiredMixin, ListView): # 追加
   template_name = 'accounts/userlist.html'
   model = User
   def get_queryset(self):
       
       return User.objects.all()

 ユーザー一覧画面を作成していきます。LoginRequiredMixinListViewを継承しUserListViewクラスを作成します。ユーザー一覧画面をuserlist.htmlとして作成しますので、template_nameは、'accounts/userlist.html'となります。使用するモデルはUserです。
 現在登録されているユーザーを取得するため、get_querysetReturnとしてUser .objects .all()を指定します。これは、Userモデルのオブジェクトを全て取得するという意味のクエリになります。objectsUserモデルで指定していました。

class User(AbstractBaseUser, PermissionsMixin):
   username = models.CharField(max_length=50, unique=True)
   email = models.EmailField(max_length=50, unique=True)
   is_active = models.BooleanField(default=True)
   is_staff = models.BooleanField(default=False)
   # プロフィール画像をavatarとして設定
   avatar = models.ImageField(blank=True, null=True)  
   EMAIL_FIELD = 'email'
   USERNAME_FIELD = 'username'
   REQUIRED_FIELDS = ['email'] 
   
   objects = UserManager() # ←ここ
   
   def get_absolute_url(self):
       return reverse_lazy('accounts:home')

 UserListViewのパスはすでに通してありますので、対応するtemplateを作成します。 ここでは、ユーザ一覧画面はuserlist.htmlとして作成していきます。userlist.htmlのコードは以下になります。

{% extends 'base.html' %}
{% block content %}
   <div class="content-wrapper">
       <div class="container-fluid">
           <!--ページタイトル-->
           <div class="card mb-3">
               <div class="card-header">
                   <h4><b> ユーザー 一覧</b></h4>
               </div>
               <div class="card-body">
                   <!-- テーブル表の定義 -->
                   <div class="table table-responsive">
                       <table id='user_list' width="100%" class="table table-striped table-bordered table-hover">
                           <!-- 表の列の定義-->
                           <thead>
                           <tr>
                               <th class="text-center">ユーザー</th>
                               <th class="text-center">画像</th>
                           </tr>
                           </thead>
                           <!-- ここまでが表の列の定義-->
                           <!-- 表のデータ部分の表示-->
                           <tbody>
                           {# デフォルトはobject_listとなるが、view.pyでcontextとして指定することも出来る#}
                           {% for item in object_list %}
                               <tr class="odd gradeX text-center">
                                   <td class="text-center" style="width: 50%">{{ item.username }}</td>
                                   <td class="text-center" style="width: 50%">
                               {# ユーザーがプロフィール画像を持っている場合#}
                               {% if item.avatar %}
                                   <img class="rounded img-fluid mx-auto d-block" src="{{ item.avatar.url }}"
                                    id="avatar-thumbnail" alt="avatar_thumbnail">
                               {# ユーザーがプロフィール画像を持ってない場合は”avatar_thumbnailを表示 #}
                               {% else %}
                                   <img class="rounded img-fluid mx-auto d-block" alt="avatar_thumbnail">
                               {% endif %}</td>
                               </tr>
                           {% endfor %}
                           </tbody>
                            <!-- ここまでが表のデータ部分の表示-->
                       </table>
                       <!-- ここまでがテーブル表の定義 -->
                   </div>
               </div>
           </div>
       </div>
   </div>
{% endblock %}

 {% extends 'base.html' %}base.htmlを読み込み{% block content %}  〜  {% endblock %}内にユーザー一覧画面のためのコードを記載していきます。<div class="content-wrapper">  〜 </div>、<div class="container-fluid">〜 </div>、<div class="card mb-3">〜 </div>、<div class="card mb-3">〜 </div>は先ほどと同じでbootstrap4のカードを使用しています<div class="card-header"> 〜 </div>内にカードヘッダーに表示する文字列を入力します。ここでは、ユーザー一覧としています。<div class="card-body">〜 </div>にユーザー一覧を表示する表をコーディングしていきます。

{% extends 'base.html' %}
{% block content %}
   <div class="content-wrapper">
       <div class="container-fluid">
           <!--ページタイトル-->
           <div class="card mb-3">
               <div class="card-header">
                   <h4><b> ユーザー 一覧</b></h4>
               </div>
               <div class="card-body">

                ・・・省略・・・
                
               </div>
               </div>
           </div>
       </div>
   </div>
{% endblock %}

 <div class="table table-responsive">〜 </div>でbootstrapのレスポンシブなテーブルを適用します。<!-- 表の列の定義-->〜<!-- ここまでが表の列の定義-->内に表の列を指定します。今表示しようとしているコンテンツは、ユーザー名とプロフィール画像ですので<thead>〜</thead>内に<th>タグで作成します。
 <!-- 表のデータ部分の表示-->〜<!-- ここまでが表のデータ部分の表示-->内にユーザー名と画像を表示していきます。少し複雑ですので、一つずつ説明していきます。

<div class="card-body">
    <!-- テーブル表の定義 -->
    <div class="table table-responsive">
        <table id='user_list' width="100%" class="table table-striped table-bordered table-hover">
        <!-- 表の列の定義-->
        <thead>
          <tr>
            <th class="text-center">ユーザー</th>
            <th class="text-center">画像</th>
          </tr>
        </thead>
        <!-- ここまでが表の列の定義-->
        <!-- 表のデータ部分の表示-->
        <tbody>
        {# デフォルトはobject_listとなるが、view.pyでcontextとして指定することも出来る#}
        {% for item in object_list %}
          <tr class="odd gradeX text-center">
            <td class="text-center" style="width: 50%">{{ item.username }}</td>
            <td class="text-center" style="width: 50%">
        {# ユーザーがプロフィール画像を持っている場合#}
        {% if item.avatar %}
            <img class="rounded img-fluid mx-auto d-block" src="{{ item.avatar.url }}" id="avatar-thumbnail" alt="avatar_thumbnail">
        {# ユーザーがプロフィール画像を持ってない場合はavatar_thumbnailを表示#}
        {% else %}
            <img class="rounded img-fluid mx-auto d-block" alt="avatar_thumbnail">
        {% endif %}</td>
          </tr>
        {% endfor %}
        </tbody>
        <!-- ここまでが表のデータ部分の表示-->
    </table>
    <!-- ここまでがテーブル表の定義 -->
    </div>
</div>

 {% for item in object_list %} 〜 {% endfor %}では、object_listに入っているデータをitemに取り出してループさせています。object_listには、UserListView内でget_querysetで取得したUser .objects .all()が渡されます。このようにして、ビューからテンプレートにデータを渡す事が出来ます。object_list以外の名前でデータを渡す事もできますが、そちらの方法については後ほどの章で触れたいと思います。データが渡されると言われても、どのようなデータが渡されているのかを理解したいと思いますので、それでは、object_listにはどのようにデータが入っているのかを確認します。

        {% for item in object_list %}
             ・・・省略・・・
            {% if item.avatar %}
            ・・・省略・・・
            {% else %}
            ・・・省略・・・
            {% endif %}
            ・・・省略・・・
        {% endfor %}

 views.pyUserListView内のget_querysetにprint((User.objects.all()))を追加して、ターミナルでクエリーセットを確認してみます。ターミナルには、<QuerySet [<User: taro>, <User: ichiro>]>が表示されます。 全てのユーザー情報がdjangoQuerysetクラスとしてUserテーブルから取得されます。今回の場合は、taroとichiroの情報が入っています。このクエリーセットがobject_listに入おりforループでitemとして一つずつ取り出しています。

class UserListView(LoginRequiredMixin, ListView): 
   template_name = 'accounts/userlist.html'
   model = User
   
   def get_queryset(self):
       print(User.objects.all())  # 追加
       return User.objects.all()

 itemとして取り出したUserモデルのデータを使って実際にテーブル内に表示していきます。1列目にはユーザー名、2列目には画像を入れることにしていました。今、itemにはユーザー情報が1件入っていますので、{{ item.username }}とする事でユーザー名を表示することが出来ます。最初のforループでは、taroの情報が入っていますので、1列目の1行目にはtaroが表示されます。<td class="text-center" style="width: 50%">style="width: 50%で列幅の割合を指定しています。ユーザー名の列幅を小さくしたければ、この割合を少なくして下さい。

        {% for item in object_list %}
          <tr class="odd gradeX text-center">
            <td class="text-center" style="width: 50%">{{ item.username }}</td>
            <td class="text-center" style="width: 50%">
        {# ユーザーがプロフィール画像を持っている場合#}
        {% if item.avatar %}
            <img class="rounded img-fluid mx-auto d-block" src="{{ item.avatar.url }}" id="avatar-thumbnail" alt="avatar_thumbnail">
        {# ユーザーがプロフィール画像を持ってない場合はavatar_thumbnailを表示#}
        {% else %}
            <img class="rounded img-fluid mx-auto d-block" alt="avatar_thumbnail">
        {% endif %}</td>
          </tr>
        {% endfor %}

 次は、2列目にプロフィール画像(avatar)を表示させます。Userモデルでは、avatar = models.ImageField(blank=True, null=True) としていましたので画像が登録されていない場合も考える必要があります。if 文を使って、avatar有無で条件分岐をさせます。まずは、ユーザーがavatar(プロフィール画像)を持っている場合の処理を{% if item.avatar %}に記載し、持っていない場合の処理を{% else %}に記載していきます。
 まずは、{% if item.avatar %}の処理ですが画像を表示したいのでimageタグを使います。imgタグで参照先を指定する属性はsrcですので、ここでavatarを指定する事で画像を表示させる事が出来ます。itemにユーザー情報が入っていますので、src="{{ item.avatar.url }}"と書くとでavatar(=画像ファイル名)のurlパスを指定する事ができます。具体例をあげると、item = ichiroのavatarファイル名をmediaフォルダから探し、そのurlをテンプレートに渡して表示させています。後で画像サイズを変更しますので、id="avatar-thumbnail"としてid属性を付けておきます。これで、プロフィール画像がある場合には画像が表示されるように設定できました。

<img class="rounded img-fluid mx-auto d-block" 
  src="{{ item.avatar.url }}" id="avatar-thumbnail" alt="avatar_thumbnail">

 次は、プロフィール画像がない場合の処理{% else %}です。プロフィール画像がないときは、その場所を空欄にするという手もありますが不親切な感じもしますのでalt="avatar_thumbnail"としてサムネイル画像が表示される事を記載しておきます。もっと分かり易く”プロフィール画像が登録されていません”としても良いかもしれません。

<img class="rounded img-fluid mx-auto d-block" alt="avatar_thumbnail">

 それでは、ターミナルからサーバーを立ち上げて正しく表示されるかを確認していきましょう。 プロフィール画像が登録されていないユーザーtaroは画像列にavatar_thumbnailが表示され、プロフィール画像をアップロードしたichiroの画像列にはいらすとやさんのパンダの画像が表示されています。正しくは、表示されているのですが画像が少し大きすぎますので、CSSで画像サイズを調整しましょう。

ユーザー一覧

 static/accountsフォルダー内にstyle.cssを作成して下さい。 VisualStudio codeのエクスプローラーでファイルの位置を確認してください。

cssフォルダ

 CSSには先ほどid属性を追加していますので#avatar-thumbnailとして高さ、幅などを指定します。枠線なども同時に指定しています。サムネイル画像ですので大きさは50ピクセル程度で良いかと思いますが、皆さんの好みに合わせてサイズは調整してください。

#avatar-thumbnail {
   height: 50px;
   width: 50px;
   object-fit: cover;
   border-radius: 20%;
   border: 1px solid #eee;
}

 CSSの設定ができましたのでターミナルからサーバーを立ち上げて再度ユーザ一覧画面を表示してみましょう。正しくCSSが適用されていれば以下の画像のようにサムネイル画像が小さくなっていることが確認できます。

ユーザー一覧CSS

 もしかしたら、CSSを読み込む設定を追記していないのにCSSが適用されて不思議に思うかもしれませんが、base.htmlを作成した際に<head>〜</head>に自作CSSを読み込む設定をしていました。これ以降も、このCSSファイルに追記していけば各htmlにCSSが適用されます。

<head>
・・・省略・・・
   {# bootstrapのCSS、自作のCSSを読み込む#}
   ・・・省略・・・
      <link rel="stylesheet" href="{% static 'accounts/style.css' %}">
・・・省略・・・
</head>

 以上で③ログイン、ログアウト機能と④ユーザー情報の更新と一覧表示までが完了しました。Part3では、⑤ポストモデルとCRUD操作を作成し発展的な機能である⑥ユーザのFollow/Unfollow機能の追加、⑦ポストのお気に入り機能追加を実装します。かなり長い記事になりそうです。 Part1をまだ見られていない方は、よろしければPart1もみて下さい。











この記事が気に入ったらサポートをしてみませんか?