読者です 読者をやめる 読者になる 読者になる

ikeikeikeike's unk blog.

http://github-awards.com/users/ikeikeikeike

PythonからElasticsearchを扱うelasticsearch-dsl-pyがなかなか良かった

elastic/elasticsearch-dsl-py · GitHub

いつの間に公式からHigh levelなPythonクライアントが出ていたので使用してみました。

PythonのElasticsearch関連のライブラリは色々とHaystack(つらい)とかelasticutils(いつの間にかDeprecatedになっていた)とかがあって 不遇だったのでコレで解消されればよいのかな。

機能面

elasticsearch-dsl-pyはElasticsearchの薄いラッパーと言うことらしいのですが、ひと通りの機能は揃っているみたいです。

あと特にDjangoやらSQLAlchemyやらを併用して使用することは今のところ想定されていないみたい。

Analysis

analyzerは kuromoji_analyzerngram_analyzer がすでに定義されているものとします

Mapping

Mappingはdynamicに定義するのとstaticに定義する方法がある

dynamic:
from elasticsearch_dsl import Mapping

m = Mapping('mix')
m.field('title', 'string')
m.field('kuromoji', 'string', analyzer="kuromoji_analyzer")
m.field('ngram', 'string', analyzer="ngram_analyzer")
m.field('published', 'boolean')
m.field('ctime', 'date')

m.meta('_all', enabled=False)
m.save('mix')  # send mapping
static: model-likeとのこと。なんか既に誰かがDjangoとかSQLAlchemy用に拡張してそうな形だ
from datetime import datetime

from elasticsearch_dsl import (
    DocType,
    String,
    Date,
    Boolean
)


class Mix(DocType):
    title = String()
    kuromoji = String(analyzer="kuromoji_analyzer")
    ngram = String(analyzer="ngram_analyzer")
    published = Boolean()
    ctime = Date()

    class Meta:
        index = 'mix'

Mix.init()  # send mapping

saveまたはinitすると以下のような物が送られます

[in env/lib/python3.4/site-packages/elasticsearch/connection/base.py:72] 2015-02-04 16:30:33 91613 140735325455120 INFO base: \
curl -XPUT 'http://localhost:9200/mix/_mapping/mix?pretty' -d '{
  "mix": {
    "properties": {
      "ctime": {
        "type": "date"
      },
      "kuromoji": {
        "analyzer": "kuromoji_analyzer",
        "type": "string"
      },
      "ngram": {
        "analyzer": "ngram_analyzer",
        "type": "string"
      },
      "published": {
        "type": "boolean"
      },
      "title": {
        "type": "string"
      }
    }
  }
}'

save document

model-likeなMixオブジェクト使用してdocumentの送信

sentence = "アナライザの実装には細かいプロパティがいくつかあり"
mix = Mix(id=1, kuromoji=sentence, ngram=sentence, title=sentence, published=True)

mix.save()  
# curl -XPUT 'http://localhost:9200/mix/mix/1' -d '{"ctime": "2015.....}'

mix = Mix(id=2, kuromoji=sentence, ngram=sentence, title=sentence, published=True)
mix.save()  
# curl -XPUT 'http://localhost:9200/mix/mix/2' -d '{"ctime": "2015.....}'
    In [1]: for mix in Mix.search().query("match", ngram="アナ").execute():
       ...:     print(mix.published)  # Resultオブジェクトとしても使われる
       ...:
    True
    True

他には filters, aggregations, sort, pagination, additional parameters, suggest, highlight などがあるようです

無い機能

上記でも書いているが、ORMのモデルとの関連付けは自分で書かないといけないですね

まぁそこはチョロっとsignralあたりを使って書いてください

こちらからは以上です

おまけ

pip登録されてないけどこれも気になってる

liberation/django-elasticsearch · GitHub

2015年 4月 2日 追記

elasticsearch-dsl-pyを使ってZero Downtime Reindexingを書いてみました

Elasticsearch Zero Downtime Reindexing using elasticsearch-dsl-py ref: https://www.elastic.co/blog/changing-mapping-with-zero-downtime gist.github.com

beego migration

なんかbee new で新しくprojectを作るとmigrationフォルダができるけどなんか使いづらかったので gooseを替わりに使用した

Download

$ go get bitbucket.org/liamstask/goose/cmd/goose    

db/dbconf.ymlを作成&編集

$ cat db/dbconf.yml
development:
    driver: postgres
    open: postgres://user:password@develop.local:5432/tablename

production:
    driver: postgres
    open: postgres://user:password@127.0.0.1:5432/tablename

environment_variable_config:
    driver: $DB_DRIVER
    open: $DATABASE_URL

'entry'.q カラムにginインデックスをはる

$ goose create InstallPgExtension sql
goose: created /path/to/gopath/src/example.com/ikeikeikeike/example/db/migrations/20150130234510_InstallPgExtension.sql
$ goose create AddGinIndexToQOnEntry sql
goose: created /path/to/gopath/src/example.com/ikeikeikeike/example/db/migrations/20150130234522_AddGinIndexToQOnEntry.sql

20150130234510_InstallPgExtension.sql の中身

-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE EXTENSION IF NOT EXISTS pg_trgm;


-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP EXTENSION IF EXISTS pg_trgm;

20150130234522_AddGinIndexToQOnEntry.sql の中身

-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE INDEX index_entry_on_q ON entry USING gin (q gin_trgm_ops);


-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP INDEX index_entry_on_q;

migration実行 -env オプションでenvironmentの変更ができる

up

$ goose up
goose: migrating db environment 'development', current version: 0, target: 20150130174127
OK    20150130234510_InstallPgExtension.sql
OK    20150130234522_AddGinIndexToQOnEntry.sql

down

$ goose down
goose: migrating db environment 'development', current version: 20150130174127, target: 20150130174043
OK    20150130174127_AddGinIndexToQOnEntry.sql

$ goose down
goose: migrating db environment 'development', current version: 20150130174043, target: 0
OK    20150130174043_InstallPgExtension.sql

よさげ

べーごー signup,login,logout

Beegoで認証あたりを処理を書いた(メール飛ばしたりはしてない)。以下やっていること

  • プロジェクト生成
  • Model定義
  • Form処理
  • ユーザー, 認証処理
  • Flash messages, Session処理
  • login filter(require)なるもの

もろもろインストール

$ go get -u github.com/beego/bee
$ go get -u github.com/astaxie/beego

プロジェクト作成コマンド

$ bee new auth

こんなツリーが出来る

$ tree
.
├── conf
│   └── app.conf
├── controllers
│   └── default.go
├── main.go
├── models
├── routers
│   └── router.go
├── static
│   ├── css
│   ├── img
│   └── js
├── tests
│   └── default_test.go
└── views
    └── index.tpl

ユーザーを定義する

Email,Password,ログイン時間,作成時間,更新時間のよくある形

package models

import (
    "time"

    "github.com/astaxie/beego"
    "github.com/astaxie/beego/orm"
)

type User struct {
    Id            int64
    Email         string    `orm:"size(64);unique"`
    Password      string    `orm:"size(32)"`
    Lastlogintime time.Time `orm:"type(datetime);null"`
    Created       time.Time `orm:"auto_now_add;type(datetime)"`
    Updated       time.Time `orm:"auto_now;type(datetime)"`
}

func init() {
    orm.RegisterModelWithPrefix(
        beego.AppConfig.String("dbprefix"),
        new(User))
}

.. doc::

Fieldに複数の属性をつける場合ははセミコロン(;)でつなげる どんな属性があるかはドキュメントを: Model Definition - beego: simple & powerful Go app framework

Modelを定義したらDDLを走らせる。 これは以下のようにorm.RunSyncdbをinitあたりに定義しておけばBeegoがやってくれる (ノリ的にDjangoのsyncdbと同じもの)

func init() {
    dbname, force, verbose := "default", false, true
    err := orm.RunSyncdb(dbname, force, verbose)
    if err != nil {
        panic(err)
    }
}

私はこんなふうに書いてます: beego-samples/db.go at master · ikeikeikeike/beego-samples · GitHub

Formの定義

先ほど定義したuserテーブルを編集, パスワード確認のRepasswordと「form:」「valud:」を追加

type User struct {
    Id            int64
    Email         string    `orm:"size(64);unique" form:"Email"      valid:"Required;Email"`
    Password      string    `orm:"size(32)"        form:"Password"   valid:"Required;MinSize(6)"`
    Repassword    string    `orm:"-"               form:"Repassword" valid:"Required"`
    Lastlogintime time.Time `orm:"type(datetime);null" form:"-"`
    Created       time.Time `orm:"auto_now_add;type(datetime)"`
    Updated       time.Time `orm:"auto_now;type(datetime)"`
}

「form:」はhtml上のkinput[name="Email"]のところ。「valud:」はValidation定義 ドキュメントはここ: Form validation - beego: simple & powerful Go app framework

上述に対応するHTMLが下記のような形になる。

<form method="POST" action='{{urlfor "LoginController.Login"}}'>
  {{ .xsrfdata }}

  <input name="Email" type="email" value="{{index .Params "Email"}}" required />
  <input name="Password" type="password" value="" required pattern=".{6,}" 
         title="パスワードは6文字以上を入力してください" />
  <input name="Repassword" type="password" required pattern=".{6,}" 
         title="パスワードは6文字以上を入力してください" />

  <input type="submit" value="ログイン"> 
</form>

次は認証処理

  1. Routerの追加

Routerは以下の物があり,Automatching, Annotations, Namespaceがあったり色々できる

  • Basic router
  • RESTful router
  • Fixed router
  • Regex router

以下の例ははSinatra風に記述ができるRegex routerを使用している

package routers

import (
    ctl "bitbucket.org/ikeikeikeike/auth/controllers"
    "github.com/astaxie/beego"
)

func init() {
    beego.Router("/login", &ctl.LoginController{}, "get,post:Login")
    beego.Router("/logout", &ctl.LoginController{}, "get:Logout")
    beego.Router("/signup", &ctl.LoginController{}, "get,post:Signup")
}
  1. Controller Signup部分

xsrf, flash message, formのパース, Sessionとか色々やってる

func (c *LoginController) Signup() {
    c.TplNames = "login/signup.tpl"
    c.Data["xsrfdata"] = template.HTML(c.XsrfFormHtml())

    if !c.Ctx.Input.IsPost() {
        return
    }

    var err error
    flash := beego.NewFlash()

    u := &models.User{}
    if err = c.ParseForm(u); err != nil {
        flash.Error("Signup invalid!")
        flash.Store(&c.Controller)
        return
    }
    if err = models.IsValid(u); err != nil {
        flash.Error(err.Error())
        flash.Store(&c.Controller)
        return
    }

    id, err := lib.SignupUser(u)
    if err != nil || id < 1 {
        flash.Warning(err.Error())
        flash.Store(&c.Controller)
        return
    }

    flash.Success("Register user: %s", u.Email)
    flash.Store(&c.Controller)

    c.SetLogin(u)

    c.Redirect(c.UrlFor("UsersController.Index"), 303)
}
  1. Ctrl Login,Logout部分

とくに変わったことなし, URL参照: beego-samples/login.go at master · ikeikeikeike/beego-samples · GitHub

次,リクエスト時にログインしているか事前に確認するようにする

Filtersを使用: Filters - beego: simple & powerful Go app framework

   beego.InsertFilter("/", beego.BeforeRouter, func(ctx *context.Context) {
        _, ok := ctx.Input.Session("userinfo").(int64)
        if !ok {
            ctx.Redirect(302, "/login")
        }
    })

    beego.Router("/", &ctl.UsersController{}, "get:Index")
    beego.Router("/login", &ctl.LoginController{}, "get,post:Login")
    beego.Router("/logout", &ctl.LoginController{}, "get:Logout")
    beego.Router("/signup", &ctl.LoginController{}, "get,post:Signup")

Prepareをoverwriteすることでも実現できる: Controller funcs - beego: simple & powerful Go app framework

func (c *UsersController) Prepare() {
    if !c.IsLogin {
        c.Ctx.Redirect(302, c.LoginPath())
        return
    }
}

以上どす

上述の例を少しだけ拡張したサンプルソースコード

beego ormとdjango orm

Beego ORMはDjango ORMは影響されているということらしいので

http://beego.me/docs/mvc/model/query.md#advanced-queries の内容をDjangoORMに置き換えようと思ってたけど途中で疲れて終わらしてます (またリンクのみの箇所は置き換える必要がないほど似ているため面倒で書いていません)

Basic Usage: Advanced Queries

Go
o := orm.NewOrm()

// Get a QuerySeter object. User is table name
qs := o.QueryTable("user")

// Can also use object as table name
user := new(User)
qs = o.QueryTable(user) // return a QuerySeter
Python
from django.contrib.auth import models

qs = models.User.objects.get_queryset()

expr

Go
qs.Filter("id", 1) // WHERE id = 1
qs.Filter("profile__age", 18) // WHERE profile.age = 18
qs.Filter("Profile__Age", 18) // key name and field name are both valid
qs.Filter("profile__age", 18) // WHERE profile.age = 18
qs.Filter("profile__age__gt", 18) // WHERE profile.age > 18
qs.Filter("profile__age__gte", 18) // WHERE profile.age >= 18
qs.Filter("profile__age__in", 18, 20) // WHERE profile.age IN (18, 20)

qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000)
// WHERE profile.age IN (18, 20) AND NOT profile_id < 1000
Python
qs.filter(id=1)  # WHERE id = 1
qs.filter(profile__age=18)  # WHERE profile.age = 18
qs.filter(Profile__Age=18)  # key name and field name are both valid
qs.filter(profile__age=18)  # WHERE profile.age = 18
qs.filter(profile__age__gt=18)  # WHERE profile.age > 18
qs.filter(profile__age__gte=18)  # WHERE profile.age >= 18
qs.filter(profile__age__in=[18, 20])  # WHERE profile.age IN (18, 20)

qs.filter(profile__age__in=[18, 20]).exclude(profile__lt=1000)

# WHERE profile.age IN (18, 20) AND NOT profile_id < 1000

Operators

exact

Go
qs.Filter("name", "slene") // WHERE name = 'slene'
qs.Filter("name__exact", "slene") // WHERE name = 'slene'
// using = , case sensitive or not is depending on which collation database table is used
qs.Filter("profile", nil) // WHERE profile_id IS NULL
Python
qs.filter(name="slene")  # WHERE name = 'slene'
qs.filter(name__exact="slene")  # WHERE name = 'slene'
# using = , case sensitive or not is depending on which collation database table is used
qs.filter(profile=None)  # WHERE profile_id IS NULL

Advanced Query API

SetCond

Go
cond := NewCondition()
cond1 := cond.And("profile__isnull", false).AndNot("status__in", 1).Or("profile__age__gt", 2000)

qs := orm.QueryTable("user")
qs = qs.SetCond(cond1)
// WHERE ... AND ... AND NOT ... OR ...

cond2 := cond.AndCond(cond1).OrCond(cond.And("name", "slene"))
qs = qs.SetCond(cond2).Count()
// WHERE (... AND ... AND NOT ... OR ...) OR ( ... )
Python
from django.db.models import Q

cond1 = Q(profile__isnull=False) & ~Q(status__in=[1]) | Q(profile__age__gt=2000)

from django.contrib.auth import models
qs = models.User.objects.filter(cond1)

cond2 = Q(cond1) | Q(name="slene")
qs.filter(cond2).count()
# Output number

All

Go
var users []*User
num, err := o.QueryTable("user").Filter("name", "slene").All(&users)
fmt.Printf("Returned Rows Num: %s, %s", num, err)
Python
users = models.User.objects.filter(name="slene")
print("Returned Rows Num: %s, %s" % (len(users), None))
Go
type Post struct {
    Id      int
    Title   string
    Content string
    Status  int
}

// Only return Id and Title
var posts []Post
o.QueryTable("post").Filter("Status", 1).All(&posts, "Id", "Title")
Python
class Post(models.Model):
    title = models.CharField(null=False, max_length=255)
    content = models.CharField(null=False, max_length=255)
    status = models.IntegerField(null=False)

    class Meta:
        db_table = 'post'

# Only return Id and Title
Post.objects.filter(status=1).values("id", "title")

One

Go
var user User
err := o.QueryTable("user").Filter("name", "slene").One(&user)
if err == orm.ErrMultiRows {
    // Have multiple records
    fmt.Printf("Returned Multi Rows Not One")
}
if err == orm.ErrNoRows {
    // No result 
    fmt.Printf("Not row found")
}
Python
try:
    user = User.objects.get(name="slene")
except User.MultipleObjectsReturned as err: 
    # Have multiple records
    print("Returned Multi Rows Not One")
except User.DoesNotExist as err:
    # No result 
    print("Not row found")

Relational Query

User and Profile is OnToOne relation

Go
user := &User{Id: 1}
o.Read(user)
if user.Profile != nil {
    o.Read(user.Profile)
}
Python
user = User.objects.get(id=1)
if user.profile:
    pass
Go
user := &User{}
o.QueryTable("user").Filter("Id", 1).RelatedSel().One(user)
// Get Profile automatically
fmt.Println(user.Profile)
// Because In Profile we defined reverse relation User, Profile's User is also auto assigned. Can directly use:
fmt.Println(user.Profile.User)
Python
user = User.objects.get(id=1)
println(user.profile.user)

Post and User are ManyToOne relation. i.e.: ForeignKey is User

Go
type Post struct {
    Id    int
    Title string
    User  *User  `orm:"rel(fk)"`
    Tags  []*Tag `orm:"rel(m2m)"`
}
Python
class Post(models.Model):
    title = models.CharField(null=False, max_length=255)
    user = models.ForeignKey("User", related_name='posts', null=False)
    tags = models.ManyToManyField("Tag", related_name='posts', null=False)

    class Meta:
        db_table = 'post'
Go
var posts []*Post
num, err := o.QueryTable("post").Filter("User", 1).RelatedSel().All(&posts)
if err == nil {
    fmt.Printf("%d posts read\n", num)
    for _, post := range posts {
        fmt.Printf("Id: %d, UserName: %d, Title: %s\n", post.Id, post.User.UserName, post.Title)
    }
}
Python
posts = Post.objects.filter(user=1)
if posts:
    print("%d posts read\n" % len(posts))
    for post in posts:
        print("Id: %d, UserName: %d, Title: %s\n" % (post.id, post.user.username, post.title))
Go
var user User
err := o.QueryTable("user").Filter("Post__Title", "The Title").Limit(1).One(&user)
if err == nil {
    fmt.Printf(user)
}
Python
try:
    user = User.objects.get(posts__title="The Title")
    print(user)
except User.DoesNotExist:
    pass

Post and Tag are ManyToMany relation

M2MはDjangoRailsの間の子みたいな感じ

Djangoぽく書いた場合
Go
type Post struct {
    Id    int
    Title string
    User  *User  `orm:"rel(fk)"`
    Tags  []*Tag `orm:"rel(m2m)"`
}
type Tag struct {
    Id    int
    Name  string
    Posts []*Post `orm:"reverse(many)"`
}
Python
class Post(models.Model):
    title = models.CharField(null=False, max_length=255)
    user = models.ForeignKey("User", related_name='posts', null=False)
    tags = models.ManyToManyField("Tag", related_name='posts', null=False)

    class Meta:
        db_table = 'post'
class Tag(models.Model):
    name = models.CharField(null=False, max_length=255)

    class Meta:
        db_table = 'tag'
Railsぽく書いた場合

中間テーブルでIndexとか自由に付けられる

Go
type Post struct {
    Id    int64  `orm:"auto"`
    Title string `orm:"size(255)"`
    User  *User  `orm:"rel(fk);index"`
    Tags  []*Tag `orm:"rel(m2m);index;rel_through(example.com/ikeikeikeike/unk/models.PostTag)"`
}
type PostTag struct {
    Id    int64  `orm:"auto"`
    Tag   *Tag   `orm:"rel(fk);index"`
    Post  *Post  `orm:"rel(fk);index"`
}
type Tag struct {
    Id    int64
    Name  string
    Posts []*Post `orm:"reverse(many)"`
}
Ruby(On Rails)
class Post < ActiveRecord::Base
  belongs_to :user, touch: true

  has_many :post_tags
  has_many :tags, through: :post_tags
end
class PostTag < ActiveRecord::Base
  belongs_to :post
  belongs_to :tag
end
class Tag < ActiveRecord::Base
  has_many :post_tags
  has_many :posts, through: :post_tags
end
Go
var posts []*Post
num, err := dORM.QueryTable("post").Filter("Tags__Tag__Name", "golang").All(&posts)
var tags []*Tag
num, err := dORM.QueryTable("tag").Filter("Posts__Post__Title", "Introduce Beego ORM").All(&tags)
Python
Post.objects.filter(tags__tag__name="golang")
Tag.objects.filter(posts__post__title="Introduce Beego ORM")
Ruby(On Rails)
Post.joins(:tags).where("tags.name" => "golang")
Tag.joins(:posts).where("posts.title" => "Introduce Beego ORM")

Load Related Field

Handling ManyToMany relation

Go
o := orm.NewOrm()
post := Post{Id: 1}
m2m := o.QueryM2M(&post, "Tags")
// In the first param object must have primary key
// The second param is the M2M field will work with
// API of QueryM2Mer will used to Post with id equals 1
Python
post = Post.objects.get(id=1)
m2m = post.tags

QueryM2Mer Add

Go
tag := &Tag{Name: "golang"}
o.Insert(tag)

num, err := m2m.Add(tag)
if err == nil {
    fmt.Println("Added nums: ", num)
}
Python
tag = Tag(name="golang")
tag.save()

m2m.add(tag)  # return None
TODO: 各例に一言説明文を書くように努力する

続きはまた明日

Ubuntu 14.04 LTS Trusty Tahrにおける各種ulimit設定

この間プライベートのサーバーが too many open files になっていたので設定した備忘録的なもの

Nginx

/etc/default/nginx

ULIMIT="-n 65536"

Redis

/etc/default/redis-server

ULIMIT=65536

Memcached

/etc/memcached.conf

-c 65536

PAM認証系

/etc/security/limits.conf

アスタリスクorユーザー名 soft nofile 65536
アスタリスクorユーザー名 hard nofile 65536

Sysvinit系

/etc/initscript

ulimit -n 65536
eval exec "$4"

Upstart

/etc/init/任意の名称.conf

limit nofile 65536 65536
#limit memlock 82000 82000

systemdは使ってないので無し

それとupstartで共通の書き方はないものか

追記

Ansibleだとこう書いた added ulimit role · a972eb1 · ikeikeikeike/ansible-playbooks · GitHub

どーしてもPEP8を守れない人用のDjango1.7の設定

以下ネタです。基本的にPEP8は準拠しませう
また使用しているDjango1.7の新機能Applications, System check frameworkの使用方法は全くもって正しくないので真似しないで下さい

Django1.7おめでとうございます!!!

Django1.7がリリースしてめでたかったので新しく1.7から追加された機能ApplicationsとSystem check frameworkの仕組みを利用して強制的にPEP8を守らしてみました。

まずDjangoのappをつくりましょう。checkpep8でよいですかね?

$ python manage.py startapp checkpep8

出来上がったcheckpep8アプリの__init__.pyにdefault_app_configを追加

checkpep8/__init__.py

default_app_config = 'checkpep8.apps.Checkpep8Config'

apps.pyを作成、下記の内容を書いておく(予めsettingsにはcheckpep8を登録しておきましょう)

checkpep8/apps.py

import sys
import pep8
from cStringIO import StringIO

from django import apps
from django.core import checks
from django.conf import settings
from django.utils.translation import ugettext_lazy as _


def capture(func, *args, **kwargs):
    out, sys.stdout = sys.stdout, StringIO()
    func(*args, **kwargs)
    sys.stdout.seek(0)
    res = sys.stdout.read()
    sys.stdout = out
    return res


def checker(**kwargs):
    app_names = (
        app_name for
        app_name in settings.INSTALLED_APPS
        if not app_name.startswith('django')
    )

    app_paths = [
        apps.AppConfig.create(app_name).path
        for app_name in app_names
    ]

    errors = []

    outs = capture(pep8.StyleGuide().check_files, app_paths)
    for out in outs.splitlines():
        errors.append(
            checks.Error(
                out,
                hint=None,
                obj=None,
                id='pep8.%s' % out.split(' ')[1],
            )
        )

    return errors


class Checkpep8Config(apps.AppConfig):
    name = 'checkpep8'
    verbose_name = _("Checkpep8")

    def ready(self):
        checks.register('pep8')(checker)

pep8に引っかかるようにcheckpep8/models.pyを編集

from django.db import models


class First(models.Model):

    def pep(self):
        return


    def too_many_blank_linespep(self):
        return

現在のProjectの状態

$ tree
.
├── checkpep8
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── manage.py
└── runserver_pep8
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

いざmakemigrations

$ python manage.py makemigrations checkpep8
CommandError: System check identified some issues:

ERRORS:
?: (pep8.E303) /Users/ikeda/.virtualenvs/django17/blog/runserver_pep8/checkpep8/models.py:13:5: E303 too many blank lines (3)

やりました!! System check framework が効いている。。。 pep8チェックで makemigrations 動かない!!

models.pyを直してみる

from django.db import models


class First(models.Model):

    def pep(self):
        return

    def too_many_blank_linespep(self):
        return

再度 makemigration

$ python manage.py makemigrations checkpep8
Migrations for 'checkpep8':
  0001_initial.py:
    - Create model First

おおお、makemigrationsがうごいたー

これでpep8準拠てか、強制の方向へ。。だれかautopep8でも試してみてください〜。

管理画面を提供しているWebFWに対応するVarnish vclの書き方

sub vcl_hash {

    # Cookie毎にキャッシュ
    hash_data(req.http.cookie);

    # Device毎にキャッシュ
    hash_data(req.http.X-Device);

    # HTML, JS, CSS毎にキャッシュ
    hash_data(req.http.Accept);

    # URL毎にキャッシュ
    hash_data(req.url);

    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }

    return (hash);
}

Varnishはvcl_hashで生成されたhash毎にキャッシュ生成するようなのでキャッシュしたい区別したい項目を列挙

管理画面なので今回はreq.http.cookieをhash_dataに入れておく。以上で私の場合はOKだった。

もちろん vcl_recv 内では req.request POST、req.http.Authorizationなどは return (pass); しています。

ansible-playbooks/default.vcl.j2 at master · ikeikeikeike/ansible-playbooks · GitHub