カテゴリー
サインイン 新規登録

間違いや改善の指摘

内容の技術的な誤り・誤字脱字やミスのご報告・解説やトピックの追記/改善のご要望は教材をさらに良くしていく上でとても貴重なご意見になります。

少しでも気になった点があれば、ご遠慮なく投稿いただけると幸いです🙏

実際には誤りではなく勘違いであっても、ご報告いただけることで教材のブラッシュアップにつながります。

質問ポリシー①

教材受講者みなさんのスムーズな問題解決のために、心がけていただきたいことがあります。

教材の内容に関する質問を投稿しましょう

教材の内容に関係のない質問や教材とは異なる環境・バージョンで進めている場合のエラーなど、教材に関係しない質問は推奨していないため回答できない場合がございます。

その場合、teratailなどの外部サイトを利用して質問することをおすすめします。教材の誤字脱字や追記・改善の要望は「文章の間違いや改善点の指摘」からお願いします。

4-1

ファイルアップロード機能の実装(前編)

第4章ではPDFファイルのアップロード機能を実装していきます。
また、アップロードされたPDFファイルからテキスト情報を抽出しExcelファイルへ情報を埋め込む処理も実装します。

全体の処理フロー

まず最初に、全体の処理フローを解説します。

4_01

上図の通り①~⑧の順番で処理を実行します。

Copied!
1. PDFをアップロード 2. pdfフォルダにアップロードされる(tutorial/media/pdf/xxxxxxxxxxx/AAA.pdf) 3. 予め用意してあるテンプレートファイルをtempフォルダにコピーして年月日時分秒つきのファイル名に変更 4. PDFからテキストデータを抽出 5. テキストデータの内容をExcelにはめ込む 6. 出来上がった請求書ファイルをユーザ毎の格納フォルダに移動 7. 請求書ファイルの完成 8. 一時フォルダを削除

各処理の解説

①、②PDFのアップロード

まず最初に、請求書PDFファイルをアップロード画面から選択します。(PDFファイルは同時に複数選択可能とします)
ファイル選択後、「アップロード」ボタンを押すと、メディアファイルで設定したフォルダ(meida/pdf/xxxxxxxxxx)配下にファイルがアップロードされます。

アップロード先のフォルダはtutorial/media/pdfとし、その直下に任意の10文字で構成されたフォルダ名を自動的に生成します。

アップロードされたPDFファイルを一意なディレクトリに格納させることで、各処理の対象とするPDFファイルを特定することが容易になります。

※もし、すべてのPDFファイルを特定のフォルダにアップロードしてしまうと、どのユーザがいつアップロードしたPDFか判別できなくなってしまいます。

各々の処理がどのPDFファイルを処理対象とするかをプログラム上一意に特定させたいのでこのような設計にしています。

③テンプレートファイルを一時作業用フォルダへコピー

予め請求書一覧ファイル.xlsxという名前で以下のようなテンプレートファイルをtutorial/media/templateフォルダ配下に配置しておきます。
PDFがアップロードされたら一時作業用フォルダtutorial/media/tempに請求書一覧ファイル.xlsxをコピーします。
その後、年月日時分秒つきのファイル名(例:請求書一覧ファイル_20190724-195001.xlsx)に変更します。

4_02

④PDFからテキスト情報を抽出

pdfminer.sixというライブラリを使ってアップロードされたPDFファイルを解析してテキスト情報を抽出します。
複数ファイルがアップロードされていた場合は、1つずつPDFファイルを解析します。

⑤テキスト情報をExcelに埋め込む

抽出したテキストデータの内容を③で作成したExcelファイル(請求書一覧ファイル_yyyymmdd-hhmmss.xlsx)に埋め込みます。

⑥請求書ファイルをユーザフォルダに移動

出来上がった請求書ファイル(請求書一覧ファイル_yyyymmdd-hhmmss.xlsx)をユーザ毎の格納フォルダtutorial/media/excel/ユーザ名に移動させます。

⑧一時フォルダの削除

アップロード時に自動生成された一時フォルダtutorial/media/pdf/xxxxx xxxxxを削除して処理を完了します。

全体の処理内容は以上です。

目標物を作成するまでの流れ

本章はボリュームが多いため4-1~4-4の4つのパートに分かれています。

前半(4-1~4-2)では、PDFファイルをアップロードする機能を実装していきます。
後半(4-3~4-4)ではアップロードされたPDFファイルを解析してテキスト情報を抽出します。
さらに、Excelファイルに抽出した情報を埋め込んで最終的にExcel形式の請求書ファイルを生成する部分まで実装していきます。

PDFファイルのアップロード機能の作成(4-1~4-2)の流れは以下の通りです。

Copied!
0. settings.pyの設定(メディアファイルの設定) #4-1 1. forms.pyの設定(アップロードフォームの定義) #4-1 2. urls.pyの定義(アップロード画面用URLパターンの定義) #4-1 3. views.pyの設定(アップロード機能の定義) #4-2 4. テンプレート設定(アップロード画面の定義) #4-2

demo_001

本パート(4-1)では上記0~2までを実装していきます。

それではPDFファイルのアップロード機能を実装していきましょう。

0. settings.pyの設定(メディアファイルの設定)

Djangoでファイルアップロードを行う場合は、メディアファイルの設定を行う必要があります。

具体的には、settings.pyでアップロード先となるパスのルート設定(MEDIA_ROOT)とアップロードしたファイルのURLのルート設定(MEDIA_URL)を行う必要があります。

それでは、settings.pyを開いて、ファイルの末尾に以下の設定を追加しましょう。

Copied!
tutorial └── tutorial └──settings.py #変更対象

追加するメディアファイルの設定

Copied!
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/'

MEDIA_ROOTは以下のようにDjangoプロジェクトフォルダ直下のmediaフォルダを指定しています。

Copied!
..\tutorial\media

もう少し補足すると、BASE_DIR変数にはDjangoプロジェクトフォルダのパスが格納されています。

なぜかというと、settings.py内に以下の設定がされているからです。

Copied!
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

上記BASE_DIRにはDjangoプロジェクトフォルダの絶対パスが格納されているため、os.path.joinメソッドを使って「..\tutorial\media」というメディアファイルのパスを生成しています。

実際にBASE_DIRの設定内容を確認したい方は、Djangoのシェルモードを起動して以下の手順で確認してみましょう。

python
1234
Copied!
$ cd tutorial $ python manage.py shell #シェルモードを起動 $ from tutorial.settings import * #settings.pyをインポート $ print(BASE_DIR)

続いて、MEDIA_URLの設定値は'/media/'としています。
具体的には、http://127.0.0.1:8000/media を設定したことになります。
最後に、設定したメディアファイルのフォルダを作成しておきましょう。

python
123456
Copied!
../tutorial ├── accounts ├── pdfmr ├── templates ├── tutorial └── media #新規作成

メディアファイルの設定は以上です。

1. forms.pyの設定(アップロードフォームの定義)

まず最初に、PDFアップロード画面フォーム定義と単純なアップロード処理部分を開発していきます。

全体図の赤枠の部分です。

4_03

pdfmrアプリケーション直下にforms.pyを新規作成します。

python
12345678910111213
Copied!
../tutorial ├── accounts ├── pdfmr | └── forms.py #新規作成 | ├── admin.py | ├── apps.py | ├── models.py | ├── tests.py | ├── urls.py | ├── views.py ├── templates ├── manage.py └── tutorial

ファイルを作成したら、以下のコードを記載してください。

全体コード

python
12345678910111213141516171819202122232425262728
Copied!
from django import forms from django.conf import settings from django.core.files.storage import default_storage import os, random, string class UploadForm(forms.Form): """PDFアップロード用フォームの定義 saveメソッドはアップロードしたPDFを一時フォルダに保存する。 """ document = forms.FileField(label="PDFアップロード", widget=forms.ClearableFileInput(attrs={'multiple': True}), ) def save(self): upload_files = self.files.getlist('document') temp_dir = os.path.join(settings.MEDIA_ROOT, self.create_dir(10)) #一時フォルダの生成 for pdf in upload_files: default_storage.save(os.path.join(temp_dir, pdf.name), pdf) #一時フォルダにPDFを保存 return temp_dir def create_dir(self, n): """一時フォルダ名生成関数""" return 'pdf\\' + ''.join(random.choices(string.ascii_letters + string.digits, k=n))

コードの解説

まず、PDFアップロード用フォームを定義するためにforms.Formを承継してUploadFormという名称のクラスを定義します。

python
1
Copied!
class UploadForm(forms.Form):

今回は特にモデルの定義(models.py)は行いませんのでモデルフォーム(Models.Form)ではなく通常のフォーム(forms.Form)を利用します。

次の行ではdocumentという名称のアップロードフィールドを定義しています。

複数のファイルを選択してアップロード可能にするため、forms.FileFieldフィールドオプションで以下の設定を追加しています。

python
123
Copied!
document = forms.FileField(label="PDFアップロード", widget=forms.ClearableFileInput(attrs={'multiple': True}), )

上記のようにattrsmultipleTrueに設定することでhtmlのinputタグでmultipleという属性が付与され複数のファイルを選択してアップロードできるようになります。

ClearableFileInputは<input type="file" ...> のようにレンダリングしてくれるフォームウィジェットです。

DjangoのウィジェットについてはDjango公式ドキュメントにいろいろと記載されていますので、興味のある方は確認してみてください。
参考URL:https://docs.djangoproject.com/ja/2.2/ref/forms/widgets/

続いて、UploadFormクラス内にsaveメソッドを独自に定義します。

python
123456
Copied!
def save(self): upload_files = self.files.getlist('document') temp_dir = os.path.join(settings.MEDIA_ROOT, self.create_dir(10)) #一時フォルダの生成 for pdf in upload_files: default_storage.save(os.path.join(temp_dir, pdf.name), pdf) #一時フォルダにPDFを保存 return temp_dir

forms.Formクラスにはsave()というメソッドは用意されていませんので、あくまで独自に実装するメソッドになります。

このsaveメソッド内には、PDFをアップロードした際にtutorial\media\pdf<ランダムな10個の文字列>にファイルが保存されるような処理を記載しています。

※これは独自に実装した処理内容ですので、メソッド名はsaveという名称でなくても可能ですが、ここではわかりやすくsaveという名称で定義しています。

【補足】

forms.Formは、Lib\site-packages\django\forms\forms.py内に以下のように定義されています。

python
1234567
Copied!
class Form(BaseForm, metaclass=DeclarativeFieldsMetaclass): "A collection of Fields, plus their associated data." # This is a separate class from BaseForm in order to abstract the way # self.fields is specified. This class (Form) is the one that does the # fancy metaclass stuff purely for the semantic sugar -- it allows one # to define a form using declarative syntax. # BaseForm itself has no way of designating self.fields.

上記の通り、Django標準のFormクラスはBaseFormというクラスを承継して作られていることがわかります。

さらに同ファイル内にBaseFormというクラスが存在していますが、このクラス内にsaveというメソッドは定義されていないため、「あくまで独自に実装したsaveメソッド」ということになります。

さて、saveメソッドの説明に戻ります。

以下のコードでフォーム画面上にアップロードされたPDFファイルをupload_files変数に格納します。

PDFファイルが複数アップロードされた場合は、このupload_files変数にすべてのPDFファイル情報が格納されます。

python
1
Copied!
upload_files = self.files.getlist('document')

ユーザーがアップロードしたファイルはrequest.FILESの中で管理されます。
フォーム側からはself.files.getlist('document')とすることでアップロードされたrequest.FILESにアクセスできます。

なお、files.getlistの()内にはUploadFormクラス内で定義した属性名documentを指定します。

つづいて、以下のコードではアップロード先となる任意の10文字のフォルダ名を自動生成しています。

python
1
Copied!
temp_dir = os.path.join(settings.MEDIA_ROOT, create_dir(10))

PDFのアップロード先はtutorial\media\pdf\任意の10文字という形で自動生成します。

具体例

Copied!
tutorial\\media\\pdf\\BOqzkvHvYr'

settings.MEDIA_ROOTはsettings.pyで定義したMEDIA_ROOTの設定値(/media)です。

また、create_dir(n)は引数に整数nを受け取りrandom.choices関数とstringを使って任意のn文字の文字列を返す関数です。

python
123
Copied!
def create_dir(self, n): """一時フォルダ名生成関数""" return 'pdf\\' + ''.join(random.choices(string.ascii_letters + string.digits, k=n))

補足

Copied!
string.ascii_letters ⇒小文字 'abcdefghijklmnopqrstuvwxyz'、大文字 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'を合わせた文字列定数を表します。 string.digits ⇒文字列 '0123456789'を表します。

参考URL:https://docs.python.org/ja/3/library/string.html

最後に、以下のコードでアップロードされたPDFファイルを1件ずつ取得します。
そして、default_storagesaveメソッドを使ってtutorial\media\pdf\ランダムな10個の文字列\pdfのファイル名としてファイルをアップロードします。

python
123
Copied!
for pdf in upload_files: default_storage.save(os.path.join(temp_dir, pdf.name), pdf) return temp_dir

default_storage(DefaultStorageオブジェクトクラス)はDjango標準で備わっているファイルオブジェクトを操作するためのメソッドで、様々なファイル操作を行うことができます。

default_storageを利用するには以下コードでdefault_storageをインポートする必要があります。

python
1
Copied!
from django.core.files.storage import default_storage

以下の形式で指定すると指定したファイルを保存先のパスに保存してくれます。

Copied!
default_storage.save(保存先のパス, 保存するファイル)

最後にアップロード先のパス情報(temp_dir)を戻り値として返しています。

パス情報(temp_dir)は、後半で実装するPDFからテキストデータを抽出する際に必要となるため、ファイル保存完了時の戻り値として取得しておきます。

2. urls.pyの定義(アップロード画面用URLパターンの定義)

続いてアプリケーション直下のurls.pyにファイルアップロード画面用のURLの設定を1つ追加します。

URLパターンの定義は以下の通りです。

Copied!
URLパターン名称:upload URLパス:http://127.0.0.1:8000/upload 呼び出す関数:UploadView.as_view()

追加するコードは以下の1行です。

python
1
Copied!
path('upload/', views.UploadView.as_view(), name='upload'), #新規追加
python
12345678910111213
Copied!
../tutorial ├── accounts ├── pdfmr | └── forms.py | ├── admin.py | ├── apps.py | ├── models.py | ├── tests.py | ├── urls.py #変更対象 | ├── views.py ├── templates ├── manage.py └── tutorial

変更前

python
123456789
Copied!
from django.contrib import admin from django.urls import path from . import views app_name ="pdfmr" urlpatterns = [ path('top/', views.top, name='top'), ]

変更後

python
12345678910
Copied!
from django.contrib import admin from django.urls import path from . import views app_name ="pdfmr" urlpatterns = [ path('top/', views.top, name='top'), path('upload/', views.UploadView.as_view(), name='upload'), #新規追加 ]

現時点でviews.pyにUploadViewクラスは存在していないためエラーになりますが無視してください。

以上で、このパートは完了です。
お疲れさまでした。

現在のパート (0)
全パート (17)
みんなで助け合おう!
現在のパートのディスカッション 全0件