Permalink
Browse files

Merge branch 'develop' of ssh+git://github.com/toscawidgets/tw2.sqla …

…into develop
  • Loading branch information...
2 parents dd17848 + 6ce0741 commit 11dfbc549cbd5fe597aa1977c4ac83a3e2c30193 @ralphbean ralphbean committed Aug 29, 2012
Showing with 509 additions and 419 deletions.
  1. +69 −35 docs/index.rst
  2. +38 −7 tests/test_widgets.py
  3. +8 −4 tw2/sqla/__init__.py
  4. +382 −0 tw2/sqla/factory.py
  5. +12 −373 tw2/sqla/widgets.py
View
@@ -12,91 +12,125 @@ tw2.sqla is a database layer for ToscaWidgets 2 and SQLAlchemy. It allows common
* Populating selection fields
* Generating widget definitions
-See the :ref:`design` document for a more detailed description of these. tw2.sqla is designed to work fully however you define your model objects - traditional, declarative base, or Elixir.
-
-
-.. warning::
- tw2.sqla itself is in good shape, but this document is out of date.
+See the :ref:`design` document for a more detailed description of these.
+tw2.sqla is designed to work fully however you define your model objects - traditional, declarative base, or Elixir.
Getting started
---------------
-If you are using tw2.sqla with another framework (e.g. Pyramid), the framework will already be providing session management. You do not need to use the session management within tw2.sqla.
-
-Any database objects used with tw2.sqla must have a ``query`` property. This is automatically present with Elixir. For declarative base, you must use the following::
-
- Base = declarative_base()
- Base.query = tws.transactional_session().query_property()
+If you are using tw2.sqla with another framework (e.g. Pyramid), the framework will already be providing session management. You do not need to use the session management within tw2.sqla. However, database objects used with tw2.sqla must have a ``query`` property.
+For standalone tw2.sqla, the repoze.tm middleware needs to be installed in the stack. This can be done by passing ``repoze_tm=True`` to ``tw2.core.make_middleware`` or ``tw2.devtools.dev_server``. For example::
-Session and Transaction Management
-----------------------------------
+ tw2.devtools.dev_server(host='127.0.0.1', repoze_tm=True)
-The repoze.tm middleware needs to be installed in the stack. This can be done by passing ``repoze_tm=True`` to ``tw2.core.make_middleware`` or ``tw2.devtools.dev_server``. For example::
+To set the query property to use ``ZopeTransactionExtension``, appropriate code must be added to your model. The examples below are for standalone tw2.sqla.
- tw2.devtools.dev_server(host='127.0.0.1', repoze_tm=True)
+For declarative base::
-For this to work correctly, ``ZopeTransactionExtension`` must be installed in the session; there is a convenience function for this ``tw2.sqla.transactional_session``
+ from sqlalchemy.ext.declarative import declarative_base
+ import tw2.sqla as tws
+ Base = declarative_base()
+ Base.query = tws.transactional_session().query_property()
-To use this with Elixir, add the following to the model file::
+For Elixir::
import elixir as el, tw2.sqla as tws
el.session = tws.transactional_session()
-With declarative base, if the query property is setup as above, no further configuration is necessary.
+Once this is setup, the application does not need to explicitly deal with sessions.
+
+**TBD** Provide further examples for other frameworks.
Loading and Saving Data
-----------------------
-The main classes to use are:
+There are several `Page` subclasses that automatically load and save data. Each have an `entity` property that must be set to an SQLAlchemy object.
`tw2.sqla.DbListPage`
This presents a list of items.
`tw2.sqla.DbFormPage`
- This allows editing of a single item. The item is loaded based on primary key columns in the query string. When the form is posted, the data is saved back to the database.
+ This allows editing of a single item. The item is loaded based on primary key columns in the query string. When the form is posted, the data is saved back to the database. The user is redirected to the URL specified by the `redirect` parameter.
-Internally, ``tw2.sqla.RelatedValidator`` is key - it converts IDs to objects. Other classes to use: `DbListForm` and `DbLinkField`.
+`tw2.sqla.DbListForm`
+
+ This allows editing of a multiple items, e.g. allow you to edit a whole list of users. This may be removed in future, if a way is found to incorporate this functionality with `DbFormPage`.
+
+In addition, `tw2.sqla.DbLinkField` can be used to generate a link to a `DbFormPage`. It adds all the primary key columns from an object to the query string.
+
+**TBD** There is no way to filter what is displayed in the list - although a partial workaround is to map the underlying SQLAlchemy object to a select statement, which performs the filtering. Also, DbFormPage has no protection against parameter tampering.
Populating selection fields
---------------------------
-Main classes:
+`DbSelectionField` automatically loads it's contents from a database table. It has an `entity` property that must be set to an SQLAlchemy object. The subclasses are:
* DbSingleSelectField
* DbRadioList
* DbCheckBoxList
* DbCheckBoxTable
+ * DbSingleSelectLink - LinkContainer with an inner DbSingleSelectField (experimental)
+
+Note: composite primary keys are **not** supported by these fields.
+
+Internally it uses ``tw2.sqla.RelatedValidator`` which converts ID values to and from objects. You must always apply the widget to a relation, not the underlying column. For example::
+
+ class User(Base):
+ group_name = sa.Column(sa.String(), sa.ForeignKey('group'))
+ group = sao.relationship('Group')
+
+ class UserForm(twf.TableForm):
+ group = tws.DbSingleSelectField(entity=Group)
+
+**TBD** There is no way to filter what is displayed in the list - although a partial workaround is to map the underlying SQLAlchemy object to a select statement, which performs the filtering. Also, there is no protection against parameter tampering.
+
+
+Automatic widgets
+-----------------
+
+`WidgetPolicy` generates widgets from SQLAlchemy property objects. It uses the column type, name, and attributes such as nullable. Two subclasses are provided: `ViewPolicy` and `EditPolicy`. For example, EditPolicy generates SQLAlchemy Date columns as `CalendarDataPicker` widgets. Users can further subclass these policies to suit their own needs.
-Note: composite primary keys are NOT supported by these fields.
+`AutoContainer` is a widget that generates its own children automatically, using an SQLAlchemy model object, and a `WidgetPolicy`. Several subclasses are provided:
+ * AutoTableForm
+ * AutoGrowingGrid
+ * AutoViewGrid
+ * AutoViewFieldSet
+ * AutoEditFieldSet
+
+For example::
-Generating Widget Definitions
------------------------------
+ class MyForm(tws.AutoTableForm):
+ entity = model.MyObject
-There is a policy class that defines the widget and its characteristics, based on:
+Individual fields can be overridden. For example, if `address` is automatically generated as a `TextField` but you need a `TextArea`, do this::
- * Database type
- * Field name (e.g. password, email)
- * Database details, e.g. nullable
+ class MyForm(tws.AutoTableForm):
+ entity = model.MyObject
+ address = twf.TextArea()
-For relations:
+To suppress a field, use `tws.NoWidget`.
- * ManyToOne - SingleSelectField
- * ManyToMany - CheckBoxList
- * OneToMany - nothing
+.. autoclass:: tw2.sqla.WidgetPolicy
+**TBD**
+
+ * Sometimes you want a way to say "only include these fields"
+ * Hints on the model, using the info attribute - experimental; needs tests & doc
+ * There are experimental widgets for `AutoListPage` and `AutoListPageEdit`. The biggest issue is linking between them.
-**Contents**
+Contents
+--------
.. toctree::
- :maxdepth: 2
+ :maxdepth: 1
design
View
@@ -71,6 +71,11 @@ class DbTestCls9(el.Entity):
def __unicode__(self):
return self.name
+ class DbTestCls10(el.Entity):
+ name = el.Field(el.String, primary_key=True)
+ def __unicode__(self):
+ return self.name
+
self.DbTestCls1 = DbTestCls1
self.DbTestCls2 = DbTestCls2
self.DbTestCls3 = DbTestCls3
@@ -80,6 +85,7 @@ def __unicode__(self):
self.DbTestCls7 = DbTestCls7
self.DbTestCls8 = DbTestCls8
self.DbTestCls9 = DbTestCls9
+ self.DbTestCls10 = DbTestCls10
el.setup_all()
el.metadata.create_all()
@@ -180,6 +186,11 @@ class DbTestCls9(Base):
account = sa.orm.relation(DbTestCls8, backref=sa.orm.backref('user', uselist=False))
def __unicode__(self):
return self.name
+ class DbTestCls10(Base):
+ __tablename__ = 'Test10'
+ name = sa.Column(sa.String(50), primary_key=True)
+ def __unicode__(self):
+ return self.name
self.DbTestCls1 = DbTestCls1
@@ -191,6 +202,7 @@ def __unicode__(self):
self.DbTestCls7 = DbTestCls7
self.DbTestCls8 = DbTestCls8
self.DbTestCls9 = DbTestCls9
+ self.DbTestCls10 = DbTestCls10
Base.metadata.create_all()
@@ -958,7 +970,7 @@ def setup(self):
declarative = True
def test_exception_manytoone(self):
- class WackPolicy(tws.widgets.WidgetPolicy):
+ class WackPolicy(tws.WidgetPolicy):
pass
props = filter(
lambda x : x.key == 'other',
@@ -972,7 +984,7 @@ class WackPolicy(tws.widgets.WidgetPolicy):
"for many-to-one relation 'other'")
def test_exception_onetomany(self):
- class WackPolicy(tws.widgets.WidgetPolicy):
+ class WackPolicy(tws.WidgetPolicy):
pass
props = filter(
lambda x : x.key == 'others',
@@ -986,7 +998,7 @@ class WackPolicy(tws.widgets.WidgetPolicy):
"for one-to-many relation 'others'")
def test_exception_onetoone(self):
- class WackPolicy(tws.widgets.WidgetPolicy):
+ class WackPolicy(tws.WidgetPolicy):
pass
props = filter(
lambda x : x.key == 'account',
@@ -1000,7 +1012,7 @@ class WackPolicy(tws.widgets.WidgetPolicy):
"for one-to-one relation 'account'")
def test_exception_default(self):
- class WackPolicy(tws.widgets.WidgetPolicy):
+ class WackPolicy(tws.WidgetPolicy):
pass
props = filter(
lambda x : x.key == 'name',
@@ -1013,7 +1025,7 @@ class WackPolicy(tws.widgets.WidgetPolicy):
assert(str(e) == "Cannot automatically create a widget for 'name'")
def test_name_widgets(self):
- class AwesomePolicy(tws.widgets.WidgetPolicy):
+ class AwesomePolicy(tws.WidgetPolicy):
name_widgets = { 'name' : twf.LabelField, }
props = filter(
@@ -1026,7 +1038,7 @@ class AwesomePolicy(tws.widgets.WidgetPolicy):
assert(False)
def test_info_on_prop(self):
- class AwesomePolicy(tws.widgets.WidgetPolicy):
+ class AwesomePolicy(tws.WidgetPolicy):
name_widgets = { 'name' : twf.LabelField, }
props = filter(
@@ -1045,7 +1057,7 @@ class SomeListPage(tws.DbListPage):
_no_autoid = True
entity = self.DbTestCls1
- class child(tws.widgets.AutoViewGrid):
+ class child(tws.AutoViewGrid):
name = twf.InputField(type='text')
environ = {
@@ -2574,3 +2586,22 @@ def test_request_post_counts_update(self):
class TestFormPageRequiredCheckboxTElixir(ElixirBase, FormPageRequiredCheckboxT): pass
class TestFormPageRequiredCheckboxTSQLA(SQLABase, FormPageRequiredCheckboxT): pass
+
+
+class DbLinkFieldT(WidgetTest):
+ widget = tws.DbLinkField
+ declarative = True
+ params = {'link':'/test'}
+ expected = """<a href="/test?id=1">foo1</a>"""
+
+ def setup(self):
+ self.widget = self.widget(entity=self.DbTestCls1, value=self.DbTestCls1.query.get(1))
+ return super(DbLinkFieldT, self).setup()
+
+ def test_encode(self):
+ d = self.DbTestCls10(name="fr&ed")
+ w = tws.DbLinkField(entity=self.DbTestCls10, value=d, link='/test')
+ tw2test.assert_eq_xml(w.display(), """<a href="/test?name=fr%26ed">fr&amp;ed</a>""")
+
+class TestLinkFieldElixir(ElixirBase, DbLinkFieldT): pass
+class TestLinkFieldSQLA(SQLABase, DbLinkFieldT): pass
View
@@ -1,10 +1,14 @@
from widgets import (
- RelatedValidator, DbFormPage, DbListForm, DbLinkField, commit_veto, transactional_session,
- DbSelectionField, DbSingleSelectField,
- DbCheckBoxList, DbRadioButtonList, DbCheckBoxTable,
- DbListPage, AutoTableForm, AutoViewGrid, AutoGrowingGrid,
+ RelatedValidator, DbFormPage, DbListForm, DbListPage, DbLinkField,
+ commit_veto, transactional_session,
+ DbSelectionField, DbSingleSelectField, DbCheckBoxList, DbRadioButtonList, DbCheckBoxTable,
+ DbSingleSelectLink)
+from factory import (
+ WidgetPolicy, ViewPolicy, EditPolicy,
+ AutoTableForm, AutoViewGrid, AutoGrowingGrid,
AutoListPage, AutoListPageEdit,
AutoEditFieldSet, AutoViewFieldSet,
NoWidget)
+
import utils
import widgets
Oops, something went wrong.

0 comments on commit 11dfbc5

Please sign in to comment.