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'
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
angular公式チュートリアルのhttpあたりを参考にして、Errorの処理を書いていく。 https://angular.io/docs/ts/latest/guide/server-communication.html
videos.service.ts にコードを追記する。
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}
の部分は削除しておく。
まとめ
- handleError全般は、公式チュートリアルから拝借して、設定すると良さそう。
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
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) }
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
- slugのauto generatorをutils.pyファイルに作る。以下のリンク参照 https://www.codingforentrepreneurs.com/blog/random-string-generator-in-python/ https://www.codingforentrepreneurs.com/blog/a-unique-slug-generator-for-django/
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)