pyramid學習筆記3-創建注冊頁麵
Pyramid可以為資源、路由、靜態資源產生URL,pyramid有一個統一的view callables概念,view callables可以是函數、類的方法,或者一個實例。顧名思義,view callables就是讓這個view callable處理對應的view URL頁麵。在mypriject->view->views.py添加/user/regist注冊view callables:
#coding=utf-8
from pyramid.view import view_defaults,view_config
from myproject.api.validator import *
from myproject.api.simpleform import Form,State
from logging import getLogger
log = getLogger(__name__)
def includeme(config):
config.scan(__name__)
config.add_route('user', '/user/{action}')
config.add_route('event', '/event/{action}')
@view_defaults(route_name='user')
class UserView(object):
def __init__(self, request):
self.request = request
self.db=request.db
@view_config(renderer='user/regist.mako', match_param=('action=regist'), request_method='GET')
@view_config(renderer='jsonp', match_param=('action=regist'), request_method='POST')
def regist(self):
if self.request.method=="POST":
validators = dict(
phone = PhoneNumber(),
name = RealName(),
password = Password()
)
form = Form(self.request, validators=validators, state=State(request=self.request), variable_decode=True)
if form.validate(force_validate=True):
log.debug(form.data)
if self.db.user.find_one({'phone':form.data.get('phone')}):
return dict(error='該號碼已經注冊')
if form.data.get('name')=='習近平':
return dict(error='姓名中不能有敏感詞匯')
self.db.user.save({'phone':form.data.get('phone'),
'name':form.data.get('name'),
'password':form.data.get('password')})
return {}
@view_defaults(route_name='event')
class EventView(object):
def __init__(self, request):
self.request = request
@view_config(renderer='event/create.mako', match_param=('action=create'), request_method='GET')
def create(self):
return {}以上用到了pyramid.config和pyramid.view模塊,詳細說明參考官方文檔:
https://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/latexindex.html
logging模塊用於開發調試用,myproject.api.simpleform和validator用於前台數據驗證,這裏我們自己封裝了api。在myproject下新建api包:創建__init__.py,validator.py和simpleform.py文件:
validator.py
#coding=utf-8
import formencode as fe
import re
from bson.objectid import ObjectId
_ = lambda s: s
class PhoneNumber(fe.FancyValidator):
def _convert_to_python(self, value, state):
validator = fe.validators.Regex(r'^(13|15|18)\d{9}$')
value = validator.to_python(value, state)
return value
_to_python = _convert_to_python
def Password(*kw, **kwargs):
return fe.validators.String(min=6, max=20)
def RealName(*kw, **kwargs):
return fe.validators.UnicodeString(min=2, max=10)
這裏用到了formcode模塊,詳細參考官方網站:
https://www.formencode.org/en/latest/
由於MongoDB是采用bson的形式存儲數據的,所以引入bson模塊的ObjectId對文檔的id進行轉換。
simpleform.py
#coding=utf-8
import warnings
from formencode import htmlfill
from formencode import variabledecode
from formencode import Invalid
from formencode.api import NoDefault
from pyramid.i18n import get_localizer, TranslationStringFactory, TranslationString
from pyramid.renderers import render
class State(object):
"""
Default "empty" state object.
Keyword arguments are automatically bound to properties, for
example::
obj = State(foo="bar")
obj.foo == "bar"
"""
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
def __contains__(self, k):
return hasattr(self, k)
def __getitem__(self, k):
try:
return getattr(self, k)
except AttributeError:
raise KeyError
def __setitem__(self, k, v):
setattr(self, k, v)
def get(self, k, default=None):
return getattr(self, k, default)
fe_tsf = TranslationStringFactory('FormEncode')
def get_default_translate_fn(request):
pyramid_translate = get_localizer(request).translate
def translate(s):
if not isinstance(s, TranslationString):
s = fe_tsf(s)
return pyramid_translate(s)
return translate
class Form(object):
"""
Legacy class for validating FormEncode schemas and validators.
:deprecated: 0.7
`request` : Pyramid request instance
`schema` : FormEncode Schema class or instance
`validators` : a dict of FormEncode validators i.e. { field : validator }
`defaults` : a dict of default values
`obj` : instance of an object (e.g. SQLAlchemy model)
`state` : state passed to FormEncode validators.
`method` : HTTP method
`variable_decode` : will decode dict/lists
`dict_char` : variabledecode dict char
`list_char` : variabledecode list char
Also note that values of ``obj`` supercede those of ``defaults``. Only
fields specified in your schema or validators will be taken from the
object.
"""
default_state = State
def __init__(self, request, schema=None, validators=None, defaults=None,
obj=None, extra=None, include=None, exclude=None, state=None,
method="POST", variable_decode=False, dict_char=".",
list_char="-", multipart=False, ignore_key_missing=False,
filter_extra_fields=True):
self.request = request
self.schema = schema
self.validators = validators or {}
self.method = method
self.variable_decode = variable_decode
self.dict_char = dict_char
self.list_char = list_char
self.multipart = multipart
self.state = state
self.ignore_key_missing = ignore_key_missing
self.filter_extra_fields = filter_extra_fields
self.is_validated = False
self.errors = {}
self.data = {}
if self.state is None:
self.state = self.default_state()
if not hasattr(self.state, '_'):
self.state._ = get_default_translate_fn(request)
if defaults:
self.data.update(defaults)
if obj:
fields = self.schema.fields.keys() + self.validators.keys()
for f in fields:
if hasattr(obj, f):
self.data[f] = getattr(obj, f)
def is_error(self, field):
"""
Checks if individual field has errors.
"""
return field in self.errors
def all_errors(self):
"""
Returns all errors in a single list.
"""
if isinstance(self.errors, basestring):
return [self.errors]
if isinstance(self.errors, list):
return self.errors
errors = []
for field in self.errors.iterkeys():
errors += self.errors_for(field)
return errors
def errors_for(self, field):
"""
Returns any errors for a given field as a list.
"""
errors = self.errors.get(field, [])
if isinstance(errors, basestring):
errors = [errors]
return errors
def validate(self, force_validate=False, params=None):
"""
Runs validation and returns True/False whether form is
valid.
This will check if the form should be validated (i.e. the
request method matches) and the schema/validators validate.
Validation will only be run once; subsequent calls to
validate() will have no effect, i.e. will just return
the original result.
The errors and data values will be updated accordingly.
`force_validate` : will run validation regardless of request method.
`params` : dict or MultiDict of params. By default
will use **request.POST** (if HTTP POST) or **request.params**.
"""
assert self.schema or self.validators, \
"validators and/or schema required"
if self.is_validated:
return not(self.errors)
if not force_validate:
if self.method and self.method != self.request.method:
return False
if params is None:
if not force_validate and self.method == "POST":
params = self.request.POST
else:
params = self.request.params
if self.variable_decode:
decoded = variabledecode.variable_decode(
params, self.dict_char, self.list_char)
else:
decoded = params
self.data.update(decoded)
if self.schema:
self.schema.ignore_key_missing = self.ignore_key_missing
try:
self.data = self.schema.to_python(decoded, self.state)
except Invalid, e:
self.errors = e.unpack_errors(self.variable_decode,
self.dict_char,
self.list_char)
if self.validators:
for field, validator in self.validators.iteritems():
value = decoded.get(field)
if value is None:
try:
if_missing = validator.if_missing
except AttributeError:
if_missing = NoDefault
if if_missing is NoDefault:
if self.ignore_key_missing:
continue
try:
message = validator.message('missing', self.state)
except KeyError:
message = self.state._('Missing value')
self.errors[field] = unicode(message)
else:
value = if_missing
try:
self.data[field] = validator.to_python(value,
self.state)
except Invalid, e:
self.errors[field] = unicode(e)
self.is_validated = True
return not(self.errors)
def bind(self, obj, include=None, exclude=['access_token']):
"""
Binds validated field values to an object instance, for example a
SQLAlchemy model instance.
`include` : list of included fields. If field not in this list it
will not be bound to this object.
`exclude` : list of excluded fields. If field is in this list it
will not be bound to the object.
Returns the `obj` passed in.
Note that any properties starting with underscore "_" are ignored
regardless of ``include`` and ``exclude``. If you need to set these
do so manually from the ``data`` property of the form instance.
Calling bind() before running validate() will result in a RuntimeError
"""
if not self.is_validated:
raise RuntimeError, \
"Form has not been validated. Call validate() first"
if self.errors:
raise RuntimeError, "Cannot bind to object if form has errors"
items = [(k, v) for k, v in self.data.items() if not k.startswith("_")]
fields = []
if self.schema:
fields = fields + self.schema.fields.keys()
if self.validators:
fields = fields + self.validators.keys()
for k, v in items:
if self.filter_extra_fields and fields and k not in fields:
continue
if include and k not in include:
continue
if exclude and k in exclude:
continue
if isinstance(obj, dict):
obj.update({k:v})
else:
setattr(obj, k, v)
return obj
def htmlfill(self, content, **htmlfill_kwargs):
"""
Runs FormEncode **htmlfill** on content.
"""
charset = getattr(self.request, 'charset', 'utf-8')
htmlfill_kwargs.setdefault('encoding', charset)
return htmlfill.render(content,
defaults=self.data,
errors=self.errors,
**htmlfill_kwargs)
def render(self, template, extra_info=None, htmlfill=True,
**htmlfill_kwargs):
"""
Renders the form directly to a template,
using Pyramid's **render** function.
`template` : name of template
`extra_info` : dict of extra data to pass to template
`htmlfill` : run htmlfill on the result.
By default the form itself will be passed in as `form`.
htmlfill is automatically run on the result of render if
`htmlfill` is **True**.
This is useful if you want to use htmlfill on a form,
but still return a dict from a view. For example::
@view_config(name='submit', request_method='POST')
def submit(request):
form = Form(request, MySchema)
if form.validate():
# do something
return dict(form=form.render("my_form.html"))
"""
extra_info = extra_info or {}
extra_info.setdefault('form', self)
result = render(template, extra_info, self.request)
if htmlfill:
result = self.htmlfill(result, **htmlfill_kwargs)
return result
然後修改myproject下的__init__.py(不是view下的__init__.py):
from pyramid.config import Configurator
from pyramid.renderers import JSONP
from pyramid.security import unauthenticated_userid
from bson.objectid import ObjectId
from bson.dbref import DBRef
from urlparse import urlparse
import pymongo, datetime
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
#using config
config = Configurator(settings=settings)
#add static view
config.add_static_view('static', 'static', cache_max_age=3600)
#add jsonp
jsonp = JSONP(param_name='callback')
jsonp.add_adapter(ObjectId, objectid_adapter)
jsonp.add_adapter(DBRef, dbref_adapter)
config.add_renderer('jsonp', jsonp)
#add request property
config.set_request_property(get_db, "db", reify=True)
#config.set_request_property(get_user, "user", reify=True)
config.include("myproject.view.views")
config.scan()
return config.make_wsgi_app()
def objectid_adapter(obj, request):
return str(obj)
def dbref_adapter(obj, request):
return {'$id': obj.id}
'''
def get_user(request):
userid = unauthenticated_userid(request)
if userid is not None and ObjectId.is_valid(userid):
user = request.db.user.find_one({'_id': ObjectId(userid)})
if user and user.get('status') == 2:
return User(user)
'''
def get_db(request):
settings = request.registry.settings
db_url = urlparse(settings['mongo_uri'])
conn = pymongo.MongoClient(host=db_url.hostname, port=db_url.port)
db = conn[db_url.path[1:]]
if db_url.username and db_url.password:
db.authenticate(db_url.username, db_url.password)
return db
為mako創建通用模板templates/template.mako:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="https://www.w3.org/1999/xhtml" xml:lang="zh-cn" xmlns:tal="https://xml.zope.org/namespaces/tal" >
<head>
<title>時刻</title>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
<meta name="keywords" content="" />
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://yui.yahooapis.com/pure/0.4.2/pure-min.css">
<link rel="stylesheet" type="text/css" href="/static/css/style.css">
<!--[if lt IE 9]>
<script src="https://css3-mediaqueries-js.googlecode.com/svn/trunk/css3-mediaqueries.js"></script>
<![endif]-->
<script type="text/javascript" src="/static/js/jquery-1.10.1.min.js"></script>
</head>
<body>
${next.body()}
</body>
</html>
修改regist.mako文件:
<%inherit />
<div >
<h1>注冊</h1>
</div>
<div >
<form method="post">
<label>手機</label>
<input name="phone" type="text" placeholder="請輸入您的手機號碼">
<label>姓名</label>
<input name="name" type="text" placeholder="請輸入您的姓名">
<label>密碼</label>
<input name="password" type="password" placeholder="請輸入密碼">
<a >注冊</a>
<a >返回</a>
</form>
</div>
<script>
$(document).ready(function(){
$('#regist-btn').click(function(){
if($('#phone').val()==""){
alert('請輸入手機號碼');
}
else if($('#password').val()==""){
alert('請輸入密碼');
}
else if($('#name').val()==""){
alert('請輸入姓名');
}
else{
$.ajax({
url:'/user/regist',
type:'POST',
dataType:'json',
data:{
'phone':$('#phone').val(),
'password':$('#password').val(),
'name':$('#name').val()
},
success:function(data){
if(data.error){
if(data.form_errors && data.form_errors.phone)
alert(data.form_errors.phone);
else if(data.form_errors && data.form_errors.password)
alert(data.form_errors.password);
else if(data.form_errors && data.form_errors.name)
alert('姓名:'+data.form_errors.name);
else
alert(data.error);
}
else if(data.result==1){
window.location.href="/share/event?id=533bd7c3fbe78e78841aa359";
}
else{
}
},
error:function(data){
console.log('係統錯誤!');
}
});
}
});
});
</script>
最後運行項目,一個完整地注冊流程就ok了。運行前需要先開啟MongoDB,並且確定創建了myproject數據庫和相應集合。
源代碼:
https://pan.baidu.com/s/15ZLBC
最後更新:2017-04-03 12:56:11