Nuxt TypeScript(Composition API)、Django REST Framework で、・・・
タイトル長すぎですね。途中で切れました。
本当は
Nuxt TypeScript(Composition API)、Django REST Framework で、Docker Compose上での開発基盤を作る
こいつの続きというか。
この悪戦苦闘の結果、まあまあ良い構成ができたと思っており、それを使って、さらにもう一つアプリを作ってます。スッキリいくと思うので、そのスッキリした記録を残そうと思ったのですが、そうは問屋はおろさず・・・それでも、上記よりは落ち着いてできたので、こちらに残しておきます。
現時点の様子はこんな感じ。
この後も開発を進めますが、下記の状態は、この時点ですね。
あれ、タイトル間違っている・・・
フォルダ名は、作ろうとしているアプリの名前をそのまま使ってしまっていますが、お気にならさらず。
Docker環境
まず、下記フォルダ構成を作成。
以下、jq-rally としているところは、自身で作成するアプリの名前にしてください。
(作業フォルダ)
├── jq-rally-api
├── jq-rally-db
│ ├── mysql
├── jq-rally-web
次に、jq-rally-api/Dockerfile を作成
FROM python:3.8
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
RUN pip install mysqlclient
ADD . /code/
# CMD python3 manage.py runserver 0.0.0.0:8000
EXPOSE 8000
requirements.txt を作成
asgiref==3.3.4
certifi==2020.4.5.1
chardet==3.0.4
coreapi==2.3.3
coreschema==0.0.4
dj-database-url==0.5.0
dj-static==0.0.6
Django==3.2
django-cors-headers==3.7.0
django-filter==2.4.0
django-rest-swagger==2.2.0
django-toolbelt==0.0.1
djangorestframework==3.12.2
djangorestframework-jwt==1.11.0
drf-writable-nested==0.6.2
gunicorn==20.1.0
idna==2.9
itypes==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
mysqlclient==1.4.6
openapi-codec==1.3.2
psycopg2==2.8.6
PyJWT==1.7.1
pytz==2019.3
requests==2.23.0
simplejson==3.17.0
sqlparse==0.3.1
static3==0.7.0
typing-extensions==3.10.0.0
uritemplate==3.0.1
urllib3==1.25.9
whitenoise==5.2.0
そして、下記、docker-compose.yml 作成
version: '3'
services:
db:
container_name: jq-rally-db
image: mysql:8.0
restart: always
environment:
MYSQL_DATABASE: rally_database
# MYSQL_USER: root
MYSQL_ROOT_PASSWORD: password
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
volumes:
- ./jq-rally-db/mysql:/var/lib/mysql
ports:
- 3306:3306
api:
container_name: jq-rally-api
build: ./jq-rally-api
command: python3 manage.py runserver 0.0.0.0:8000
volumes:
- ./jq-rally-api/:/code
ports:
- "8000:8000"
depends_on:
- db
# web:
# container_name: jq-rally-web
# build: ./jq-rally-web
# ports:
# - 3000:3000
# volumes:
# - ./jq-rally-web:/app
# - /app/node_modules
# tty: true
# working_dir: /app
# command: sh -c "cd jq-rally/ && yarn install && yarn dev"
(MySQLのrootのパスワードの設定は8.0で変わりましたかね)
この状態
(作業フォルダ)
├── jq-rally-api
│ ├── Dockerfile
│ ├── requirements.txt
├── jq-rally-db
│ ├── mysql
├── jq-rally-web
├── docker-compose.yml
では、起動
docker compose up --build
注意:webのところをコメントアウトしていないと
ERROR: Service 'web' failed to build : Build failed
というエラー。
また、現時点では、Django プロジェクトを作っていないので、
jq-rally-api | python3: can't open file 'manage.py': [Errno 2] No such file or directory
jq-rally-api exited with code 2
となっている。
起動確認してみると下記状態。
$ docker compose ps
NAME SERVICE STATUS PORTS
jq-rally-db db restarting
このペースで行くと、どんだけ長くなるんだという感じなので、飛ばし飛ばし・・・
Django プロジェクト生成
下記を参照させていただいています。
conifg アプリケーション
この辺は飛ばしていきます。
作成された settings.py を編集(またあとで変更しますが、いったん)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'rally_database',
'USER': 'root',
'HOST': 'db',
'PORT': 3306,
'OPTIONS': {
'charset': 'utf8mb4',
'sql_mode': 'TRADITIONAL,NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY',
},
}
}
起動しているが、
django.db.utils.OperationalError: (2061, 'RSA Encryption not supported - caching_sha2_password plugin was built with GnuTLS support')
のエラー
対応としては、下記参照
MySQLでcaching_sha2_passwordのエラーが出る
MySQL8.0新機能 (caching_sha2_password 認証プラグイン)を回避したい。
$ docker exec -it jq-rally-db sh
# mysql -u root -p
mysql> show variables like 'default_authentication_plugin';
+-------------------------------+-----------------------+
| Variable_name | Value |
+-------------------------------+-----------------------+
| default_authentication_plugin | caching_sha2_password |
+-------------------------------+-----------------------+
mysql> exit
# cat /etc/mysql/my.cnf
注意:従来はパスワードなしで入れていたが、mysql 8.0 で変わっている。
/etc/mysql/my.cnf は編集できない(viはインストールされていない)ので、
これが参照している
/etc/mysql/conf.d/mysql.cnf
に追記してあげる。
ついでに、文字コードもセットしておく。
下記で、ファイル全上書きします。
# cat - > /etc/mysql/conf.d/mysql.cnf
[mysql]
default_authentication_plugin=mysql_native_password
default-character-set=utf8mb4
[mysqld]
character-set-server = utf8mb4
skip-character-set-client-handshake
collation-server = utf8mb4_general_ci
init-connect = SET NAMES utf8mb4
Ctrl+Cで抜けます。
しかし、これはだめでした。
# mysql -u root -p
mysql: [ERROR] unknown variable 'default_authentication_plugin=mysql_native_password'.
いったん戻して(この行だけ消しました)、こちらを参照。
PythonからMySQLへの接続でcaching_sha2_passwordのエラーが出た場合
作成するユーザjqは、自身のアプリに応じて名前を決めてください。
# mysql -u root -p
mysql> use mysql;
mysql> create user 'jq'@'%' identified WITH mysql_native_password by 'password';
mysql> grant all on rally_database.* to 'jq'@'%' with grant option;
mysql> flush privileges;
mysql> select user, host, plugin from mysql.user;
+------------------+-----------+-----------------------+
| user | host | plugin |
+------------------+-----------+-----------------------+
| jq | % | mysql_native_password |
| root | % | caching_sha2_password |
| jq | localhost | mysql_native_password |
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session | localhost | caching_sha2_password |
| mysql.sys | localhost | caching_sha2_password |
| root | localhost | caching_sha2_password |
+------------------+-----------+-----------------------+
# mysql -u jq -p
mysql> use rally_database;
Database changed
jqで接続すれば、mysql_native_password で接続できるはず。
こうした上で、作成したユーザで接続するよう、settings.py を修正。
docker再起動。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'rally_database',
'USER': 'jq',
'PASSWORD': 'password',
'HOST': 'db',
'PORT': 3306,
'OPTIONS': {
'charset': 'utf8mb4',
'sql_mode': 'TRADITIONAL,NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY',
},
}
}
localhost:8000 を開くと
Django 3.2
The install worked successfully! Congratulations!
という画面が表示されます。
アプリケーション追加
これも、基本的に上述の下記サイトの通りです。
途中エラー
'staticfiles' is not a registered tag library. Must be one of:
admin_list
・・・
下記で、staticfiles ではなく、static にします。
$ docker exec -it jq-rally-api /bin/bash
cat /usr/local/lib/python3.8/site-packages/django/template/defaulttags.py
grep -A 3 -B 1 -n staticfiles /usr/local/lib/python3.8/site-packages/rest_framework_swagger/templates/rest_framework_swagger/index.html
sed -i -e "s/load staticfiles/load static/" /usr/local/lib/python3.8/site-packages/rest_framework_swagger/templates/rest_framework_swagger/index.html
localhost:8000/swagger が開くようになります。
jq-rally-api/config/settings.py
djangorestframework-jwt の有効化
以上で、Django REST framework のプロジェクト生成が一通り完了となります。
Nuxtアプリケーション
アプリケーション作成
Dockerfile 作成
FROM node:14.17.0-alpine
ENV LANG=C.UTF-8 TZ=Asia/Tokyo
WORKDIR /app
RUN apk update
# COPY ./package*.json ./
RUN npm install
COPY ./ .
ENV HOST 0.0.0.0
EXPOSE 3000
続いて、docker-compose.yml の最後の1行以外を有効化
web:
container_name: jq-rally-web
build: ./jq-rally-web
ports:
- 3000:3000
volumes:
- ./jq-rally-web:/app
- /app/node_modules
tty: true
working_dir: /app
# command: sh -c "cd jq-rally/ && yarn install && yarn dev"
docker compose up --build
途中に
jq-rally-web | Welcome to Node.js v14.17.0.
というメッセージが見えます。
別のターミナルで、create nuxt-app を実行します。
TypeScript にして、Vuetify.js を使います。
$ docker exec -it jq-rally-web sh
/app# yarn create nuxt-app jq-rally
create-nuxt-app v3.7.0
✨ Generating Nuxt.js project in jq-rally
? Project name: jq-rally
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: Vuetify.js
? Nuxt.js modules: Axios - Promise based HTTP client
? Linting tools: ESLint
? Testing framework: Jest
? Rendering mode: Single Page App
? Deployment target: Static (Static/Jamstack hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Continuous integration: None
? Version control system: Git
docker compose を一旦終了し、docker-compose.yml の最後の1行を有効化し、docker compose up
docker compose up --build
localhost:3000
vueのロゴが変わった・・・
composition-api追加
本構成の大きなポイントである composition-api 追加をやっておきます。
$ docker exec -it jq-rally-web sh
/app # cd jq-rally/
/app/jq-rally # yarn add @nuxtjs/composition-api
特に問題なく動いている。念の為再起動しても問題なし。
Typescript関連環境調整
nuxt.configをTSに変更
git mv jq-rally-web/jq-rally/nuxt.config.js jq-rally-web/jq-rally/nuxt.config.ts
shims-vue.d.ts ファイル追加
tsconfig.json にちょっと先の話も含めて追記
firebase、jestはまだコメントアウト
{
"compilerOptions": {
"target": "ES2018",
"module": "ESNext",
"moduleResolution": "Node",
"lib": [
"ESNext",
"ESNext.AsyncIterable",
"DOM"
],
"esModuleInterop": true,
"allowJs": true,
"sourceMap": true,
"strict": true,
"noEmit": true,
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"~/*": [
"./*"
],
"@/*": [
"./*"
]
},
"resolveJsonModule": true,
"types": [
"@nuxt/types",
"@nuxtjs/axios",
// "@nuxtjs/firebase",
"@types/node",
"vuetify",
// "@types/jest",
// "vue-scrollto"
]
},
"files": [
"shims-vue.d.ts"
],
"include": [
"*.ts",
"api/**/*.ts",
"components/**/*.ts",
"components/**/*.vue",
"compositions/**/*.ts",
"constants/**/*.ts",
"layouts/**/*.ts",
"layouts/**/*.vue",
"store/**/*.ts",
"plugins/**/*.ts",
"utils/**/*.ts",
"types/**/*.ts",
"pages/**/*.ts",
"pages/**/*.vue",
"test/**/*.ts"
],
"exclude": [
"node_modules",
".nuxt",
"dist"
]
}
そして、上階層にtsconfig.jsonの拡張用のファイルを置くことで、エディタ上のエラーを回避します。
{
"extends": "./jq-rally-web/jq-rally/tsconfig.json",
"compilerOptions": {
"baseUrl": "./jq-rally-web/jq-rally",
}
}
いったん
no such file or directory, open '/app/jq-rally/nuxt.config.js'
のエラーが発生しますが、再起動すれば問題なし
MemoデータのCRUDを作る(まずはREAD)
長くなりましたが、ここで画面作成。
API経由で、MemoデータのCRUDをおこないます。
まず、メニュー追加。修正箇所抜粋。追加の前のカンマも追加してますが。
items: [
{
icon: 'mdi-apps',
title: 'Welcome',
to: '/'
},
{
icon: 'mdi-chart-bubble',
title: 'Inspire',
to: '/inspire'
},
// 追加
{
icon: 'mdi-pencil',
title: 'Memo',
to: '/memo'
},
],
ページ追加。フォルダ作成して、index.vue を置く形にしておきます。
<template>
<div>
<v-card dark>
<v-card-title class="pa-2">
<h3 class="title grow">メモ</h3>
</v-card-title>
</v-card>
<!-- メモリスト -->
<!-- <memo-list /> -->
</div>
</template>
<script lang="ts">
// import {
// defineComponent,
// } from '@nuxtjs/composition-api'
// import MemoList from '~/components/memo/MemoList.vue'
// export default defineComponent({
// components: { MemoList },
// setup() {
// return {
// }
// }
// })
</script>
コンポーネントを呼び出すスクリプトはコメントアウトしておきます。
そういうことしなくて良いように、Storybook を使うんですよね、きっと。
そこはまだ導入してません。
で、コンポーネントを作りますが、その手順はこんな感じです。
jq-rally-web/jq-rally/types/index.ts
jq-rally-web/jq-rally/api/xxxRepository.ts
jq-rally-web/jq-rally/api/index.ts
jq-rally-web/jq-rally/api/createRepository.ts
jq-rally-web/jq-rally/compositions/useXxx.ts
jq-rally-web/jq-rally/compositions/index.ts
types/index.ts 最初なのでファイルを作りますが、以降何かコンポーネントを作る時は、追記していきます。
export type ResponseType<V> = Promise<V>
export type ResponseMapType<K extends string, V> = Promise<{ [P in K]: V }>
export type ResponseTypes<T> = Promise<T>
export type CustomErrors = {
errors: {
errorName: string[]
}
}
export interface ListRequestType {
limit?: number
offset?: number
}
export interface MemoType {
id: number
title: string
memo: string
}
同じフォルダに、plugin-types.d.ts も置いておきます。
import { Repository } from '@/api'
declare module 'vue/types/vue' {
interface Vue {
$repository: Repository
}
}
declare module '@nuxt/types' {
interface NuxtAppOptions {
$repository: Repository
}
interface Context {
$repository: Repository
}
}
declare module 'vue/types/vue' {
interface Vue {
$scrollTo: any
}
}
これは最初に作っておけば、以降は気にしなくて良いです。
export const ErrorType = {
Unauthorized: 401,
Forbidden: 403,
NotFound: 404,
Unprocessable: 422,
}
export const LIMIT_LIST_ITEM = 20
export const LIMIT_LIST_ITEM_LARGE = 60
これをデータごとに作っていきます。
import { NuxtAxiosInstance } from '@nuxtjs/axios'
import {
Memo,
ResponseType,
ResponseTypes,
CustomErrors,
} from '@/types'
import { LIMIT_LIST_ITEM } from '@/constants'
type Memoid = Memo['id']
export interface MemoListRequest {
limit?: number
offset?: number
}
export type CreateMemoRequest = Pick<
Memo,
'title' | 'memo'
>
export type UpdateMemoPayload = Partial<CreateMemoRequest>
export type UpdateMemoRequest = {
payload: UpdateMemoPayload
id: Memoid
}
// type MemoResponse = ResponseType<'memoData', Memo>
type MemoResponse = ResponseType<Memo>
// type MemoListResponse = ResponseTypes<{
// memos: Memo[]
// memosCount: number
// }>
type MemoListResponse = ResponseTypes<Memo[]>
export const memoRepository = (axios: NuxtAxiosInstance) => ({
getMemo(memoid: Memoid): MemoResponse {
return axios.$get(`/memo/${memoid}`)
},
getMemoList({
limit = LIMIT_LIST_ITEM,
offset = 0,
}: MemoListRequest = {}): MemoListResponse {
const defaultParam = {
}
console.log('getMemoList', axios.$get('/memo', { params: {...defaultParam, limit, offset}, }))
return axios.$get('/memo/', {
params: {...defaultParam, limit, offset},
})
},
createMemo(payload: CreateMemoRequest): MemoResponse | CustomErrors {
console.log('createMemo', payload)
return axios.$post('/memo/', payload)
},
updateMemo(request: UpdateMemoRequest): MemoResponse | CustomErrors {
return axios.$put(`/memo/${request.id}/`, request.payload )
},
deleteMemo(memoid: Memoid) {
return axios.$delete(`/memo/${memoid}/`)
},
})
export type MemoRepository = ReturnType<typeof memoRepository>
apiを増やすごとに追加していきます。
export * from './createRepository'
export * from './memoRepository'
これもapiを増やすごとに、最初と最後の部分を追加していきます。
import { Context } from '@nuxt/types'
import {
memoRepository,
MemoRepository,
} from '@/api'
import { ErrorType } from '@/constants'
export type Repository = {
memo: MemoRepository
}
/**
* @see https://axios.nuxtjs.org
* @see https://github.com/gothinkster/realworld/tree/3155494efe68432772157de38a90c49b3698897f/api
*/
const createRepository = ({ app, $axios, redirect }: Context): Repository => {
$axios.onError((error) => {
console.log('createRepository error', error, error.response?.data)
if (!error.response) {
return
}
const code = error.response.status
if (code === ErrorType.Unprocessable) {
return Promise.reject(error.response.data.console.errors)
}
if (code === ErrorType.Unauthorized) {
redirect('/login')
return
}
if (code === ErrorType.Forbidden) {
app?.router?.back()
return
}
if (code === ErrorType.NotFound) {
redirect('/')
}
})
console.log('createRepository', $axios)
return {
memo: memoRepository($axios),
}
}
export default createRepository
実際画面から呼び出す処理。
import { reactive, useContext } from '@nuxtjs/composition-api'
import { MemoListRequest, CreateMemoRequest, UpdateMemoRequest } from "@/api/memoRepository";
import { Memo } from "@/types";
// import MemoKey from '~/store/memo-key';
type MemoPayload = Required<CreateMemoRequest>
type CreateState = MemoPayload
type State = {
memoData: Memo
memoList: Memo[]
memoCount: number
}
const initCreateState = {
title: '',
memo: '',
}
const initState = {
memo: {
id: 0,
title: '',
memo: '',
},
}
export default function useMemo() {
const { $repository } = useContext()
const createState = reactive<CreateState>(initCreateState)
// const state = reactive<State>(initState)
const state = reactive<State>({
memoData: {
id: 0,
title: '',
memo: '',
},
memoList: [],
memoCount: 0,
})
const getMemo = async (memoid: Memo['id']) => {
const memoData = await $repository.memo.getMemo(memoid)
console.log('memoid', memoid)
console.log('getMemo', memoData)
state.memoData = memoData
}
const getMemoList = async(payload: MemoListRequest = {}) => {
const memos= await $repository.memo.getMemoList(payload)
console.log('memos', memos)
state.memoList = memos
state.memoCount = memos.length
}
const createMemo = async (payload: CreateMemoRequest) => {
const response = await $repository.memo.createMemo(payload)
console.log('createMemo response', response)
if (response) {
await getMemoList()
return response
}
return false
}
const updateMemo = async (payload: UpdateMemoRequest) => {
const memoData = await $repository.memo.updateMemo(payload)
if (memoData) {
return memoData
}
return false
}
const deleteMemo = async (memoid: Memo['id']) => {
await $repository.memo.deleteMemo(memoid)
await getMemoList()
}
return {
createState,
state,
getMemo,
getMemoList,
createMemo,
updateMemo,
deleteMemo,
}
}
処理追加ごとに追記
import useMemo from "./useMemo";
export {
useMemo,
}
準備が整ったので、画面コンポーネントを作成
<template>
<div>
<v-card dense>
<v-data-table
:headers="headers"
:items="memoList"
:search="search"
hide-default-header
dense>
<template v-slot:item="{ item }">
{{ item }}
</template>
</v-data-table>
</v-card>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, useFetch, defineComponent } from '@nuxtjs/composition-api';
import { useMemo } from '@/compositions';
export default defineComponent({
name: 'MemoList',
setup() {
const headers = [
{ text: 'タイトル', value: 'title' },
{ text: 'メモ', value: 'memo' },
{ text: '', value: 'actions', sortable: false },
];
const state = reactive({
search: '',
filter: {},
sortDesc: false,
});
const { state: memoState, getMemoList } = useMemo();
const fetchData = async (offset = 0) => {
await getMemoList({ offset });
};
const { fetchState } = useFetch(() => fetchData());
return {
headers,
...toRefs(state),
...toRefs(memoState),
fetchState,
};
},
});
</script>
そして、pageのコメントを外す
<template>
<div>
<v-card dark>
<v-card-title class="pa-2">
<h3 class="title grow">メモ</h3>
</v-card-title>
</v-card>
<!-- メモリスト -->
<memo-list />
</div>
</template>
<script lang="ts">
import {
defineComponent,
} from '@nuxtjs/composition-api'
import MemoList from '~/components/memo/MemoList.vue'
export default defineComponent({
components: { MemoList },
setup() {
return {
}
}
})
</script>
ここまでやると、こんなエラー
./node_modules/@nuxtjs/composition-api/dist/runtime/index.mjs 276:47-55
Can't import the named export 'computed' from non EcmaScript module (only default export is available)
./node_modules/@nuxtjs/composition-api/dist/runtime/index.mjs 303:11-19
Can't import the named export 'computed' from non EcmaScript module (only default export is available)
./node_modules/@nuxtjs/composition-api/dist/runtime/index.mjs 304:11-19
Can't import the named export 'computed' from non EcmaScript module (only default export is available)
・・・ずっと続く
ここまで現時点の推奨版node:14.17でやっていましたが、16.3に変更
docker compose up --build
そういう問題でもなかった
問題は
./node_modules/@nuxtjs/composition-api/dist/runtime/index.mjs
なので、いちど削除
バージョン指定して再インストール
$ docker exec -it jq-rally-web sh
/app # cd jq-rally/
/app/jq-rally # yarn remove @nuxtjs/composition-api
/app/jq-rally # yarn add '@nuxtjs/composition-api@^0.22.4'
composition-api を入れた後は、
nuxt.config.ts への追記も忘れずに。
plugins: [
'@/plugins/composition-api',
'@/plugins/repository',
// '@/plugins/vue-scrollto'
],
buildModules: [
// https://go.nuxtjs.dev/typescript
'@nuxt/typescript-build',
// https://go.nuxtjs.dev/vuetify
'@nuxtjs/vuetify',
'@nuxtjs/composition-api'
],
axios: {
baseURL: 'http://localhost:8000/api/',
},
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'
Vue.use(VueCompositionApi)
// import { MemoRepository } from "@/repositories/memoRepository";
// export interface Repositories {
// memo: MemoRepository
// }
// export default function( { $axios }: any, inject: any) {
// const memo = new MemoRepository($axios)
// const repositories: Repositories = {
// memo
// }
// inject('repositories', repositories)
// }
import { Plugin } from '@nuxt/types'
import createRepository from '@/api/createRepository'
const repository: Plugin = (ctx, inject) => {
const repositoryWithAxios = createRepository(ctx)
inject('repository', repositoryWithAxios)
}
export default repository
API側でCORS対応もしておく
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CORS_ORIGIN_WHITELIST = (
'http://localhost:3000',
'https://jqrally-web.netlify.app',
)
'corsheaders.middleware.CorsMiddleware',
は
'django.middleware.common.CommonMiddleware',
より上に
これで、データ表示がされました。
MemoデータのCRUDを作る(CUD)
constを定義しておいて
import { MemoType } from "@/types";
export const defaultMemoItem: MemoType = {
id: 0,
title: '',
memo: '',
};
CRUD用のコンポーネント作成
<template>
<v-card>
<v-card-title> メモの管理 </v-card-title>
<v-card-text>
<!-- メモ追加 -->
<v-card>
<v-card-text>
<v-form lazy-validation>
<v-text-field
label="タイトル"
dense
outlined
clearable
v-model="form.title"
>
</v-text-field>
<v-text-field
label="メモ"
dense
outlined
clearable
v-model="form.memo"
>
</v-text-field>
<v-btn color="primary" @click="handleCreateMemo">新規メモ</v-btn>
</v-form>
</v-card-text>
<v-snackbar v-model="snackbar">
更新しました。
<template v-slot:action="{ attrs }">
<v-btn
color="success"
outlined
text
v-bind="attrs"
@click="snackbar = false"
>
Close
</v-btn>
</template>
</v-snackbar>
</v-card>
<!-- メモ一覧 -->
<v-data-table :headers="headers" :items="memoList" :search="search" dense>
<template v-slot:top>
<v-text-field
dense
v-model="search"
clearable
flat
solo-inverted
hide-details
prepend-inner-icon="mdi-magnify"
label="Search"
></v-text-field>
</template>
<template v-slot:[`item.actions`]="{ item }">
<v-icon small @click="editItem(item)">mdi-pencil</v-icon>
<v-icon small @click="deleteItem(item)"> mdi-delete </v-icon>
</template>
</v-data-table>
<v-dialog v-model="dialogEdit" max-width="500px">
<v-card>
<v-card-title class="headline">構成員情報更新</v-card-title>
<v-text-field
label="タイトル"
dense
outlined
clearable
v-model="editedItem.title"
>
</v-text-field>
<v-text-field
label="メモ"
dense
outlined
clearable
v-model="editedItem.memo"
>
</v-text-field>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="updateItem">更新</v-btn>
<v-btn color="blue darken-1" text @click="close"
>Cancel</v-btn
>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialogDelete" max-width="500px">
<v-card>
<v-card-title class="headline">削除しますか?</v-card-title>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="close"
>Cancel</v-btn
>
<v-btn color="blue darken-1" text @click="deleteItemConfirm"
>OK</v-btn
>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
</v-card-text>
</v-card>
</template>
<script lang="ts">
import { PropType } from "vue";
import {
Data,
defineComponent,
reactive,
ref,
toRef,
toRefs,
useFetch,
onMounted,
} from "@nuxtjs/composition-api";
import { useMemo } from "@/compositions";
import { defaultMemoItem } from "@/compositions/util/const";
import { MemoType } from "@/types";
export default defineComponent({
name: "MemoAdmin",
setup(_, { root }) {
const headers = [
{ text: "タイトル", value: "title" },
{ text: "メモ", value: "memo" },
{ text: "", value: "actions", sortable: false },
];
const state = reactive({
search: "",
dialogDelete: false,
dialogEdit: false,
editedIndex: 0,
editedItem: defaultMemoItem,
snackbar: false,
});
const {
state: memoState,
createState: createMemoState,
createMemo,
updateMemo,
getMemoList,
deleteMemo,
} = useMemo();
const editItem = (item: MemoType) => {
state.editedIndex = item.id;
state.editedItem = item;
console.log("item", state.editedItem);
state.dialogEdit = true;
};
const updateItem = async () => {
console.log("MemoAdmin updateItem", state.editedItem);
await updateMemo({ id: state.editedItem.id, payload: state.editedItem });
close();
};
const deleteItem = (item: MemoType) => {
state.editedIndex = item.id;
state.editedItem = item;
state.dialogDelete = true;
};
const deleteItemConfirm = async () => {
console.log("deleteItemConfirm", state.editedItem);
const memoid: number = state.editedItem.id;
deleteMemo(memoid);
// fetchData()
close();
};
const close = () => {
state.dialogEdit = false;
state.dialogDelete = false;
root.$nextTick(() => {
state.editedItem = Object.assign({}, defaultMemoItem);
state.editedIndex = 0;
});
};
const handleCreateMemo = async () => {
try {
console.log("createMemoState", createMemoState);
const newMemo = await createMemo(createMemoState);
if (!newMemo) {
return;
}
state.snackbar = true;
} catch (error) {
console.log("error", error);
}
};
const fetchData = async (offset = 0) => {
await getMemoList({ offset });
console.log("fetchData");
};
const { fetchState } = useFetch(() => fetchData());
// console.log('fetchState', fetchState)
return {
headers,
...toRefs(state),
fetchState,
fetchData,
...toRefs(memoState),
form: createMemoState,
handleCreateMemo,
editItem,
updateItem,
deleteItem,
deleteItemConfirm,
close,
};
},
});
</script>
<style>
</style>
コンポーネントをページに表示します。
<template>
<div>
<!-- メモ編集 -->
<memo-admin />
</div>
</template>
<script lang="ts">
import {
defineComponent,
} from '@nuxtjs/composition-api'
import MemoAdmin from '~/components/memo/MemoAdmin.vue'
export default defineComponent({
components: { MemoAdmin },
setup() {
return {
}
}
})
</script>
git add .
git commit -m "CRUD"
git push
トップページデザイン
Carousels
Latest
Two lines and subheader
以上!
やっぱりまとまりないですよね・・・。
Discussion