個人開発記録　2024/04/30~2024/05/2

# 目的

* とにかくやりきることが一番の目的
* 毎日発信することで、発信する習慣作り
* 個人開発をやりきることで、実績作り


# 開発するもの

X(旧Twitter)のパクリ

# 構成

* バックエンド
    * python
    * django REST framework
* DB
    * postgreSQL
* フロントエンド
    * React

# 本編

前回会員登録画面の表示ができたので、会員登録APIの作成とreactから会員登録APIを実行するところを作成する。

## djangoで会員登録APIの作成をする

### `user`アプリを作成する

[この記事](https://qiita.com/kare-pan/items/d9c8730f6f7e71433702)で`tweet`アプリは作成した。
それとは別に`user`を作成することで、機能を分ける。

```bash
django-admin startapp user
```

設定を追加

```python:likeX/settings.py
INSTALLED_APPS = [
    'likeX.user.apps.UserConfig',
]
```

### Userモデルの作成

`tweet`のmodels.pyから移設。

```python:/likeX/user/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    bio = models.CharField(max_length=280, blank=True)
    location = models.CharField(max_length=100, blank=True)
    birth_date = models.DateField(null=True, blank=True)
    # groups と user_permissions の related_name を変更する
    groups = models.ManyToManyField(
        'auth.Group',
        verbose_name='groups',
        blank=True,
        help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.',
        related_name='tweet_user_set',  # ここを変更
        related_query_name='user',
    )
    user_permissions = models.ManyToManyField(
        'auth.Permission',
        verbose_name='user permissions',
        blank=True,
        help_text='Specific permissions for this user.',
        related_name='tweet_user_set',  # ここを変更
        related_query_name='user_permission',
    )
```

デフォルト設定だと、`django.contrib.auth`が適用されていて、認証userとして`User`が適用されないため、以下の設定を追加。

```python:likeX/settings.py
AUTH_USER_MODEL = 'user.User'
```

すでに作成済みのテーブルをすべて削除し、マイグレーションを実行。
こうすることで、作成済みの`django.contrib.auth`のテーブルが削除され、`User`のテーブルが認証系のテーブルとして適用される。

```bash
python manage.py migrate
```

### serializer,viewの作成

```python:/likeX/user/serializer.py
from rest_framework import serializers

from .models import User


class UserSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = User
        fields = ('username', 'email', 'password')

    def create(self, validated_data):
        # パスワードを validated_data から削除し、ユーザー作成後に設定する
        password = validated_data.pop('password', None)
        user = User.objects.create_user(**validated_data)
        if password is not None:
            user.set_password(password)
            user.save()
        return user
```

```python:/likeX/user/views.py
from django.db import transaction
from rest_framework import permissions, generics
from rest_framework.response import Response
from rest_framework import status
from .serializer import UserSerializer
from .models import User


# ユーザ作成のView(POST)
class UserRegister(generics.CreateAPIView):
    permission_classes = (permissions.AllowAny,)
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @transaction.atomic
    def post(self, request):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
```

### 会員登録APIのURLを設定

```python:/likeX/user/urls.py
from django.urls import re_path
from .views import UserRegister

urlpatterns = [
    re_path(r'^register/$', UserRegister.as_view()),
]
```

### 全体のURL設定に会員登録APIのURLを追加

```python:/likeX/urls.py
from django.urls import include, path
from rest_framework import routers

from likeX.tweet import views

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

urlpatterns = [
    path('', include(router.urls)),
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    path('api/user/', include('likeX.user.urls')), # ←追加
]
```

## reactで会員登録APIを実行する処理を追加

### axisoをインストール

`--save`が`package.json`に保存するという意味であることを今日知った。。

```bash
npm install axios --save
```

### 会員登録APIを実行するJSを作成

`bulletproof-react`にならってAPI実行のJSを作成。
`bulletproof-react`では、DTOを作成していたが、Typescriptではないため断念。。
ゆくゆくはTypescriptにも移行したい。

```js:/src/features/auth/api/register.js
import axios from 'axios';
import { API_URL } from '../../../config';

export const registerUser = (data) => {
    const jsonData = JSON.stringify(data);
    const config = {
        headers: {
            'Content-Type': 'application/json'
        }
    };
    const url = `${API_URL}/api/user/register/`;
    return axios.post(url, jsonData, config)
};
```

### 入力フォームのコンポーネントにAPI実行の処理を追加

chatGPT作のため、コメントが多い。。
usernameはdjango側では英数字で入力する、`username`だが、フロント側では氏名になっていしまっているがここはゆくゆく直していく。

```js:/src/features/auth/components/RegisterForm.js
  const handleSubmit = (event) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);

    // FormDataから直接データを取得してオブジェクトに格納し、usernameをfirstNameとlastNameから構成
    const registrationData = {
      email: data.get('email'),
      password: data.get('password'),
      username: `${data.get('firstName')}${data.get('lastName')}`
    };

    // API 関数を使用してデータを送信
    registerUser(registrationData)
      .then((response) => {
        console.log('Registration successful:', response);
        // ここに成功時の処理を書く、例えばログインページへリダイレクト等
      })
      .catch((error) => {
        console.error('Registration failed:', error);
        // ここにエラー処理を書く、例えばエラーメッセージの表示等
      });
  };
```

## 画面から実行

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2263720/02fcfaff-defb-5e70-51cb-2b995a0431b6.png)

CORSエラーが発生!!
(CORSエラーが発生しても登録自体はできているぽい。。)

```
Access to XMLHttpRequest at 'http://127.0.0.1:8000/api/user/register/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
```

## CORSエラーの解消

### インストール

`django-cors-headers`をvenvに追加。

### 設定の変更

```
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # ←CommonMiddlewareの上に追加
    'django.middleware.common.CommonMiddleware',
]

# すべてのオリジンからのアクセスを許可する場合
CORS_ALLOW_ALL_ORIGINS = False

# 特定のオリジンだけを許可する場合
CORS_ALLOWED_ORIGINS = [
    'http://localhost:3000',  # Reactなどのフロントエンドが動作しているオリジン
]

# 特定のオリジンでのクッキーを許可する場合（認証を伴う場合など）
CORS_ALLOW_CREDENTIALS = True
```

再実行して、CORSエラーが発生していないことを確認。

# 感想

1日2時間程度で試行錯誤しながら記事も書きながらだと、なかなか思うように進まない。。
どこかのタイミングで知識の整理とコードの整理をしたい。
やると決めたからにはやる！