見出し画像

初めてのWebアプリケーション作成


自分はPythonを学び始めて3か月くらいになるプログラミング初心者です。今回、初めて一からWebアプリを作ってみたので、その詳細と感想をここに書きたいと思います。

参考までに自分の学習状況を説明します。主にPyQというオンライン学習サービスを使ってPythonの基礎からDjangoを使ったWebアプリの作り方まで一通り学びました。さらに、DjangoGirlsというサイトを使って自分でWebアプリを作る方法を学びました。書籍は『みんなのPython』を辞書的に使っています。

このドキュメントの目的

自分が作ったプログラムの詳細をプログラムがわかる人に向けて説明します。

作ったもの

乃木坂46のメンバー情報を閲覧できるWebアプリケーションです。

乃木坂46データベース:https://nogidb.herokuapp.com
GitHub:https://github.com/omajun/nogidb

作った理由

最近、乃木坂46のファンになったのですが、メンバーの年齢がいまいち覚えられなかったので、一覧で学年別のリストが見れるといいなと思い、作り始めました。

これまでPyQやDjangoGirlsで学んできたことを使って、複数の要素を持つデータを扱ったWebアプリを作ってみたかったので、データの量がそこそこ多く、変更が頻繁にある大人数アイドルのデータベースはちょうどよいテーマだったと思います。

環境

OS:Windows10
Python:Python 3.8.0
Editor:Visual Studio Code

制作期間

2019/10/19 ~ 11/9
のべ40時間くらい。


作ったものの機能詳細


トップページの表示

トップページに表示される項目は、「メンバーリスト」、「学年別リスト」、「管理画面へ」です。

画像1


メンバーリストの表示

メンバーの一覧を表形式で表示します。各メンバーについて「名前」・「生年月日」・「加入時期(何期生か)」・「活動状態(活動中or卒業生)」が表示されます。名前をクリックすると、個別ページへ遷移します。表の上部には並べ替え用のボタンがあります。表の下部にページネーションを付けても良かったのですが、ページを分けてしまうと一覧で表示する意味がなくなってしまうと思ったので今回は付けませんでした。

画像2


メンバーリストの並べ替え

メンバーリストを並べ替えます。「年齢順」・「加入時期」・「名前順」・「活動状態」で並べ替えることができます。


学年別リスト

メンバーを学年ごとのグループにして表示します。このページで表示されるメンバーについての情報は「画像」と「名前」です。メンバーをクリックすると、個別ページに移ります。

スクリーンショット (26)


個別ページの表示

メンバーの個別ページを表示します。個別ページには該当メンバーの「名前」・「写真」・「生年月日」・「年齢」・「加入時期」・「活動状態」・「その他」が表示されます。

スクリーンショット (25)


管理画面の表示

トップページの「管理画面」ボタンをクリックすると、サイトの管理画面を表示します。管理画面はDjangoのデフォルトの管理画面を使います。

スクリーンショット (28)



プログラムの説明


モデルの設定

models.pyにモデルを書きます。Memberというモデルを定義し、要素として、「名前」「名前(かな)」「誕生日」「加入時期」「ステータス」を持たせました。

「名前」「名前(かな)」は文字列型、「誕生日」は日付型として保存しています。「加入時期」と「ステータス」に関してはモデル自体には数字で保存しており、その数字と対応するタプルをあらかじめ定義しています。それが上部にある、○○_CHOICESというタプルです。

管理画面からメンバーを追加することができます。

class Member(models.Model):

   JOIN_CLASS_CHOICES = (
       (1, '1期生'),
       (2, '2期生'),
       (3, '3期生'),
       (4, '4期生'),
   )

   ACTIVE = 0
   GRADUATE = 1

   STATUS_CHOICES = (
       (ACTIVE, '活動中'),
       (GRADUATE, '卒業生'),
   )

   name = models.CharField('名前', max_length=50)

   name_kana = models.CharField('名前(かな)', max_length=50, validators=[validators.RegexValidator(
           regex=u'^[ぁ-んァ-ンー]+$', 
           message='「名前(かな)」は、ひらがな・カタカナのみで空白を入れずに入力してください')]
   )

   birthday = models.DateField(verbose_name='生年月日', auto_now=False, auto_now_add=False)

   join_class = models.PositiveIntegerField(verbose_name='加入時期', choices=JOIN_CLASS_CHOICES)

   status = models.PositiveIntegerField('活動状況', choices=STATUS_CHOICES)


Views関数づくり


メンバーリストの表示

Memberモデルで保存されている全てのメンバーの情報をmembers変数に返し、それを生年月日と活動状態で並べ替え、テンプレートに渡します。リクエストに対して、メンバーリストを表示するHTMLとmembers変数を返します。HTML上ではそのデータを一人分ずつ出力し、表形式にします。

def member_list(request):
   members = models.Member.objects.all().order_by('status', 'birthday')
   return TemplateResponse(request, 'nogidb/member_list.html', {'members': members}


並べ替え機能

「年齢順」のリクエストを受け取ると、members変数の内容を年齢順にソートしたデータをメンバーリストのHTMLに返します。「加入時期」・「活動状態」・「名前順」についても同様です。操作としては、メンバーリストページをフレキシブルに並べ替えて表示しているのではなく、あらかじめ並べ替えたページを用意しておいて、リクエストがあればその並べ替えたページを返すということをしています。したがって、「加入時期順かつ名前順」のように複雑な並べ替えをすることは出来ません。

def condition(request, condition):
   if condition == 0:
       members = models.Member.objects.all().order_by('birthday')
   elif condition == 1:
       members = models.Member.objects.all().order_by('join_class', 'birthday')
   elif condition == 2:
       members = models.Member.objects.all().order_by('status', 'birthday')
   elif condition == 3:
       members = models.Member.objects.all().order_by('name_kana')
   else:
       return Http404
   return TemplateResponse(request, 'nogidb/member_list.html', {'members': members})


URLの設定

urls.pyというファイル内でどのURLが踏まれたらどの関数を実行するかを決めています。メンバーリストの並べ替えでは、どの条件を指定しても同じ関数を実行することになっています。それぞれの条件に数字を割り当てて、リクエストとともにその数字を関数に渡すことにより、操作を分けています。


メンバー画像の追加


Imageモデル

Memberモデルを設定して、メンバーリストを表示するまでが第一段階でした。そこまで何とかたどり着いたので、次に個別メンバーページにメンバー画像とメンバー情報を表示したいと思いました。そこで画像を保存するモデル(Imageモデル)を追加しました。

本当はMemberモデルの中に要素として画像のフィールドを追加したかったのですが、変に既存のプログラムを変更して全部動かなくなるのは怖かったので、画像のモデルを新たに作る方法を調べて、プロジェクトの中にalbumアプリを新しく作り、その中にImageモデルを作りました。

Imageモデルには画像フィールドとForeignKeyフィールドを持たせています。

class Image(models.Model):
   picture = models.ImageField('画像', upload_to='images/')
   name = models.ForeignKey(verbose_name='名前', to=Member, on_delete=models.CASCADE)
   def __str__(self):
       return self.name.name
   
   class Meta:
       db_table = 'image'
       verbose_name_plural = 'メンバー画像'


Memberモデルとの紐づけ

個別ページには画像とメンバー情報を一緒に表示したいので、MemberモデルとImageモデルを紐づける必要があります。そこでImageモデルにForeignKeyというフィールドを作り、Memberモデルと紐づけました。Memberモデルを削除すると紐づけられたImageモデルも削除されるようになっています。

モデルの紐づけは出来たのですが、紐づけたデータがどういう形で引き出されるのかわからず、かなり苦戦しました。Imageモデルの構造は理解できたので、次はMemberモデルに直接画像フィールドを追加してみようと思います。

[ 追記:ImageモデルをMemberモデルに統合し、Imageモデルをなくしました。これにより、ForeignKeyフィールドを用いてMemberモデルと紐づける必要がなくなり、処理の数が減りました。

さらに、ImageKitフィールドを使ってサムネイル用に一様のサイズで画像を保存するthumbnailという要素を追加しました。

class Member(models.Model):

   """
   省略
   """

   picture = models.ImageField(verbose_name='メンバー画像', null=True, upload_to='images/')

   thumbnail = ImageSpecField(source='picture', processors=[ResizeToFill(250,330)], format='JPEG')

]


Views関数づくり2


個別ページの表示

メンバーの画像と情報を個別に表示します。nogidbアプリのviewsファイルに関数を書きました。リクエストがあるとこの関数に該当メンバーのMemberモデルIDを渡します。そのIDを元にメンバー情報を引き出し、そのメンバー情報を元に、紐づけられた画像データを引き出します。そしてそれらのデータとHTMLを返します。

def member_detail(request, member_id):
   member = models.Member.objects.get(id=member_id)
   age = member.age() 
   return TemplateResponse(request, 'nogidb/member_detail.html', {'member': member, 'age':age})


学年別リストの表示

Memberモデルからそれぞれの年度内に生まれた人を抽出し、年度ごとにまとめて返します。たとえば、1990年度生まれであれば、以下のようになります。

members_1990 = models.Member.objects.filter(birthday__range=["1990-04-02", "1991-04-01"]).order_by('birthday')

これを各年度分作る必要があったので、単純にこれを各年度分書きました。もう少しスマートに書きたかったのですが、いい方法が思いつきませんでした。

def grade(request):
   members_1990 = models.Member.objects.filter(birthday__range=["1990-04-02", "1991-04-01"]).order_by('birthday')
   members_1991 = models.Member.objects.filter(birthday__range=["1991-04-02", "1992-04-01"]).order_by('birthday')
   members_1992 = models.Member.objects.filter(birthday__range=["1992-04-02", "1993-04-01"]).order_by('birthday')
   members_1993 = models.Member.objects.filter(birthday__range=["1993-04-02", "1994-04-01"]).order_by('birthday')
   members_1994 = models.Member.objects.filter(birthday__range=["1994-04-02", "1995-04-01"]).order_by('birthday')
   members_1995 = models.Member.objects.filter(birthday__range=["1995-04-02", "1996-04-01"]).order_by('birthday')
   members_1996 = models.Member.objects.filter(birthday__range=["1996-04-02", "1997-04-01"]).order_by('birthday')
   members_1997 = models.Member.objects.filter(birthday__range=["1997-04-02", "1998-04-01"]).order_by('birthday')
   members_1998 = models.Member.objects.filter(birthday__range=["1998-04-02", "1999-04-01"]).order_by('birthday')
   members_1999 = models.Member.objects.filter(birthday__range=["1999-04-02", "2000-04-01"]).order_by('birthday')
   members_2000 = models.Member.objects.filter(birthday__range=["2000-04-02", "2001-04-01"]).order_by('birthday')
   members_2001 = models.Member.objects.filter(birthday__range=["2001-04-02", "2002-04-01"]).order_by('birthday')
   members_2002 = models.Member.objects.filter(birthday__range=["2002-04-02", "2003-04-01"]).order_by('birthday')
   members_2003 = models.Member.objects.filter(birthday__range=["2003-04-02", "2004-04-01"]).order_by('birthday')
   members_2004 = models.Member.objects.filter(birthday__range=["2004-04-02", "2005-04-01"]).order_by('birthday')

   return TemplateResponse(
       request,
       'nogidb/grade.html',
       {
       'members_1990': members_1990,
       'members_1991': members_1991,
       'members_1992': members_1992,
       'members_1993': members_1993,
       'members_1994': members_1994,
       'members_1995': members_1995,
       'members_1996': members_1996,
       'members_1997': members_1997,
       'members_1998': members_1998,
       'members_1999': members_1999,
       'members_2000': members_2000,
       'members_2001': members_2001,
       'members_2002': members_2002,
       'members_2003': members_2003,
       'members_2004': members_2004,
       }
       )

冗長です…。


HTMLを書く

base.html に基本のHTMLを書きました。個々のページのHTMLにエクステンションを書きました。aタグにURLを書くときに相対パスなのか絶対パスなのか、どこまで書けばいいのかなど苦戦しました。HTMLについてはPyQのクエスト内で使われているテンプレートを参考にしました。


CSSを書く

ある程度勉強し、最低限のデザインを自分で施しました。特に学年別リストページは力を入れて作りました。

とはいえ、基本的な部分はDjangoGirlsのチュートリアルで使われているCSSを参考にし、色とかを変えて使っています。


テストを書く

まだ書けていません。


本番環境で公開

Webアプリを公開するための方法を調べると、レンタルサーバー・AWS・PaaSなどいくつか方法が見つかりました。今回は、拙くてもいいからとりあえずWebアプリを形にするということを第一に考えていたので、一番簡単そうだった、Herokuを使って公開する方法を採用しました。


Heroku

簡単そうではありましたが、実際はかなり苦戦しました。特にデータベースをSQLiteからpostgreSQLへ変更するのにとても時間がかかりました。

そしてかなりの時間をかけて公開するとこに成功したのですが、ここでひとつ大きな問題が…。


画像が表示されない!

開発環境ではちゃんと表示されるのに、なぜか本番環境では画像が表示されません。調べてみると、Djangoでは開発環境(DEBUG=True)のときはあえて画像を表示させているらしいです。つまり、本番環境(DEBUG=False)で画像が表示されないのは、Herokuセットアップ時になにか問題があったわけではなく、正常な挙動ということ。そして画像を表示させるにはAWSのS3やそれに類するものを介す必要があるとのことでした。

というわけで、AWSアカウントを作って、S3のバケットを設定し、settingsファイルを書き換えました。これもかなり苦戦しました。そもそもHerokuとS3が繋がらず、さらに繋がってもHTMLに反映されないという2点でだいぶ格闘しました。

そして、2日間の格闘の末、ついに公開に至りました!


今回の制作で得られたこと

素材は色々なところから持ってきましたが、自力で一からWebアプリを作り、それを目に見える形にできたことはとても自信になりました。また、Djangoフレームワーク内でデータがどのようにやりとりされているかを理解できたこともとても大きな収穫でした。実際に取り掛かってみて感じたのは、「思ったよりも書けない」ということです。本で読んだり、オンライン学習サイトで課題をこなすだけでは、できるようなつもりになるだけなんだなと実感しました。


難しかった点

どのコードがどのコードにつながっているのかを理解するのがとても大変でした。ただ単にいろんなサイトから見本のコードを集めてくると、ベースは簡単にできるかもしれないけど、細かいところで調整が必要でそれが一番大変でした。結局調整のために何がエラーなのか読んで、それを調べて修正するということを繰り返す必要があるので、その過程でかなり色々な知識と経験を得ることができました。

具体的には、MemberモデルとImageモデルの紐づけと表示、URLにパラメーターを渡すときの処理、viewsファイルの関数の定義などが難しかったです。

また、Pythonを使ってアプリのコードを書くことと同じくらいかそれ以上、サーバーを設定して(してないけど)公開に持っていくということが大変でした。


今後の課題

・テストを書けるようになる。
・ユーザーが何か操作できるようなWebアプリを作りたい。たとえば投稿機能とか、注文機能とか、出品機能とかそういった機能をもったもの。

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