18.

djangoにおけるファイルのアップロードとファイル名および拡張子の取得

編集
この記事の要点
  • Laravel でファイルアップロード処理
  • $request->file('fieldname'): UploadedFile オブジェクト取得
  • $file->store('dir'): 自動ファイル名で保存
  • $file->storeAs('dir', 'name.jpg'): 名前指定で保存
  • $file->getClientOriginalName(): アップロード時のファイル名

 

HTML フォーム

@csrf

コントローラ

public function upload(Request $request)
{
    // バリデーション
    $request->validate([
        'image' => 'required|image|mimes:jpg,png,gif|max:2048',  // 2MB
        'documents.*' => 'required|file|mimes:pdf,doc,docx|max:10240',
    ]);

    // ファイル取得
    $file = $request->file('image');

    // 自動ファイル名で保存
    $path = $file->store('uploads');
    // → storage/app/uploads/abc123def456.jpg のような名前

    // public ディスクに保存(Web 公開)
    $path = $file->store('uploads', 'public');
    // → storage/app/public/uploads/abc...
    // → アクセス URL: /storage/uploads/abc... (storage:link 必要)

    // 名前指定で保存
    $path = $file->storeAs('uploads', 'my-file.jpg', 'public');

    return back()->with('success', "保存先: $path");
}

UploadedFile の主要メソッド

$file = $request->file('image');

// アップロード時の情報
$file->getClientOriginalName();        // "my-photo.jpg"
$file->getClientOriginalExtension();   // "jpg"
$file->getClientMimeType();            // "image/jpeg"
$file->getSize();                       // バイト数

// サーバ上の情報
$file->getRealPath();                  // 一時ファイルの絶対パス
$file->getMimeType();                  // 実際の MIME (検知)
$file->guessExtension();               // 推定拡張子 (jpeg, png 等)
$file->hashName();                     // ユニークなハッシュ名

// 保存
$file->store('dir');                  // 自動名
$file->store('dir', 'disk');         // ディスク指定
$file->storeAs('dir', 'name.jpg');   // 名前指定
$file->storeAs('dir', 'name.jpg', 'public');
$file->storePublicly('dir');          // public ディスク

// 任意の場所に直接保存
$file->move(public_path('images'), 'photo.jpg');

ファイル名のカスタマイズ

// パターン 1: 元の名前を保持
$originalName = $file->getClientOriginalName();
$path = $file->storeAs('uploads', $originalName);

// パターン 2: タイムスタンプ + 元の名前
$name = time() . '_' . $file->getClientOriginalName();
$path = $file->storeAs('uploads', $name);

// パターン 3: ハッシュベース (ユニーク保証)
$name = hash('sha256', uniqid() . $file->getClientOriginalName())
        . '.' . $file->getClientOriginalExtension();
$path = $file->storeAs('uploads', $name);

// パターン 4: ユーザ別フォルダ
$userId = auth()->id();
$path = $file->storeAs("uploads/user_{$userId}", $file->getClientOriginalName());

// パターン 5: 日付別フォルダ
$dir = 'uploads/' . date('Y/m/d');
$path = $file->store($dir);

// パターン 6: スラグ化したファイル名
$name = Str::slug(pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME))
        . '.' . $file->getClientOriginalExtension();
$path = $file->storeAs('uploads', $name);

ファイル名の安全化

// ❌ 危険: 元の名前そのまま (パストラバーサル攻撃)
$path = $file->storeAs('uploads', $_POST['name']);
// 攻撃者が "../../etc/passwd" のような名前を送ってきたら...

// ✅ 安全な対処

// 1. basename で抽出
$safeName = basename($userInput);

// 2. Str::slug で英数字のみ
$safeName = Str::slug(pathinfo($userInput, PATHINFO_FILENAME))
            . '.' . pathinfo($userInput, PATHINFO_EXTENSION);

// 3. ハッシュ化で完全置換 (推奨)
$safeName = hash('sha256', uniqid()) . '.' . $file->getClientOriginalExtension();

// 4. UUID
use Illuminate\Support\Str;
$safeName = Str::uuid() . '.' . $file->getClientOriginalExtension();

// 5. 元の名前は DB の別カラムに保存
DB::table('files')->insert([
    'stored_name' => $safeName,
    'original_name' => $file->getClientOriginalName(),
    'mime_type' => $file->getMimeType(),
    'size' => $file->getSize(),
]);

複数ファイルアップロード

// HTML: name="documents[]" multiple

public function uploadMultiple(Request $request)
{
    $request->validate([
        'documents' => 'required|array',
        'documents.*' => 'required|file|mimes:pdf,jpg,png|max:10240',
    ]);

    $paths = [];
    foreach ($request->file('documents') as $file) {
        $paths[] = $file->store('uploads', 'public');
    }

    return response()->json(['paths' => $paths]);
}

ストレージドライバ (S3 / GCS / ローカル)

# config/filesystems.php
'disks' => [
    'local' => [
        'driver' => 'local',
        'root' => storage_path('app'),
    ],
    'public' => [
        'driver' => 'local',
        'root' => storage_path('app/public'),
        'url' => env('APP_URL').'/storage',
        'visibility' => 'public',
    ],
    's3' => [
        'driver' => 's3',
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION'),
        'bucket' => env('AWS_BUCKET'),
    ],
],

// アプリ側
$path = $file->store('uploads', 's3');  // S3 にアップロード
$url = Storage::disk('s3')->url($path);   // 公開 URL 取得
Storage::disk('s3')->temporaryUrl($path, now()->addHour());  // 署名付き

画像のリサイズ・最適化

// Intervention Image パッケージ
// composer require intervention/image

use Intervention\Image\Facades\Image;

public function uploadImage(Request $request)
{
    $file = $request->file('image');

    // リサイズ
    $img = Image::make($file->getRealPath());
    $img->resize(800, null, function ($constraint) {
        $constraint->aspectRatio();
        $constraint->upsize();
    });

    // 保存
    $path = 'uploads/' . uniqid() . '.jpg';
    $img->save(storage_path("app/public/$path"), 80);  // 品質 80

    return back()->with('path', $path);
}

進捗表示 (フロント側)

// JavaScript (axios)
const formData = new FormData();
formData.append("file", fileInput.files[0]);

axios.post("/upload", formData, {
    headers: { "Content-Type": "multipart/form-data" },
    onUploadProgress: (e) => {
        const percent = Math.round((e.loaded * 100) / e.total);
        document.getElementById("progress").value = percent;
    }
}).then(response => console.log(response.data));

// または fetch + XHR
const xhr = new XMLHttpRequest();
xhr.open("POST", "/upload");
xhr.upload.addEventListener("progress", e => {
    if (e.lengthComputable) {
        const percent = (e.loaded / e.total) * 100;
        console.log(`${percent}%`);
    }
});
xhr.send(formData);

制限・セキュリティ

  • 容量制限: php.iniupload_max_filesize / post_max_size
  • nginx: client_max_body_size 50M;
  • MIME 検証: mimes: / mimetypes: バリデーション
  • 拡張子と内容の整合性: 偽装ファイル対策(mime_content_type 等)
  • ファイル名の正規化: パストラバーサル対策
  • 実行可能ファイル禁止: .php / .exe / .sh 等を排除
  • 仮想スキャン: ClamAV 等

関連記事

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 環境構築とプロジェクト/アプリの作成
  2. MVC(MVT)のそれぞれの使い方と説明
  3. データベースへの接続と操作
  4. Django Administration
  5. git管理
  6. エラー一覧
  7. バージョンの確認方法
  8. ログ出力方法
  9. SQLのログ出力方法
  10. ログのローテート設定
  11. settings.pyの定数にアクセスする方法
  12. 本番環境へのインストールとアプリのデプロイ(apache編)
  13. 本番環境へのインストールとアプリのデプロイ(nginx編)
  14. djangoアプリの本番の開始URLを変更する
  15. 静的(static)ファイルの置き場所と読み込み(画像、css、js )
  16. CSRFトークンをAjaxで使用する方法
  17. ajaxの使用例(POST編)
  18. ファイルのアップロードとファイルの名前
  19. クイックスタート/チュートリアル
  20. ログイン機能
  21. テンプレート側のログイン判定
  22. ビュー側のログイン判定
  23. 管理者ユーザーの作成/判定と管理画面
  24. モデルのjson化とレスポンス
  25. runserverでポートを指定する方法
  26. cronによるバッチ実行
  27. テンプレートで利用する共通のcontextを定義する方法
  28. プログラムが本番サーバーで反映されない場合の対処法
  29. APIの作成
  30. cron用コマンド・ファイルの作成