投稿日: | 最終更新日:
【API】DRFのアプロダ作成 その③バリデーション【Django】
DRFのアップローダアプリを作成します。フォルダ操作・ファイル操作に対してバリデーションを作成します。
バリデーションの追加
前回、アップローダーをツリー構造(入れ子)にしました。今回は、バリデーション(エラーチェック)を追加します。Compositeの作成に関して、エラーメッセージのケースを考えます。
- 指定した親ディレクトリが自分
- 同じ階層に、同じ名前のファイル・ディレクトリ
- ファイルなのに、ファイルが添付されてない
- ディレクトリなのに、ファイルが添付されてる
これらのチェックを書く場所は幾つかありますが、今回はシンプルに、シリアライザーのvalidateメソッドに書いていきます。
作業の流れ
mac側
1.VSCODEを起動します。
サンドボックス(Vagrant側)
1.vagrantを起動して、Amazon Linux2にログインします。
2.Djnangoプロジェクトを作成したディレクトリへ移動します。
前回まで
今回の作業は、以下の続きです。
→【仮想環境】MacにVirtualBoxをインストール【Mac】
→【Vagrant】MacにAWS公式のAmazon Linux2 Vagrant 環境を作成【オンプレミス】
→【Vagrant】MacにAWS公式のAmazon Linux2 Vagrant Boxを作成、起動【オンプレミス】
→Vagrant】Macに用意したVagrantのAmazon Linux2 環境にVirtualBox Guest Additionsをインストール【オンプレミス】
→【Vagrant】Macに用意したAmazon Linux2 環境にPythonをインストール【オンプレミス】
→【Vagrant】Macに用意したAmazon Linux2 環境にuwsgi、Django、Django Restframeworkをインストール【オンプレミス】
→【Vagrant】Macに用意したAmazon Linux2 環境にsqlite3、nginxをインストール【オンプレミス】
→【Vagrant】Macに用意したAmazon Linux2 環境にDjangoプロジェクト作成【オンプレミス】
→【Vagrant】DRFアプリケーション作成、テスト起動【Django】
※今回の記事は、既にAmazonLinux2のVagrant環境を作成済みであるとします。
環境
私のPC環境は以下の通りです。
PC | MacBook Air (Retina, 13-inch, 2019) |
---|---|
CPU | 1.6 GHz デュアルコアIntel Core i5 |
メモリ | 16 GB 2133 MHz LPDDR3 |
OS | 10.15.2 |
VirtualBox | 6.1.12r139181 |
Vagrant | 2.2.9 |
仮想環境
仮想環境は以下の通りです。
OS | Amazon Linux release 2 (Karoo) |
---|---|
Python | 3.7.9 |
Django | 3.2 |
djangorestframework | 3.12.4 |
djangorestframework-jwt | 1.11.0 |
django-cleanup | 5.2.0 |
uWSGI | 2.0.19.1 |
Nginx | 1.18.0 |
Sqlite3 | 1.18.0 |
Vagrantのディレクトリ構成
- ~/Work/
- Vagrant/
- AmazonLinux2/
AmazonLinux2仮想環境へログイン
①「ターミナル」を開いて下記のコマンドを入力します。
cd ~/Work/Vagrant/AmazonLinux2
②下記のコマンドを入力して仮想環境を起動します。
Vagrant up
③下記のコマンドを入力してログインします。
vagrant ssh
シリアライザにバリデーション追加
①以下のようなDjangoアプリ構成を作ります。プロジェクト名が「config」で、アプリケーション名が「nuploader1」です。プロジェクトの設定は済ませたものとします。赤字のファイルは、今回変更する部分です。
- mysite/
- config/
- __init__.py
- settings.py
- urls.py
- wsgi.py
- nuploader1/
- __init__.py
- __pycache__
- migrations
- models.py
- serializers.py
- urls.py
- views.py
- templates
- db.sqlite3
- manage.py
②nuploader1/serializers.pyに以下の内容を記述します。チェックを書く場所は幾つかありますが、シリアライザーのvalidateメソッドに書いていきます。
from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from .models import Composite class CompositeSerializer(serializers.ModelSerializer): parent = SimpleCompositeRelation(queryset=Composite.objects.filter(is_dir=True), required=False, allow_null=True) composite_set = SimpleCompositeSerializer(many=True, read_only=True) class Meta: model = Composite fields = ('pk', 'name', 'is_dir', 'src', 'parent', 'zip_depth', 'composite_set') def validate(self, attrs): parent = attrs['parent'] name = attrs['name'] is_dir = attrs['is_dir'] if (self.instance and parent) and (parent.pk == self.instance.pk): raise serializers.ValidationError('親ディレクトリが自分です') # 同名ファイル・ディレクトリがないかチェック same_names = Composite.objects.filter(parent=parent, name=name) if self.instance: # 更新の場合は、自分が同名ファイルとして出てくるので、それは除く same_names = same_names.exclude(pk=self.instance.pk) if same_names.exists(): raise serializers.ValidationError('同じ名前のファイル・ディレクトリが既に存在します') # ファイルの送信処理があった if 'src' in attrs: src = attrs['src'] # 送られたファイルの中身があり、ディレクトリ指定 if is_dir and src: raise serializers.ValidationError('ディレクトリの時は、ファイルを添付しないでください') # ファイルフラグだが、ファイルの中身は空 if not is_dir and not src: raise serializers.ValidationError('ファイルの時は、ファイルを添付してください') # ファイルの送信はなかった else: if not self.instance: # ファイルの送信はなかったのに、ファイルフラグ if not is_dir: raise serializers.ValidationError('ファイルの時は、ファイルを添付してください') else: src = self.instance.src # ファイルの送信はなかったし、アップロード済みでもないのにファイルフラグ if not is_dir and not src: raise serializers.ValidationError('ファイルの時は、ファイルを添付してください') return attrs
こういったバリデーションはフロントエンド側でもある程度はできますが、サーバー側でもチェックは必須です。
解説
def validate(self, attrs): parent = attrs['parent'] name = attrs['name'] is_dir = attrs['is_dir']
Validate関数またはビューでカスタム検証を適用します。
if (self.instance and parent) and (parent.pk == self.instance.pk): raise serializers.ValidationError('親ディレクトリが自分です')
指定した親ディレクトリが自分だった場合、「親ディレクトリが自分です」を表示します。
③SimpleCompositeRelationにも処理を追加します。
from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from .models import Composite class CompositeSerializer(serializers.ModelSerializer): ~(省略)~ class SimpleCompositeRelation(serializers.RelatedField): default_error_messages = { 'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'), 'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'), } def to_representation(self, value): return SimpleCompositeSerializer(value).data def to_internal_value(self, data): try: return self.get_queryset().get(pk=data) except ObjectDoesNotExist: self.fail('does_not_exist', pk_value=data) except (TypeError, ValueError): self.fail('incorrect_type', data_type=type(data).__name__) def get_choices(self, cutoff=None): queryset = self.get_queryset() if queryset is None: return {} if cutoff is not None: queryset = queryset[:cutoff] # https://github.com/encode/django-rest-framework/issues/5141 return dict([ ( item.pk, self.display_value(item) ) for item in queryset ])
ホームのファイル・ディレクトリ一覧
あるCompositeに紐づくCompositeの一覧は取得できています。しかし、何処にも紐づかないComposite、parentがNoneなComposite…最上位にあるファイル・ディレクトリの一覧が取得できません。
- mysite/
- config/
- __init__.py
- settings.py
- urls.py
- wsgi.py
- nuploader1/
- __init__.py
- __pycache__
- migrations
- models.py
- serializers.py
- urls.py
- views.py
- templates
- db.sqlite3
- manage.py
①今あるビュの一覧処理を上書きします。nuploader1/views.pyに記述します。
from django.shortcuts import render # render(request, 'hello.html', context) from django.http import HttpResponse # HttpResponse('Hello, World !!') from django.shortcuts import render from django.views import View from .models import Composite from .serializers import CompositeSerializer from rest_framework import viewsets, permissions # 追加 class CompositeViewSet(viewsets.ModelViewSet): queryset = Composite.objects.all() serializer_class = CompositeSerializer permission_classes = [permissions.IsAuthenticatedOrReadOnly] # 追加 def list(self, request, *args, **kwargs): queryset = Composite.objects.filter(parent__isnull=True) queryset = self.filter_queryset(queryset) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data)
解説
def list(self, request, *args, **kwargs): queryset = Composite.objects.filter(parent__isnull=True) queryset = self.filter_queryset(queryset) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data)
listメソッドは全てのデータを取得する際に呼ばれるメソッドです。listメソッドは、「http://127.0.0.1:8000/uploader/api/composites/」のURLで呼ばれます。
if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data)
このURLへのアクセスだった場合にだけ、parentがNoneな一覧を取得するようにしています。中身の処理は、もともとのlistメソッドの処理とほぼ同じで、parentがNoneなものに絞り込んだだけです。
「http://127.0.0.1:8000/uploader/api/composites/1/」とかの単体データの取得では呼ばれないメソッドです。また、作成や更新でも呼ばれないため、他への影響はありません。
ただし、今回のように上書きすると全てのデータを取得するエンドポイントがなくなります。
ログイン必須にする
データの作成や更新、削除処理はログイン済でなければできないようにします。通常のDjangoでは、「django.contrib.auth.mixins.LoginRequired」といったMixinがありましたが、DRFでは以下のように書きます。
- mysite/
- config/
- __init__.py
- settings.py
- urls.py
- wsgi.py
- nuploader1/
- __init__.py
- __pycache__
- migrations
- models.py
- serializers.py
- urls.py
- views.py
- templates
- db.sqlite3
- manage.py
from django.shortcuts import render # render(request, 'hello.html', context) from django.http import HttpResponse # HttpResponse('Hello, World !!') from django.shortcuts import render from django.views import View from .models import Composite from .serializers import CompositeSerializer from rest_framework import viewsets, permissions # 追加 class CompositeViewSet(viewsets.ModelViewSet): queryset = Composite.objects.all() serializer_class = CompositeSerializer permission_classes = [permissions.IsAuthenticatedOrReadOnly] # 追加 〜(省略)〜
permissions.IsAuthenticatedOrReadOnlyは、読み取りは自由で、作成などの処理はログイン必須にします。
次回
フロントエンド(Vue.js)の準備をします。
- Python 114
- 制作 54
- RaspberryPi 41
- Django 40
- WordPress 40
- Linux 27
- VPS 22
- JavaScript 21
- PHP 20
- HTML・CSS 19
- AWS 16
- 仮想環境 15
- レスポンシブデザイン 13
- マイコン 11
- WEB全般 11
- 動画製作 9
- Webサービス 8
- 統合開発環境 8
- 機械学習 8
- PyCharm 7
- jQuery 7
- AfterEffects 7
- 起業・設立 7
- Django REST framework 6
- C# 6
- デザイン 6
- SEO 6
- pydata 6
- Visual Studio 5
- 数学 5
- 携帯サイト 5
- heroku 5
- Mac 5
- illustrator 5
- node.js 5
- Anaconda 5
- Nginx 4
- Jupyter Notebook 4
- インフラ 4
- Google Colaboratory 4
- symfony 4
- Webスクレイピング 3
- photoshop 3
- Go言語 3
- PC 3
- ツール 3
- Docker 3
- facebook 3
- 作業効率化 3
- データベース 3
- Cloud9 3
- コマンド 2
- micro:bit 2
- Kali Linux 2
- Webサーバー 2
- MariaDB 2
- ドローン 2
- コンテナ 2
- DaVinci Resolve 2
- ネットワーク 2
- Java 2
- movie 2
- PCDJ 2
- 音楽 2
- XSERVER 2
- Ansible 1
- Vue.js 1
- JSON 1
- Bootstrap 1
- バージョン管理システム 1
- SSL 1
- S3 1
- ムームードメイン 1
- ネットワーク 1
- アニメーション 1
- D3.js 1
- Rhino 1
- アニメ 1
- git 1
- windows 1
- アクセス解析 1
- スマートフォン 1
- アフィリエイトノウハウ 1
- 知識 1
- TypeScript 1
- 役立つ本・書籍 1
- データサイエンス 1
- ESP32 1
- AI 1
- ownCloud 1
- API 1