Django REST APIを公開

DjangoAPI-Demo

皆さん、こんにちは。 今日は、PythonのウェブフレームワークDjangoで作成したREST APIを公開するので是非使ってみてください。 APIの概要 このAPIはコールセンターをイメージしたDBを構成し、CRUD操作(Create,Read,Update,Delete)を可能にしたAPIです。 もう一つにトークン認証でログインしたユーザーのみアクセスできるAPIもあります。 そちらは、81番ポートからアクセスしてください。今回はトークン認証の不要なAPIのみ紹介します。 使えるAPI 利用可能なAPI http://172.104.81.40/api/ このURL(URI)にアクセスすると、下記のように各URLからAPIにアクセスできます。 Office:オフィス情報のAPIです。 Staff:従業員のAPIです。Officeの情報が紐づいています。 Tickets:お客様からのコール情報に他のテーブル(スタッフ、店舗情報、チケットカテゴリ)が紐づいています。 Ticket-Categories:コールのカテゴリ情報です。 Company:企業情報です。 StoreS:各企業の店舗情報です。 Customer-contact:お客様の連絡先です。 Djangoセットアップの手順 下記のURLにソースコードがアップロードされているので自由にクローンして使ってください。 https://github.com/TraitOtaku/Django-TicketAppAPI Gitを使ってクローンからセットアップまでの手順です。 モデルの内容 各アプリのmodels.pyを参照して受け入れるデータタイプとデータが必須かどうかを確認してください。 blank=True, null=Trueの場合はデータがなしでもOKです。 シリアライザーの内容 各アプリのSerializers.pyが先ほどのDBとコミュニケーションをとるmodels.pyをシリアル化してAPIの役目をします。チケットの例でいうとReadのGETリクエストが来たときとその他のリクエストで別々のリスポンスを返すようにしています。その設定はViews.pyで行いますが、APIの内容はここで決めます。 https://github.com/TraitOtaku/Django-TicketAppAPI/blob/master/tickets/serializers.py UIからデータを操作してみる では、実際にDjangoのAPIのUIを使用してデータを操作してみましょう。 http://172.104.81.40/api/tickets/ 上記のJSONデータを入れてあげると、DjangoからAcceptのリスポンスが返されてデータが追加されました。 レコードの更新と削除 先ほど作成したレコードはIDが1で作成されました。このデータにアクセスするには下記のようにURLの最後にIDを追加すればOKです。 http://172.104.81.40/api/tickets/1/ そうするとDELETEのオプションと下記にPUTとPATCHのオプションが追加されるのでそこからレコードを削除するなり修正するなりできます。

ViteでReactを始めよう!

Viteでreactを始めよう

皆さん、こんにちは。

今日はViteというコマンドツールでReactのアプリをセットアップしていきます。

今日の条件

Node.js バージョン16 (コマンド node -v)

npm バージョン8(npm -v)

今までのやり方

通常のReactのセットアップは下記のコマンドです。

npx create-react-app my-app

これで自動的にBabelとWebPackがインストールされたreactのプロジェクトが作成されます。

これらのパッケージが古いブラウザにも対応したJavaScriptファイルなどに変換してくれるのですが、その為にファイルが大きくなる+遅くなることがあります。

なので今日使うViteでサクサクの一般的なブラウザのみに対応したセットアップの仕方で設定する方法をお勧めします。

Viteとは

ViteはもともとVueJSの創設者が作成したビルドツールになります。Vueのプロジェクト以外にも対応しており、reactのデベロッパーにも人気です。

ViteでVueのプロジェクトを始めたい場合は下記の記事からどうぞ。

ViteでReactを始める(Yarn)

yarn create vite

#プロジェクト名を聞かれたら
my-vite-app

#フレームワークを聞かれたら
reactを選択

cd プロジェクトフォルダ
yarn 
yarn dev

ViteでReactを始める

ではコマンドラインに下記を叩き込みます。

ファイルパスが自分がプロジェクトを作成したいパスにいることを確認してください。

npm create vite@latest

y(いえす)でエンターを押す。そのままエンターでもOKです。

プロジェクト名を聞かれるので適当に入力します。

画像っだとmovie-appにしてみました。

では使いたいフレームワークを選択するのでここで、reactを選択してエンター

次にJavaScriptを使うのかTypeScriptを使うのか聞かれます。

今回はJavaScriptにしてエンター。

ではreactのベースとなるアプリができたのでフォルダの中に移動してパッケージをインストールして、起動してみます。

表示された通りに順番に入力しましょう。

npm installはnpm iでも同じことができます。

npm run dev

ここで開発用のアプリが起動したのでブラウザから見てみましょう。

カウントのところをクリックすると数が増えていきますね。

へえ。

お疲れ様でした。

VueでPiniaを使ってみよう

VueではState Management SystemのVuexが公式のプラグインとして紹介されてきました。しかし、最新のVue3ではPiniaを使うようにとVue生みの親のEvan Youさんもお勧めしています。

では、State Management(状態管理)って何でしょうか?

State Management(状態管理)

State Managementとはいわばストレージ/ストア(倉庫)のことです。アプリケーションでストアを作っておいてそこにデータを保管できるようになります。

例えばユーザーがログインしたときのトークン、APIでフェッチしたデータ、アプリケーションの状態(例:フォームが提出したとかの状態)があげられます。

PiniaのAPIの使い方

PiniaはVue2でもVue3でも使う事ができます。また、Options API(一般的に初心者向け)でもCompotion APIの書き方でもどちらでも対応しています。私の個人的な意見ではVue3でComspostion APIで書く方法が一番良いと思います。

なぜPiniaなのか

Piniaを使う事でこの状態管理システム(Store Library)を各コンポーネントやページのどこでも使う事ができます。

もちろん、同じことが export const state = reactive({}) でもできますよね。

しかし、このやり方だとセキュリティに脆弱性があり、何を管理しているのか見られてしまう可能性があります。

この他にもデベロッパー用のツールがあったり、サーバー側でもレンダーにも対応することができるなど色々メリットがあります。

Piniaをインストール

npmかyarnのコマンドでインストールしましょう。

yarn add pinia
# or with npm
npm install pinia

※NuxtJSの場合はこちらから

インストールが完了したらmain.jsにPiniaを追加します。

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

Piniaのファイルを作成しよう

ではPiniaが追加できたらJSファイルでストアしたいデータを保管できる場所を作っていきましょう。

慣習的にsrcディレクトリの直下にstoreというフォルダを作成してそこにJavaScriptファイルで下記のコードを作るのが一般的になります。

import { defineStore } from 'pinia'

export const useCounterStore = defineStore({
  id: 'counter',
  state: () => ({
    counter: 0
  }),
  getters: {
    doubleCount: (state) => state.counter * 2
  },
  actions: {
    increment() {
      this.counter++
    }
  }
})
  • stateは保管するデータの初期値をリターンするファンクションです。(dataと同じ概念)
  • getters は stateを使ってデータをモディファイ(変更)したいときに使うファンクションです。(copmputedと同じ概念)
  • actionsはasyncにできるファンクションのことです。(methodと同じ概念)

<script setup>的なPiniaの使い方

他のサイトでは上記のやり方でPiniaを使っていますが、今年からVueで使えるようになった<script setup>を使ったやり方に合わせてPiniaも書きたい人は下のやり方をお勧めします。

import { defineStore } from 'pinia'
import { ref } from 'vue';
import EventService from "@/plugins/EventService";

export const useMemberStore = defineStore('member', ()=> {

  const data = ref(null)

  const getData = () => {
    EventService.getMember()
      .then((response) => {
        data.value = response.data;
      })
      .catch((error) => {
        console.log("data:" + error);
      });
  }

  return {
    data,
    getData
  }
})

上のコードを見ても分かるようにgetterとかactionsとかの概念はなく普通のJavaScriptのコードで<script setup>と同じようにVanilla JavaScriptに近い状態で書くこともできます。

個人的にはこちらの方が書きやすいと思ったので是非試してみてください。

ではこれをコンポーネントから読み込めるようにしましょう。

<script setup>

import { useOfficeStore } from "@/stores/members/office";

const officePinia = useOfficeStore();

const officeData = officePinia.data
</script>

このようにどのコンポーネントからでもStoreにアクセスでき、グローバルにデータを管理することで後から見やすくなりますね。

Viteで@/componentsのショートカットが使えない問題

ViteでVueのアプリを作成してコンポーネントをインポートしようとすると何か気づいたことはありませんか?

そうです。Vue CLIで作った際に使えていた@/~のショートカットが使えないです!

なので、毎回コンポーネントをインポートする際にこのように、かなり面倒になってきます。

import Component from '../../../../components/Component.vue'

//本来ならこうしたい。。。
import Component from '@/components/Component.vue'

Viteで@を使えるようにする方法

これはWebPackでついてきたショートカットを再現することで同じように@が使えるようになります。

では、Viteのコンフィグファイルを作成して、コードを書いていきます。

Viteのコンフィグファイルの書き方

Viteのコンフィグはvite.config.jsの名称でプロジェクトのルート(アプリの一番上の階層)にファイルを作成することでできます。

これで、Viteが読み込む際にこのコンフィグファイルも自動で読み込んでくれます。

import { fileURLToPath, URL } from "url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
});

あとはいつも通りにコンポーネント側で@を使ってコンポーネントをインポートできるようになります。

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld msg="Hello Vue 3 + Vite" />
</template>

<script>
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

[Vue入門] asyncコンポーネント

基本的な使い方

大規模なアプリケーションでは、アプリを小さなチャンクに分割し、必要なときにのみサーバーからコンポーネントを読み込む必要があるかもしれません。これを実現するために、Vue には defineAsyncComponent 関数があります:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...サーバーからコンポーネントを読み込む
    resolve(/* 読み込まれたコンポーネント */)
  })
})
// ... `AsyncComp` を普通のコンポーネントと同じように使用する

このように、defineAsyncComponent は Promise を返すローダー関数を受け取ります。Promise の resolve コールバックは、コンポーネントの定義をサーバーから取得したときに呼ばれます。読み込みが失敗したことを示すために、reject(reason) を呼ぶこともできます。

ES モジュールの動的インポート も Promise を返すためにほとんどの場合には defineAsyncComponent と合わせて使用します。Vite や webpack などのバンドラーもこの構文をサポートしているため、の Vue SFC をインポートするためにも使用できます。

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

結果的に得られる AsyncComp は、実際にページ上にレンダリングされるときにローダー関数を呼ぶだけのラッパーコンポーネントです。さらに、内側のコンポーネントに任意の props を渡せるため、非同期ラッパーを使用すると、コンポーネントをシームレスに置換するとともに、遅延読み込みも実現できます。

ローディングとエラーの状態

非同期の操作は必然的にローディングとエラーの状態に関係してきます。そのため、defineAsyncComponent() ではこれらの状態のハンドリングを高度なオプションによりサポートしています。

const AsyncComp = defineAsyncComponent({
  // ローダー関数
  loader: () => import('./Foo.vue'),

  // 非同期コンポーネントの読み込み中に使用するコンポーネント
  loadingComponent: LoadingComponent,
  // ローディングコンポーネント表示前の遅延。デフォルト: 200ms。
  delay: 200,

  // 読み込みに失敗した場合に使用するコンポーネント
  errorComponent: ErrorComponent,
  // エラーコンポーネントは timeout が与えられて
  // その時間を超えた場合に表示される。デフォルト: Infinity。  
  timeout: 3000
})

ローディングコンポーネントが与えられた場合、内側のコンポーネントが読み込まれている間に表示されます。ローディングコンポーネントが表示されるまでに、デフォルトで 200ms の遅延があります。このようになっているのは、高速なネットワークではローディング状態が短く、置き換えが速すぎて、ちらつきのように見えてしまう恐れがあるためです。

エラーコンポーネントが与えられた場合、ローダー関数から返された Promise が reject されたときに表示されます。リクエストが長すぎる場合にエラーコンポーネントを表示するために、timeout を指定することもできます。

Suspense とともに使用する

非同期コンポーネントは、ビルトインコンポーネント <Suspense> とともに使用することもできます。

Vue Routerをインストールしよう

Vue RouterはVueアプリケーションで複数のURLをアプリケーションに導入してView(ページ)を作成する場合に使います。

Vue Cliからインストールする場合

Vue CLI(コマンドラインインターフェイス)を使う場合はアプリを作成するときにVue Routerをインストールするにチェックをするだけで勝手にインストールしてくれます。

npmでインストールする場合

npm install vue-router@4

yarnでインストールする場合

yarn add vue-router@4

main.jsにRouterを追加

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";

const app = createApp(App);

app.use(router);
app.mount("#app");

Routerディレクトリを作成

routerフォルダーをsrcフォルダの直下に作成します。その中にindex.jsを作成してrouterのコンフィグレーションを書いていきます。ファイル名はindex.jsでなくてもOKです。

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})
export default router

viewsディレクトリを作成

では、次に同じようにsrcディレクトリ直下にviewsをいう名前でフォルダーを作成しましょう。ここにページの枠組みとなるVueコンポーネントを作成していきます。

例でいうとAbout.vueとHome.vueになります。これでラウティングの設定が完了しました。あとはメニューバーなどを設置してユーザーを誘導させるようにすればよいですね。

router-linkとrouter-viewを設置

ではApp.vueに下記のようにコードを書き、ラウティングで指定したHome
とAboutにユーザーがアクセスできるようにしましょう。

その行先をクリックした際にViewを表示させるのが<router-view/>になります。

<template>
  <div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
  </div>
  <router-view/>
</template>

はい、上のようにメニューバーができて、クリックに応じてView(ページ)が変わりましたね。

これが基本的な使い方になります。

[Vue入門]Provide / Inject

Prop の過剰的な使用(Prop Drilling)

通常、親コンポーネントから子コンポーネントにデータを渡す必要がある場合、props を使用します。しかし、大きなコンポーネントツリーがあり、深くネスト化されたコンポーネントが遠い場合に親元から複数先のコンポーネントにデータを飛ばしたいシナリオを想定してください。

propsのやり方だと、親コンポーネントからつながるすべてのcomponentに同じ prop を渡さなければなりません:

<Footer> コンポーネントは親から受け取るの props を全く使わない場合でもデータの渡し役として、コンポーネントを記載する必要があります。それから<DeepChild> が<Footer>コンポーネントからこのデータにアクセスできるようにしてやっと<Root>コンポーネントのデータを受け取ることができるようになります。これでは、コードが煩雑になり、デバッグの作業も面倒になりますね。

このPropsを何階層も下に投げる作業を省くには provide と inject を使うことで解決できます。親コンポーネントは、そのすべての子コンポーネントに対して 依存関係を提供するプロバイダー (dependency provider) として機能することができます。子ツリー内のどのコンポーネントも、その深さに関係なく、親チェーン内の上位コンポーネントが提供する依存性を注入 (inject) することができます。

Provide

親コンポーネントから子コンポーネントにデータを提供するには provide() 関数を使います:

<script setup>
import { provide } from 'vue'

provide(/* key */ 'message', /* value */ 'hello!')
</script>

<script setup> を使わない場合、setup() 内で provide() が同期的に呼び出されていることを確認してください:

import { provide } from 'vue'

export default {
  setup() {
    provide(/* key */ 'message', /* value */ 'hello!')
  }
}

provide() 関数は 2 つの引数を受け付けます。第 1 引数はインジェクションキーと呼ばれ、文字列または Symbol となります。provideで使用するデータを投げるために使うニックネームのようなものですね。

このインジェクションキーは、子のコンポーネントが、インジェクション(注入)に必要な値を探すのに使われます。1 つのコンポーネントが異なる値を提供するために、異なるインジェクションキーで provide() を複数回呼び出すことができます。

第 2 引数は提供される値です。この値は refs のようなリアクティブな状態を含む、任意の型にすることができます:

import { ref, provide } from 'vue'

const count = ref(0)
provide('key', count)

リアクティブな値を提供することで、提供された値を使用する子孫コンポーネントが、プロバイダーコンポーネントとのリアクティブな接続を確立することができます。

アプリケーションレベルの Provide

コンポーネント内だけでなく、アプリケーションレベルでデータを提供することも可能です:

import { createApp } from 'vue'

const app = createApp({})

app.provide(/* key */ 'message', /* value */ 'hello!')

アプリケーションレベルの Provide は、アプリケーションでレンダリングされるすべてのコンポーネントで利用可能です。これは特にプラグインを書くときに便利です。プラグインは通常、コンポーネントを使ってデータを提供することができないからです。

Inject

親コンポーネントが提供するデータを注入するには inject() 関数を使用します:

<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>

提供された値が ref である場合、そのまま注入され、自動的にアンラップされることはありません。これにより、インジェクターコンポーネントはプロバイダーコンポーネントとのリアクティビティの接続を保持することができます。

繰り返しますが、もし <script setup> を使用しないのであれば、inject() は setup() の内部でのみ同期的に呼び出す必要があります:

import { inject } from 'vue'

export default {
  setup() {
    const message = inject('message')
    return { message }
  }
}

インジェクションのデフォルト値

デフォルトでは、inject は注入されるキーが親チェーンのどこかで提供されることを想定しています。キーが提供されていない場合、実行時が出ます。

インジェクトされたプロパティをオプションのプロバイダーで動作させたい場合は、props と同様にデフォルト値を宣言する必要があります:

// もし "message" にマッチするデータがなかった場合は、
// `value` は "default value" になります
const value = inject('message', 'default value')

場合によっては、関数を呼び出したり、新しいクラスをインスタンス化したりして、デフォルト値を作成する必要があるかもしれません。オプションの値が使用されないケースで不要な計算や副作用を避けるために、デフォルト値を作成するためのファクトリー関数を使用することができます:

const value = inject('key', () => new ExpensiveClass())

リアクティビティと共に利用する

リアクティブな値を provide / inject する場合、可能な限り、リアクティブな状態への変更を provider の内部で維持することが推奨されます。これは、提供されるステートとその可能な変更が同じコンポーネントに配置されることを保証し、将来のメンテナンスをより容易にするためです。

インジェクターコンポーネントからデータを更新する必要がある場合があります。そのような場合は、状態の変更を担当する関数を使うことをおすすめします:

<!-- プロバイダーコンポーネント内部 -->
<script setup>
import { provide, ref } from 'vue'

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})
</script>
<!-- インジェクターコンポーネント内部 -->
<script setup>
import { inject } from 'vue'

const { location, updateLocation } = inject('location')
</script>

<template>
  <button @click="updateLocation">{{ location }}</button>
</template>

最後に、provide を通して渡されたデータが注入されたコンポーネントによって変更されないようにしたい場合は、提供された値を readonly() でラップすることができます。

<script setup>
import { ref, provide, readonly } from 'vue'

const count = ref(0)
provide('read-only-count', readonly(count))
</script>

シンボルキーと共に利用する

今までの例では、文字列のインジェクションキーを使っていました。もしあなたが多くの依存関係を提供するプロバイダーを持つ大規模なアプリケーションで作業していたり、他の開発者が使用する予定のコンポーネントを作成している場合は、名前の重複を避けるためにシンボルインジェクションキーを使用するのがベストです。

シンボルは専用のファイルに書き出しておくことをおすすめします:

// keys.js
export const myInjectionKey = Symbol()
// プロバイダーコンポーネント内
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'

provide(myInjectionKey, {
  /* 提供するデータ */
})
// インジェクターコンポーネント内
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'

const injected = inject(myInjectionKey)

Django RESTクイックスタート

今日はシンプルなAPIを作成し、アドミンユーザーがブラウザから実際にAPIを見れるようにするところまで紹介したいと思います。

始める前に。。。Django REST APIはDjangoの基礎を理解していることを前提に学ぶことをお勧めします。

プロジェクトのセットアップ

 tutorialという名前のプロジェクトを作成し、 quickstartという名前でアプリを作成します。

# Create the project directory
mkdir tutorial
cd tutorial

# Create a virtual environment to isolate our package dependencies locally
python3 -m venv env
source env/bin/activate  # On Windows use `env\Scripts\activate`

# Install Django and Django REST framework into the virtual environment
pip install django
pip install djangorestframework

# Set up a new project with a single application
django-admin startproject tutorial .  # Note the trailing '.' character
cd tutorial
django-admin startapp quickstart
cd ..

プロジェクトのファイル構成はこんな感じになります。

$ pwd
<some path>/tutorial
$ find .
.
./manage.py
./tutorial
./tutorial/__init__.py
./tutorial/quickstart
./tutorial/quickstart/__init__.py
./tutorial/quickstart/admin.py
./tutorial/quickstart/apps.py
./tutorial/quickstart/migrations
./tutorial/quickstart/migrations/__init__.py
./tutorial/quickstart/models.py
./tutorial/quickstart/tests.py
./tutorial/quickstart/views.py
./tutorial/asgi.py
./tutorial/settings.py
./tutorial/urls.py
./tutorial/wsgi.py

ではデータベースを更新しましょう。

python manage.py migrate

次にDjango側のアドミンを作成します。例として、名前は admin でパスワードはpassword123にしましょう。このユーザーを後からAuthenticate(認証)で使います。

python manage.py createsuperuser --email admin@example.com --username admin

これで初期の設定はOKです。では早速ロジックを作るべく、コーディングにとりかかりましょう。

Serializers(シリアル化)

まずは、Serializersの設定をおこないます。慣習としてserializers.pyというファイル名を使うようにしましょう。では、 tutorial/quickstart/serializers.pyのファイルを作成し、データベースのデータをシリアル化させる役目をここで指示します。

from django.contrib.auth.models import User, Group
from rest_framework import serializers


class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ['url', 'username', 'email', 'groups']


class GroupSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Group
        fields = ['url', 'name']

今回は例としてHyperlinkedModelSerializerを使用しています。これはModelSerializerに似ているものです。違いは、primary keyがModelSerializerになるのですが、HyperlinkedModelSerializerはurlがpライマリーキーフィールドとして設定されます。ハイパーリンクはRESTfulのデザインとして良い慣習とされているので覚えておきましょう。

Views

Viewsでは、ユーザーからリクエストがあった際にどのような動きをさせるか指示させるファイルでしたね。通常のDjangoならHTMLのテンプレートを返したりしますが、今回はAPIを返すように指示します。

では、次の tutorial/quickstart/views.pyを開きコーディングしましょう。

from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from rest_framework import permissions
from tutorial.quickstart.serializers import UserSerializer, GroupSerializer


class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all().order_by('-date_joined')
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]


class GroupViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows groups to be viewed or edited.
    """
    queryset = Group.objects.all()
    serializer_class = GroupSerializer
    permission_classes = [permissions.IsAuthenticated]

ここでViewSetsの登場です。ViewSetsはCRUDのすべてのリクエストに対応できるスーパーセットです。これで、いちいちDELETEのリクエストやPUTのリクエストがあった動きを毎回書く必要がなくなりました。

もちろん、何か特別な動きをさせたいときはこれを上書きすることもできます。

今はこのロジックを使ってクリーンなコードのままにしておきます。

URLs

では次の tutorial/urls.pyからAPIのエンドポイントとなるURI(URL)を設定していきます。

from django.urls import include, path
from rest_framework import routers
from tutorial.quickstart import views

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
    path('', include(router.urls)),
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

ここで覚えておきたいのが、ViewSetsはひとつのURLでCRUDのすべてのリクエストを受け取ることができることです。

なので、ここでは単純に先ほど作成したViewSetsと登録するだけでOKです。

繰り返しになりますが、単純にGETリクエストだけを受け付けたい場合は普通のクラスベースのViewを設定することでできます。

最後に、Djangoでついてくるログインとログアウトのロジックを追加することができることを紹介します。例えば顧客情報などの重要な情報は認証されたユーザーしかアクセスできないようにしたいですよね。

他にもいろいろなやり方でAPIを守る方法があるのでこれから学んでいきましょう。

Pagination(ページ)

ページネーションでは、1つのリクエストに対して返すデータの数を制限することができます。

これは tutorial/settings.pyで設定することができます。

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}

Settings

次に、'rest_framework' をINSTALLED_APPSに追加してDjango側でライブライが追加されたことを登録してあげます。このセッティングのファイルはtutorial/settings.pyになります。

INSTALLED_APPS = [
    ...
    'rest_framework',
]

いいですね。


APIをテストする

では早速テストサーバーを起動してAPIを確認してみましょう。

python manage.py runserver

コマンドラインやcurlのようなツールを使ってAPIをテストすることもできます。

bash: curl -H 'Accept: application/json; indent=4' -u admin:password123 http://127.0.0.1:8000/users/
{
    "count": 2,
    "next": null,
    "previous": null,
    "results": [
        {
            "email": "admin@example.com",
            "groups": [],
            "url": "http://127.0.0.1:8000/users/1/",
            "username": "admin"
        },
    ]
}

もしくはhttpieでもこのようにテストできます。

bash: http -a admin:password123 http://127.0.0.1:8000/users/

HTTP/1.1 200 OK
...
{
    "count": 2,
    "next": null,
    "previous": null,
    "results": [
        {
            "email": "admin@example.com",
            "groups": [],
            "url": "http://localhost:8000/users/1/",
            "username": "paul"
        },
    ]
}

一番手っ取り早いのがDjangoのブラウザで確認することですね。URL http://127.0.0.1:8000/users/

Quick start image

もし、ブラウザからAPIを確認したい場合はDjangoアプリケーションにログインしていることを忘れずに確認してください。

では、これでDjango REST Frameworkのクイックスタートの説明を終了します。

[Vue入門] スロット

このページは、コンポーネントの基本をすでに読んでいることを前提としています。コンポーネントを初めて使用する場合は、最初にそれをお読みください。

スロットコンテンツとその出力先

コンポーネントはpropsを受け入れることができることを学びましたね。propは色々なタイプのJavaScriptのデータを格納することができます。しかし、テンプレートコンテンツはどうでしょうか?再利用可能なコンポーネントを作成するうえで、親のコンポーネントから自在に意図した場所にデータをレンダーしたい場合にスロットが役に立ちます。

たとえば、次の<FancyButton>ような使用法をサポートするコンポーネントがある場合があるとします。

<FancyButton>
  Click me! <!-- slot content -->
</FancyButton>

のテンプレートは<FancyButton>次のようになります。

<button class="fancy-btn">
  <slot></slot> <!-- slot outlet -->
</button>

この<slot>エレメントは、親が提供するスロットコンテンツをレンダリングする場所を示すスロットアウトレットです。

いわば枠組みを作ってあげてその中(スロット)に意図したデータを投げてあげるわけですね。

そして、最終的にレンダリングされたDOMはこのようになります。:

<button class="fancy-btn">
  Click me!
</button>

スロットを使用すると、<FancyButton>は外側(およびその派手なスタイリング)のレンダリングを担当し、<button>内側のコンテンツは親コンポーネントによって提供されます。

スロットを理解するもう1つの方法として、スロットをJavaScript関数と比較することができます。

// parent component passing slot content
FancyButton('Click me!')

// FancyButton renders slot content in its own template
function FancyButton(slotContent) {
  return (
    `<button class="fancy-btn">
      ${slotContent}
    </button>`
  )
}

スロットコンテンツはテキストだけに限定されません。有効なテンプレートコンテンツであれば何でもOKです。たとえば、複数のHTMLエレメント、または他のコンポーネントを渡すことができます。

<FancyButton>
  <span style="color:red">Click me!</span>
  <AwesomeIcon name="plus" />
</FancyButton>

スロットを使用することで、<FancyButton>をより柔軟で再利用可能なコンポーネントとして使用できるようになります。

Vueコンポーネントのスロットメカニズムは、ネイティブのWebコンポーネント<slot>要素で記載されていますが、後で説明する他の機能も備えています。

レンダリングスコープ

スロットコンテンツは、親で定義されているため、親コンポーネントのデータスコープにアクセスできます。例えば:

<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>

ここでは、両方の{{ message }}補間で同じコンテンツがレンダリングされます。

スロットコンテンツは、子コンポーネントのデータにアクセスできません。原則として、次の点に注意してください。

親テンプレートのすべてが親スコープでコンパイルされ、子テンプレートのすべてが子スコープでコンパイルされます。

スロット内容が指定されていない場合の初期値

コンテンツが提供されていない場合にのみレンダリングされるように、スロットのフォールバック(つまりデフォルト)コンテンツを指定できるという便利な機能があります。たとえば、<SubmitButton>コンポーネントでは次のようになります。

<button type="submit">
  <slot></slot>
</button>

<button>親コンポーネントでスロットコンテンツを提供しなかった場合は、「Submit」というテキストをの中に表示したい場合を想定します。<slot>タグの間にフォールバックコンテンツ(スロット内容が指定されていない場合の初期値)を記載することで初期値を「Submit」にできます。

<button type="submit">
  <slot>
    Submit <!-- fallback content -->
  </slot>
</button>

親コンポーネントで使用する場合<SubmitButton>、スロットにコンテンツを提供なかった場合。

<SubmitButton />

これにより、フォールバックコンテンツ(デフォルト値)「Submit」がレンダリングされます。

<button type="submit">Submit</button>

ただし、スロットコンテンツを提供する場合:

<SubmitButton>Save</SubmitButton>

次に、提供されたコンテンツが代わりにレンダリングされます。

<button type="submit">Save</button>

複数のスロットがある場合

1つのコンポーネントに複数のスロットがあると便利な場合があります。次のテンプレート<BaseLayout>を使用するコンポーネントでは、次のようになります。

<div class="container">
  <header>
    <!-- We want header content here -->
  </header>
  <main>
    <!-- We want main content here -->
  </main>
  <footer>
    <!-- We want footer content here -->
  </footer>
</div>

このような場合、<slot>エレメントには特別な属性として、nameというものがあります。これを使用して、コンテンツをレンダリングする場所を指定できるようになり、さまざまなスロットに意図したIDを割り当てることができます。

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

例を見てわかるようにスロットネームのないデフォルト用のの<slot>があります。name slotを使用する親コンポーネント<BaseLayout>では、それぞれが異なるスロット出力先をターゲットとする複数のスロット出力先に提供する方法が必要です。ここで名前付きスロットの出番です。

名前付きスロットを渡すには、ディレクティブで<template>要素を使用してから、v-slotを使ってスロットの名前を引数として次のように渡す必要があります。

<BaseLayout>
  <template v-slot:header>
    <!-- content for the header slot -->
  </template>
</BaseLayout>

v-slotは省略して#記載できるので、<template v-slot:header>から<template #header>と記載することができます。

3つのスロットすべてのコンテンツを<BaseLayout>省略構文を使用して渡す場合は次のとおりになります。

<BaseLayout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

  <template #default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</BaseLayout>

コンポーネントがデフォルトスロットと名前付きスロットの両方を受け入れる場合、すべての上階層の<template>ではないノード(HTML)は自動的にデフォルトスロットのコンテンツとして扱われます。したがって、上記は次のように書くこともできます。

<BaseLayout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

  <!-- implicit default slot -->
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</BaseLayout>

これで、エレメント内のすべて<template>が対応するスロットに渡されます。最終的にレンダリングされるHTMLは次のようになります。

<div class="container">
  <header>
    <h1>Here might be a page title</h1>
  </header>
  <main>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </main>
  <footer>
    <p>Here's some contact info</p>
  </footer>
</div>

繰り返しになりますが、JavaScript関数の例を使用すると、名前付きスロットをよりよく理解するのに役立つ場合と思います。

// passing multiple slot fragments with different names
BaseLayout({
  header: `...`,
  default: `...`,
  footer: `...`
})

// <BaseLayout> renders them in different places
function BaseLayout(slots) {
  return (
    `<div class="container">
      <header>${slots.header}</header>
      <main>${slots.default}</main>
      <footer>${slots.footer}</footer>
    </div>`
  )
}

ダイナミックなスロット名

動的ディレクティブ引数はv-slotでも使用可能で、動的スロット名の定義をすることができます。

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>

  <!-- with shorthand -->
  <template #[dynamicSlotName]>
    ...
  </template>
</base-layout>

式は動的ディレクティブ引数の構文制約に従うことに注意してください。

スコープスロット

レンダリングスコープで説明したように、スロットコンテンツは子コンポーネントの状態にアクセスできません。

ただし、スロットのコンテンツが親スコープと子スコープの両方のデータを利用できると便利な場合があります。これを実現するには、子がデータをレンダリングするときにスロットにデータを渡す方法が必要になります。

これは、Propsをコンポーネントに渡すのと同じように、属性をスロットアウトレットに渡すことがで実現可能になります。:

<!-- <MyComponent> template -->
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>

単一のデフォルトスロットを使用する場合と名前付きスロットを使用する場合では、スロットプロップの受信の仕方が少し異なります。v-slot子コンポーネントタグを直接使用して、最初に単一のデフォルトスロットを使用してPropsを受け取る方法を記載します。

<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

子によってスロットに渡されるpropsは、対応するv-slotディレクティブの値として使用でき、スロット内の式からアクセスできます。

スコープスロットは、子コンポーネントに渡される関数と考えることができます。次に、子コンポーネントはそれを呼び出し、Propsを引数として渡します。

MyComponent({
  // passing the default slot, but as a function
  default: (slotProps) => {
    return `${slotProps.text} ${slotProps.count}`
  }
})

function MyComponent(slots) {
  const greetingMessage = 'hello'
  return (
    `<div>${
      // call the slot function with props!
      slots.default({ text: greetingMessage, count: 1 })
    }</div>`
  )
}

実際、これはスコープ付きスロットのコンパイル方法、および手動レンダリング関数でスコープ付きスロットを使用する方法に非常に近いものです。

スロット機能のシグネチャとv-slot=”slotProps”がどのように一致するかに注意してください。関数の引数と同じように、v-slot内で次の場所で各データを使用できます。

<MyComponent v-slot="{ text, count }">
  {{ text }} {{ count }}
</MyComponent>

名前付きスコープスロット

名前付きスコープスロットも同様に機能します。スロットのPropsは、v-slotディレクティブの値としてアクセスできますv-slot:name="slotProps"。省略した記載方法を使用すると、次のようになります。

<MyComponent>
  <template #header="headerProps">
    {{ headerProps }}
  </template>

  <template #default="defaultProps">
    {{ defaultProps }}
  </template>

  <template #footer="footerProps">
    {{ footerProps }}
  </template>
</MyComponent>

名前付きスロットにPropsを渡す:

<slot name="header" message="hello"></slot>

nameスロットでnameというストリングが使用されるため、nameというプロップは制限され、Propsには含まれないことに注意してください。そのため、結果headerPropsは{ message: ‘hello’ }になります。

ファンシーリストの例

スコープ付きスロットの良い使い方は何か疑問に思われるかもしれません。次に例を記載します。<FancyList>アイテムのリストをレンダリングするコンポーネントを想像してみてください。リモートデータの読み込み、データを使用したリストの表示、さらにはページネーションや無限スクロールなどの高度な機能のロジックをカプセル化できます。ただし、各アイテムの外観に柔軟性を持たせ、各アイテムのスタイルをそれを使用する親コンポーネントに任せる必要があります。したがって、望ましい使用法は次のようになります。

<FancyList :api-url="url" :per-page="10">
  <template #item="{ body, username, likes }">
    <div class="item">
      <p>{{ body }}</p>
      <p>by {{ username }} | {{ likes }} likes</p>
    </div>
  </template>
</FancyList>

<FancyList>内部では、異なるアイテムデータを使用して同じものを複数回レンダリングできます(オブジェクトをスロットプロップとして渡すために<slot>v-bindを使用していることに注意してください)。

<ul>
  <li v-for="item in items">
    <slot name="item" v-bind="item"></slot>
  </li>
</ul>

レンダーレスコンポーネント

上で説明した<FancyList>ユースケースは、再利用可能なロジック(データのフェッチ、ページネーションなど)とビジュアル出力の両方をカプセル化し、スコープ付きスロットを介してビジュアル出力の一部をコンシューマーコンポーネントとして使用したものになります。

この概念をもう少しひねると、ロジックのみをカプセル化し、それ自体では何もレンダリングしないコンポーネントを作ることができます。ビジュアル出力は、スコープスロットを備えたコンシューマーコンポーネントに完全に任されることになります。このタイプのコンポーネントをレンダーレスコンポーネントと呼びます。

レンダーレスコンポーネントの例としては、現在のマウス位置を追跡するロジックをカプセル化したものがあります。

<MouseTracker v-slot="{ x, y }">
  Mouse is at: {{ x }}, {{ y }}
</MouseTracker>

興味深いパターンですが、レンダリングレスコンポーネントで実現できることのほとんどは、余分なコンポーネントのネストのオーバーヘッドを発生させることなく、CompositionAPIを使用してより効率的な方法で実現できます。後で、Composableと同じマウス追跡機能を実装する方法を説明します。

まとめとしてスコープスロットは、<FancyList>の例のように、ロジックの格納化とビジュアル出力の作成の両方が必要な場合で役立つことが分かりますね。

[Vue入門] フォールスルー属性

このページは、コンポーネントの基礎をすでに読んでいることを想定して説明しています。初めてコンポーネントに触れる方は、まずそちらをお読みください。

属性の継承

“フォールスルー属性”とは、あるコンポーネントに渡されたものの、受け取ったコンポーネントの props や emits で明確に宣言されていない属性、または v-on イベントリスナーを指します。よくある例としては、classstyleid 属性などがあります。

コンポーネントが単一のルート要素をレンダリングする時、フォールスルー属性は自動的にルート要素の属性に追加されます。例えば、次のようなテンプレートを持つ <MyButton> コンポーネントがあったとします:

<!-- <MyButton> のテンプレート -->
<button>click me</button>

そして、このコンポーネントを使う親が以下です:

<MyButton class="large" />

最終的に DOM は以下のようにレンダリングされます:

<button class="large">click me</button>

class と style のマージ

もし、子コンポーネントのルート要素にすでに class や style 属性がある場合は、親から継承された class や style の値にマージされます。先ほどの例の <MyButton> のテンプレートを次のように変更するとします:

<!-- <MyButton> の テンプレート -->
<button class="btn">click me</button>

そうすると、最終的にレンダリングされる DOM は、こうなります:

<button class="btn large">click me</button>

v-on リスナーの継承

同じルールが v-on イベントリスナーにも適用されます:

<MyButton @click="onClick" />

click リスナーは <MyButton> のルート要素、つまりネイティブの <button> 要素に追加されます。ネイティブの <button> がクリックされた時、親コンポーネントの onClick メソッドがトリガーされます。もし、ネイティブの <button> が既に v-on でバインドされた click リスナーを持っている場合、両方のリスナーがトリガーされます。

ネストされたコンポーネントの継承

あるコンポーネントが他の 1 つのコンポーネントをルートノードとしてレンダリングする場合を考えてみましょう。例として、<MyButton> をルートとして <BaseButton> をレンダリングするようにリファクタリングしました:

<!-- シンプルに他の 1 つのコンポーネントをレンダリングする <MyButton/> のテンプレート -->
<BaseButton />

この時、<MyButton> が受け取ったフォールスルー属性は、自動的に <BaseButton> に転送されます。

以下の点に注意してください:

  1. 転送された属性には、<MyButton> が props として宣言した属性や、宣言したイベントの v-on リスナーは含まれません。言い換えると、宣言した props とリスナーは <MyButton> によって “消費” されています。
  2. 転送された属性は、 <BaseButton> が宣言していれば、 props として受け取ることができます。

属性の継承の無効化

コンポーネントに自動的な属性の継承をさせたくない場合は、コンポーネントのオプションで inheritAttrs: false を設定することができます。

<script setup> を使用するなら、このオプションは別の通常の <script> ブロックを使って宣言する必要があります:

<script>
// 通常の <script> でオプションを宣言
export default {
  inheritAttrs: false
}
</script>

<script setup>
// ロジックのセットアップ
</script>

属性の継承を無効にする一般的なシナリオは、ルートノード以外の要素に属性を適用する必要がある場合です。 inheritAttrs オプションを false に設定することで、フォールスルー属性を適用する場所を完全に制御することができます。

これらのフォールスルー属性は、テンプレート式で $attrs として直接アクセスすることができます:

<span>Fallthrough attributes: {{ $attrs }}</span>

$attrs オブジェクトには、コンポーネントの props や emits オプションで宣言されていないすべての属性 (例えば classstylev-on リスナーなど) が含まれます。

備考:

  • props とは異なり、フォールスルー属性は JavaScript では元のケーシングを保持します。したがって、 foo-bar のような属性は $attrs['foo-bar'] としてアクセスされる必要があります。
  • @click のような v-on イベントリスナーは、オブジェクトで $attrs.onClick という関数として公開されます。

前のセクションで紹介した <MyButton> コンポーネントの例では、スタイリングのために実際の <button> 要素を <div> でラップする必要がある場合があります:

<div class="btn-wrapper">
  <button class="btn">click me</button>
</div>

class や v-on リスナーなどのすべてのフォールスルー属性を、外側の <div> ではなく、内側の <button> に適用されるようにしたいです。これは、 inheritAttrs: false と v-bind="$attrs" で実現できます:

<div class="btn-wrapper">
  <button class="btn" v-bind="$attrs">click me</button>
</div>

引数なしの v-bind はオブジェクトのすべてのプロパティをターゲット要素の属性としてバインドすることを覚えておきましょう。

複数のルートノードでの属性継承

ルートノードが 1 つのコンポーネントと異なり、複数のルートノードを持つコンポーネントは、自動的に属性をフォールスルーするふるまいがありません。 $attrs が明示的にバインドされていない場合は、実行時に警告が出ます。

<CustomLayout id="custom-layout" @click="changeValue" />

もし <CustomLayout> が以下のようなマルチルートのテンプレートを持っている場合、 Vue はどこにフォールスルー属性を適用すればよいか分からないため、警告されます:

<header>...</header>
<main>...</main>
<footer>...</footer>

警告は $attrs が明示的にバインドされている場合は抑制されます:

<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>

JavaScript 内でフォールスルー属性にアクセスする

必要であれば、<script setup> 内で useAttrs() API を使用してコンポーネントのフォールスルー属性にアクセスすることができます:

<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

もし <script setup> を使用していない場合、 attrs は setup() コンテキストのプロパティとして公開されます:

export default {
  setup(props, ctx) {
    // フォールスルー属性が ctx.attrs として公開される
    console.log(ctx.attrs)
  }
}

ここで attrs オブジェクトは常に最新のフォールスルー属性を反映していますが、リアクティブではないことに注意してください(パフォーマンス上の理由です)。ウォッチャーを使ってその変更を監視することはできません。リアクティビティが必要であれば、 prop を使ってください。または、 onUpdated() を使用して、更新されるたびに最新の attrs による副作用を実行することもできます。