個人開発記録　2024/05/07

## 目的

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


## 開発するもの

X(旧Twitter)のパクリ

## 構成

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

## 前回

https://qiita.com/kare-pan/items/7a910401dc0035761813

## 本編

今回は必須項目を`username`から`email`に変更しようかと思います。

### dj-rest-authをインストール

Django REST Frameworkを使ったユーザー認証をするための便利パッケージ。
※django-rest-authと間違わないように

https://qiita.com/howaito01/items/c20f62e7012cdfac873e

### Userモデルの修正

大きな変更点は以下。

* `User`の修正
    * `User`の継承を`AbstractUser`から`AbstractBaseUser`に変更
    * `USERNAME_FIELD`に`email`を指定
        * これにより`username`の代わりに`email`を使用できるようになる
* `UserManager`の追加
    * これにより`email`を必須項目にできる

```python:likeX/user/models.py
class UserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError("The Email field must be set")
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)

        return self.create_user(email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True)
    first_name = models.CharField(max_length=30, blank=True)
    last_name = models.CharField(max_length=30, blank=True)
    bio = models.CharField(max_length=280, blank=True)
    location = models.CharField(max_length=100, blank=True)
    birth_date = models.DateField(null=True, blank=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    # superuserを作成する際にemail以外で追加で必須にする項目を定義
    REQUIRED_FIELDS = []

    def __str__(self):
        return self.email
```

マイグレーションを実行。

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

### CustomLoginSerializerを作成

今回から追加したdj-rest-authの`LoginSerializer`を継承。

```python:likeX/user/serializers.py
from dj_rest_auth.serializers import LoginSerializer
from django.contrib.auth import authenticate
from rest_framework import serializers

class CustomLoginSerializer(LoginSerializer):
    username = None
    email = serializers.EmailField(required=True)

    def get_auth_user(self, username, email, password):
        user = authenticate(username=email, password=password)
        if not user:
            raise serializers.ValidationError("Invalid email or password")
        return user

```

### CustomLoginViewを作成

こちらもdj-rest-authの`LoginView`を継承。
`urls.py`でCustomLoginViewを使用するように修正。

```python:likeX/user/views.py
class CustomLoginView(LoginView):
    serializer_class = CustomLoginSerializer
```

```python:likeX/user/urls.py
urlpatterns = [
    re_path(r'^register/$', UserRegister.as_view()),
    path('login/', CustomLoginView.as_view()),
]
```

### 会員登録、ログインのフロント修正

修正といっても、`username`を`email`に変更するだけ。

```js:src/auth/components/RegisterForm.jsx
  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')}`
      first_name: data.get('firstName'),
      last_name: data.get('lastName')
    };

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

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

    const loginData = {
      // 削除→　username: data.get('email'),
      email: data.get('email'),
      password: data.get('password'),
    };

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

### CSRFエラーが発生

CSRFエラーが発生していろいろ修正。。
明日整理する。

## 感想

chat-GPTに効きながら作成していったが、言ってることが正しいのかどうかが判断できないと遅くなる。。
（今回だと`django-rest-auth`を最初に推奨された）
今回は二転三転して、時間がかなりかかった。