目次

2023.12.14 React-adminとDjangoの連携

DjangoでREST APIを作成

27 Docker Djnago の方法で、インストールしている想定

プロジェクト名は、testproject で作成している形で説明

今回は、React-admin Django両方とも、25 Let's Encrypt リバースプロキシ でSSL化している想定で説明します。

1. settings.py 修正

rest_frameworkを追加と
後のCORS (オリジン間リソース共有、 Cross-Origin Resource Sharing) の為、corsheadersを追加。

testproject/settings.py

INSTALLED_APPS = [
    ...
    ...
    ...
    'rest_framework',
    'corsheaders',
    'testproject',
]

2. モデルの作成

testproject/models.py

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

このマイグレーションファイルが出来上がります。

testproject/migrations/0001_initial.py

# 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/

この画面になる場合

CSRF_TRUSTED_ORIGINSにURLを追加してあげる

testproject/settings.py

CSRF_TRUSTED_ORIGINS = ['https://django.fl8.jp']

今度は正しく表示される。

追加したテーブルを編集できるように変更

testproject/admin.py

from django.contrib import admin

# Register your models here.

from .models import Product

admin.site.register(Product)

編集すると、管理画面に追加したテーブルが表示されるので、テスト用にいくつかレコードを追加しておく。

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クラスを作成

testproject/serializers.py

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を使用して実装
一覧取得、登録、更新、削除などの処理が使えるようになります。

testproject/views.py

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追加

testproject/urls.py

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/

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に追加しておく。

testproject/middleware.py

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',を追加

testproject/settings.py

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は、30 React-admin で作成している想定です。

1. post.tsx用意

src/post.tsx

// in src/posts.tsx
import { SimpleForm, List, Datagrid, Create, Edit, TextInput, TextField, NumberField, ReferenceField } from "react-admin";

export const PostList = () => (
    <List>
        <Datagrid rowClick="edit">
	    <NumberField source="id" />
            <TextField source="name" />
            <TextField source="price" />
        </Datagrid>
    </List>
);

export const PostCreate = () => (
    <Create>
        <SimpleForm>
      <TextInput source="name" />
      <TextInput source="price" />
        </SimpleForm>
    </Create>
);
export const PostEdit = () => (
    <Edit>
        <SimpleForm>
      <TextInput source="name" />
      <TextInput source="price" />
        </SimpleForm>
    </Edit>
);

2. dataProvider修正

src/dataProvider.ts

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修正

src/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 = () => (
  <Admin dataProvider={dataProvider} authProvider={authProvider}>
  <Resource name="products" list={PostList} edit={PostEdit} create={PostCreate}/>
    <Resource
      name="posts"
      list={ListGuesser}
      edit={EditGuesser}
      show={ShowGuesser}
    />
    <Resource
      name="comments"
      list={ListGuesser}
      edit={EditGuesser}
      show={ShowGuesser}
    />
  </Admin>
);

4.ページ確認

このエラーになる場合、CORSの設定ができてない。

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.

25 Let's Encrypt リバースプロキシ に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

そうすると、今度はちゃんとデータ取得できるようになる。