Djangoroidの奮闘記

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

Angular4 + Django1.11 vol.10

参考サイト

https://www.codingforentrepreneurs.com/projects/angular-django/

Go Live with Heroku, Django, & Angular

  • heroku へのdeployの準備をする。

  • settings.pyのコードの修正をする。

# settingsフォルダを作成して、その中に、settingsファイルを設定するため、BASE_DIRの階層を1つ深くしておく。
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  • tryangular/settings フォルダを作成する。

  • settingsフォルダ内に, __init__.py, base.py, local.py, production.pyを作成する。

  • __init__.pyにコードを追記する。

from .base import *

from .production import *
  • production.pyにdeploy用の設定を書いていく。まず、Emailの設定をする。
# EMAILの設定
EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_HOST_USER = '*************' # sendgridのuser
EMAIL_HOST_PASSWORD = '*************' # sendgridのpass
EMAIL_PORT = 587
EMAIL_USE_TLS = True

DEFAULT_FROM_EMAIL = 'Your Name <you@email.com>'
ADMINS = (
    ('You', 'you@email.com')
)
MANAGERS = ADMINS
  • herokuのdeployに必要なmoduleをpip installする。
$ pip install dj-database-url dj-static django-toolbelt gunicorn psycopg2 static3
  • requirements.txtを作成する。
$ pip freeze > requirements.txt
  • .gitignoreファイルをbackend/src内に作成する。(.gitignoreは、djangoデプロイ用と、djangular4プロジェクト全体を分けておく)
.DS_Store
client/node_modules
client/e2e
client/bootstrap-overview

tryangular/settings/local.py

backend/bin
backend/include
backend/lib


# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# dotenv
.env

# virtualenv
.venv
venv/
ENV/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# compiled output
/dist
/tmp
/out-tsc

# dependencies
/node_modules

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
testem.log
/typings

# e2e
/e2e/*.js
/e2e/*.map

# System Files
.DS_Store
Thumbs.db
  • Procfileを、backend/src内に作成する。
web: gunicorn tryangular.wsgi --log-file -
  • production.py にheroku用のdatabaseの設定を追記する。
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
## 以下を追記
import dj-database-url
db_from_env = dj-database-url.config()
DATABASES['default'].update(db_from_env)
  • さらに, production.pyにSTATICFILES_STORAGEを設定する。
STATIC_URL = '/static/'

# STATIC_ROOTをstaticherokuに変更する。
STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'staticheroku')

STATICFILES_DIRS = [
        os.path.join(BASE_DIR, 'static')
]

# STATICFILES_STORAGEを設定する。
STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
  • src内に、staticherokuフォルダを作成しておく。

  • wsgi.py にコードを追記する。heroku公式チュートリアル参照。

import os

from django.core.wsgi import get_wsgi_application
from whitenoise.django import DjangoWhiteNoise

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tryangular.settings")

application = get_wsgi_application()
application = DjangoWhiteNoise(application)
  • テストで、python manage.py runserverしてみると、error発生! whitenoiseをpip installしていないとのことなので、installする。
$ pip install whitenoise
$ pip freeze > requirements.txt
  • 再度、python manage.py runserverを実行する。。。成功!

  • $ python manage.py collectstatic で、staticファイルを集める。。。。問題発生。backend/src/staticherokuにcollectstaticしたいが、現状では、backend/staticheroku にコピーされてしまっている。

  • settings.pyの、STATIC_ROOTの設定が間違っているので、修正する。production.pyの方を修正する。

STATIC_ROOT = os.path.join(BASE_DIR, 'staticheroku')
  • src/tryangular/settings/__init.pyにlocal.pyについてのコードを追記する。
from .base import *

from .production import *

try:
    from .local import *
except:
    pass
  • heroku のrepositoryを$ heroku create tryangular-djangoroid コマンドで作成する。

  • $ git remote -v で、gitのremoteの状態がわかる。別のrepositoryを参照にしてある場合は、$git remote remove heroku で削除できる。

  • $git initして、$ git remote add heroku https://git.heroku.com/tryangular-djangoroid.git で、さっきcreateしたrepositoryに、remote addできる。

$ git init
$ git remote add heroku https://git.heroku.com/tryangular-djangoroid.git
  • git remoteの状態を確認する。
$ git remote -v
heroku  https://git.heroku.com/tryangular-djangoroid.git (fetch)
heroku  https://git.heroku.com/tryangular-djangoroid.git (push)
  • 以下の手順で、herokuにdeployする。
$ git add --All
$ git commit -m "first commit"
$ git push heroku master
  • とりあえず、デプロイはうまくいった!!サイトにアクセスしてみる。。。。error!
DisallowedHost at /
Invalid HTTP_HOST header: 'tryangular-djangoroid.herokuapp.com'. You may need to add u'tryangular-djangoroid.herokuapp.com' to ALLOWED_HOSTS.
  • production.pyの、ALLOWED_HOSTSを修正する。
ALLOWED_HOSTS = ['tryangular-djangoroid.herokuapp.com', '.herokuapp.com']
  • runtime.txt で、pythonのversionを指定できるので、作成しておく。
python-3.4.3
  • heroku repositoryに、postgresのaddonを追加する。
$ heroku addons:create heroku-postgresql:hobby-dev
  • $ git push heroku master && heroku run python manage.py migrate を実行してみる。。。error!
remote: -----> Installing python-3.4.3
remote: -----> Installing pip
remote: -----> Installing requirements with pip
remote:        /app/tmp/buildpacks/779a8bbfbbe7e1b715476c0b23fc63a2103b3e4131eda558669aba8fb5e6e05682419376144189b29beb5dee6d7626b4d3385edb0954bffea6c67d8cf622fd51/bin/steps/pip-install: line 5: /app/.heroku/python/bin/pip: No such file or directory
  • 上記はたぶん、3.4.3にherokuが対応してないぽいerrorな気がする。。。ので、runtime.txt を、python-3.6.0に変更して、再度デプロイしてみる。。。できた〜〜〜!!(^^)

  • $ heroku run python manage.py createsuperuserで, superuserを作成する。

  • https://tryangular-djangoroid.herokuapp.com/にアクセスしてみる、、、できた〜〜!!

まとめ

  • やっぱheroku便利やなぁw

Angular4 + Django1.11 vol.9

参考サイト

https://www.codingforentrepreneurs.com/projects/angular-django/

Catch All 404 + 500 Server Page

  • 404 error, 500 error のときのページの処理を、angularのrouterを使って実装する。

  • HomeComponentのrouterの設定をpathMatch fullに設定する。

const appRoutes: Routes = [
    {
        path:"",
        component: HomeComponent,
        pathMatch: 'full' // fullでpathがmatchしないとHomeComponentは起動しない
    },
  ]
  • $ ng g component not-foundで、not-found componentを作成する。

  • not-found.component.htmlのコードを修正する。

<div class="text-center">
  <h1>404 - Page Not Found</h1>
</div>
  • app.routing.tsに、NotFoundComponentをimportして、routerを設定する。
import { NotFoundComponent } from './not-found/not-found.component';
const appRoutes: Routes = [
    {
        path:"",
        component: HomeComponent,
        pathMatch: 'full'
    },
    {
        path:"search",
        component: SearchDetailComponent,
    },
    {
        path:"videos",
        component: VideoListComponent,
    },
    {
        path:"videos/:slug",
        component: VideoDetailComponent,
    },
    {
        path:"**", // **を2つにしておく。
        component: NotFoundComponent,
    }
]
  • これで、routerで設定されていないページに遷移したときに、404errorが表示されるようになっていればOK!

  • 500 errorは、サーバーサイドのerrorなので、djangoのtemplateに設定する。500.htmlを、templatesフォルダに作成する。

{% load static %}
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Try Angular + Django </title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
      <!-- index.html -->
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="{% static 'ang/styles.bundle.css' %}" rel="stylesheet"/></head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-sm-12 text-center">
                <h1>500 Server Error | しばらくたってから、再度アクセスしてください。</h1>
            </div>
        </div>
    </div>
</body>
</html>

まとめ

  • 404errorは、angularのrouterで設定できる、500errorは、backend側で設定する。
  • 404errorのときは、pathを**にする。

Angular4 + Django1.11 vol.8

参考サイト

https://www.codingforentrepreneurs.com/projects/angular-django/ https://angular.io/docs/ts/latest/guide/server-communication.html http://qiita.com/kura07/items/c9fa858870ad56dfec12

Handle Angular Http Error

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http'; //Responseをimportする

import { Observable } from 'rxjs/Observable'; //Observableをimportする

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)
  }

  featured(){
      return this.http.get(endpoint + "featured/")
              .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)
  }

  // 以降はangularの公式ページからそのままコピペする。
  private handleError (error: Response | any) {
    // In a real world app, you might use a remote logging infrastructure
    let errMsg: string;
    if (error instanceof Response) {
      const body = error.json() || '';
      const err = body.error || JSON.stringify(body);
      errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
    } else {
      errMsg = error.message ? error.message : error.toString();
    }
    console.error(errMsg);
    return Observable.throw(errMsg);
  }


  // private handleError(error:any, caught:any): any{
  //     // console.log(error, caught)
  //     if (error.status == 404){
  //       alert("Oopps. Not found")
  //     } else {
  //       alert("何かのミスが出てます。URLの確認をお願いします。")
  //     }
  // }

}
  • このままだと、TypeError: u.Observable.throw is not a functionが表示されるので、修正する。
import { Observable } from 'rxjs/Rx';
  • video-detail.component.ts, videos-detail.component.html をerrormessageを表示させるように少し修正する。
export class VideoDetailComponent implements OnInit, OnDestroy {
    private routeSub:any;
    private req:any;
    video: VideoItem;
    slug:string;
    errorStr: string; // errorStrに修正する。
  constructor(private route: ActivatedRoute, private router: Router, private _video:VideoService) { }

  ngOnInit() {
      this.routeSub = this.route.params.subscribe(params => {
          this.slug = params['slug']
          this.req = this._video.get(this.slug).subscribe(data=>{
            this.video = data as VideoItem
          }, error=>{
              console.log(error)
              this.errorStr = error; //errorStrにerrorを代入する
              // this.router.navigate(['/videos'])
          })
      })
  }
}
<div *ngIf='!video && !errorStr'>Loading</div>
<div *ngIf='errorStr'>{{ errorStr }}</div>
  • 404 - Not Found {"detail":"Not found."}のような感じで表示されればOK。ただ、{"detail":"Not found."}の部分は不要なので、handleErrorの、${err}の部分は削除しておく。

まとめ

Angular4 + Django1.11 vol.7

参考サイト

https://www.codingforentrepreneurs.com/projects/angular-django/ http://qiita.com/kura07/items/c9fa858870ad56dfec12

Handling 404 with Object Lookups

  • 指定したurlが無いときの、404 errorのページを設定する。

  • video-detail.component.ts を修正する。

ngOnInit() {
    this.routeSub = this.route.params.subscribe(params => {
        this.slug = params['slug']
        this.req = this._video.get(this.slug).subscribe(data=>{
          this.video = data as VideoItem
        }, error=>{ // subscribeがrrorの場合の内容をconsole.logに表示する
            console.log(error) //
        })
    })
}
  • 実際にerrorが出るpageにアクセスしてみると以下のようなerrorがconsoleに表示される。
eaders:t
ok:false
status:404
statusText:"Not Found"
type:2
url:"http://127.0.0.1:8000/api/videos/bound-method-videotitle-/"
_body:"{"detail":"Not found."}"
  • videos.service.ts にalertが出るように追記する。
private handleError(error:any, caught:any): any{
    console.log(error, caught)
    if (error.status == 404){ // statusが、404のときに、alertが表示されるように設定する
      alert("Oopps. Not found")
    }
}
  • さらに、errorがでたときには、video一覧に戻るようにrouterを設定する。設定するのは、video-detail.component.typescript.
import { ActivatedRoute, Router } from '@angular/router'; // Routerをimport
import { VideoItem } from '../videos/video';
import { VideoService } from '../videos/videos.service';
...
...
  constructor(private route: ActivatedRoute, private router: Router, private _video:VideoService) { }
  // constructorにprivate router: Routerで型定義する。

  ngOnInit() {
      this.routeSub = this.route.params.subscribe(params => {
          this.slug = params['slug']
          this.req = this._video.get(this.slug).subscribe(data=>{
            this.video = data as VideoItem
          }, error=>{
              console.log(error)
              this.router.navigate(['/videos']) // subscribeがerrorのときに、/videos にアクセスする。
          })
      })
  }
  • errorのときの処理をさらに修正する。
export class VideoDetailComponent implements OnInit, OnDestroy {
    private routeSub:any;
    private req:any;
    video: VideoItem;
    slug:string;
    error: Boolean; //errorの型定義をする
  constructor(private route: ActivatedRoute, private router: Router, private _video:VideoService) { }

  ngOnInit() {
      this.routeSub = this.route.params.subscribe(params => {
          this.slug = params['slug']
          this.req = this._video.get(this.slug).subscribe(data=>{
            this.video = data as VideoItem
          }, error=>{
              console.log(error)
              this.error = true; // errorがでたときには、errorをtrueにする。
              // this.router.navigate(['/videos'])
          })
      })
  }
}
  • video-detail.component.htmlに追記する。
<div *ngIf='!video && !error'>Loading</div>
<div *ngIf='error'>Error. Please try again.</div>

まとめ

  • errorは、subscribeのerror時の処理を使うと良さそう。

Angular4 + Django1.11 vol.6

参考サイト

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

Better Backend Queries

  • featuredのqueryをmodelsでやるようにする。
from django.db import models
from django.db.models.signals import pre_save
# Create your models here.

from .utils import unique_slug_generator

class VideoQuerySet(models.query.QuerySet): # models.query.QuerySetを継承した、VideoQuerySet classを作成する
    def featured(self): # featuredメソッドを追加する。
        return self.filter(featured=True) # featuredメソッドは、querysetにfeatured=Trueのfilterをかけて、その結果を返す。

class VideoManager(models.Manager): #models.Managerを継承したVideoManagerClassを作成する。
    def get_queryset(self): # get_querysetで、VideoQuerySetのinstanceを返すようにセットする。
        return VideoQuerySet(self, using=self._db)

    def featured(self):
        return self.get_queryset().featured() # featuredメソッドは、VideoQuerySetのfeaturedメソッドの値を返すメソッド。

class Video(models.Model):
    name = models.CharField(max_length=220)
    slug = models.SlugField(unique=True, blank=True)
    embed = models.CharField(max_length=120, help_text='Youtube Embed Code', null=True, blank=True)
    featured = models.BooleanField(default=False)
    timestamp = models.DateTimeField(auto_now_add=True)

    objects = VideoManager() # objectsには、VideoManagerのinstanceを代入する。

    def __str__(self):
        return self.name

    def title(self):
        return self.name


def video_pre_save_receiver(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = unique_slug_generator(instance)


pre_save.connect(video_pre_save_receiver, sender=Video)
  • views.pyを以下のように修正する。
class 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).featured()
        else:
          qs = Video.objects.featured()
        return qs
  • 次はsearchメソッドを追記する。まずは、models.pyにsearchメソッドを追加する。
from django.db.models import Q
...
class VideoQuerySet(models.query.QuerySet):
    def featured(self):
        return self.filter(featured=True)

    def search(self, query):
        return self.get_queryset().filter(
            Q(name__icontains=query) |
            Q(slug__icontains=query) |
            Q(embed__icontains=query)
        )


class VideoManager(models.Manager):
    def get_queryset(self):
        return VideoQuerySet(self.model, using=self._db)

    def featured(self):
        return self.get_queryset().featured()

    def search(self, query):
        return self.get_queryset().search(query)
  • views.pyに追記する。
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)
          qs = Video.objects.search(query)
        else:
          qs = Video.objects.all()
        return qs

class 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.featured().search(query)
        else:
          qs = Video.objects.featured()
        return qs
  • activeフィールドも追加して、最終的にmodels.pyは以下のようになった。
from django.db import models
from django.db.models import Q
from django.db.models.signals import pre_save
# Create your models here.

from .utils import unique_slug_generator

class VideoQuerySet(models.query.QuerySet):
    def active(self):
        return self.filter(active=True)

    def featured(self):
        return self.filter(featured=True)

    def search(self, query):
        return self.get_queryset().filter(
            Q(name__icontains=query) |
            Q(slug__icontains=query) |
            Q(embed__icontains=query)
        )


class VideoManager(models.Manager):
    def get_queryset(self):
        return VideoQuerySet(self, using=self._db)

    def all(self):
        return self.get_queryset().active()

    def featured(self):
        return self.get_queryset().featured().active()

    def search(self, query):
        return self.get_queryset().search(query).active()


class Video(models.Model):
    name            = models.CharField(max_length=220)
    slug            = models.SlugField(unique=True, blank=True)
    embed           = models.CharField(max_length=120, help_text='Youtube Embed Code', null=True, blank=True)
    active          = models.BooleanField(default=True)
    featured        = models.BooleanField(default=False)
    timestamp       = models.DateTimeField(auto_now_add=True)

    objects = VideoManager()

    def __str__(self):
        return self.name

    def title(self):
        return self.name


def video_pre_save_receiver(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = unique_slug_generator(instance)


pre_save.connect(video_pre_save_receiver, sender=Video)
  • error 発生! _metaがmanagerにはないとのこと。どうもquerysetの箇所を以下のように修正する必要があるようだ。
class VideoManager(models.Manager):
    def get_queryset(self):
        return VideoQuerySet(self.model, using=self._db)

まとめ

  • VideoManager と、VideoQuerySetをsetで使うと、better queryを作成できる。こちらの方がrobustのようだ。確かに、queryは、modelで完結させたほうが、わかりやすい気がする。

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を使う仕様で作成したほうが楽そうだなぁ(^^)

Angular4 + Django1.11 vol.3

参考サイト

https://www.codingforentrepreneurs.com/projects/angular-django/ https://www.codingforentrepreneurs.com/blog/random-string-generator-in-python/ https://www.codingforentrepreneurs.com/blog/a-unique-slug-generator-for-django/

Videos Django App

  • ng buildを起動した状態にしておく。
ng build --prod --output-path /Users/yassy/Desktop/djangular4/backend/src/static/ang/ --watch --output-hashing none
  • deploy.shに上記のコマンドを記載しておく。

  • 現在の仕様では、video.service.tsが、videos.jsonからデータを取得しているが、これをdjangoのデータベースから直接持ってくるように設定したい。

  • まず、videos appを作成する。$ python manage.py startapp videos

  • videos の modelを作成する。

from django.db import models

# Create your models here.

class Video(models.Model):
    name = models.CharField(max_length=220)
    slug = models.SlugField(unique=True, blank=True)
    embed = models.CharField(max_length=120, null=True, blank=True)
    featured = models.BooleanField(default=False)
    timestamp = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

'''
     "name": "Welcome",
     "slug": "item-1",
     "embed": "1hyjLD7pk10",
     "image": "/static/ang/assets/images/nature/4.jpg",
     "featured": true
    },
'''

Auto Generate Slug

import random
import string
from django.utils.text import slugify


def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits):
    return ''.join(random.choice(chars) for _ in range(size))

'''
random_string_generator is located here:
http://joincfe.com/blog/random-string-generator-in-python/
'''

def unique_slug_generator(instance, new_slug=None):
    if new_slug is not None:
        slug = new_slug
    else:
        slug = slugify(instance.title)

    Klass = instance.__class__
    qs_exists = Klass.objects.filter(slug=slug).exists()
    if qs_exists:
        new_slug = "{slug}-{randstr}".format(
                    slug=slug,
                    randstr=random_string_generator(size=4)
                )
        return unique_slug_generator(instance, new_slug=new_slug)
    return slug
    ```

* 最終的にvideos/modelssは以下のように修正

from django.db import models from django.db.models.signals import pre_save

Create your models here.

from .utils import unique_slug_generator

class Video(models.Model): name = models.CharField(max_length=220) slug = models.SlugField(unique=True, blank=True) embed = models.CharField(max_length=120, help_text=‘Youtube Embed Code’, null=True, blank=True) featured = models.BooleanField(default=False) timestamp = models.DateTimeField(auto_now_add=True)

def __str__(self):
    return self.name

def title(self):
    return self.name

def video_pre_save_receiver(sender, instance, *args, **kwargs): if not instance.slug: instance.slug = unique_slug_generator(instance)

pre_save.connect(video_pre_save_receiver, sender=Video)