概要
DjangoでModelのFileFieldを文字列やファイルで初期化する方法が分からなくて調べていた。
※ 以下のようにModelForm
を使用してファイルアップロード機能を実現する方法はいくつかヒットしたが、FileFieldを自分で初期化する方法はほとんど出てこなかった。
やりかたが分かったのでメモしておく。
結論
以下のように書ける。
前提
Modelの定義は以下とする。
class TextFile(models.Model): class Meta: db_table = "text_file" file_title = models.CharField(max_length=32) file_path = models.FileField(max_length=256)
またMEDIA_ROOT
を定義しておくこと。
[settings.py]
MEDIA_ROOT = os.path.join(BASE_DIR, "media/")
①文字列から初期化する場合
from django.core.files.base import ContentFile # 省略 text_file = TextFile( file_title="sample1", file_path=ContentFile("aaa", "sample1.txt") ) text_file.save()
②ファイルから初期化する場合
from django.core.files import File # 省略 with open("/tmp/aaa.txt", "r", encoding="utf-8") as f: text_file = TextFile( file_title="sample2", file_path=File(f, "sample2.txt"), ) text_file.save()
結果
以下のように期待通りの結果となった。
・mediaディレクトリに保存されたファイル
$ tree media media ├── sample1.txt └── sample2.txt
・text_fileテーブルに保存されたデータ
sqlite> select * from text_file; 1|sample1|sample1.txt 2|sample2|sample2.txt
根拠
公式ドキュメントに、上記の初期化方法について直接説明している箇所はなかったが、以下のFile Uploads
のセクションに記載されている内容から、上記の初期化方法が正しいといえるのではないかと考えた。
docs.djangoproject.com
このドキュメントには
If you are constructing an object manually, you can assign the file object from request.FILES to the file field in the model:
とあり、以下のようにModelFormを使わずにViewでFileFieldを持つModelを初期化する方法が記載されている。
[公式ドキュメントのサンプルコード]
from django.http import HttpResponseRedirect from django.shortcuts import render from .forms import UploadFileForm from .models import ModelWithFileField def upload_file(request): if request.method == 'POST': form = UploadFileForm(request.POST, request.FILES) if form.is_valid(): instance = ModelWithFileField(file_field=request.FILES['file']) instance.save() return HttpResponseRedirect('/success/url/') else: form = UploadFileForm() return render(request, 'upload.html', {'form': form})
ここで、以下のセクションより、request.FILES['file']
の型はUploadedFile
とのことである。
docs.djangoproject.com
※ UploadedFileのドキュメントは以下
docs.djangoproject.com
Django本体のソースコードを読むと、UploadedFileはdjango.core.files.base.File
を継承していることが分かる。
github.com
ここは推論になってしまうが、以下の理由よりModelのFileFieldは、django.core.files.base.File
(派生クラスも含む)で初期化できる仕様になっていると考えられる。
- DjangoのFileオブジェクトの公式ドキュメントに以下のように記載があり、Django上でのファイルの表現は基本的には
django.core.files.base.File
が使用されるとのことである。
The File class is a thin wrapper around a Python file object with some Django-specific additions. Internally, Django uses this class when it needs to represent a file.
- 一般的な設計原則として、派生クラスである
UploadedFile
に依存しているとは考えにくい。
加えて、「①文字列から初期化する場合」に関しては、django.core.files.base.File
をファイルではなく文字列やバイト列から生成したい場合は、Fileオブジェクトの派生クラスであるdjango.core.files.base.ContentFile
を使うことができる。
docs.djangoproject.com
補足
今回はテキストファイルを例としたが、バイナリファイル(画像など)でも同じ方法で実現できることも確認した。