====== 2023.12.14 React-adminとDjangoの連携 ====== ===== DjangoでREST APIを作成 ===== [[06_virtualization:05_container:27_docker_django]] の方法で、インストールしている想定 プロジェクト名は、testproject で作成している形で説明 今回は、React-admin Django両方とも、[[06_virtualization:05_container:25_let_s_encrypt_proxy]] でSSL化している想定で説明します。 ==== 1. settings.py 修正 ==== rest_frameworkを追加と 後のCORS (オリジン間リソース共有、 Cross-Origin Resource Sharing) の為、corsheadersを追加。 INSTALLED_APPS = [ ... ... ... 'rest_framework', 'corsheaders', 'testproject', ] ==== 2. モデルの作成 ==== from django.db import models class Product(models.Model): name = models.CharField(max_length=200) price = models.IntegerField() def __str__(self): return self.name === マイグレーションファイル作成 === $ docker-compose exec django python manage.py makemigrations testproject このマイグレーションファイルが出来上がります。 # Generated by Django 5.0 on 2023-12-14 13:01 from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Product', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=200)), ('price', models.IntegerField()), ], ), ] === マイグレーション実行 === マイグレーション実行すると、テーブルが作成されます。 $ docker-compose exec django python manage.py migrate === テーブル確認 === docker-compose exec db mysql -u root -p db mysql> show tables; +----------------------------+ | Tables_in_db | +----------------------------+ | auth_group | | auth_group_permissions | | auth_permission | | auth_user | | auth_user_groups | | auth_user_user_permissions | | django_admin_log | | django_content_type | | django_migrations | | django_session | | testproject_product | +----------------------------+ 11 rows in set (0.00 sec) mysql> desc testproject_product; +-------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+--------------+------+-----+---------+----------------+ | id | bigint | NO | PRI | NULL | auto_increment | | name | varchar(200) | NO | | NULL | | | price | int | NO | | NULL | | +-------+--------------+------+-----+---------+----------------+ 3 rows in set (0.01 sec) ==== 3. 管理画面での確認 ==== === 管理ユーザを作成する === $ docker-compose exec django python manage.py createsuperuser Username (leave blank to use 'root'): root Email address: hogehoge@hogehoge.com Password: Password (again): Superuser created successfully. === 管理画面にアクセス === 先程作成した管理ユーザでアクセス。 https://django.fl8.jp/admin/ この画面になる場合 {{:50_dialy:2023:12:pasted:20231214-130608.png}} CSRF_TRUSTED_ORIGINSにURLを追加してあげる CSRF_TRUSTED_ORIGINS = ['https://django.fl8.jp'] 今度は正しく表示される。 {{:50_dialy:2023:12:pasted:20231214-121722.png}} === 追加したテーブルを編集できるように変更 === from django.contrib import admin # Register your models here. from .models import Product admin.site.register(Product) 編集すると、管理画面に追加したテーブルが表示されるので、テスト用にいくつかレコードを追加しておく。 {{:50_dialy:2023:12:pasted:20231214-131147.png}} mysql> select * from newgen_product; +----+--------------------+--------+ | id | name | price | +----+--------------------+--------+ | 1 | みかん | 200 | | 2 | すいか | 300 | | 3 | おにぎり | 50 | | 4 | さんま | 30 | | 5 | ケーキ | 100 | +----+--------------------+--------+ 5 rows in set (0.01 sec) ==== 4. Serializersクラスを作成 ==== APIでデータのやり取りを行うためのSerializersクラスを作成 from rest_framework import serializers from .models import Product class ProductSerializer(serializers.ModelSerializer): class Meta: model = Product fields = ['id', 'name', 'price'] ==== 5. Viewの作成 ==== APIの処理を、ViewSetを使用して実装 一覧取得、登録、更新、削除などの処理が使えるようになります。 from rest_framework import viewsets from .models import Product from .serializers import ProductSerializer class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer ==== 6. URL追加 ==== from django.contrib import admin from django.urls import include, path from rest_framework.routers import DefaultRouter from .views import ProductViewSet router = DefaultRouter(trailing_slash=False) router.register('products', ProductViewSet) urlpatterns = [ path('admin/', admin.site.urls), path('api/', include(router.urls)), ] ==== 7.API確認 ==== https://django.fl8.jp/api/ {{:50_dialy:2023:12:pasted:20231214-131601.png}} === curlでも確認 === # curl -s https://django.fl8.jp/api/products| jq [ { "id": 1, "name": "みかん", "price": 200 }, { "id": 2, "name": "すいか", "price": 300 }, { "id": 3, "name": "おにぎり", "price": 50 }, { "id": 4, "name": "さんま", "price": 30 }, { "id": 5, "name": "ケーキ", "price": 100 } ] # curl -s https://django.fl8.jp/api/products/3| jq { "id": 3, "name": "おにぎり", "price": 50 } ==== 8. Content-Rangeを追加する設定 ==== DjangoのREST APIをReact-adminのdataProviderとして利用する場合に、Content-Rangeを求められるので、Middlewareに追加しておく。 from .models import Product class ContentRangeMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): response = self.get_response(request) if '/api/products' in request.path: total_products = Product.objects.count() response['Content-Range'] = f'items 0-{total_products}/{total_products}' # 実際の値を指定 return response ※MIDDLEWAREの一番下に、'testproject.middleware.ContentRangeMiddleware',を追加 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', 'testproject.middleware.ContentRangeMiddleware', ] === headerにContent-Rangeが追加された事確認 === # curl -is https://django.fl8.jp/api/products/3 HTTP/2 200 server: nginx/1.23.4 date: Thu, 14 Dec 2023 13:42:46 GMT content-type: application/json content-length: 41 vary: Accept, Cookie, origin allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS content-range: items 0-9/20 x-frame-options: DENY x-content-type-options: nosniff referrer-policy: same-origin cross-origin-opener-policy: same-origin strict-transport-security: max-age=31536000 {"id":3,"name":"おにぎり","price":50} ===== React-adminでREST APIを実行 ===== React-adminは、[[06_virtualization:05_container:30_react-admin]] で作成している想定です。 ==== 1. post.tsx用意 ==== // in src/posts.tsx import { SimpleForm, List, Datagrid, Create, Edit, TextInput, TextField, NumberField, ReferenceField } from "react-admin"; export const PostList = () => ( ); export const PostCreate = () => ( ); export const PostEdit = () => ( ); ==== 2. dataProvider修正 ==== import fakeRestDataProvider from "ra-data-fakerest"; import simpleRestProvider from 'ra-data-simple-rest'; import data from "./data.json"; export const dataProviderfake = fakeRestDataProvider(data, true); export const dataProvider = simpleRestProvider('https://django.fl8.jp/api'); ==== 3. App.tsx修正 ==== import { Admin, Resource, ListGuesser, EditGuesser, ShowGuesser, } from "react-admin"; import { dataProvider } from "./dataProvider"; import { authProvider } from "./authProvider"; import {PostList,PostCreate,PostEdit} from './posts'; export const App = () => ( ); ==== 4.ページ確認 ==== このエラーになる場合、CORSの設定ができてない。 {{:50_dialy:2023:12:pasted:20231214-150308.png}} Access to fetch at 'https://django.fl8.jp/api/products?filter=%7B%7D&range=%5B0%2C9%5D&sort=%5B%22id%22%2C%22ASC%22%5D' from origin 'https://react-admin.fl8.jp' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. [[06_virtualization:05_container:25_let_s_encrypt_proxy]] にCORSの設定を入れてあげる。 # docker exec -it proxy bash # vi /etc/nginx/vhost.d/django.fl8.jp add_header 'Access-Control-Allow-Origin' 'https://react-admin.fl8.jp'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; add_header 'Access-Control-Allow-Headers' 'User-Agent,Keep-Alive,Content-Type,Range,Content-Range'; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; # docker restart proxy そうすると、今度はちゃんとデータ取得できるようになる。 {{:50_dialy:2023:12:pasted:20231214-151200.png}} {{tag>日記 Django React}}