Ptah¶
Ptah Walkthrough¶
You should have a virtualenv with ptah installed. Let’s create an add-on:
~/venv/src$ ../bin/pcreate -t ptah_starter mypkg
~/venv/src$ cd mypkg
~/venv/src/mypkg$ ../../bin/python setup.py develop
Now let’s start the system. –reload will start the file watcher in paster and will restart the process any time a .py file is changed:
~/venv/src/mypkg$ ../../bin/pserve settings.ini --reload
Go to http://localhost:6543/ and click around. Things to look out for:
- There is a simple webpage based on the bootstrap CSS library.
- On the right hand side there is a tab that says DT and has a pyramid. This is the pyramid_debugtoolbar which provides all sorts of useful feedback during development.
- There is a button “Goto Ptah Manage UI”. This Management UI has a lot of features for you to explore.
What you see on screen when you go to http://localhost:6543/ is a View registered with the / route under the mypkg folder in a file, views.py:
@view_config(renderer='mypkg:templates/homepage.pt',
route_name='home')
class HomepageView(object):
def __init__(self, request):
self.request = request
..
There is no regular expression which matches /, but a name of a route, ‘home’. Let’s open up app.py in the same folder to see where the home route is defined. If you scan through the main() function you will see:
config.add_route('home', '/')
That is it. Let’s take this opportunity to review the main() function. All comments have been removed:
def main(global_config, **settings):
config = Configurator(settings=settings,
session_factory = session_factory,
authentication_policy = auth_policy)
config.include('ptah')
config.ptah_init_sql()
config.ptah_init_settings()
config.ptah_init_manage(managers=('*',))
Base = ptah.get_base()
Base.metadata.create_all()
config.add_route('home', '/')
config.add_static_view('_mypkg', 'mypkg:static')
config.scan()
return config.make_wsgi_app()
Stepping through the code¶
- Instantiate a Pyramid Configurator.
- Notify Pyramid to run the ‘ptah’ extension.
- Set up the RDBMS. (See the “settings.ini” file for the connection string.)
- Activate ptah settings management with config.ptah_init_settings() which initializes additional ptah.settings and sends ptah.events.SettingsInitializing and ptah.events.SettingsInitialized.
- config.ptah_init_manage() enables the Ptah Manage Interface and manager=(‘*’,) allows anyone access to it.
- Set up the SQLAlchemy ORM and create tables if necessary.
- config.add_route(‘home’, ‘/’) registers / to the HomepageView
- config.add_static_view(‘_mypkg’, ‘mypkg:static’) allows you to call http://localhost:6543/_mypkg/app.css which you can see on filesystem, mypkg/static/app.css
- config.scan() imports all python modules in your application and performs registration. You will note there is no import .views inside the app.py module, because the scan makes that unnecessary.
- return config.make_wsgi_app() is Pyramid returning a configured WSGI application.
In summary, you put your application configuration inside of the function which will return a WSGI application. Any Pyramid extension, such as Ptah, is included via config.include(‘package_name’). We initialize Ptah. Then add your application views and routes using Pyramid syntax. We run a scan and then enable the Manager Interface. Lastly, return the configured WSGI application.
views.py¶
Now that we know how the application gets configured and we know how / calls the HomepageView, let’s look at how the static resources get included on the homepage. We will examine views.py and the template homepage.pt.
Let’s look at views.py:
import ptah
class HomepageView(object):
def __init__(self, request):
self.request = request
ptah.include(request, 'bootstrap')
ptah.include(request, 'bootstrap-js')
def __call__(self):
request = self.request
self.rendered_includes = ptah.render_includes(request)
self.rendered_messages = ptah.render_messages(request)
return {}
Every time the view gets created it annotates the request object with its requirements, in this case “bootstrap” and “bootstrap-js”. Subsequenty, when Pyramid __calls__ the view, passing the view and the return value to the template, 2 additional functions are called: render_includes and render_messages. Both take the request object as a parameter.
render_includes¶
You specified what ptah.library you needed by using ptah.include in the constructor. Now we need to convert those into HTML for the <head> tag; we call ptah.render_includes which will return an HTML string ready to be included in the <head>. ptah.library supports dependencies and render_includes() will compute that dependency correctly.
render_messages¶
User performed actions such as submitting forms, logging in, or providing a user feedback notification is done with messages. These have been called “flash messages” in other web frameworks. Any messages your application has generated must be consumed (i.e. rendered) by calling render_messages().
Even though we do not create messages in the homepage.pt template, we still want to pump any previously generated messages. For instance, you might experiment with the Ptah Manage interface and somehow be redirected to the Homepage – you would want to see any messages created in previous requests immediately. This is why messages are usually handled in master (layout) templates.
homepage.pt¶
Now let’s go and look at the template which renders the HTML. It can be found in mkpkg/templates/homepage.pt and there are only a few lines of interest in the <head>:
<head>
<meta charset="utf-8">
<title>mypkg, made with Ptah!</title>
${structure: view.rendered_includes}
<link type="text/css" rel="stylesheet"
href="${request.static_url('empty:static/app.css')}" />
<link rel="shortcut icon"
href="${request.static_url('empty:static/ico/favicon.ico')}" />
</head>
This line:
${structure: view.rendered_includes}
...generates the HTML:
<link type="text/css" rel="stylesheet" href="http://localhost:6543/_ptah/static/bootstrap/bootstrap-1.4.0.min.css" />
<script src="http://localhost:6543/_ptah/static/jquery/jquery-1.7.min.js"> </script>
<script src="http://localhost:6543/_ptah/static/bootstrap/bootstrap-2.0.1.min.js"> </script>
Lastly to reference static assets this line:
<link rel="shortcut icon"
href="${request.static_url('mypkg:static/ico/favicon.ico')}" />
...generates:
<link type="text/css" rel="stylesheet"
href="http://localhost:6543/_mypkg/app.css" />
Conclusion¶
This demonstrates most of the view functionality. In the examples repository you can look at ptah_models for an example of using ptah.library. It ships with a colourpicker widget which requires a javascript library.
More Examples¶
There is a separate repository for examples. You can read the Examples documentation on-line at http://ptah-examples.readthedocs.org.
Database Structure¶
If you add Ptah to your project there are some database schema requirements. You name your tables whatever you like.
ptah_blobs¶
This table provides the ability to support large binary objects. It is used by the ptah.cms.blob.Blob
model.
Name | Type | Null | Default | Comments |
---|---|---|---|---|
id | int | False | ‘’ | PK, FK ptah_nodes.id |
mimetype | varchar | True | ‘’ | |
filename | varchar | True | ‘’ | |
size | int | True | 0 | |
data | blob | True |
ptah_content¶
The ptah_content table provides a definition for base content model. It is used by the ptah.cms.Content
model.
Name | Type | Null | Default | Comments |
---|---|---|---|---|
id | int | False | ‘’ | PK, FK ptah_nodes.id |
path | varchar | True | ||
name | varchar | True | Maxlength 255 | |
title | varchar | True | ||
description | varchar | True | ||
created | datetime | True | ||
modified | datetime | True | ||
effective | datetime | True | ||
expires | datetime | True | ||
lang | varchar | True |
ptah_nodes¶
The ptah_nodes table provides the base model for all data elements in the system. This table is used by the ptah.cms.Node
model.
Name | Type | Null | Default | Comments |
---|---|---|---|---|
id | int | False | ‘’ | Primary key |
type | varchar | True | ‘’ | |
uri | varchar | False | Maxlength=255 | |
parent | varchar | True | ‘’ | FK: ptah_nodes.uri |
owner | varchar | True | ‘’ | Principal URI |
roles | text | True | ‘{}’ | JSON |
acls | text | True | ‘[]’ | JSON |
annotations | varchar | True | ‘{}’ | JSON |
ptah_settings¶
The ptah_settings table provides key, value for internal ptah settings machinery, in particular the ptah.settings.SettingRecord
model.
Name | Value | Null | Default | Comments |
---|---|---|---|---|
name | varchar | False | Primary key | |
value | varchar | True | ‘’ |
ptah_tokens¶
The ptah_tokens table provides a space for transient tokens which are generated by application, such as password-reset tokens. You use the token service API but this table is used by ptah.token.Token
model table.
Name | Value | Null | Default | Comments |
---|---|---|---|---|
id | int | False | Primary key | |
token | varchar | True | MaxLegnth 48 | |
valid | datetime | True | ||
data | varchar | True | ||
type | varchar | True | MaxLength 48 |
ptah_db_versions¶
The ptah_db_versions table contains migration revisions information.
Name | Value | Null | Default | Comments |
---|---|---|---|---|
package | str | False | Primary key | |
version_num | varchar | True | MaxLegnth 32 |
Manage Interface¶
The Ptah Management UI is a dashboard into your application. The Manage Interface is simple, extensible, and has quite a few features out of the box.
By default the Manage Interface is disabled.
managers sequence are login attributes from the ptah.auth_service
:
>> from ptah import auth_service
>> print auth_service.get_current_principal().login
>> runyaga@gmail.com
Configuring¶
The Manage Interface is configured through Ptah Settings. You will do this inside of your WSGI entry point where you return make_wsgi_app(). config is the Pyramid configurator.:
config.ptah_init_manage(
managers = ['*'],
disable_modules = ['rest', 'introspect', 'apps', 'permissions', 'settings'])
Enable¶
Inside of Ptah Settings you can set the managers argument the userid’s you want to allow access. * means everyone. By default it is empty and no one is allowed access. Granting everyone:
managers = ['*']
Granting a few people:
managers = ['bob@dobbs.com', 'runyaga@gmail.com']
Disable¶
By default the Manage Interface is disabled. If Manage Interface is enabled but you want to prevent users from able to access it add the following to your .ini file:
ptah.manage = ""
Out-of-the-box Modules¶
The listing of modules you see when you open up http://localhost:6543/ptah-manage interface are all modules which your account has permission to view. Below are the out-of-the-box modules and a description.
REST¶
This module provides a interactive javascript REST introspector for the Ptah application. If you want to see this in action see the ptah_minicms in examples repository.
Introspect¶
A comprehensive view into all registrations in your application. It provides mechanisms to query URI’s, see events registered, subscribers, and you can jump directly to the source code where registration takes place.
Permissions¶
A list of permission sets which are used by all applications running in the system.
Settings¶
Listing of all settings for Pyramid and Ptah. Ptah has extra settings features. This settings module will show more variables than the .ini file that you used to start Pyramid. These extra settings are from Ptah such as ptah.formatter
strings.
SQLAlchemy¶
Uses SQLAlchemy reflection capabilities to display all tables & rows that are accessible in the database. If a table is polymorphic it is not editable.
Models¶
CRUD (CReate Update and Delete) interface for models. Displays a list of registered models, allows you to modify the records.
Applications¶
A list of all Ptah applications registered in the system.
Field types¶
A preview of most registered form Fields in the system. If a field does not provide a preview it will now show up. You can see how each field will be rendered.
Extending¶
The simplest module example to look at is in ptah/manage/rest.py which registers a template.
Module¶
Create a class which subclasses ptah.manage.PtahModule. Decorate the class with ptah.manage.module()
decorate. The label you register using the manage.module decorator is the internal key for that module. If you wanted to disable it you would use this name in the ptah_settings[‘disable_modules’] registration.
An example:
import ptah
@ptah.manage.module('rest')
class RestModule(ptah.manage.PtahModule):
"""
REST Introspector
"""
title = 'REST Introspector'
View¶
The module views for the Manage Interface use traversal. It is important to note that you do not have to use ptah.View but you will need to use wrapper so your template will look like the rest of the Manage Interface. Here is an example, again, from the REST module:
from pyramid.view import view_config
@view_config(
context=RestModule,
wrapper=ptah.wrap_layout(),
renderer='ptah.manage:templates/rest.pt')
class RestModuleView(ptah.View):
def update(self):
self.url = self.request.params.get('url','')
Nothing special. Just a Pyramid view with wrapper=ptah.wrap_layout() and you can do whatever you like in that view.
Ptah Forms¶
ptah.form is an optional form library package. It provides some benefits when using it with the integrated environment such as autogeneration of forms for models, validation using the sqlalchemy constraints, and JSON representations for the REST api.
Form¶
Supports HTML generation, CSRF protection, additional field validation.
Form Validation¶
The default form validation uses CSRF prtoection to validate data input.
Fieldset¶
A ordered dictionary of Field instances. Fieldset supports validation and extraction.
Fieldset Validation¶
Is used when you have validation dependencies between Fields. For instance if Field Y depends
Fieldset Extraction¶
Internal implementation details and only needed for expert usage. Possibly needs renaming or refactoring.
Field¶
Fields are important in Ptah not only because its how forms are used in the HTML interface but they are also used in the REST interface. For instance when you send update via HTTP (html POST or json POST) the same field implementation is used for deserialization and validation.
Field Serialization¶
- serialize converts the field and data python structure into string representation. e.g. dattime.date(2011, 12, 12) into ‘2011-12-12’.
- deserailize converts a string representation into the internal representation. e.g. 2011-12-12 into datetime.date(2011, 12, 12)
Field Modes¶
Display mode and input mode.
Field Validation¶
Specific validation rules or whether a field is required is validated through the field validation.
1 2 3 4 5 6 7 | TELEPHONE_REGEX = u'^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$'
class Telephone(form.Regex):
""" Telephone number validator """
def __init__(self, msg=None):
if msg is None:
msg = "Invalid telephone number"
super(Telephone, self).__init__(TELEPHONE_REGEX, msg=msg)
|
A ptah.form.Field accepts a validator in its constructor. The fields’ validator will be called by the form with both field and value as parameters.
1 2 3 4 5 | form.TextField(
'phone',
title = u'Telephone number',
description=u'Please provide telephone number',
validator = Telephone()),
|
Field Extraction¶
Extracts the value from request.
Field Factory¶
Expert level usage. This is how Ptah’s internals work.
Examples¶
There are 2 form examples which can be found in ptah_models package in the examples repository. You can find both examples in ptah_models/views.py.
Manual Form & Fieldset¶
The contact-us form in ptah_models.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | contactform = form.Form(context, request)
contactform.fields = form.Fieldset(
form.TextField(
'fullname',
title = u'First & Last Name'),
form.TextField(
'phone',
title = u'Telephone number',
description=u'Please provide telephone number',
validator = Telephone()),
form.TextField(
'email',
title = u'Your email',
description = u'Please provide email address.',
validator = form.Email()),
form.TextAreaField(
'subject',
title = u'How can we help?',
missing = u''),
)
# form actions
def cancelAction(form):
return HTTPFound(location='/')
def updateAction(form):
data, errors = form.extract()
if errors:
form.message(errors, 'form-error')
return
# form.context is ...
form.context.fullname = data['fullname']
form.context.phone = data['phone']
form.context.email = data['email']
form.context.subject = data['subject']
# You would add any logic/database updates/insert here.
# You would probably also redirect.
log.info('The form was updated successfully')
form.message('The form was updated successfully')
contactform.label = u'Contact us'
contactform.buttons.add_action('Update', action=updateAction)
contactform.buttons.add_action('Cancel', action=cancelAction)
# form default values
contactform.content = {}
# compute the form
result = contactform.update()
if isinstance(result, HTTPFound):
return result
# generate HTML from form
html = contactform.render()
|
Manual Form & Auto-Fieldset¶
In the ptah_models package there is a content model, Link. This model can
be found in ptah_models/models.py
. This code-snippet is found in
the ptah_models/views.py
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | linkform = form.Form(context,request)
linkform.fields = models.Link.__type__.fieldset
def cancelAction(form):
return HTTPFound(location='/')
def updateAction(form):
data, errors = form.extract()
if errors:
form.message(errors, 'form-error')
return
link = models.Link(title = data['title'],
href = data['href'],
color = data['color'])
ptah.get_session().add(link)
form.message('Link has been created.')
return HTTPFound(location='/')
linkform.label = u'Add link'
linkform.buttons.add_action('Add', action=updateAction)
linkform.buttons.add_action('Cancel', action=cancelAction)
result = linkform.update() # prepare form for rendering
if isinstance(result, HTTPFound):
return result
rendered_form = linkform.render()
|
Everything Manual¶
Using form without context and request.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | from ptah import form
from ptah_models.models import Link
def action1(form):
print ('action1', form)
def action2(form):
print ('action2', form)
eform = form.Form(None, None)
eform.params = {}
eform.method = 'params'
eform.fields = Link.__type__.fieldset
eform.buttons.add_action('Test submit', name='ac1', action=action1)
eform.buttons.add_action('Test action2', name='ac2', action=action2)
print "==== execute action1 ===="
eform.params = {'%sbuttons.ac1'%eform.prefix: 'value'}
eform.update()
print
print "==== extract data ====="
data, errors = eform.extract()
print
print "DATA:"
pprint(data)
print
print "ERRORS:"
pprint(errors)
|
Class-based Form¶
Example of subclassing ptah.form.Form.
Authentication Service¶
Need to integrate user logins to new authentication service? e.g. using LDAP, OAuth, Openid, Mongo, or ZODB to source user credentials.
There are 4 facilities of which 2 are optional:
- User provider, required
- User resolver, required
- Password changer, optional
- User searcher, optional
Example¶
You can find an “in-memory” user provider in examples/auth_provider.py
User provider¶
A Principal (User) must have at least 3 attributes:
- user.uri, which must be resolvable to the User model
- user.login, which is identifier such as email
- user.name, human readable user name
The Provider class provides 2 methods:
- authenticate, which takes a mapping {‘login’:’user’, ‘password’:’pw’}
- get_principal_bylogin, which takes a login string and returns User
You register a Provider by calling ptah.register_auth_provider and provide a uri scheme and instance.
User resolver¶
Resolvers in Ptah are the way we indirect lookup between components. For instance, instead of storing the primary key of the user for say, the ptah.cms.node.Owner field; we make that a string and store a URI. URIs can be resolved into a object.
This code registeres a function which returns a object given a URI:
@ptah.register_uri_resolver('user+crowd', 'Crowd principal resolver')
def getPrincipal(uri):
return User.get(uri)
Any uri prefixed with ‘user+crowd’ will be sent to this function, getPrincipal. For instance, uri.resolve(‘user+crowd:bob’) would be sent to getPrincipal to return a Principal with that uri.
Password changer¶
A function which is responsible for changing a user’s password. An example:
ptah.pwd_tool.register_password_changer('user+crowd', change_pw)
Password changer is optional.
Principal searcher¶
A function which accepts a string and returns a iterator. This is registered with URI scheme and function:
import ptah
ptah.register_principal_searcher('user+crowd', search)
Superuser¶
There is another authentication service, ptah+auth which provides a sole superuser Principal. The name is superuser. It is a special Principal. You can not login with this Prinipcal. It is useful for integration tests.
Command-line utilities¶
Your ptah application can be controlled and inspected using a variety of command-line utilities. These utilities are documented in this chapter.
Application Settings¶
You can use the ptah-settings
command in a terminal window to print a
summary of settings to your application registered with
ptah.register_settings()
api. Much like the any pyramid
command, the ptah-settings
command accepts one argument with the
format config_file#section_name
. The
config_file
is the path to your application’s .ini
file, and
section_name
is the app
section name inside the .ini
file which
points to your application. By default, the section_name
is main
and
can be omitted.
For example:
1 2 3 4 5 6 7 8 9 10 11 | [fafhrd@... MyProject]$ ../bin/ptah-settings development.ini
* Ptah settings (ptah)
- ptah.secret: Authentication policy secret (TextField: secret-
ptah!)
The secret (a string) used for auth_tkt cookie signing
- ptah.manage: Ptah manage id (TextField: ptah-manage)
...
|
By default ptah-settings
shows all sections. It possible to show
only certain settings section like “ptah” or “format”.
It is possible to print all settings in .ini
format.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | [fafhrd@... MyProject]$ ../bin/ptah-settings development.ini -p
[DEFAULT]
format.date_full = "%%A, %%B %%d, %%Y"
format.date_long = "%%B %%d, %%Y"
format.date_medium = "%%b %%d, %%Y"
format.date_short = "%%m/%%d/%%y"
format.time_full = "%%I:%%M:%%S %%p %%Z"
format.time_long = "%%I:%%M %%p %%z"
format.time_medium = "%%I:%%M %%p"
format.time_short = "%%I:%%M %%p"
format.timezone = "us/central"
ptah.disable_models = []
ptah.disable_modules = []
...
ptah.pwd_letters_digits = false
ptah.pwd_letters_mixed_case = false
ptah.pwd_manager = "plain"
ptah.pwd_min_length = 5
|
Application Information¶
You can use the ptah-manage
command in a terminal window to print a
summary of different information of your application. The ptah-manage
command accepts one argument with the format config_file#section_name
.
List management modules¶
Use --list-modules
argument to the ptah-manage
to list all
registered ptah management modules.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [fafhrd@... MyProject]$ ../bin/ptah-manage development.ini --list-modules
* apps: Applications (disabled: False)
A listing of all registered Ptah Applications.
* crowd: User management (disabled: False)
Default user management. Create, edit, and activate users.
* fields: Field types (disabled: False)
A preview and listing of all form fields in the application. This
is useful to see what fields are available. You may also interact
with the field to see how it works in display mode.
...
|
List db models¶
Use --list-models
argument to the ptah-manage
to list all
registered ptah models.
1 2 3 4 5 6 7 8 9 10 | [fafhrd@... MyProject]$ ../bin/ptah-manage development.ini --list-models
* cms-type:app: Application (disabled: False)
Default ptah application
class: ApplicationRoot
module: MyProject.root
file: .../root.pyc
...
|
Data population¶
You can use the ptah-populate
command in a terminal window to execute a
populate steps registered with ptah.populate()
api. Much like
the any pyramid command, the ptah-populate
command accepts one argument
with the format config_file#section_name
.
Use -l
argument to list all registered steps.
1 2 3 4 5 6 7 | [fafhrd@... MyProject]$ ../bin/ptah-populate development.ini -l
* ptah-db-schema: Create db schema (active)
* ptah-crowd-admin: Create admin user (active)
...
|
It shows step name, then step title, and activity state. If step is active
it is beeing executed automatically with -a
argument.
Use -a
argument to execute all active steps.
1 2 3 4 5 6 7 8 | [fafhrd@... MyProject]$ ../bin/ptah-populate development.ini -a
2012-01-03 12:43:46,796 INFO [ptah][MainThread] Executing populate step: ptah-db-schema
2012-01-03 12:43:46,797 INFO [ptah][MainThread] Creating db table `ptah_crowd`.
2012-01-03 12:43:46,931 INFO [ptah][MainThread] Creating db table `ptah_blobs`
...
2012-01-03 12:43:48,087 INFO [ptah][MainThread] Executing populate step: ptah-crowd-admin
2012-01-03 12:43:48,092 INFO [ptah_crowd][MainThread] Creating admin user `admin` Ptah admin
...
|
Its possible to execute inactive steps or specific step with all required steps. Specify step names in command line after your ini file.
1 | [fafhrd@... MyProject]$ ../bin/ptah-populate development.ini ptah-db-schema ptah-crowd-admin
|
Data migration¶
You can use the ptah-migrate
command in a terminal window to execute a
migration steps registered with ptah.register_migration()
api.
Much like the any pyramid command, the ptah-migrate
command accepts
first argument with the format config_file#section_name
.
Use list
argument to list all registered migrations.
1 2 3 4 5 6 7 | [fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini list
* ptah: Ptah database migration
ptah:migrations
/...src/ptah/ptah/migrations
...
|
Use revision
argument to create new revision for registered migration.
1 2 3 | [fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini revision ptah
Generating /../src/ptah/ptah/migrations/3fd73c8b8727.py...done
...
|
Additional arguments:
-r
specify custom revision id. revision id has to contain only letters and numbers.
-m
specify revision message
Full command can look like:
1 2 | [fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini revision ptah -r 001 -m "Add new column X"
...
|
Use upgrade
argument to upgrade package to specific revision. You can
specify one or more packages after upgrade
argument.
1 2 3 | [fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini upgrade ptah
2012-01-11 17:00:44,506 INFO [ptah.alembic] ptah: running upgrade None -> 0301
...
|
To specify specific revision number use :
and revision number. For example
ptah:0301
Use current
argument to check current package revision. You can
specify any number of package after current
argument. If package is not
specified script shows information for all packages.
1 2 | [fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini current ptah
Package 'ptah' rev: 0301(head) Migration to ptah 0.3
|
Use history
argument to check migrations history.
1 2 3 4 5 | [fafhrd@... MyProject]$ ../bin/ptah-migrate development.ini history ptah
ptah
====
0301: Ptah 0.3.0 changes
|
Data population¶
Data population process consists of populate steps. Each step can be marked
as active or inactive. Active steps are executed by ptah_populate
pyramid directive and ptah-populate -a `` command-line script.
also it is possible to specify ``requires
for each step. requires
is a list of steps that should be executed before step.
Define step¶
Interface of populate step is very simple. It is function that accepts
one argument pyramid registry
ptah.interfaces.populate_step
.
import ptah
@ptah.populate('custom-populate-step')
def populate_step(registry):
...
Check ptah.populate
for detailed description of this directive.
Populate data during start up¶
Use ptah_populate() pyramid directive for populate system data during startup.
import ptah
from pyramid.config import Configurator
def main(global_settings, **settings):
config = Configurator(settings=settings)
config.include('ptah')
...
config.ptah_populate()
...
return config.make_wsgi_app()
Populate steps are executed after configration commited.
Command line script¶
Ptah providers ptah-populate
command-line script for data population.
[fafhrd@... MyProject]$ ../bin/ptah-populate development.ini -a
...
Check Data population for detailed description of this script.
Data migration¶
Ptah migration based on alembic package. Ptah adds per package migration. Migration is not required alembic environment.
Create package migration¶
You can use alembic operations for ddl manipulation. Here are the steps for ptah migrations generation.
- Create directory in your package that will contain migration steps.
For example:
1 2 $ cd ptah_minicms $ mkdir migrationsSo directory listing should look like this:
1 2 3 4 5 6 $ ls -l drwxrwxr-x 2 fafhrd fafhrd 4096 2012-01-11 15:58 migrations drwxrwxr-x 2 fafhrd fafhrd 4096 2011-12-16 11:33 static drwxrwxr-x 2 fafhrd fafhrd 4096 2011-12-29 14:50 templates -rw-rw-r-- 1 fafhrd fafhrd 1457 2011-12-29 14:50 actions.py ...
- Register package migrations with
ptah.register_migration()
api.
1 2 3 4 5 import ptah ptah.register_migration( 'ptah_minicms', 'ptah_minicms:migrations', 'Ptah minicms example migration')First parameter is package name, second parameter is asset style path and third parameter migration title.
- To create new revision you should use
ptah-migrate
script.
1 2 $ /bin/ptah-migrate settings.ini revision ptah_minicms -r 001 -m "Add column" Generating /path-to-virtualenv/ptah_minicms/migrations/001.py...doneGenerated script contains empty
update()
anddowngrade()
function. Add code that does migration from previous revision to this function.
- Now you can use
ptah-migrate
script to execute migration steps.
1 2 $ bin/ptah-migrate settings.ini upgrade ptah_minicms 2012-01-11 16:14:42,657 INFO [ptah.alembic] ptah_minicms: running upgrade None -> 001
Check Data migration chapter for ptah-migrate
script detailed description.
Migration data during start up¶
Use ptah_migrate() pyramid directive for migration data schema during startup.
import ptah
from pyramid.config import Configurator
def main(global_settings, **settings):
config = Configurator(settings=settings)
config.include('ptah')
...
config.ptah_migrate()
...
return config.make_wsgi_app()
Migration steps are executed after configration commited.
Notes¶
- Ptah stores package revision numbers in
ptah_db_versions
table. During data population process ptah checks ifptah_db_versions
table contains version info, if it doesnt contain version informationptah
just set latest revision without running migration steps. It assumes if there is no version information then database schema is latest. ptah-migrate
script executesPOPULATE_DB_SCHEMA
populate step before running any upgrade steps.
Ptah Q & A¶
What is scope of Ptah?¶
Ptah aims to provide a framework which makes low level choices for developers so the programmer can get on with solving their problems on a unrealistic deadline. ptah.cms
is an API it is not an applictaion. The API does not have advanced CMS functionality such as staging, workflow or versioning. That is someone elses job.
Ptah is a framework, an implementation and set of opinions around the Pyramid web framework.
Ptah, like Pyramid, supports both URL dispatch, traversal. Unlike Pyramid it provides a data model, content heirarchy, form library, and high level security primitives (permissions, roles, and principals). Any of this is additional to Pyramid and augements your application.
Where does Pyramid and Ptah differ?¶
Ptah attempts to provide a “full stack” on top of Pyramid whereas Pyramid urges you to find and use individual libraries.
Why does Ptah not use deform?¶
Ptah does not use deform; but you can. Ptah ships with a form subsystem, ptah.form
which you should give a shot.
Why does Ptah use a Folder paradigm?¶
Ptah does not require a Folder paradigm or containment. examples/ptah_minicms demonstrates the features of ptah.cms and one of those features are content hierarchies. Thus the Page/Folder experience in ptah_minicms.
Why does Ptah use sqlite?¶
Ptah uses SQLAlchemy which supports many different database drivers. sqlite ships with Python obviating the need to install a separate database daemon. ptah.cms will not depend on database specific features to gain performance or scalability.
SQLAlchemy is complex and scary¶
SQLAlchemy is a comprehensive library and an effect of that is it can feel overwhelming when reviewing the documentation. SQLAlchemy is the best thing we have in Python.
Traversal, wtf?¶
Traversal is not required. It is optional. It is a feature which can you use if you like. Traversal works quite well & if you have used Apache - you have used traversal - instead of a database Apache uses a filesystem.
Layout vs. Macros/Inheritance¶
Layouts are 100% optional. They provide an alternative for template inheritance. Layouts are renderer independent. This means you can use Jinja, Chameleon, & Mako with the Layout subsystem.
Several reasons exist for Layouts:
- A layout is a view with a template. This provides a encapsulated template and view. Meaning a template which is used as a layout has its own view (methods, data, etc).
- The contract between layouts is a string. Layouts can only be passed a
content
string which is the result of rendering the “inner” block.- Since layout’s work from the “inside-out” there is a desirable side-effect, by the time the <HEAD> layout is rendered all static assets (CSS, JS) that are requirements for form elements or for your custom view will have been computed. This is not possible to do generically with the ZPT/MACRO or the Jinja inheritance system.
- Layouts can be context dependent. This is a advanced usage. It is unclear if this is possible with other template composition mechanisms. This is a feature you will most likely need or use or understand why it is needed.
Getting a pkg_resources.DistributionNotFound: myapp Exception¶
This means that you did not run python setup.py develop
on your package. This is Python and you need to add your
package to the python path/virtual environment. e.g.:
$ bin/pcreate -t template mypackage
$ cd mypackage
$ ../bin/python setup.py develop
$ ../bin/pserve settings.ini
Where did Paster Go?¶
ptah 0.1 used Pyramid 1.2 and Paster. Pyramid 1.3 removed the dependency on Paster and rolled the functionality directly into the pyramid framework. pcreate and pserver are scripts which are now generated by Pyramid.
(OperationalError) no such table:¶
Previously to ptah 0.3; all of the tables were created automatically without intervention. As of 0.3 you explicit must bootstrap your schema using ptah-populate
script:
$ virtualenv/bin/ptah-populate settings.ini
The ptah-populate
script takes the .INI settings file as a parameter. Once this completes you will be able to start your application. For details check
Data population chapter.
API¶
ptah¶
URI¶
Layout¶
Settings¶
Security¶
Status messages¶
UI Actions¶
Data population¶
POPULATE_DB_SCHEMA
¶Id for database schema creation step. Use it as
requires
dependency to make sure that db schema is cerated before execute any other steps.
Data migration¶
ptah.form¶
Form¶
Field¶
Vocabulary¶
Validators¶
Predefined fields¶
Any field can be create with two different way. Using field class:
field = ptah.form.TextField( 'field', title='Text', description='Field description')Or using field factory:
field = ptah.form.FieldFactory( 'text', 'field', title='Text', description='Field description')
Pyramid directives¶
Pyramid Configuration directive from Ptah. An example:
auth_policy = AuthTktAuthenticationPolicy('secret') session_factory = UnencryptedCookieSessionFactoryConfig('secret') def main(global_config, **settings): """ This is your application startup.""" config = Configurator(settings=settings, session_factory = session_factory, authentication_policy = auth_policy) config.include('ptah') config.ptah_init_settings() config.ptah_init_sql()
ptah_init_sql(prefix=’sqlalchemy.’)¶
This directive creates new SQLAlchemy engine and bind session and declarative base.
param prefix: INI settings prefix, default is sqlalchemy.
Example:
[app:ptah] sqlalchemy.url = sqlite:///%(here)s/var/db.sqlite
ptah_init_settings(settings=None)¶
Initialize settings management system and load settings from system settings. Also it sends
ptah.events.SettingsInitializing
and thenptah.events.SettingsInitialized
. By default it reads info fromconfig.registry.settings
. Its possible to pass custom settings as first parameter.
param settings: Custom settings config = Configurator() config.include('ptah') # initialize ptah setting management system config.ptah_initialize_settings() .. config.ptah_initialize_settings({'ptah.managers': '["*"]'})
ptah_init_manage()¶
Initialize and enable ptah management subsystem.
param name: Management ui prefix. Default is ptah-manage
.param access_manager: Set custom access manager. param managers: List of user logins with access rights to ptah management ui. param manager_role: Specific role with access rights to ptah management ui. param disable_modules: List of modules names to hide in manage ui param enable_modules: List of modules names to enable in manage ui Also it possible to enable and configure management subsystem with settings in ini file:
[app:ptah] ptah.manage = "ptah-manage" ptah.managers = ["*"] ptah.manager_role = ... ptah.disable_modules = ...
ptah_init_mailer(mailer)¶
Set mailer object. Mailer interface is compatible with
repoze.sendmail
andpyramid_mailer
. By default stub mailer is beeing used.
param mailer: Mailer object
ptah_auth_checker(checker)¶
Register auth checker. Checker function interface
ptah.interfaces.auth_checker
param checker: Checker function config = Configurator() config.include('ptah') def my_checker(info): ... config.ptah_auth_checker(my_checker)
ptah_auth_provider(name, provider)¶
Register auth provider. Authentication provider interfaceptah.interfaces.AuthProvider
ptah_principal_searcher(name, searcher)¶
Register principal searcher function. Principal searcher function interfaceptah.interfaces.principal_searcher()
ptah_uri_resolver(schema, resolver)¶
Register resolver for given schema. Resolver function interface
ptah.interfaces.resolver()
param schema: uri schema param resolver: Callable object that accept one parameter. config = Configurator() config.include('ptah') def my_resolver(uri): .... config.ptah_uri_resolver('custom-schema', my_resolver)
ptah_password_changer(schema, changer)¶
Register password changer function for specific user uri schema. Password changer interface
ptah.intefaces.password_changer()
param schema: Principal uri schema. param changer: Function config = Configurator() config.include('ptah') config.ptah_password_changer('custom-schema', custom_changer)
ptah_layout(...)¶
Registers a layout.
param name: Layout name param context: Specific context for this layout. param root: Root object param parent: A parent layout. None means no parent layout. param renderer: A pyramid renderer param route_name: A pyramid route_name. Apply layout only for specific route param use_global_views: Apply layout to all routes. even is route doesnt use use_global_views. param view: Layout implementation (same as for pyramid view) config = Configurator() config.include('ptah') config.ptah_layout( 'page', parent='page', renderer='ptah:template/page.pt') config.add_view(' index.html', wrapper=ptah.wrap_layout(), renderer = '...')
ptah_populate()¶
Execute active populate steps.
config = Configurator() config.include('ptah') config.ptah_populate()
ptah_populate_step()¶
Register populate step. Step interface
ptah.interfaces.populate_step
.
param name: Step name param factory: Step callable factory param title: Snippet context param active: View implementation param requires: List of step names that should be executed before this step. config = Configurator() config.include('ptah') def create_db_schemas(registry): ... config.ptah_populate_step('ptah-create-db-schemas', factory=create_db_schemas, title='Create db scehams', active=True, requires=())
ptah_migrate()¶
Execute all registered database migration scripts. Check Data migration chapter for detailed description.
config = Configurator() config.include('ptah') config.ptah_migrate()
Ptah settings¶
All .INI settings set in file are in JSON format. All examples in this document are given in JSON format. An example entry in .ini file:
[app:ptah]
ptah.managers = ["*"]
ptah.pwd_manager = "ssha"
All values passed inside of Pyramid configurator are in Python format:
config.ptah_init_manage(
managers = ['*'],
disable_modules = ['rest', 'introspect', 'apps', 'permissions'])
ptah.disable_modules
Hide Modules in Management UI. List of modules names to hide in manage ui. e.g.:
ptah.disable_modules = ["rest", "apps"]
ptah.enable_modules
Enable Modules in Management UI. List of modules names to enable in manage ui. e.g.:
ptah.enable_modules = ["rest", "apps"]
ptah.disable_models
Provides a mechanism to hide models in the Model Management UI. A list of models to hide in model manage ui. e.g.:
ptah.disable_models = ["link"]
ptah.email_from_name
Site admin name. Default is
Site administrator
. e.g.:ptah.email_from_name = "Site Administrator"
ptah.email_from_address
Site admin email address. e.g.:
ptah.email_from_address = "no-reply@myapplication.com"
ptah.manage
Ptah manage id. Default value is
ptah-manage
. Also this value is being used for ptah management url http://localhost:6543/ptah-manage/... e.g.:ptah.manage = "manage"The Ptah Manage UI would then be available at http://localhost:6543/manage
ptah.manager_role
Specific role with access rights to ptah management ui.
ptah.managers
List of user logins with access rights to ptah management ui. Default value is empty string, ‘’ which means no one logins allowed. “*” allows all principals. Must be a list of strings, e.g.:
ptah.managers = ["userid"]
ptah.pwd_manager
Password manager (plain, ssha, ..)
ptah.pwd_min_length
Minimum length for password.
ptah.pwd_letters_digits
Use letters and digits in password. Boolean value.
ptah.pwd_letters_mixed_case
Use letters in mixed case. Boolean value.
ptah.secret
Authentication policy secret. The secret (a string) used for auth_tkt cookie signing. e.g.:
ptah.secret = "s3cr3t"
ptah.db_skip_tables
Do not create listed tables during data population process. e.g.:
ptah.db_skip_tables = ["ptah_nodes", "ptah_content"]
ptah.default_roles
List of default principal roles:
ptah.default_roles = ["role:Editor"]
License¶
This software is licensed under a Simplified BSD License. Read more http://en.wikipedia.org/wiki/BSD_licenses
Copyright (c) 2011, RUNYAGA, LLC. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY RUNYAGA, LLC. ‘’AS IS’’ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of RUNYAGA, LLC..
Subprojects¶
- Ptah Examples and its associated documentation.
- ptah cmf and its associated documentation.
- ptah_crowd, an out-of-the-box user registration/management sub-system and its associated documentation.