Djangoroidの奮闘記

python,django,angularJS1~三十路過ぎたプログラマーの奮闘記

Angular4 + Django1.11 vol.5

参考サイト

https://www.codingforentrepreneurs.com/projects/angular-django/ http://www.django-rest-framework.org/api-guide/generic-views/#listapiview http://qiita.com/kura07/items/c9fa858870ad56dfec12

Integrate Django API with Angular

  • videos.service.ts のendpointを、django rest_framework のapiのurlに変更する。
const endpoint = '/api/videos/'
  • お〜〜表示された!すげー!

Detail View

  • detail viewを作成するために、まずは api/views.pyに、RetrieveAPIViewを追加する。
from django.contrib.auth.models import User

from rest_framework import generics
from rest_framework.permissions import IsAdminUser

from videos.models import Video
from .serializers import VideoSerializer

class VideoList(generics.ListAPIView):
    queryset                = Video.objects.all()
    serializer_class        = VideoSerializer
    permission_classes      = []
    authentication_classes  = []

class VideoDetail(generics.RetrieveAPIView):
    queryset                = Video.objects.all()
    serializer_class        = VideoSerializer
    permission_classes      = []
    authentication_classes  = []
  • 次に、urls.pyにdetail viewのurlを追加する。
from django.conf.urls import url

from .views import VideoList, VideoDetail

urlpatterns = [
    url(r'^$', VideoList.as_view(), name='list'),
    url(r'^(?P<slug>[\w-]+)/$', VideoDetail.as_view()),
]
# ここでは、slugからvideoDetailをgetするようにセットしてある
  • http://127.0.0.1:8000/api/videos/bound-method-videotitle-of-video-new-title/ にアクセスしてみる。。。error発生!
AssertionError at /api/videos/bound-method-videotitle-of-video-new-title/
Expected view VideoDetail to be called with a URL keyword argument named "pk". Fix your URL conf, or set the `.lookup_field` attribute on the view correctly.
  • lookup_fieldのセットが必要ぽいので、セットする。
class VideoDetail(generics.RetrieveAPIView):
    queryset                = Video.objects.all()
    serializer_class        = VideoSerializer
    lookup_field            = "slug"
    permission_classes      = []
    authentication_classes  = []
  • これだけで修正完了(^^)すごい!

  • VideoDetail用のVideoDetailSerializerも作成する。

class VideoDetailSerializer(serializers.ModelSerializer):
    image       = serializers.SerializerMethodField()
    is_promo    = serializers.SerializerMethodField()
    class Meta:
        model = Video
        fields = [
            'name',
            'slug',
            'embed',
            'featured',
            'image',
            'is_promo'
            ]

    def get_image(self,obj):
        return "/static/ang/assets/images/nature/4.jpg"

    def get_is_promo(self,obj):
        return False
  • VideoDetailに、serializer_class=VideoDetailSerializerを、セットすれば完了。

  • videos.service.tsのget(slug)メソッドを修正する。

get(slug){
  return this.http.get(endpoint + slug + "/") // endpoint + slug + "/"に設定する。
          .map(response=>response.json())
          .catch(this.handleError)
    // return this.http.get(endpoint)
    //         .map(response=>{
    //                let data = response.json().filter(item=>{
    //                                 if (item.slug == slug) {
    //                                     return item
    //                                 }
    //                             })
    //                if (data.length == 1){
    //                    return data[0]
    //                }
    //                return {}
    //          })
    //         .catch(this.handleError)
}
  • これで、get methodは、django rest_frameworkのVideoDetailからJSONデータをゲットして、表示させるというロジックになる。

Backend API Search

  • backendのdjangoに、Search後のqueryを出力する機能をつける。
class VideoList(generics.ListAPIView):
    queryset                = Video.objects.all()
    serializer_class        = VideoSerializer
    permission_classes      = []
    authentication_classes  = []

    def get_queryset(self):
        query = self.request.GET.get("q")
        if query:
          qs = Video.objects.filter(name__icontains=query)
        else:
          qs = Video.objects.all()      
        return qs
  • http://127.0.0.1:8000/api/videos/?q=newになどのようにアクセスして、検索結果が表示されればOK(^^)

  • videos.service.ts のsearch(query)メソッドのendpointを修正する。以下のように予想した。

search(query){
  return this.http.get(endpoint + "?q=" + query)
          .map(response=>response.json())
          .catch(this.handleError)
        }
  • 上記でも動く気がするんだけど、動画内では以下のように記載していた。
search(query){
  let querystring = `?q=${query}`
  return this.http.get(endpoint + querystring)
          .map(response=>response.json())
          .catch(this.handleError)
  // return this.http.get(endpoint)
  //           .map(response=>{
  //                  let data = []
  //                  let req = response.json().filter(item=>{
  //                                 if (item.name.indexOf(query) >=0) {
  //                                      data.push(item)
  //                                 }
  //                             })
  //
  //                  return data
  //            })
  //           .catch(this.handleError)

}
  • videos.service.tsは以下の通りに全体を修正したある。
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';


// const endpoint = '/static/ang/assets/json/videos.json' // http://www.yourdomain.com/api/videos/
const endpoint = '/api/videos/'

@Injectable()
export class VideoService {
  constructor(private http: Http) { }

  list(){
      return this.http.get(endpoint)
              .map(response=>response.json())
              .catch(this.handleError)
  }
  get(slug){
    return this.http.get(endpoint + slug + "/")
            .map(response=>response.json())
            .catch(this.handleError)
  }

  search(query){
    let querystring = `?q=${query}`
    return this.http.get(endpoint + querystring)
            .map(response=>response.json())
            .catch(this.handleError)
  }

  private handleError(error:any, caught:any): any{
      console.log(error, caught)
  }

}
  • home.component.ts の、featuredされたitemのみを表示するという機能もbackendに移行させる。
ngOnInit() {
    this.req = this._video.featured().subscribe(data=>{
        //console.log(data.json())
        //this.homeImageList
        this.homeImageList = data as [VideoItem]
        // data.filter(item=>{
        //     if(item.featured){
        //       let dataItem = item
        //         this.homeImageList.push(dataItem)
        //     }
        // })
        // this.homeImageList = data.json();
     })
}
  • featuredメソッドは、まだ定義されていないので、videos.service.tsに追加する。
featured(){
    return this.http.get(endpoint + "featured/")
            .map(response=>response.json())
            .catch(this.handleError)
}
  • backendのdjangoに、featured のurlを作成していないので、設定する。
lass VideoFeatured(generics.ListAPIView):
    queryset                = Video.objects.all()
    serializer_class        = VideoSerializer
    permission_classes      = []
    authentication_classes  = []

    def get_queryset(self):
        query = self.request.GET.get("q")
        if query:
          qs = Video.objects.filter(name__icontains=query).filter(featured=True)
        else:
          qs = Video.objects.filter(featured=True)
        return qs
  • urls.py に、featured viewのurlをセットする。
from django.conf.urls import url

from .views import VideoList, VideoDetail, VideoFeatured

urlpatterns = [
    url(r'^$', VideoList.as_view(), name='list'),
    url(r'^featured/$', VideoFeatured.as_view(), name='featured'),
    url(r'^(?P<slug>[\w-]+)/$', VideoDetail.as_view()),
]
  • http://127.0.0.1:8000/api/videos/featured/にアクセスして、rest_framework viewが表示されればOK(^^)

まとめ

  • endpointから必要なデータを取得して、client側では加工しないほうが、シンプルな実装ができそうだな。frontendとbackendを分けるメリットはこのへんにあるのか。

  • let querystring = ?q=${query} については、ちゃんと意味を調べないとな。。とりあえず、バッククオートは、複数行のコメントをするときに便利らしい。あと${変数}の表現が使える。

  • あと、${変数}で、+などの演算子を使わずに、文字列をつなげることができるらしい。なので、let querystring = `?q=${query}は、let querystring = '"?q=" + queryと同じはず。でも書いてみて思ったけどたしかに文字列をつなげるときは、${}のほうが使いやすいそう(^^)

  • このqiitaの記事がわかりやすかったです! http://qiita.com/kura07/items/c9fa858870ad56dfec12

  • はじめから、apiを使う仕様で作成したほうが楽そうだなぁ(^^)