2020年12月8日火曜日

SPA用のサーバー設定(Apache)

SPA(シングルページアプリケーション)を page.js で一生懸命作って、サーバー(Apache)のディレクトリにアップしたところ、ルーティングが全然うまくいかない。 WordPressの設定に倣って、以下のようにしたところ無事解決しました。
.htaccess
<ifmodule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /spa/
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /spa/index.html [L]
</ifmodule>

/spa/index.html がSPAのファイルです。

2020年11月10日火曜日

ChromeのCanvasアニメーションが激重

いつからかChromeでCanvasのアニメーションが、惨いほど遅くなった。ほとんどアニメーションが効いていなくて、パッと切り替わってしまう位だ。

どうにも動作が遅いので、調査したところ以下の設定をEnabledにしてやると問題ないレベルで滑らかに動作するようになった。

ChromeのURL欄に chrome://flags/ と入力すると以下のような画面が現れる。


以下の4つをEnabledにした。

  • Override software rendering list
  • GPU rasterization
  • Out of process rasterization
  • Zero-copy rasterizer

とりあえず解決されてよかったですが、この現象が発生しているのは古めのPCで、何が原因で遅くなってしまったかが不明です。


2020年10月10日土曜日

Django Admin FileFieldからアップロードしたZIPファイルを自動解凍する

Admin画面のFileFieldからアップロードした直後のファイルの処理がなかなか分からなかったが以下のようにmodels.pyにdef save(self, *args, **kwargs):を追加して解決しました。

例 models.py:

class DataFile(models.Model):
    data = models.FileField()
    def save(self, *args, **kwargs):
        super(DataFile, self).save(*args, **kwargs)
        # Do anything you'd like with the data in filename
        filename = str(self.data)
        filepath = os.path.join(settings.MEDIA_ROOT, filename)
        with zipfile.ZipFile(filepath) as existing_zip:
            existing_zip.extractall(os.path.dirname(filepath))


参考

Django admin file upload handling · Eviatar Bach
http://eviatarbach.com/2014/12/12/django-admin-upload-handling/

2020年6月29日月曜日

django-CMS SQLite3をMySQLに変更する

以下を参考に、Django(django-CMS)のSQLite3 DBデータを dump してやってみましたが、どうもエラーが発生してうまくいきません。

SQLite3のデータをdumpしてMySQLに移行する
https://qiita.com/nnsnodnb/items/9e99e7f0ca3f82bf2171%C2%A0%C2%A0


そこで、以下のようにjsonファイルでエクスポート/インポートしたらできました。

1.仮想環境に入ってから以下を実行する。
(venv)$ python manage.py dumpdata > datadump.json

2.MySQL内にDBをCREATEしておきます。
mysql > CREATE DATABASE dbname CHARACTER SET utf8mb4;

3.次にsettings.pyを編集してDBをMySQLに変更します。
  ※先にMySQL内にインポートするDBをCREATEしておくのを忘れないようにします。

4.Migrateします。
(venv)$ python manage.py migrate

※このままでloaddataをするといかのようなエラーが発生するので、手順4のようにContentTypeを削除します。
django.db.utils.ProgrammingError: Problem installing fixture '/xxx/myproject/datadump.json': Could not load contenttypes.ContentType(pk=1): (1146, "Table 'dbname .django_content_type' doesn't exist")

5.Pythonのシェルに入ります。
(venv)$ python manage.py shell

そして以下を実行します。
>>> from django.contrib.contenttypes.models import ContentType
>>> ContentType.objects.all().delete()
>>> quit()

6.データをインポートします。
(venv) $ python manage.py loaddata datadump.json


これでOKでした。

参考
What's the best way to migrate a Django DB from SQLite to MySQL?

2020年6月11日木曜日

Django urls.py 単純なリダイレクトの方法

Djangoで以下のようにリダイレクトしたい場合の設定方法

example.co.jp/abc/ ⇒ example.co.jp/xyz/
example.co.jp/abc/aiueo/ ⇒ example.co.jp/xyz/aiueo/

urls.py

from django.views.generic.base import RedirectView

urlpatterns = [

    url(r'^abc/(?P<path>.*)$', RedirectView.as_view(url='/xyz/%(path)s')),

]


Django 2.0以降のパスコンバーターを使った場合は以下ですが、
/abc/の後ろが空の場合が対応できない。
example.co.jp/abc/ ⇒ example.co.jp/xyz/ ×

from django.urls import path

urlpatterns = [

    path('abc/<path:pathstrings>', RedirectView.as_view(url='/xyz/%(pathstrings)s')),
# abc/以降が空の場合の以下を追加して対応
path('abc/', RedirectView.as_view(url='/xyz/')),
]

日本発のサーバーレスアプリケーション開発向けフレームワーク『Jeffy』を試したので紹介する - Sweet Escape

Vue.jsのサイトジェネレータGridsomeが最高かもしれない - Sweet Escape

2020年4月2日木曜日

mod_wsgi で WSGIScriptAlias を複数設定する - MonotaRO Tech Blog

https://tech-blog.monotaro.com/entry/2018/07/04/084733

mod_wsgi で WSGIScriptAlias を複数設定する

はじめに

私たちのWeb アプリケーションの中には、 mod_wsgi を使っているものがあります。 これまで、アプリをデプロイする際は、 VirtualHost あたり一つだけ WSGIScriptAlias を指定するような使い方をしてきたのですが、最近、新たなアプリを組み込むために、一つの VirtualHost に複数の WSGIScriptAliasをもたせる方式を検討することになりました。

結局この方式は見送られ、別のやりかたに変えたのですが、その経緯をお話したいと思います。

やろうとしたこと

WSGIScriptAlias は、特定のパスへのリクエストを mod_python が処理するように指示するためのディレクティブです。

単純な Web アプリをデプロイするときは、WSGI アプリを application という名前で定義した Python コードの入ったファイルを指定します:

<VirtualHost *:80>        WSGIScriptAlias /hoge /path/to/app/hoge.wsgi    </VirtualHost>  

これで、 mod_wsgi は /hoge 以下のリクエストを hoge.wsgi で処理しようと試みるようになります。 複数のアプリケーションをデプロイしたければ、

<VirtualHost *:80>        WSGIScriptAlias /hoge /path/to/app/hoge.wsgi      WSGIScriptAlias /moge /path/to/app/moge.wsgi    </VirtualHost>  

のように、エンドポイントごとに別のスクリプトを指定していけばよいはずです。

実際、開発を行ったチームも、この構成で一通り動作確認をして、単体テストレベルでは /hoge と /moge の両方のアプリケーションがそれぞれ正常に稼働することを確認していました。

困ったことがおきた

しかし、リリースバージョンをテストする環境で自動テストを流したところ、突然テストが失敗しはじめました。既存のアプリケーションで 404 エラーが多発し始めたのです。

開発チームは、最初はリリースバージョンの他の変更が原因ではないかと考えたので、他の変更をロールバックしました。いったん症状は改善したかにみえます。・・・しかし何度かテストを走らせると、やっぱりエラーが発生します。しかも、新規のアプリケーションにも同じような症状が現れ始めました。

この時点で、 mod_wsgi の挙動を疑い始めます。テスト用サーバの config を確認したところ、 WSGIApplicationGroup が SERVER に設定されており、これが原因でした。

WSGIApplicationGroup とは

WSGIApplicationGroup とは、ひとことでいえば、「リクエストを mod_wsgi のどのインタプリタで処理するか」の設定です。

Apache では、リクエストを処理する際、ハンドラと呼ばれる処理を順に呼び出します。 mod_wsgi のようなモジュールでは、このハンドラを登録しておき、 config に設定したパスに一致するリクエストが来たときに、WSGIスクリプトを実行してリクエストを処理するような仕組みになっています。

f:id:ymasuda_whosaysni:20180703181833p:plain

スクリプトの実行は mod_wsgi に組み込まれた Python インタプリタ上で行われ、毎回インタプリタを初期化したり、モジュールを読み込み直さなくてもよいように、インタプリタを再利用する仕組みがあります。

しかし、Apacheが受け取るすべてのリクエストを「一つのインタプリタ」の再利用で処理しているとマズいことになります。

例えば、内容の違う同じ名前のモジュールが使われていると、先にロードした方がキャッシュされ、後続のリクエスト処理で参照されてしまいます。

この挙動を実演するために、以下のようなライブラリを考えましょう。(下記のコードは少々残念な作りですが、そこはお察しください):

# lib.py  import config    def site_name():      return config.SITE_NAME  

このライブラリを使って、以下のようなアプリを組みます:

# app.wsgi  import os, sys; sys.path.insert(0, os.path.dirname(__file__))  from lib import site_name    def application(environ, start_response):      start_response('200 OK', [])      return ['{}, {}'.format(__file__, site_name())]  

複数の VirtualHost A と B で運用してみます。 このとき、A と B の config.py にそれぞれ SITE_NAME='SITE A', SITE_NAME='SITE B' を設定しておきます。:

<VirtualHost *:8081>          ...          WSGIScriptAlias /app "/home/developer/app/app1/app.wsgi"  </VirtualHost>    <VirtualHost *:8082>          ...          WSGIScriptAlias /app "/home/developer/app/app2/app.wsgi"  </VirtualHost>  

この状態で、それぞれの VirtualHost に繰り返しアクセスしてみます::

watch -n .5  curl http://localhost:8081/app  

各々、'...app1/app.wsgi SITE A', '...app2/app.wsgi SITE B' が表示されます。期待した通りの動作ですよね。

しかし、 WSGIScriptAlias を %{GLOBAL} に設定すると・・・両方のサイトで表示が混じり始めます。 WSGI スクリプトは app1 なのに SITE B と出たり、その逆になったり。リクエストを繰り返していると切り替わったりします。

f:id:ymasuda_whosaysni:20180629194204p:plain

実は、デフォルトの mod_wsgi の動作ではこういう挙動は滅多に起きません。 mod_wsgi は、一つのインタプリタですべてのリクエストを処理するのではなく、内部的に複数のインタプリタ(サブインタプリタ)を生成して、リクエストの内容に応じて使うサブインタプリタを区別しているからです。

f:id:ymasuda_whosaysni:20180703185424p:plain

この区別の方法を指定するための設定値が WSGIApplicationGroup で、デフォルトの設定値は "%{RESOURCE}" になっています。

設定できる値とその意味は http://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIApplicationGroup.html で詳しく解説していますが、おおざっぱにいうと以下の通りです。:

  • {GLOBAL} どのリクエストも一つのインタプリタを使う。つまり区別しない。
  • {HOST} リクエストを受けた VirtualHost のホスト名部分で区別する。
  • {SERVER} ホスト名に加えて、ポート番号も区別する。
  • {RESOURCE} ホスト名、ポート番号に加えて、WSGIScriptAlias のパス名でも区別する

RESOUCEが指定されているデフォルトの状態では、各々のサブインタプリタは "<host名>:<ポート番号>|<WSGIScriptAliasのパス名>" という名前で区別され、VirtualHostごと、かつアプリごとにインタプリタを使い分けているので、アプリ間でインタプリタを再利用することがありません。

f:id:ymasuda_whosaysni:20180703191418p:plain

ところがGLOBALを指定すると、すべてのリクエストを(他のVirtualHostへのアクセスも含めて!)一つのインタプリタで処理しようとします。

f:id:ymasuda_whosaysni:20180703190706p:plain

我々の環境では、対象ホストに {SERVER} の設定が入っていたためこの事象を踏んでしまったのでした。 SERVERの設定では、VirtualHost間は区別するので、VirtualHostごとに単一のアプリをデプロイしているうちは問題になりませんが、一つのVirtualHostで複数アプリを動かすようになると・・・ひどい目にあいます。

f:id:ymasuda_whosaysni:20180703191251p:plain

mod_wsgi のドキュメントによると、 サブインタプリタ間は「ほぼ完全に区別」されていて、各々のサブインタプリタに影響を与えることはまずないとされています。とはいえ、拡張モジュールの中にはサブインタプリタ間で問題を起こすものがある (http://modwsgi.readthedocs.io/en/develop/user-guides/application-issues.html#multiple-python-sub-interpreters) ことや、対象のシステムはできるだけ安全に(保守的に?)動かしたかったところから、今回は導入を見送ることになりました。

おまけ: サブインタプリタにどんな名前がついているか調べる

mod_wsgi は、共有ライブラリの形式をとっています。そのため、Python から DLLへのアクセスを可能にする ctypes モジュールを使って、動いている Apache プロセス中の mod_wsgi の中をのぞきこむことができます。

例えば、以下の内容のwsgiアプリケーションをmod_wsgi上で動かすと、 mod_wsgi の静的領域にある wsgi_interpreters というオブジェクトにアクセスできます。このオブジェクトは、サブインタプリタの名前とインタプリタオブジェクトを対応付けていて、実際に mod_wsgi がリクエストを処理するインタプリタを検索するときに使われます。 Pythonの辞書オブジェクトなので、簡単に内容を表示できます:

from json import dumps  from ctypes import cdll, py_object  dll = cdll.LoadLibrary('/usr/lib/apache2/modules/mod_wsgi.so')    def application(environ, start_response):      start_response('200 OK', [('Content-type', 'application/json')])      interpreters = py_object.in_dll(dll, 'wsgi_interpreters').value      response = {          'i': [(k, str(v)) for k, v in interpreters.items()],      }      return [dumps(response).encode('utf-8')]  

mod_wsgi のドキュメントによれば、WSGIApplicationGroup が %{GLOBAL} のときには、全てのリクエストは最初に生成されるただ一つのインタプリタで処理され、そのインタプリタの名前は空文字列になります。実際、実行してみると、レスポンスは以下のようになります。いくつアプリケーションを追加しても、この辞書は大きくなりませんし、Apacheのプロセス自体が入れ替わるまで、インタプリタオブジェクトも変化しません::

{"i": [["", "<mod_wsgi.Interpreter object at 0x7f1728fd3660>"]]}  

%{SERVER} に変えて、それぞれのサーバにアクセスすると、別のサブインタプリタが登場します:

{"i": [["", "<mod_wsgi.Interpreter object at 0x7f1eb95ec660>"],         ["10.0.X.X:8081", "<mod_wsgi.Interpreter object at 0x7f1eb95ec9f0>"],         ["10.0.X.X:8082", "<mod_wsgi.Interpreter object at 0x7f1eb81c16f0>"]]}  

%{RESOURCE} に変えると、インタプリタ名にパス名も入ります。

{"i": [["", "<mod_wsgi.Interpreter object at 0x7f45345ca660>"],         ["10.0.X.X:8081|/app", "<mod_wsgi.Interpreter object at 0x7f45345ca9f0>"],         ["10.0.X.X:8082|/app", "<mod_wsgi.Interpreter object at 0x7f4530166fc0>"],         ...  ]}  

キーが空文字列のインタプリタは、どの設定の時も存在しますが、GLOBAL の設定以外で直接リクエストを処理することはありません。こうして実際に mod_wsgi の中身をのぞくと、ドキュメント通りにインタプリタに名前がついていることがわかって、ちょっと安心できますね。意外なところで使える ctypes 。楽しいです。

まとめ

mod_wsgi の WSGIApplicationGroup をうっかり設定してしまってハマったというお話でした。 それだけではもったいないので WSGIApplicationGroup を変えたときにサブインタプリタの管理がどのように変わるか調べてみました。 でも、そろそろ mod_wsgi は卒業したいですね。

2020年3月18日水曜日

Django Rest Framework: SerializerMethodField()がないというエラー

from rest_framework import serializers
####
product = SerializerMethodField()

上記のようだとSerializerMethodField()がないよと言われる。

↓以下のようにすればOK。

from rest_framework import serializers  
####
product = serializers.SerializerMethodField()


2020年2月10日月曜日

Font Awesome 5をCSS疑似要素で指定するとアイコンが表示されるものと表示されないものがある

Font Awesome 5をCSSの疑似要素で指定するとアイコンが表示されない場合の対処方法 | ジーニアスブログ – WEB制作会社ジーニアスウェブのお役立ちブログ
https://www.genius-web.co.jp/blog/design-tips/if-you-specify-font-awesome-5-as-a-css-pseudo-element-how-to-do-this-if-the-icon-is-not-displayed.html

CSS疑似要素だと表示されるものと表示されないものがありました。
font-weightを以下のように指定しなければいけないようです。

分類説明font-weight接頭辞
(HTMLに記述)
SOLID通常アイコン、白抜きのもの900fas
REGULARアウトラインをとったようなもの400far
LIGHT線が細いもの300fal
BRANDSTwitterなどのブランドロゴ400fab

2020年2月7日金曜日

CSS テキストを2カラムに流し込む

CSS : 本文テキストを2段組にする - NOBODYKNOWSBLOG
https://blog.nobodyknows.design/2017/10/css-2column.html

以下のようにしてできました。ありがとうございます。

本文テキストを<div class="multicolumnbox"></div>で囲む。

.multicolumnbox {
-moz-column-count: 2;
-webkit-column-count: 2;
column-count: 2;
-moz-column-gap: 30px;
-webkit-column-gap: 30px;
column-gap: 30px;
}

2020年2月3日月曜日

Linux ユーザ作成関連コマンド

ユーザの作成
$ useradd -m <ユーザ名>
「-m」オプションはユーザのホームディレクトリを作成してくれるオプション

パスワードの作成
$ passwd <ユーザ名>

ユーザをグループに追加
$ usermod -aG <グループ名> <ユーザ名>

所属グループの確認
$ groups <ユーザ名>

※wheelグループに追加した場合、
$ su で一度rootログインしないとroot権限が有効にならない。

2020年1月16日木曜日

Django 自動採番されたidを使用してURLやTemplateに利用する

Django で自動採番された id を使用する | コーヒー飲みながら仕事したい
https://coffee-nominagara.com/django-autoincrement-id

Django Adminリスト一覧で選択したレコードの特定フィールドを一括変更する

Adminリスト一覧で選択したレコードの特定フィールドを一括変更するというのは、
プルダウンで表示されるActionのことです。

たとえば、レコードに「公開/非公開」のフィールドがあった場合、一括で 「公開/非公開」変更したかったのです。以下を参考にしました。

Django calling save on a QuerySet object - 'QuerySet' object has no attribute 'save'

Updating multiple objects at once
https://docs.djangoproject.com/en/2.0/topics/db/queries/#updating-multiple-objects-at-once


Productというモデルが、publishというフィールドを持って、publishが1のとき「公開」です。
以下のように updateメソッドを使いました。

admin.py 内


    actions = ['publish']
    def publish(selfrequestqueryset):
        self.message_user(request, "公開しました。")
        queryset.update(publish=1)




Open Graph Meta Tags: Everything You Need to Know