Agile育成ブログ
未来を変える喜びを
Django Rest Framework

Django ReatFramework[SNS編]


Warning: count(): Parameter must be an array or an object that implements Countable in /home/xs638785/agile-software.site/public_html/wp-content/plugins/rich-table-of-content/functions.php on line 490
source code

仮想環境

仮想環境構築

python3 -m venv myvenv

仮想環境構築

source myvenv/bin/activate

requirements.txt

Djangoで画像ファイルを扱えるようにするためにpillowをインストールしておきます。

pip3 install -r requirements.txt

個別にpillowをインストールする場合のコマンド

pip install pillow

Django~=3.1.4
djangorestframework==3.11.0
pillow~=8.1.0

プロジェクト作成

django-admin startproject sns .

アプリケーション作成

django-admin startapp core
django-admin startapp api_user
django-admin startapp api_dm

settings.py

INSTALLED_APPS = [   
    'rest_framework',
    'rest_framework.authtoken',  
    'core.apps.CoreConfig',
    'api_user.apps.ApiUserConfig',
    'api_dm.apps.ApiDmConfig',
]
TIME_ZONE = 'ASIA/TOKYO'

ローカルホストの末尾にmedia

MEDIA_ROOT = os.path.join(BASE_DIR, '_media')
MEDIA_URL = '/media/'

coreフォルダで作成したUserモデルをデフォルトで使用するために下記のコードを追記する必要があります。

AUTH_USER_MODEL = 'core.User'

urls.py

api_userとapi_dmフォルダにurls.pyを作成します。
rest_frameworkに搭載されているobtain_auth_tokenを使用します。

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

staticファイルのurlパターンを簡単に生成してくれるメソッド

from django.urls import path, include
from rest_framework.authtoken import views
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [   
  path('authen/', views.obtain_auth_token),
    path('api/user/', include('api_user.urls')),
    path('api/dm/', include('api_dm.urls')),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
router

ModelViewSet系ビューにおける一覧・詳細・登録・更新・一部更新・削除のURLパターンをまとめて追加することができます。

router = DefaultRouter()
router.register('profile',views.ProfileViewSet)
router.register('approval', views.FriendRequestViewSet)
URLパターンを登録
from django.urls import path, include
from api_user import views
from rest_framework.routers import DefaultRouter

app_name = 'user'

router = DefaultRouter()
router.register('profile',views.ProfileViewSet)
router.register('approval', views.FriendRequestViewSet)

urlpatterns = [
    path('create/', views.CreateUserView.as_view(), name='create'),
    path('myprofile/', views.MyProfileListView.as_view(), name='myprofile'),
    path('',include(router.urls))
]
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from api_dm import views

app_name = 'dm'

router = DefaultRouter()
router.register('message', views.MessageViewSet, basename="message")
router.register('inbox', views.InboxListView, basename='inbox')

urlpatterns = [
    path('',include(router.urls))
]

models.py

User

create_userはあらかじめBaseUserManagerで定義されている。
emailが入されなかった場合はエラーを表示するように設定。
大文字で入力された場合に小文字に変換してからデータベースに登録するようにします。

https://www.agile-software.site/2021/08/24/python-%e3%82%af%e3%83%a9%e3%82%b9/
class UserManager(BaseUserManager):

    def create_user(self, email, password=None, **extra_fields):

        if not email:
            raise ValueError('email is must')

        user = self.model(email=self.normalize_email(email), **extra_fields)
        user.set_password(password)
        user.save(using=self._db)

        return user

email認証に変更しているためsuperuserの記述が必要になります。
オーバーライドした部分についてはデータベースにsaveします。

    def create_superuser(self, email, password):
        user = self.create_user(email, password)
        user.is_staff = True
        user.is_superuser = True
        user.save(using= self._db)

        return user

uniqueをtrueにしていることで違うユーザーが同じemailで新規アカウントを作ることができないようにしています。

  • AbstractBaseUser
  • PermissionsMixin:
  • BaseUserManager

AbstractBaseUserはパーミッションの機能を持っていないため同時に継承 する必要があります。

class User(AbstractBaseUser, PermissionsMixin):

    email = models.EmailField(max_length=50, unique=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = 'email'

    def __str__(self):
        return self.email

Profile

一人のユーザーに対して一つのプロフィールをOnetoOneFieldで定義。

参照先のユーザーが削除された場合プロフィールも削除されるようにon_deleteを作成します。

auto_now_add=Trueにすることでプロフィールを作成したときの時刻を登録

プロフィール画像を保存するための設定

class Profile(models.Model):
    nickName = models.CharField(max_length=20)
    userPro = models.OneToOneField(
        settings.AUTH_USER_MODEL, related_name='userPro',
        on_delete=models.CASCADE
    )
    created_on = models.DateTimeField(auto_now_add=True)
    img = models.ImageField(blank=True, null=True, upload_to=upload_path)

    def __str__(self):
        return self.nickName

upload_pathの指定

拡張子だけを取り出すことが出来ます。
mediaフォルダのimageフォルダに画像を格納するようにします。

def upload_path(instance, filename):
    ext = filename.split('.')[-1]
    return '/'.join(['image', str(instance.userPro.id)+str(instance.nickName)+str(".")+str(ext)])

FriendRequest

ユーザーの中から1人を選ぶためにForeignKeyを使います。

1度だけ申請を行うように行うようにaskFromとaskToの組み合わせが重複しないように

    class Meta:
        unique_together = (('askFrom', 'askTo'),)
class FriendRequest(models.Model):
    askFrom = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='askFrom',
        on_delete=models.CASCADE
    )
    askTo = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='askTo',
        on_delete=models.CASCADE
    )
    approved = models.BooleanField(default=False)

    class Meta:
        unique_together = (('askFrom', 'askTo'),)

    def __str__(self):
        return str(self.askFrom) + '----->' + str(self.askTo)

Message

class Message(models.Model):

    message = models.CharField(max_length=140)
    sender = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='sender',
        on_delete=models.CASCADE
    )
    receiver = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='receiver',
        on_delete=models.CASCADE
    )

    def __str__(self):
        return str(self.sender)

python3 manage.py makemigrations
python3 manage.py migrate

admin.py(core)

マーキング

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.translation import gettext as _
from core import models

field
Personal Info
Permissions



class UserAdmin(BaseUserAdmin):
    ordering = ['id']
    list_display = ['email']
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal Info'), {'fields': ('',)}),
        (
            _('Permissions'),
            {
                'fields': (
                    'is_active',
                    'is_staff',
                    'is_superuser',
                )
            }
        ),
        (_('Important dates'), {'fields': ('last_login',)}),
    )
    add_fieldsets = (
        (None, {
            'classes':('wide',),
            'fields':('email','password1','password2')
        }),
    )



admin.site.register(models.User, UserAdmin)
admin.site.register(models.Profile)
admin.site.register(models.Message)
admin.site.register(models.FriendRequest)
(myvenv) ~$ python3 manage.py makemigrations
(myvenv) ~$ python3 manage.py migrate
python3 manage.py createsuperuser

serializers.py

api_user>serializers.py

from django.contrib.auth import get_user_model
from rest_framework import serializers
from rest_framework.authtoken.models import Token
from core.models import Profile, FriendRequest

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = get_user_model()
        fields = ('id','email','password')
        extra_kwargs= {'password': {'write_only': True}}

    def create(self, validated_data):
        user = get_user_model().objects.create_user(**validated_data)
        Token.objects.create(user=user)
        return user

class ProfileSerializer(serializers.ModelSerializer):

    created_on = serializers.DateTimeField(format="%Y-%m-%d", read_only=True)

    class Meta:
        model=Profile
        fields = ('id', 'nickName', 'userPro', 'created_on', 'img')
        extra_kwargs = {'userPro': {'read_only': True}}

class FriendRequestSerializer(serializers.ModelSerializer):
    class Meta:
        model = FriendRequest
        fields = ('id','askFrom','askTo','approved')
        extra_kwargs = {'askFrom': {'read_only': True}}

api_dm>serializers.py

Qオブジェクトをimportしています。
Qオブジェクトについては下記記事を参照してください。

https://www.agile-software.site/2021/08/09/q%e3%82%aa%e3%83%96%e3%82%b8%e3%82%a7%e3%82%af%e3%83%88/

ログインしているユーザーがsenderになる。

友達申請が承認されているユーザーに対してメッセージが送れるようにFriendFilterで制限しています。
askToがログインしているユーザーかつapproved=Trueで承認されている

from rest_framework import serializers
from core.models import Message, User, Profile, FriendRequest
from django.db.models import Q

class FriendsFilter(serializers.PrimaryKeyRelatedField):

    def get_queryset(self):
        request = self.context['request']
        friends = FriendRequest.objects.filter(Q(askTo=request.user) & Q(approved=True))

        list_friend = []
        for friend in friends:
            list_friend.append(friend.askFrom.id)

        queryset = User.objects.filter(id__in=list_friend)
        return queryset

class MessageSerializer(serializers.ModelSerializer):

    receiver = FriendsFilter()

    class Meta:
        model = Message
        fields = ('id', 'sender', 'receiver', 'message')
        extra_kwargs = {'sender': {'read_only': True}}

views.py

ユーザーの新規作成

class CreateUserView(generics.CreateAPIView):
    serializer_class = serializers.UserSerializer

You cannot copy content of this page