雑食日誌

PythonとJS。ときどきチーム開発

DjangoのORMでちょっとしたSQLをかいた

この記事はDjango Advent Calendar 2018 15日目の記事です。

qiita.com

はじめに

最近色々な勉強会でORMの是非について話を聞きます。 もともと自分はDjangoで単純なSQLしか書いたことがなかったのできちんと議論できませんでした。
そこで、今回はクエリ中心にちょっとしたSQLDjangoのORMで実装してみます。

動作環境

Dockerについて

SQLを試してみるにあたって、ローカルのPCのデータベースをセットアップすることは面倒です。
そのため、データベースやPythonのランタイムはDocker Composeを使ってセットアップしています。

実際のDockerfileは以下

FROM python:3.7

ENV PYTHONUNBUFFERED 1
ENV DOCKERIZE_VERSION v0.6.1
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \
    tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \
    rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz

WORKDIR /code/
COPY Pipfile* ./
RUN pip install pipenv && \
         pipenv install --system --deploy

ADD . /code

Docker Compose でマイグレーションと初期データの設定を行なっています。

version: "3"

services:
  db:
    image: postgres:10
    environment:
      POSTGRES_DB: $POSTGRES_DB
      POSTGRES_PASSWORD: $POSTGRES_PASSWORD
    env_file:
      - .env
  backend:
    build: .
    command: bash -c "dockerize -wait tcp://db:5432 && pipenv install --system --dev && python3 manage.py migrate && python3 manage.py loaddata init.json && python3 manage.py runserver 0.0.0.0:8000"
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    working_dir: "/code/orm"
    env_file:
      - .env
    depends_on:
      - db

コードはリポジトリにて公開しています。

github.com

準備

テーブルはブログを題材に作りました。モデル定義は以下になります。

from django.db import models


class Post(models.Model):
    title = models.CharField(max_length=255)
    text = models.TextField()


class Comment(models.Model):
    post = models.ForeignKey('Post', on_delete=models.CASCADE)
    text = models.TextField(blank=True, null=True)

ダミーデータはフィクスチャで準備し、各検証はDjango Extensions を使いました。

試すクエリ

以下の5つのクエリを試してみたいと思います。

  • 1テーブルから全データを取得
  • 外部キー含めたデータを取得
  • カラムに条件指定して取得する
  • Case式 を使った分岐
  • Countによる集計

1. テーブルから全データを取得

Postテーブルから記事データを全て取得してみます。
DjangoのORMでは all メソッドで全データを取得することができます。

>> posts = Post.objects.all()
>> posts.values()                                                                                                                                                                          
<QuerySet [{'id': 1, 'title': 'PyConに行ってきた', 'text': '楽しかった'}, {'id': 2, 'title': 'Djangoチュートリアル', 'text': '投票アプリの構築までできた'}]>

>>  print(posts.query) 
SELECT "blog_post"."id", "blog_post"."title", "blog_post"."text" FROM "blog_post"

2. 外部キー含めたデータを取得

Commentと外部キーであるPostを併せて取得してみます。

>> comments = Comment.objects.select_related().all()
>> comments.values()
 <QuerySet [{'id': 1, 'post_id': 1, 'text': '一番よかったセッションは?'}, {'id': 2, 'post_id': 1, 'text': '懇親会はどうだった?'}, {'id': 3, 'post_id': 2, 'text': '自分は難しくてできなかった'}]>

>> [comment.post.title for comment in comments] 
 ['PyConに行ってきた', 'PyConに行ってきた', 'Djangoチュートリアル']

>> print(comments.query)
SELECT "blog_comment"."id", "blog_comment"."post_id", "blog_comment"."text", "blog_post"."id", "blog_post"."title", "blog_post"."text" 
FROM "blog_comment" 
INNER JOIN "blog_post" 
ON ("blog_comment"."post_id" = "blog_post"."id")

3. カラムに条件指定して取得する

特定の条件下のデータを取得してみます。

>>  pycon_comment = Comment.objects.filter(post__id=1)
>>  pycon_comment
 <QuerySet [<Comment: Comment object (1)>, <Comment: Comment object (2)>]>

>> print(pycon_comment.query)
SELECT "blog_comment"."id", "blog_comment"."post_id", "blog_comment"."text" 
FROM "blog_comment" 
WHERE "blog_comment"."post_id" = 1

4. CASE式を使った分岐

SQLのCASE-WHENを使った例です。
Postにを新しくジャンルというカラムを作って分類してみます。

>> genre_post = Post.objects.annotate( 
...:     genre=Case( 
...:             When(title__contains='Django', then=Value('Django')), 
...:             default=Value('Python'), 
...:             output_field=CharField()) 
...: ).values_list('title', 'genre')    
>> genre_post                                                                                                                       
<QuerySet [('PyConに行ってきた', 'Python'), ('Djangoチュートリアル', 'Django')]>

>> print(genre_post.query)
SELECT "blog_post"."title", 
  CASE WHEN "blog_post"."title"::text LIKE %Django% 
  THEN Django 
  ELSE Python 
END AS "genre" 
FROM "blog_post"

5. Countによる集計

記事についているコメント数を集計してみます。
aggregate メソッドは dict を返します。

>>  Comment.objects.aggregate( 
...:     pycon=Count('pk', filter=Q(post__id=1)), 
...:     django=Count('pk', filter =Q(post__id=2)) 
...:   )      
{'pycon': 2, 'django': 1}

まとめ

Case式やCountのオブジェクトが用意されているのは初めて知りました。
Upsertみたいな更新系もやってみたいです。

The Agile Guildでのチーム開発

これはThe Agile Guild(TAG) Advent Calendar 2018 6日目の記事です。

qiita.com

The Agile Guild(TAG)に参加して取り組んだプロジェクトについて振り返りたいと思います。

自己紹介

私は元々Pythonメインのサーバーサイドエンジニアで DjangoによるAPIを実装したりしてました。
半年前くらいにでVue.jsに携わる機会があり、TAGではフロントエンドに多く関わらせていただいてます。 フロントエンド開発楽しいです。

TAGで関わってるプロジェクトについて

プロジェクトではIonicを使ったモバイルアプリのフロントエンドを担当しています。
メンバーはエンジニアとしてTAGから3人、POやマネージャーが一人づつのチームです。

TAGメンバーは複業のため、リモートかつ活動する時間帯も異なっています。
なので、情報の同期に遅れがあり、スピーディーにやりとりできない環境でとても難しいです。
開発サイクルは1スプリント1週間で、毎週月曜日にふりかえりをやってます。

ジョインしてからの課題

プロジェクトの参加当初はスムーズに開発に入れずに悩んでいました。
言語化すると以下のような課題があったように思えます。 (今も全て解決しているわけではないですが)

  • 開発プロセスがわからず、進められない
  • 他のチームメンバーの状況が見えにくい
  • 一つのフューチャーに対してどこまで開発すればいいかわからない
  • 知識の偏りがある(マネージャーがIonicに一番詳しかった)

モヤモヤしながら動けないまま数スプリントを過ごしてしまいました。

カイゼン活動

そんな中で他のTAGメンバーやマネージャー主体で、以下のカイゼン活動がなされました。

リモート朝会ではその日に稼働できるかや作業状況を共有する仕組みです。
開発プロセスの整理ではアーキテクチャからGitの運用フローなど細かいところまで決め事を作ってもらいました。

チームビルディングではドラッガー風エクササイズを使って、お互いの思いを共有しました。
そこからチームに足りていない役割やタスクを洗い出して、誰がやるかをざっくり定めていました。
これらのカイゼンはとても参考になっていて、本業の方でも導入したりしています。

また、AWS環境構築やサーバー開発など含めてリモートモブプロ会をよく開催しています。
作業のノウハウをチームに共有することで、いろんな人が対応できるようになるのが面白いです。

できていないこと・今後やりたいこと

できていないが今後やりたいことは以下になります。

  • チームに不足している役割や宙に浮いているタスクを積極的に巻き取ること
  • フロントエンドだけでなくバックエンドやインフラ周りの知識を得ること
  • 本業の忙しさに負けないこと

まだ、課題が多くやらなければいけないことがありますが、カイゼンしながら開発したいと思います。
頑張って年内にリリースしたいです。

VueにAmplifyを入れてみた

この記事はVue.js #3 Advent Calendar 2018の4日目の記事です。
フロントエンドのライブラリかつAWSのリソースを作ることができるツールとして話題のAmplifyをVuejsに組み込んでみたいと思います。

Amplifyとは

AmplifyはAWSリソースとの接続を支援するオープンソースJavaScriptライブラリです。
強力なCLIツールが提供されていて、AWSリソースの作成をコマンド上で実行し、ライブラリに反映することができます。 現在はCognitoによる認証や、AppSyncを用いたGraphQLの利用がサポートされています。

The foundation for your cloud-powered mobile & web apps

CLIのインストール

インストールはNPMで行います。

$ npm i -g @aws-amplify/cli

下記コマンドで詳細が出力されたらインストール成功です。

$ amplify

Vueプロジェクトの初期化

Amplifyの初期化はプロジェクトのルートディレクトで実行することを推奨されているため、先にVueのプロジェクトを作成します。 実行はVue CLIを用いています。

keinuma% vue create amplify-vue-demo 

Vue CLI v3.1.3
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, Linter
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript for auto-detected polyfills? Yes
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: ESLint
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json
? Save this as a preset for future projects? No

Amplifyの初期化

Amplify CLIを用いてAWS環境の初期化を行います。

$ amplify init

実行すると開発環境およびプロジェクトの情報を渡してあげます。

? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using vue
? Source Directory Path:  src
? Distribution Directory Path: dist
? Build Command:  yarn build
? Start Command: yarn serve

Amplifyは現状CloudFormationから各リソースを作成するため、認証情報を教えないといけません。
AWS上でユーザーを作成するか聞かれるので、専用のユーザーを用意する場合はYesを選択しましょう。
リージョンとアカウント名を入力するとIAM作成画面に遷移し、ポリシーの設定を行います。

Using default provider awscloudformation
AWS access credentials can not be detected.
? Setup new user Yes
Follow these steps to set up access to your AWS account:

Sign in to your AWS administrator account:
https://console.aws.amazon.com/
Press Enter to continue

Specify the AWS Region
? region:  ap-northeast-1
Specify the username of the new IAM user:
? user name:  amplify-demo
Complete the user creation using the AWS console
https://console.aws.amazon.com/iam/home?****

ユーザーを作成したら、コンソールに戻ってクレデンシャル情報を入力します。
Access Key IDおよびSecret Access Keyを入力したら、Amplify上で管理するための名前をつけることができます。

Press Enter to continue
Enter the access key of the newly created user:
? accessKeyId:  A****************
? secretAccessKey:  *******************************
This would update/create the AWS Profile in your local machine
? Profile Name:  demo

Successfully set up the new user.

認証情報の設定が完了すると改めて、ユーザー選択を行うので先ほど作ったdemoを選択します。

? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use demo
⠇ Initializing project in the cloud...

CREATE_IN_PROGRESS amplifyvuedemo-20181126103438 AWS::CloudFormation::Stack Mon Nov 26 2018 10:34:38 GMT+0900 (日本標準時) User Initiated
CREATE_IN_PROGRESS UnauthRole                    AWS::IAM::Role             Mon Nov 26 2018 10:34:42 GMT+0900 (日本標準時)
CREATE_IN_PROGRESS DeploymentBucket              AWS::S3::Bucket            Mon Nov 26 2018 10:34:42 GMT+0900 (日本標準時)
CREATE_IN_PROGRESS AuthRole                      AWS::IAM::Role             Mon Nov 26 2018 10:34:42 GMT+0900 (日本標準時)
CREATE_IN_PROGRESS AuthRole                      AWS::IAM::Role             Mon Nov 26 2018 10:34:43 GMT+0900 (日本標準時) Resource creation Initiated
CREATE_IN_PROGRESS UnauthRole                    AWS::IAM::Role             Mon Nov 26 2018 10:34:43 GMT+0900 (日本標準時) Resource creation Initiated
CREATE_IN_PROGRESS DeploymentBucket              AWS::S3::Bucket            Mon Nov 26 2018 10:34:44 GMT+0900 (日本標準時) Resource creation Initiated
⠹ Initializing project in the cloud...

CREATE_COMPLETE UnauthRole       AWS::IAM::Role  Mon Nov 26 2018 10:35:02 GMT+0900 (日本標準時)
CREATE_COMPLETE AuthRole         AWS::IAM::Role  Mon Nov 26 2018 10:35:02 GMT+0900 (日本標準時)
CREATE_COMPLETE DeploymentBucket AWS::S3::Bucket Mon Nov 26 2018 10:35:05 GMT+0900 (日本標準時)
⠸ Initializing project in the cloud...

CREATE_COMPLETE amplifyvuedemo::AWS::CloudFormation::Stack Mon Nov 26 2018 10:35:09 GMT+0900 (日本標準時)
✔ Successfully created initial AWS cloud resources for deployments.

Your project has been successfully initialized and connected to the cloud!

Some next steps:
"amplify status" will show you what you've added already and if it's locally configured or deployed
"amplify <category> add" will allow you to add features like user login or a backend API
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

Pro tip:
Try "amplify add api" to create a backend API and then "amplify publish" t

CloudFormationのStack、デプロイ用のバケットが作成されると初期化は完了になります。
実行完了後に以下のファイルが作成されています。

  • .amplifyrc
  • amplify/

.amplifyrcはCloudFormationで作成されたリソース情報が記載されていて、amplifyディレクトリ以下にはプロジェクトの情報やリソースの詳細が格納されています。

Cognitoの作成

AmplifyでCognitoユーザープールを作成してみたいと思います。
addコマンドでユーザープールの設定を行います。 認証方法はEmailで、パスワードの強度などをカスタマイズしています。 IdeintityプールはCognitoからAPI以外の他のAWSリソースに紐づけるときに使用します。
例えばS3へのアップロードを許可する場合などですね。

$ amplify add auth
Using service: Cognito, provided by: awscloudformation
 The current configured provider is Amazon Cognito.
 Do you want to use the default authentication and security configuration? No, I will set up my own configuration.
? Select the authentication/authorization services that you want to use: User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Stor
age features for images or other content, Analytics, and more)
? Please provide a friendly name for your resource that will be used to label this category in the project: amplifyauthdemo
? Please enter a name for your identity pool. amplifyvuedemo96530290_identitypool_96530290
? Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM) No
? Do you want to enable 3rd party authentication providers in your identity pool? No
? Please provide a name for your user pool: amplifyvuedemo96530290_userpool_96530290
? Multifactor authentication (MFA) user login options: OFF
? Email based user registration/forgot password: Enabled (Requires per-user email entry at registration)
? Please specify an email verification subject: パスワード再設定
? Please specify an email verification message: 認証コードはこちらになります。 {####}
? Do you want to override the default password policy for this User Pool? Yes
? Enter the minimum password length for this User Pool: 8
? Select the password character requirements for your userpool: Requires Lowercase, Requires Uppercase, Requires Numbers
? Userpool users are created with a standard set of attributes defined by the OpenID Connect specification.  Mark the required attributes below:
? Specify the app's refresh token expiration period (in days): 30
? Do you want to specify the user attributes this app can read and write? Yes
? Specify read attributes: Email
? Specify write attributes:
Successfully added resource amplifyauthdemo locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

作成したCognitoをAWSに反映してみる。 init時に作成したCloudFormationにネストしてリソースを作成していることがわかります。

$ amplify push
| Category | Resource name   | Operation | Provider plugin   |
| -------- | --------------- | --------- | ----------------- |
| Auth     | amplifyauthdemo | Create    | awscloudformation |
? Are you sure you want to continue? true
⠙ Updating resources in the cloud. This may take a few minutes...

UPDATE_IN_PROGRESS amplifyvuedemo-20181126103438 AWS::CloudFormation::Stack Mon Nov 26 2018 11:16:29 GMT+0900 (日本標準時) User Initiated
CREATE_IN_PROGRESS authamplifyauthdemo           AWS::CloudFormation::Stack Mon Nov 26 2018 11:16:34 GMT+0900 (日本標準時)
CREATE_IN_PROGRESS authamplifyauthdemo           AWS::CloudFormation::Stack Mon Nov 26 2018 11:16:36 GMT+0900 (日本標準時) Resource creation Initiated
⠼ Updating resources in the cloud. This may take a few minutes...
...
✔ All resources are updated in the cloud

完了するとCognitoが作成されていることを確認できます。
また、パスワードの設定などがカスマイズされていることも確認できます。

プロジェクトの src/aws-exports.js にユーザープールのIDが保存されます。

AppSyncの作成

続けてAppSyncを作成してみます。
認証方法は先ほど作成したCognitoを用いたいと思います。

$ amplify add api
? Please select from one of the below mentioned services GraphQL
? Provide API name: amplifyvuedemo
? Choose an authorization type for the API Amazon Cognito User Pool
Use a Cognito user pool configured as a part of this project
? Do you have an annotated GraphQL schema? No
? Do you want a guided schema creation? true
? What best describes your project:
? What best describes your project: One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)
? Do you want to edit the schema now? No
? Press enter to continue

GraphQL schema compiled successfully. Edit your schema at ***/amplify-vue-demo/amplify/backend/api/amplifyvuedemo/schema.graphql
Successfully added resource amplifyvuedemo locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

今回はSchemaを定義せずにチュートリアルで用意されているブログ用のスキーマを選択しました。
チュートリアルではデータソースにDynamoDBが使われているので、AppSyncと直接同期することができます。
完了するとamplify/backend/api/{API name}/build/schema.graphqlが作成されて、スキーマが定義されます。

schema.graphql

type Blog @model {
  id: ID!
  name: String!
  posts: [Post] @connection(name: "BlogPosts")
}
type Post @model {
  id: ID!
  title: String!
  blog: Blog @connection(name: "BlogPosts")
  comments: [Comment] @connection(name: "PostComments")
}
type Comment @model {
  id: ID!
  content: String
  post: Post @connection(name: "PostComments")
}

Resolverが amplify/backend/api/{API name}/build/resolver 以下に格納され、リソースの詳細情報が保存されています。 作成したAppSyncをAWSに反映しましょう。

$ amplify push
| Category | Resource name   | Operation | Provider plugin   |
| -------- | --------------- | --------- | ----------------- |
| Api      | amplifyvuedemo  | Create    | awscloudformation |
| Auth     | amplifyauthdemo | No Change | awscloudformation |
? Are you sure you want to continue? true

GraphQL schema compiled successfully. Edit your schema at ***/amplify-vue-demo/amplify/backend/api/amplifyvuedemo/schema.graphql
? Do you want to generate code for your newly created GraphQL API (Y/n)
? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions (src/graphql/**/*.js)
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions (Y/n)
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes

⠇ Updating resources in the cloud. This may take a few minutes...

UPDATE_IN_PROGRESS amplifyvuedemo-20181126103438 AWS::CloudFormation::Stack Mon Nov 26 2018 11:34:52 GMT+0900 (日本標準時) User Initiated
UPDATE_IN_PROGRESS authamplifyauthdemo           AWS::CloudFormation::Stack Mon Nov 26 2018 11:34:57 GMT+0900 (日本標準時)
...

✔ Generated GraphQL operations successfully and saved at src/graphql
✔ All resources are updated in the cloud

GraphQL endpoint: https://***.appsync-api.ap-northeast-1.amazonaws.com/graphql

言語を選択してコード生成ができます。今回はJavaScriptを選択しています。

完了するとプロジェクトに以下のファイルが生成されています。

  • src/graphql
  • .graphqlconfig.yml

.graphqlconfig.ymlはAppSyncの情報が格納されています。
src/graphql は定義されているクエリやミューテーションが文字列として格納されている。 使用するときにクエリを選ぶことができる。

Vueパッケージをインストール

Vueのライブラリとして使用するためのパッケージをインストールする

$ npm i aws-amplify aws-amplify-vue

これでAmplifyをライブラリとして使う準備ができました。

ユーザーの認証画面を作成する

AmplifyにはいくつかUIが用意されています。
認証では Authenticator コンポーネントを組み込むことで認証フローの実装をスキップできます。

認証情報はストア上で行うため、先にストアの設定を行います。

store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    user: null
  },
  mutations: {
    setUser(state, user) {
      state.user = user
    }
  },
  actions: {
  }
})

認証情報はページ遷移ごとにチェックする必要があるため、ルーティングに認証チェックフローを実装します。
Vue Routerと一緒にAmplifyのライブラリのインポートを行います。

router.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from '../views/Home.vue'
import AmplifyStore from '../store'
import { AmplifyEventBus, AmplifyPlugin, components } from 'aws-amplify-vue'
import * as AmplifyModules from 'aws-amplify'

Vue.use(Router)
Vue.use(AmplifyPlugin, AmplifyModules)

認証状態がパスワードチェック前なのかサインイン前なのかなどをEventBusで確認します。

router.js

let user;

getUser().then((user) => {
  if (user) {
    router.push({path: '/'})
  }
})

AmplifyEventBus.$on('authState', async (state) => {
  if (state === 'signedOut'){
    user = null;
    AmplifyStore.commit('setUser', null);
    router.push({path: '/auth'})
  } else if (state === 'signedIn') {
    user = await getUser();
    router.push({path: '/'})
  }
});

function getUser() {
  return Vue.prototype.$Amplify.Auth.currentAuthenticatedUser().then((data) => {
    if (data && data.signInUserSession) {
      AmplifyStore.commit('setUser', data);
      return data;
    }
  }).catch(() => {
    AmplifyStore.commit('setUser', null);
    return null
  });
}

ルーティング前にユーザーが設定されているかで、ログイン画面にルーティングするかを決める処理を実装します。

router.beforeResolve(async (to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    user = await getUser();
    if (!user) {
      return next({
        path: '/auth',
        query: {
          redirect: to.fullPath,
        }
      });
    }
    return next()
  }
  return next()
})

Amplifyが用意しているAuthenticatorコンポーネント/auth に宣言します。

const router = new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/auth',
      name: 'auth',
      component: components.Authenticator
    }
  ]
})

ローカル上で起動し http://localhost:8080/auth にアクセスするとログイン画面にルーティングされます。

f:id:keinumata:20181203235853p:plain

これで認証機能を作成することができました。
Authenticator コンポーネントはストアのユーザー情報とCogntioにリクエストした認証状態を確認し、適切なルーティングを行ってくれます。

GraphQLにリクエストしてみる

AppSyncのリソースと同時に作成された src/graphql/ を使うことでリクエストを簡単に実装できます。 はじめに必要なライブラリをインポートしましょう。

import { API, graphqlOperation } from 'aws-amplify'
import * as mutaions from '@/graphql/mutations'

メソッドにGraphQLへのリクエストを持ったコンポーネントを実装します。

export default {
  name: 'CreatePost',
  data: function() {
    return {
      postData: {
        author: '',
        content: ''
      },
      myPost: ''
    }
  },
  methods: {
    createPost: function () {
      API.graphql(graphqlOperation(mutaions.createPost, this.postData ))
        .then((response) => {
          console.log(response)
          this.myPost = response
        })
        .catch((err) => {
          console.log(err)
        })
    }
  }
}

graphqlOperation に使用するクエリとパラメータを指定したオブジェクトをAPI.graphql 関数に渡しています。 クエリを変更するだけで、リクエストを変えることができます。

実装の全体はGitHubリポジトリを作成しているのでこちらをご確認ください。

github.com

まとめ

AmplifyをVueに組み込んでみました。
CLIAWSリソースを作るのは便利ですが、CI/CDとの兼ね合いが難しそうです。

VueのAmplify UIはReactに比べてまだデザインの拡張性は難しいですが、簡単にAWSとの連携を実装できます。
AmplifyはOSSなので欲しい機能などはPRを出してみたいです。

参考:

Vue

Django備忘録: 管理画面のパスワードリセットにメールを使う

Djangoの管理画面のパスワードリセットにメールアドレス認証を実装する機会があったので、記事にまとめてみました。 公式ドキュメントを参考に実装しています。

Django の admin サイト | Django documentation | Django

GitHubに実装を公開しています。

github.com

目次

Djangoの初期設定

Djangoのマネージドコマンドを使ってプロジェクトの初期化を行います。

$ django-admin startproject django_admin_email

URLの追加

Djangoは管理画面のパスワードリセットのURL設定を追記するだけで設定できます。
URLの name はテンプレートで決められているため、変えられません。
urls.pyに以下を追記する。

from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path

urlpatterns = [
   path('admin/', admin.site.urls),
   path(
       'admin/password_reset/',
       auth_views.PasswordResetView.as_view(),
       name='admin_password_reset',
   ),
   path(
       'admin/password_reset/done/',
       auth_views.PasswordResetDoneView.as_view(),
       name='password_reset_done',
   ),
   path(
       'reset/<uidb64>/<token>/',
       auth_views.PasswordResetConfirmView.as_view(),
       name='password_reset_confirm',
   ),
   path(
       'reset/done/',
       auth_views.PasswordResetCompleteView.as_view(),
       name='password_reset_complete',
   ),
]

/admin/login にアクセスするとログイン画面にパスワードリセットのリンクが追加されます。

f:id:keinumata:20181119002406p:plain

管理画面のログインのテンプレートHTMLを確認してみましょう。
password_reset_url がURLに登録されているかで、リンクを出し分けていることがわかります。

django/login.html at master · django/django · GitHub

  {% if password_reset_url %}
  <div class="password-reset-link">
    <a href="{{ password_reset_url }}">{% trans 'Forgotten your password or username?' %}</a>
  </div>
  {% endif %}

Gmailで送ってみる

開発環境でのメール送信はc-bataさんの記事が参考にになります。

ここではGmailを使ったメール送信を設定してみます。 settings.pyに以下の設定を追加します。 EMAIL_HOST_USER , EMAIL_HOST_PASSWORD は適宜変更してください。

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'example@gmail.com'
EMAIL_HOST_PASSWORD = 'password'
EMAIL_PORT = '587'

送信する前にGoogle側のパスワード設定を変える必要があります。
アカウント設定の「ログインとセキュリティ」から「 安全性の低いアプリの許可」をONにします。

実際に送られたメールはこちら。

このメールは 0.0.0.0:8000 で、あなたのアカウントのパスワードリセットが要求されたため、送信されました。

次のページで新しいパスワードを選んでください:

http://0.0.0.0:8000/reset/MQ/xxxxxxxxxxxxxxxxx/

あなたのユーザー名 (念のため): keinuma

ご利用ありがとうございました!

 0.0.0.0:8000 チーム

ローカル感丸出しですが無事に送れました。

SendGridやAWS SESを組み込んだメール送信もやってみたいです。

Vue Fes Japan参加レポート

日本初のVueカンファレンスVue Fes Japanに参加してきました。 実業務でのお話やVueのこれからのようなコアな話まで聞けたので、レポートにまとめたいと思います。

キーノート

Evan氏によるVue3.0の展望についてでした。 主に以下の変更が加えられます。

  • メモリ使用量低下による高速化
  • ビルドファイルの軽量化
  • メンテナンスしやすさの向上
  • Hooks API

何点か気になった話を書いていきます。
ビルドファイルの軽量化ではTree Shakingが出てきました。 使われていない v-ifv-model などをバンドルせずにビルドできるそうです。

メンテナンスのしやすさではデバッグ機能の追加がありました。
Devtoolの確認に加えて、イベントなどのブレークポイントを見れるようになるのはでかいですね。

Hooks APIはReactで出てきた機能らしいです。アフターパーティーで詳しく聞けました。
ストア管理するほどでもない状態を楽にできるそうなので、Reactを触って先に体験したいですね。

勉強不足で多くを理解できなかったですが、復習して3.0を待ちたいと思います。

ランチセッション

LINEさん

ランチセッション一発目はLINEさんでした アプリだけではなく、クーポンやスタンプのページはVueで開発されているようです。 新規開発もNuxtやVueを用いて開発されてるみたいですねー。

ブースではVueのクイズに答えると抽選でVue.js入門をゲットできる催しをやってました。 抽選は逃してしまいましたが、面白い企画でした。

Scouterさん

NuxtMeetUpでおなじみのScouterさんの発表でした。 みなさんだいたい2014年のVueがメジャーバージョンになる前から使い始めてますね。 Scouterさんはエンジニア10名で継続的にプロダクトをローンチしていてすごいです。

Reproさん

ラストは普段もくもく会でお世話になっているReproさんのセッションでした。 最初はjQueryで開発されていたものをVueで書き換えたということでした。 jQueryからの移行しやすさはVueの魅力の一つなのかなと思いました。

セッション

Vue.js と Web Components のこれから

speakerdeck.com

WebComponents(以下WC)とVueとの共存についてのセッションでした。 WCのメリットは以下のようです。

デメリットは以下

  • 属性値にstringしか渡せない
  • 外部からのイベントハンドリングが難しい
  • CSS設計方法が大きく変わる

Vue CLI3はWCへのbuildをサポートしているためビルド時にtargetオプションをつけることで動かせます。(参考)

$ vue-cli-service build --target wc --name element ./src/App.vue

そのため、Vueで開発してきたものをWCを用いて描画することが簡単にできます。

WCはWeb標準機能のため、相対的に寿命が長く、激しい変更が少ないことが見込まれます。 いつVueが終わってしまうかという心配事が減ることが期待できますね。

WCはあくまでDOMの可視化にまつわる仕組みなのでアプリケーションフレームワークとしての機能は有していません。
データ管理やイベントなどフレームワークとしてはVueが、UI部分はWCに置き換わるのが今後の展望とのことでした。
WCのデフォルトでマテリアルデザインをサポートし始めたら強いですね。

記法がVueに似ている(VueがWCに着想を得ている)とのことだったので自作アプリにも組み込みたいです。

Vue Designer: デザインと実装の統合

slides.com

デザインと実装に距離ができてしまう問題を解決するツールについてのセッションでした。
デザインとフロントエンドの実装では以下の課題がみられます。

  • デザインファイルと実装で使うツールが異なる
  • デザインと実装の管理が重複する
  • 動的なデザインを考慮しにくい

現在開発されていて、上記課題を解決しようとしているツールには以下があるそうです。

  • vuegg
    • デザインしたものをVueファイルとして出力できる
    • コード上で改修したものをキャンバスに戻せない
  • framer X

そこでkatashinさんはSFCでデザインできるようにするツールを開発中でした。
GitHub - ktsn/vue-designer: Vue component design tool

SFCをプレビューし、ドラッグ&ドロップやインスペクターでデザインを変更します。
変更はSFCのファイルにも反映されて、デザインとSFCファイルの双方向バインディングが実現されていました。
デザイナーにも使ってもらえることを目標にしているので、デザインツールで使えるような機能も考えているようです。

実装はクライアント・サーバ型でできていて、SFCをASTに変換し、WebSocketを通してレンダリングされていました。
クライアント・サーバ型のメリットは以下になるそうです。

パースはvue-eslint-parser, postcss, babelを使っていて、TypeScript対応もあるみたいですね。

一人でパーサーを書いて、WebSocketを使い、デザインツールを追従していることが驚きでした。

今後はコンポーネントカタログの自動生成も考えているそうなので、とても期待しています。

Atomic Design のデザインと実装の狭間

デザイナーとエンジニア間の協業という観点のエモいお話でした。

現在Atomic Designが主流になる中でデザイナーからコンポーネントベースの手法が適応されている背景があります。 コンポーネントベースでデザインする上での難点は以下になっているそうです。

  • 学習コストが高い
  • デザインの自由度が下がる
  • 認識が異なる

コンポーネントはエンジニアの概念のため、デザイナーに適応することは難しいそうです。
例として挙げられていたのは構造化プログラミング vs オブジェクト指向プログラミングでした。
Javaが出てきた当初はスピードなどを槍玉に挙げられて非難されていたが、オブジェクト指向に慣れると、 効率が上がるため現在では主流になっています。
デザインの世界では構造化プログラミング的手法のため、いきなり実践するということは無理とのことです。

そもそもデザイナーとエンジニアは責任が異なります。
デザイナーはインターフェースの価値を高めて、ユーザーに届け、エンジニアは効率を重視し、保守性を高めています。

そんな中での現実的な解決策は以下が挙げられていました。

  • エンジニアがデザインを覚える
  • いい感じのツールを作る(さっきのトークの続きだ…)
  • デザイナーの横でコンポーネントをひたすら写経する

弊社ではエンジニアがコンポーネント化する作業を行なっているので、より良いプロセスを考えていきたいです。

noteをNuxt.jsで再構築した話

speakerdeck.com

SPAからNuxtへ移行したためになるお話でした。

noteは当初Angularで開発されていたようです。
この時の技術選定はAngularのみメジャーバージョンに上がっていることが大きかったようです。

しかし、SPAのみでは以下の課題がありました。

  • 初期表示速度の遅延
  • コーディング規約などの負債

フレームワークを変えてのフルスクラッチは難しいと思います。
noteさんはCEOが技術よりということで理解が得られていました。

Vueを選んだ理由は以下になるそうです。

  • 学習コストの低さ
  • ドキュメントの充実度(日本語が充実)
  • コミュニティの活発度が高い

移行手順はページごとに社内リリースから初めて徐々に本番に移行していくやり方ですね。

  • 既存UIを確実に移行
  • ページごとにURLベースで振り分ける
    • 社内リリース、Dog Fooding、一般公開の順番

設計方針ではVuex + Atomic Designを取り入れていました。
Vueを使って堅牢なシステムを作るときのベストなのかなと思います。

個人的にAtomic Designは分子と有機体の違いがわからなくなりますね…
Vuexの参照は有機体以上にしているそうです。
ルールを明文化して、コードレビューの観点にすることが大事なんだと思いました。

Atomic Designを用いることでコンポーネント数が肥大化し、チームが把握できない問題を解決するためにStory Bookを導入していました。
私自身設定周りで挫折した経験があるのですが、Nuxt2.0になったことで楽になっているそうです。
ストーリーをメンテナンスするのが大変なのと、通信周りのスタブ化が辛いため、対象を原子・分子に絞っていました。

インフラはNuxt on Lambdaの構成でした。
ハマりポイントは mya-akeさんのブログで解消できるそうです。

インフラ管理のコストやスケーリングを考えると良さそうですね。
以下の欠点を超えられれば使いたいです。

  • NodeのバージョンがLambdaに依存する
  • デプロイできる容量が決まっている

これまでの移行プロジェクトはnoteのなかでまとまっています。 note.mu

Nuxtの導入を考えているのでとても参考になりました。

1年間単体テストを書き続けた現場から送る Vue Component のテスト

speakerdeck.com

ラストはコンポーネントテストのお話でした。
コンポーネントの何をどこまでテストするかはいつも悩むのでノウハウを聞けてよかったです。

単体テストを自動化するのは外部からみた振る舞いをテストしていました。
「外部からみた振る舞い」は何かしらのインプットに対してアウトプットが期待とおりかチェックすることです。

コンポーネントの場合のインプットは

これに対してアウトプットは

  • HTML, CSS
  • Event
  • Storeのアクション

にまとめられていました。

Lifecycleでは必要なデータを取得するぐらいなので優先度は低いそうです。
他のテストに影響が出るので、場合によってはモックしてもよさそうですね。

PropsやStoreはスナップショットテストを用いていました。
コード修正の前後の差分を確認し、期待通りであればUpdateする機能です。
今まで使いこなせていない機能なので活用したいと思います。

文字列だけではなくCSSのテストをVisual Testにて行なっていました。
スナップショットテストの応用で画像を取得して差分を出力する仕組みです。
CIに組み込むことで、レビュー観点の一つになるため、レビュワーの負担が軽減するのが良さげです。
レビュワーがチェックアウトしてビルド・確認をする手間が省けるのは大きいですね。

ツールはStorybookreg-suitoを用いていました。

User Interfaceのテストも同様にVisual Testを使っていました。
テスト範囲はクリックとinputイベントに絞っていて、難しいテストは実施しない選択を取っているそうです。

変更に強くするためのテストであることを忘れずに自分のプロジェクトに適応していきたいです。

まとめ

フロントエンドのカンファレンス自体初めてでしたがとても内容の濃い1日でした。
実践的なお話をたくさん聞けたので、どんどん投入していきたいと思います。
スタッフの方々本当にありがとうございました。

GraphQLを試してみた

こんにちは。ぬまたです。
約半年ぶりの投稿です。空けすぎました。
最近AppSyncを触る機会があったので、基礎となるGraphQLを学びたいと思います。

目次

GraphQLとは

GraphQLはフロントエンドがサーバのデータをいい感じに取得するためのクエリ言語です(怒られそう)。 ざっと調べる限りは以下のような特徴がありました。

  • 読み取りと書き込みが分離している
  • エンドポイントが一つ
  • データ群の処理結果などをパラメータに追加できる
  • Schemaや型を定義する
  • 変数やコメントアウトを使うことができる
  • キャッシュが比較的容易

実際に使ってみないとわからないので、GitHub APIで試してみます。 GitHubAPIのバージョン4からGraphQLを導入しています。

参考: - GitHub API v4

検証環境

GitHubのデベロッパーツールを使用します。

GraphQLはエンドポイントをリソースごとに分けずにパラメータで指定します。 GitHubではリクエストを https://api.github.com/graphql に向け、パラメータにクエリを入れています。

GitHub API を試してみる

ログインしているユーザーのメールアドレス取得

単一のリソースfieldsから指定したキーの値を取得できます。 現在ログインしているユーザのメールアドレスを取得してみます。

GitHubviewer という fieldsにログインユーザー情報が入っています。 クエリ内にパラメータ(email)を指定して取得することができます。

クエリ

{
 viewer {
   email
 }
}

結果

{
 "data": {
   "viewer": {
     "email": "nununu.mono@gmail.com"
   }
 }
}

Dockerの関連トピックを取得

fields は引数を取ることができます。 GitHubのDockerトピックに関連するトピックを取得してみます。

クエリ

query {
 topic(name: "docker") {
   relatedTopics {
     name
   }
 }
}

結果

{
 "data": {
   "topic": {
     "relatedTopics": [
       {
         "name": "docker-container"
       },
       {
         "name": "jhipster"
       },
       {
         "name": "docker-image"
       },
       {
         "name": "database"
       },
       {
         "name": "ruby"
       },
       {
         "name": "composer"
       },
       {
         "name": "nginx"
       },
       {
         "name": "yii"
       }
     ]
   }
 }
}

relatedTopics はオブジェクトのリストになっています。 ネストしたオブジェクトのキー(今回は name )を選択できることが異なる点ですね。

リポジトリ内のPR一覧を取得

nodes を使ってリスト値と欲しいパラメータを取得することができます。 今回はよく使うDjangoRESTFrameworkのPR一覧とそれぞれのコミット数を調べてみます。

クエリ

query { 
 repository(owner: "encode" name: "django-rest-framework") { 
   pullRequests(last: 5 states: OPEN) {
     nodes {
       number
       title
       commits {
         totalCount
       }
     }
   }
 }
}

結果

{
 "data": {
   "repository": {
     "pullRequests": {
       "nodes": [
         {
           "number": 6279,
           "title": "Let SearchFilter subclasses dynamically set search fields",
           "commits": {
             "totalCount": 4
           }
         },
         {
           "number": 6282,
           "title": "Ensure serializer and field validators support both lists and tuples",
           "commits": {
             "totalCount": 2
           }
         },
         {
           "number": 6284,
           "title": "Don't force implement get_queryset",
           "commits": {
             "totalCount": 1
           }
         },
         {
           "number": 6286,
           "title": "permissions must return a boolean to allow &/| operator comparison",
           "commits": {
             "totalCount": 3
           }
         },
         {
           "number": 6288,
           "title": "Fix DjangoObjectPermissionsFilter deprecation note",
           "commits": {
             "totalCount": 2
           }
         }
       ]
     }
   }
 }
}

pullRequests の引数にPRが OPEN であることと、最新の5つを取得するよう指定しています。 DjangoRESTFrameworkのGitHubをみると正しそうです。

nodes はPRのリスト値にあたり、その中でPRのNoやコミット数を指定しています。 commitstotalCount パラメータが入っているので、データを全て取得せずに総数を把握することができますね。 自分でSchemaを定義する場合はクライアントが欲しい値を自由に追加することができます。

特定条件のリポジトリ数の取得

GraphQLのPaginationを使ってクエリ結果の総数を取得できます。 Pythonで書かれているスター数1000以上のリポジトリが何個あるか調べてみます。

クエリ

query {
 search(query: "language: Python stars:>1000", type: REPOSITORY) {
   repositoryCount
}

結果

{
 "data": {
   "search": {
     "repositoryCount": 28
   }
 }
}

searchGitHubで定義されているコネクションです。クエリに加えて読み取り開始位置 after などを定義できるようになっています。

今回は querytype に検索したい条件をして、値に repositoryCount を取ることでリポジトリ数を取得しています。 search の引数を変えて、色々な検索をかけられるのはよいですね。

まとめ

今回は読み取りの Query を中心にGraphQLを試してみました。 GraphQLのメリットはクライアントの負担が減ることなのかなという所感です。

Schemaの定義はクライアントの要求を取り入れつつ、データベース内のモデルの知識が求められそうですね。 その辺りも今後調べたいと思います。

BacklogAPIライブラリを作ってみた

Pythonエンジニア見習いのぬまやんです。

今回はBacklogAPIのクライアントライブラリを作ってみました。
Backlogの課題やプロジェクトをオブジェクトとして扱えるようにしています。

目次

動機

今まで作りかけだった自作のライブラリにエキスパートPythonプログラミングの内容を実践してみたいと思ったことがきっかけです。
エキスパートPythonプログラミングは言語の基本文法だけでなく、パッケージの作り方やテスト、プロジェクトの管理 についてPythonのベストプラクティスをまとめていただいている本になります。

エキスパートPythonプログラミング改訂2版

エキスパートPythonプログラミング改訂2版

作ったもの

作成したライブラリはGitHubにて管理していて、PyPIに公開しています。

pypi.org

使い方

実現したかったことはBacklogのプロジェクトや課題をオブジェクトとして扱うことです。
そのため以下のような操作感にしています。

from backlogapi import BacklogClient

client = BacklogClient(api_key='apiKey', space_name='spaceName'
# Space内の全プロジェクトを取得する
projects = client.project.all()
project1 = projects[0]

# プロジェクト内の課題を取得する
issues = project1.get_issues()

# 取得した課題を削除する
for issue in issues:
    issue.delete()

エキスパートPythonプログラミングを参考にしたところ

PyPIのへのアップロード

今回のライブラリの目標はPyPIにアップロードして、公開することでした。 公開するにはsetup.py, MANIFEST.in, requirements.txtなど準備するものが多いです。
本書にはこの設定方法について詳細や一般的な手段が記されています。

本書内で何箇所か説明されている新しいPyPIはすでに公開されています。 pypi.org
さらに Project DescriptionについてMarkdownの対応も完了しています。 対応方法はsetup.pyのsetup関数に以下を追加するだけです。

long_description_content_type='text/markdown',

コード管理手法

コードの管理として、継続的デプロイが紹介されています。
私はGitHubにてホスティングしていたため、TravisCIを用いて自動テストを行いました。 TravisCIを用いた所感は以下になります。

  • 新規の仮想環境にてテストを行うことができる
  • GitHubとの連携が簡単
  • 設定方法のドキュメントが読みやすい

他にも紹介されているJenkinsやBuildbotについても試していきたいと思います。

テスト駆動開発

テストについては基礎から応用まで幅広く記されています。 具体的にはunittestを用いた基本的なテスト方法から拡張モジュールであるnose, pytestの使い方が説明されています。 今回はBacklogという外部APIを用いたライブラリなので、Mockを作成する方法やtoxを使ったマトリックステストも使ってみました。

本書から引用したツール・ライブラリは以下になります。

作ってみた感想

ベースとなる書籍があったためPyPIでの公開はスムーズにできました。
しかし、ライブラリ自体を使用する側の目線に立って作ることが難しかったです。
現状はまだまだ使いにくいものなので、改善していきたいと思います。 また、使用感を教えていただけると大変嬉しいです。