kay frameworkでform自動生成
kayの実際のコード例をあまり見かけないので、試用中のコードをここに晒してみたい。あんまりkayの事を良く分かっていない中で作ったので、良い使用例かどうかは判断がつかない。(もう幾らかやってみたらMLで色々聞いてみよう)
下記の例では、入力データの加工(Fieldでのconvert関数)、また、そのフィールドをモデルから自動生成する場合の方法が書いてある。templateは{{ form()|safe }}だけなので、省略。
実はmodelformをimportする時にgoogle.appengine.ext.db.XXXX系は全部googleではなく、kayのものとすり替わっている。そのやや黒い処理を行っている__metaclass__がmonkey_patchという名前の関数(関数だけどmetaclassとはこれ如何に*1 )だった。使えなくなったりしないだろうか(ビクビクッ)
patchちからでdb.Propertyクラスに、
- get_form_field(self, form_class=forms.TextField, **kwargs)
- get_value_for_form(self,instance)
- make_value_from_form(self,instance)という名前から動作の想像に難くないメソが導入される。
ちなみに、datastoreAPIにmake/get_value_for/from_datastoreというものがあるので、対称性の点からこういう名前になっていると思われる。
あと、下記のforms.TextField(validators=[is_unique_fabric_code])のように、ちょっとしたバリデーションも追加しやすいのも嬉しい。
# -*- coding: utf-8 -*- # fabric.models from kay.i18n import _ from google.appengine.ext import db from kay.utils import forms from kay.utils.forms.modelform import ModelForm from kay.utils.validators import ValidationError # rgbの各要素値。#がついてるとHexとして扱う class ColorUnitField(forms.NumberField): def convert(self, value): if value and value.startswith('#'): value = int('0x%s'%value[1:], 16) return super(ColorUnitField, self).convert(value) # rgbの各要素値のDatastore Property class ColorUnitProperty(db.IntegerProperty): def get_form_field(self, **kwargs): defaults = {'form_class': ColorUnitField, 'min_value': 0, 'max_value': 255,} defaults.update(kwargs) return super(ColorUnitProperty, self).get_form_field(**defaults) class Fabric(db.Model): code = db.StringProperty(required=True) r = ColorUnitProperty(required=True) g = ColorUnitProperty(required=True) b = ColorUnitProperty(required=True) stock = db.IntegerProperty(required=True, choices=[0,1], default=1) created = db.DateTimeProperty(required=True, auto_now_add=True) updated = db.DateTimeProperty(required=True, auto_now=True) def color(self, format='rgb'): # manage.py test で doctestもできるようにしたい。 """ return formatted color string >>> f = Fabric(code='a', r=255, g=192, b=0, stock=1) >>> f.color() rgb(255, 192, 0) >>> f.color('rgb') 'rgb(255, 192, 0)' >>> f.color('#') '#ffc000' >>> f.color('list') ['255', '192', '0'] >>> f.color('dict') == {'r': 'ff', 'g': 'c0', 'b': '00'} True """ if format.startswith('#'): r,g,b = map(lambda v: '%02x'%int(v), (self.r, self.g, self.b)) format = format[1:] else: r,g,b = map(str, (self.r, self.g, self.b)) return 'rgb(%s, %s, %s)'%(r,g,b) if format == 'rgb' \ else list((r,g,b)) if format == 'list' \ else {'r': r, 'g': g, 'b': b} if format == 'dict' \ else '#%02s%02s%02s'%(r,g,b) class FabricForm(ModelForm): class Meta: model = Fabric exclude = ('created', 'updated') ## def is_unique_fabric_code(form, value): ## if Fabric.all().filter('code =', value).count(1) > 0: ## raise ValidationError(_(u"'%(value)s' is already existed")%{'value': value}) ## code = forms.TextField(validators=[is_unique_fabric_code]) # save済みの場合はスキップ def validate(self, *args, **kwds): def is_unique_fabric_code(form, value): if Fabric.all().filter('code =', value).count(1) > 0: raise ValidationError(_(u"'%(value)s' is already existed")%{'value': value}) if not self.instance or not self.instance.is_saved(): self.code.validators.append(is_unique_fabric_code) return super(FabricForm, self).validate(*args, **kwds) # これで、フォームの選択肢の表示はできるが、(フォームの関係ない)単なる表示系とかを考えると、 # どういう風に配置するのが一番良いのか悩む FabricForm.stock.choices = [ (k, {'0': u'在庫無し', '1': u'在庫あり'}[v]) for k, v in FabricForm.stock.choices] FabricForm.stock.choices.insert(0, ('', u'')) FabricForm.r.help_text = \ FabricForm.g.help_text = \ FabricForm.b.help_text = _(u'color must be in ther range 0 - 255 (or #00 - #FF by hex)') --- views.py def new(request): form = FabricForm() # instance=Fabric.get(key)とかやるとupdate系の処理もしてくれる errors = {} if request.method == 'POST' and form.validate(request.form): new = form.save() return redirect(reverse('fabric/index', focus=new.key())) return render_to_response('fabric/new.html', {'form': form.as_widget()})
*1:別にclassでなくともcallできてinstanceがreturnされればなんでもいいのである