【Django】REST frameworkでプロジェクトにデータPOSTしてくるクライアントのIPアドレスをDBに格納する

#はじめに
DjangoのREST frameworkを使うようなアプリケーションを持つプロジェクトではよく「uplink」でデータ収集をすることがあります．私はその逆方向の通信を行う「downlink」をする機会があったため，「そもそもuplink時に誰がデータをPOSTしてきてるの？」っていうことを動的に知る必要がありました．
そこで，データPOSTしてくるゲートウェイ等のクライアントのIPアドレスをDBに格納しておいてdownlinkのときに，そのIPアドレスを使おうってことになりました．

#結論
Djangoプロジェクトへのアクセスリクエストに含まれるHTTPヘッダの`'REMOTE_ADDR'`からクライアントのIPアドレスを取得してあげれば良いみたいです．

###参考
[DjangoでクライアントのIPアドレスを取得する](https://qiita.com/3244/items/0b47d3ad91968fe15eb9)


#実装手順
###前提
Python,Django及びREST frameworkに関して経験のあることを前提として記事を書いております．POSTAPIの作成に関しては以下の記事がわかりやすいと思います．

[Django Rest Framework で RESTful な APIを作成する](https://qiita.com/Ajyarimochi/items/1345a49d70805db289a7)

###環境
CentOS Linux release 7.9.2009 (Core)
Python 3.6.15
Django 3.2.0
djangorestframework 3.12.3
###1.REST frameworkをインストールする

```sh
$ pip install djangorestframework
```

###2.設定ファイルにREST frameworkを追加
※設定ディレクトリ名は`config`であると仮定してます

```diff:./config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
+   'rest_framework', # データPOSTAPI
　　　   …
]
```

###3.uplink用のアプリケーションを作成する

```sh
$ python manage.py startapp uplink
```

###4.設定ファイルにuplinkアプリケーションを追加
※設定ディレクトリ名は`config`であると仮定してます

```diff:./config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # データPOSTAPI
+   'uplink.apps.UplinkConfig', # アップリンク（データ収集）機能
　　　　　　　　…
]
```

###5.uplinkアプリケーションのURLを定義する

```diff:./config/urls.py
urlpatterns = [
    …
+   path('uplink/', include('uplink.urls')),
    …
]
```

###6.IPアドレスフィールドをカラムに持つモデルを作成する

```python:./uplink/models.py
from django.db import models
from django.utils import timezone

class PostData(models.Model):
    """
    POSTされるデータのモデルクラス

        create_at: データPOST時刻               
    client_ipaddr: データをPOSTしたクライアントのIPアドレス
    """
    class Meta:
        db_table="post_data"
        verbose_name='PostData'
        verbose_name_plural='PostData'

    create_at = models.DateTimeField(
        verbose_name='create_at',
        blank=False,
        null=False,
        editable=False, #編集不可
        default=timezone.now,
    )
    
    client_ipaddr = models.GenericIPAddressField(
        verbose_name='Client IP address',
        blank=False,
        null=False,
        default='0.0.0.0',
        editable=False, #編集不可
    )
```

###7.adminにモデルを追加する
```python:./uplink/admin.py
from django.contrib import admin

from .models import PostData

@admin.register(PostData)
class PostDataAdmin(admin.ModelAdmin):
    list_display = (
        'create_at',
        'client_ipaddr',
    )
```

###8.URLを定義する
```python:./uplink/urls.py
from django.urls import path
from . import views

app_name = 'uplink'
urlpatterns = [
    path('data_post/', views.CreateData.as_view(), name='data_post'),
]
```


###9.シリアライザを定義する
※このシリアライザを定義する際に，`client_ipaddr = serializers.IPAddressField()`を定義しないとうまくいきませんでした．

```python:./uplink/serializer.py
"""
データをPOSTするときのシリアライザ
"""

from rest_framework import serializers

from .models import PostData

class PostDataSerializer(serializers.ModelSerializer):
    # IPアドレス型のデータとして用意しておく
    client_ipaddr = serializers.IPAddressField()

    class Meta:
        model = PostData
        fields = (
            '__all__'
        )
        read_only_fields = ('create_at', 'client_ipaddr', )
```

###10.Viewを定義する

```python:./uplink/views.py
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser

from django.utils import timezone

from .serializer import PostDataSerializer

class CreateData(APIView):
    """
    ゲートウェイからPOSTされたデータをデータベースに格納する
    """
    parser_classes = (MultiPartParser, FormParser, )

    def post(self, request, format=None):
        # POSTデータのペイロードをシリアライズするために整形
        data = {}
        data['create_at'] = timezone.localtime(timezone.now())
        client_ipaddr = request.META.get('REMOTE_ADDR')
        data['client_ipaddr'] = client_ipaddr
        # シリアライザにかける
        serializer = PostDataSerializer(data=data)
        # シラアライズの成否で処理を変更
        if serializer.is_valid():
            serializer.save()
            print('●Valied data post.')
            print(serializer.validated_data)
            return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
        # エラーを吐いたら詳細を見せてもらう
        print('●Data post is invalied.')
        print(serializer.errors)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
```

###11.データベースマイグレーション
```sh
$ python manage.py makemigrations
$ python manage.py migrate
```

#まとめ
色々と書きましたが，REST frameworkを問題なく使える方にとっては「結論」の内容とシリアライザを定義するときの注意点だけ覚えていってもらえればいいと思います．Djagnoを使ったdownlinkも限定的なものになってしまいますが，随時書ければなと思います．

※疑問点や修正点などはご連絡頂けると嬉しいです

#続き
この記事の手順を完了したあとの確認方法を書いていませんでした．
[【Django】REST frameworkでプロジェクトにデータPOSTしてくるクライアントのIPアドレスをDBに格納する（続）](https://qiita.com/masaoguchi/items/bd13bf90f40f6ac90ea5)にその確認方法を書きましたので，是非ご確認ください．

#参考
[アップリンク（uplink）とは - IT用語辞典 e-Words](https://e-words.jp/w/%E3%82%A2%E3%83%83%E3%83%97%E3%83%AA%E3%83%B3%E3%82%AF.html)
[DjangoでクライアントのIPアドレスを取得する](https://qiita.com/3244/items/0b47d3ad91968fe15eb9)
[IPAddressField in serializers – Django REST Framework](https://www.geeksforgeeks.org/ipaddressfield-in-serializers-django-rest-framework/)
[Django Rest Framework で RESTful な APIを作成する](https://qiita.com/Ajyarimochi/items/1345a49d70805db289a7)