プログラミングを頑張る土木系専攻大学院生のブログ

主にプログラミングについて開発備忘録的な形で投稿しています。

レシピ追加機能の実装 YouTubeのサムネイル画像を取得、JavaScriptの記述【レシピ提案アプリ③】

 

siip.hateblo.jp

 

こんにちは!前回に続いて、レシピ提案アプリの開発日記第3弾です🎉
今回は「レシピ追加機能」の実装について詳しく紹介します!


レシピ追加機能について

ユーザーがレシピを追加するために入力する情報は以下のとおりです。

  • YouTubeのURL

  • レシピ名

  • サムネイル画像(入力がなければ自動取得)

  • 使用する食材

  • 備考(特殊な調味料の説明など)

理想の機能

PyTubeを用いて、自動でタイトルや使用する食材等を抽出できればいいですが、

今回は簡単に実装していきたいので、後回しにしました。

 

YouTubeから取得する情報 アプリに反映するフィールド
タイトル レシピ名
概要欄 使用する食材
サムネイル画像 サムネイル画像

さらに、食材リストは将来的に50種類以上になる予定なので、食材を検索・絞り込みできる機能も必要と考えました!

 

 


YouTubeのサムネイル画像を簡単に取得する方法

将来的にはPythonpytubeライブラリで情報を自動取得したいですが、今回はもっとシンプルな方法を採用しました!

参考:YouTubeのサムネイルURLについて(外部サイト)

方法

YouTubeのURL例:

https://www.youtube.com/watch?v=UOzLY-2aJ30

この場合、?v=以降の**UOzLY-2aJ30** が Video ID です!

最高解像度のサムネイルURLはこのように作れます👇

https://img.youtube.com/vi/UOzLY-2aJ30/maxresdefault.jpg

他にも取得できるサイズ

  • sddefault.jpg

  • hqdefault.jpg

  • mqdefault.jpg

  • default.jpg

必要に応じて使い分けが可能です👌


実際に作成したページ(add_recipe.html)

<!-- 一部抜粋 -->
<div class="container mt-5">
  <h1 class="text-center">レシピ追加</h1>
  <form id="add-recipe-form" class="mt-4">
    <div class="mb-3">
      <label for="youtube-url" class="form-label">YouTube URL</label>
      <input type="url" class="form-control" id="youtube-url" name="youtube_url" required>
    </div>
    <div class="mb-3">
      <label for="thumbnail" class="form-label">サムネイル画像</label>
      <input type="text" class="form-control" id="thumbnail" name="thumbnail">
    </div>
    <div class="mb-3">
      <label for="ingredients" class="form-label">使用する食材</label>
      <select class="form-control" id="ingredients" name="ingredients" multiple>
        <!-- サーバーから食材一覧を取得して表示 -->
      </select>
    </div>
    <!-- 他にもカテゴリ選択や備考欄など -->
    <button type="submit" class="btn btn-primary">追加</button>
  </form>
</div>

JavaScriptの記述を振り返る

JavaScriptはまだ学習期間が浅く、初心者なので今回使ったコードの内容を

振り返りました。

1. CSRFトークンを取得する関数をJavaScriptで記述

DjangoのデフォルトのCSRF機能({% csrf_token %})を使わなかった理由は、

食材を入力する欄で、入力したワードを元に絞り込む機能をつくりたかったため(食材の数は50個くらいを想定しており探すのはめんどくさい)

このような非同期リクエストの機能を実装するにはDjangoのデフォルトのCSRF機能({% csrf_token %})では記述できないからです。

 

非同期リクエス

ウェブページがサーバーと通信を行う際に、ページ全体を再読み込みすることなくデータの送受信を行う仕組みのこと

function getCSRFToken() {
  const cookies = document.cookie.split(';');
  for (let cookie of cookies) {
    const [name, value] = cookie.trim().split('=');
    if (name === 'csrftoken') {
      return value;
    }
  }
  return null;
}

2. レシピ追加フォームの非同期送信

フォーム送信時にページ全体をリロードせず、データだけ送信する仕組み

document.getElementById('add-recipe-form').addEventListener('submit', async function(event) {
  event.preventDefault();

  const formData = new FormData(event.target);
  const data = {
    name: formData.get('name'),
    url: formData.get('youtube_url'),
    thumbnail: formData.get('thumbnail'),
    ingredients: formData.getAll('ingredients'),
    categories: formData.getAll('categories'),
    notes: formData.get('notes')
  };

  if (!data.name || !data.url || !data.ingredients.length || !data.categories.length) {
    alert('必須フィールドをすべて入力してください。');
    return;
  }

  try {
    const response = await fetch('/recipes/add-recipe/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRFToken': getCSRFToken(),
      },
      body: JSON.stringify(data)
    });

    if (response.ok) {
      alert('レシピが正常に追加されました!');
      event.target.reset();
    } else {
      const errorData = await response.json();
      alert(`エラーが発生しました: ${errorData.message || '不明なエラー'}`);
    }
  } catch (error) {
    alert(`通信エラーが発生しました: ${error.message}`);
  }
});

食材検索機能を追加

食材が50個以上あると選びにくくなるので、検索ボックスを追加しました!

次回の記事でこの部分は詳細を書いていく予定です。

document.addEventListener('DOMContentLoaded', function() {
  const ingredientSelect = document.getElementById('ingredients');
  const searchBox = document.createElement('input');
  searchBox.type = 'text';
  searchBox.placeholder = '食材を検索';
  searchBox.classList.add('form-control', 'mb-3');

  ingredientSelect.parentNode.insertBefore(searchBox, ingredientSelect);

  searchBox.addEventListener('input', function() {
    const filter = searchBox.value.toLowerCase();
    const options = ingredientSelect.options;

    for (let i = 0; i < options.length; i++) {
      const option = options[i];
      const text = option.textContent.toLowerCase();
      const reading = option.getAttribute('data-reading')?.toLowerCase() || '';
      option.style.display = text.includes(filter) || reading.includes(filter) ? '' : 'none';
    }
  });
});

これで、「とり」と入力すると「鶏むね肉」、「鶏もも肉」だけが表示されるようになります

 

 

レシピ提案アプリ開発記事一覧はこちらから ↓

siip.hateblo.jp