class _IncomeSearchSchema(ResourceSearchSchema): invoice_id = colander.SchemaNode( colander.Integer(), missing=None ) account_id = colander.SchemaNode( colander.Integer(), missing=None ) currency_id = colander.SchemaNode( colander.String(), missing=None, validator=currency_validator, ) payment_from = colander.SchemaNode( Date(), missing=None ) payment_to = colander.SchemaNode( Date(), missing=None ) sum_from = colander.SchemaNode( colander.Decimal(), missing=None ) sum_to = colander.SchemaNode( colander.Decimal(), missing=None )
class _CrosspaymentSearchSchema(ResourceSearchSchema): subaccount_from_id = colander.SchemaNode( colander.Integer(), missing=None, ) subaccount_to_id = colander.SchemaNode( colander.Integer(), missing=None, ) account_item_id = colander.SchemaNode( colander.Integer(), missing=None, ) date_from = colander.SchemaNode( Date(), missing=None ) date_to = colander.SchemaNode( Date(), missing=None ) sum_from = colander.SchemaNode( colander.Decimal(), missing=None ) sum_to = colander.SchemaNode( colander.Decimal(), missing=None )
class UpdateRecurringSchema(colander.MappingSchema): name = colander.SchemaNode(colander.String(), validator=colander.Length(max=60), missing=colander.drop) amount = colander.SchemaNode(colander.Decimal('0.01'), validator=colander.Range(0, 20000), required=True) start_date = colander.SchemaNode(colander.Date(), missing=today) trial_amount = colander.SchemaNode(colander.Decimal('0.01'), validator=colander.Range(0, 20000), missing=colander.drop) total_occurrences = colander.SchemaNode(colander.Integer(), validator=colander.Range(1, 9999), missing=9999) trial_occurrences = colander.SchemaNode(colander.Integer(), validator=colander.Range(1, 99), missing=colander.drop) credit_card = CreditCardSchema(validator=CreditCardSchema.validator, missing=colander.drop) bank_account = BankAccountSchema(validator=BankAccountSchema.validator, missing=colander.drop) customer = CustomerBaseSchema(missing=colander.drop) order = OrderSchema(missing=colander.drop) billing = AddressSchema(missing=colander.drop) shipping = AddressSchema(missing=colander.drop)
class WithdrawSchema(CSRFSchema): address = colander.SchemaNode( colander.String(), title="To address", validator=validate_ethereum_address, widget=deform.widget.TextInputWidget(size=6, maxlength=6, template="textinput_placeholder", placeholder="0x0000000000000000000000000000000000000000") ) amount = colander.SchemaNode( colander.Decimal(), validator=validate_withdraw_amount, widget=deform.widget.TextInputWidget(size=6, maxlength=6, template="textinput_placeholder", placeholder="0.00") ) note = colander.SchemaNode( colander.String(), title="Note", missing="", description="The note is recorded for your own transaction history.") advanced = Advanced( title="Advanced", widget=deform.widget.MappingWidget( template="mapping_accordion", open=False))
class TimeEntrySchema(colander.Schema): title = colander.SchemaNode( colander.String(), title=_("Title"), missing="", ) tariff_uid = colander.SchemaNode( colander.String(), title=_("Tariff"), missing="", widget=selectable_tariff_radio, ) created = colander.SchemaNode( LocalDateTime(), missing=None, widget=deform.widget.DateTimeInputWidget(time_options={'interval': 5}), title=_("Start time")) stop_time = colander.SchemaNode( LocalDateTime(), missing=None, widget=deform.widget.DateTimeInputWidget(time_options={'interval': 5}), title=_("End time")) bill_hours = colander.SchemaNode(colander.Decimal(), missing=None, title=_("Bill hours"))
def getNode(_type,tr,config): from bips.workflows.flexible_datagrabber import Data, DataBase if _type == type(traits.Int()): col_type = colander.SchemaNode(colander.Int(), name=tr,description=config.trait(tr).desc) elif _type == type(traits.Float()): col_type = colander.SchemaNode(colander.Decimal(),name=tr) elif _type == type(traits.String()) or _type==type(traits.Str()): col_type = colander.SchemaNode(colander.String(),name=tr) elif _type == type(traits.Enum('')): values=config.trait(tr).trait_type.values the_values = [] for v in values: the_values.append((v,v)) col_type = colander.SchemaNode( deform.Set(), widget=deform.widget.SelectWidget(values=the_values), name=tr) elif _type == type(traits.Bool()): col_type = colander.SchemaNode(colander.Boolean(),widget=deform.widget.CheckboxWidget(),name=tr) elif _type == type(traits.Code()): col_type = colander.SchemaNode(colander.String(),name=tr,widget=deform.widget.TextAreaWidget(cols=100,rows=20)) elif _type == type(traits.Instance(Data,())): from bips.workflows.flexible_datagrabber import create_datagrabber_html_view col_type = create_datagrabber_html_view() elif _type == type(traits.List()): col_type =get_list(_type,tr,config) else: print "type: ", _type, "not found!" col_type = colander.SchemaNode(colander.String(),name=tr) return col_type
class Fees(colander.Schema): member_type = colander.SchemaNode( colander.String(), title=_(u'Please tell us wether you\'re an individual, ' u'freelancer or company or want to support us ' u'generously as a sustaining member'), widget=deform.widget.RadioChoiceWidget( values=[(member_type, t_description) for fee, member_type, t_description in customization.membership_fees]), oid='member_type') # not validating here: depends on ^ # http://deformdemo.repoze.org/require_one_or_another/ member_custom_fee = colander.SchemaNode( colander.Decimal('1.00'), title=_(u'custom membership fee'), widget=deform.widget.MoneyInputWidget( symbol=customization.currency, showSymbol=True, defaultZero=True), description=_( u'Sustaining members: You can set your fees (minimum 100 €)'), oid='membership_custom_fee', default=customization.membership_fee_custom_min, validator=Range( min=customization.membership_fee_custom_min, max=None, min_err= _(u'please enter at least the minimum fee for sustaining members' )))
class _VatSchema(ResourceSchema): date = colander.SchemaNode( Date(), validator=date_validator, ) account_id = colander.SchemaNode( colander.String(), validator=account_validator ) service_id = colander.SchemaNode( colander.String(), validator=service_validator ) vat = colander.SchemaNode( colander.Decimal('.01'), validator=colander.Range(min=0, max=100), ) calc_method = colander.SchemaNode( colander.String() ) descr = colander.SchemaNode( colander.String(), validator=colander.Length(max=255), missing=None )
def colander_literal_type(self, data_input): # LOGGER.debug('data input type = %s', data_input.dataType) if 'boolean' in data_input.dataType: return colander.Boolean() elif 'integer' in data_input.dataType: return colander.Integer() elif 'float' in data_input.dataType: return colander.Float() elif 'double' in data_input.dataType: return colander.Float() elif 'decimal' in data_input.dataType: return colander.Decimal() elif 'string' in data_input.dataType: return colander.String() elif 'dateTime' in data_input.dataType: return colander.DateTime() elif 'date' in data_input.dataType: return colander.Date() elif 'time' in data_input.dataType: return colander.Time() elif 'duration' in data_input.dataType: # TODO: check correct type # http://www.w3.org/TR/xmlschema-2/#duration return colander.Time() # guessing from default elif hasattr(data_input, 'defaultValue'): try: dateutil.parser.parse(data_input.defaultValue) except Exception: return colander.String() else: return colander.DateTime() else: return colander.String()
class _OutgoingSearchSchema(ResourceSearchSchema): account_id = colander.SchemaNode( colander.Integer(), missing=None ) account_item_id = colander.SchemaNode( colander.Integer(), missing=None ) sum_from = colander.SchemaNode( colander.Decimal(), missing=None ) sum_to = colander.SchemaNode( colander.Decimal(), missing=None )
class _CommissionSchema(ResourceSchema): service_id = colander.SchemaNode(SelectInteger(Service), ) percentage = colander.SchemaNode(colander.Decimal(), validator=colander.Range(min=0, max=100)) price = colander.SchemaNode(colander.Money(), ) currency_id = colander.SchemaNode(SelectInteger(Currency), ) descr = colander.SchemaNode(colander.String(), validator=colander.Length(max=255), missing=u'')
class SubscriptionSchema(Schema): subscription_type = colander.SchemaNode(colander.String(), widget=subscription_type_widget, title=_('Subscription type')) price = colander.SchemaNode(colander.Decimal(), widget=deform.widget.MoneyInputWidget( size=20, options={'allowZero': True}), title=_('Price'))
class AmountItemSchema(colander.MappingSchema): name = colander.SchemaNode(colander.String(), validator=colander.Length(max=31), missing=colander.drop) description = colander.SchemaNode(colander.String(), validator=colander.Length(max=255), missing=colander.drop) amount = colander.SchemaNode(colander.Decimal('0.01'), validator=colander.Range(min=0), missing=colander.drop)
class RefundTransactionSchema(colander.MappingSchema): amount = colander.SchemaNode(colander.Decimal('0.01'), validator=colander.Range(0, 20000), required=True) transaction_id = colander.SchemaNode(colander.String(), validator=colander.Length(max=60), required=True) last_four = colander.SchemaNode(colander.String(), validator=colander.Length(max=16), required=True)
class TariffSchema(colander.Schema): title = colander.SchemaNode(colander.String(), title=_("Title")) price = colander.SchemaNode(colander.Decimal(), title=_("Price"), default=0) currency = colander.SchemaNode(colander.String(), title=_("Currency"), missing="") vat = colander.SchemaNode(colander.Int(), title=_("VAT in integer percentage"), default=0)
class TransactionBaseSchema(colander.MappingSchema): line_items = LineItemsSchema(validator=colander.Length(max=30), missing=colander.drop) order = OrderSchema(missing=colander.drop) tax = AmountItemSchema(missing=colander.drop) duty = AmountItemSchema(missing=colander.drop) shipping_and_handling = AmountItemSchema(missing=colander.drop) amount = colander.SchemaNode(colander.Decimal('0.01'), validator=colander.Range(0, 20000), required=True) split_tender_id = colander.SchemaNode(colander.String(), missing=colander.drop) tax_exempt = colander.SchemaNode(colander.Boolean(), missing=colander.drop)
class CreateRecurringSchema(UpdateRecurringSchema): interval_length = colander.SchemaNode(colander.Integer(), validator=colander.Range(1, 999), required=True) interval_unit = colander.SchemaNode(colander.String(), validator=colander.OneOf(['days', 'months']), required=True) start_date = colander.SchemaNode(colander.Date(), missing=today) total_occurrences = colander.SchemaNode(colander.Integer(), validator=colander.Range(1, 9999), missing=9999) amount = colander.SchemaNode(colander.Decimal('0.01'), validator=colander.Range(0, 20000), required=True)
class LineItemSchema(colander.MappingSchema): item_id = colander.SchemaNode(colander.String(), validator=colander.Length(max=31), missing=colander.drop) name = colander.SchemaNode(colander.String(), validator=colander.Length(max=31), missing=colander.drop) description = colander.SchemaNode(colander.String(), validator=colander.Length(max=255), missing=colander.drop) quantity = colander.SchemaNode(colander.Integer(), validator=colander.Range(min=0, max=99), missing=colander.drop) unit_price = colander.SchemaNode(colander.Decimal('0.01'), validator=colander.Range(min=0), missing=colander.drop) taxable = colander.SchemaNode(colander.Boolean(), missing=colander.drop)
def colander_literal_type(self, data_input): # LOGGER.debug('data input type = %s', data_input.dataType) if 'boolean' in data_input.dataType: return colander.Boolean() elif 'integer' in data_input.dataType: return colander.Integer() elif 'float' in data_input.dataType: return colander.Float() elif 'double' in data_input.dataType: return colander.Float() elif 'angle' in data_input.dataType: return colander.Float() elif 'decimal' in data_input.dataType: return colander.Decimal() elif 'string' in data_input.dataType: if data_input.maxOccurs > 1: # we are going to use a SelectWidget with multiple=True return colander.Set() elif data_input.identifier.endswith("FileUpload"): # we want to upload a file but just return a string containing # the path return deform.FileData() else: return colander.String() elif 'dateTime' in data_input.dataType: return colander.DateTime() elif 'date' in data_input.dataType: return colander.Date() elif 'time' in data_input.dataType: return colander.Time() elif 'duration' in data_input.dataType: # TODO: check correct type # http://www.w3.org/TR/xmlschema-2/#duration return colander.Time() # guessing from default elif hasattr(data_input, 'defaultValue'): try: dateutil.parser.parse(data_input.defaultValue) except Exception: return colander.String() else: return colander.DateTime() else: return colander.String()
class Transfer(colander.MappingSchema): donor = colander.SchemaNode( name='donor', typ=colander.Int(), validator=colander.Range(min=0), missing=colander.required, description=u'Id account - уникальный индификатор донора') recipient = colander.SchemaNode( name='recipient', typ=colander.Int(), validator=colander.Range(min=0), missing=colander.required, description=u'Id account - уникальный индификатор получателя') delta = colander.SchemaNode(name='delta', typ=colander.Decimal(), validator=colander.Range(min=0), missing=colander.required, description=u'Сумма перевода')
class TransactionBaseSchema(colander.MappingSchema): line_items = LineItemsSchema(validator=colander.Length(max=30), missing=colander.drop) user_fields = UserFieldsSchema(validator=colander.Length(max=30), missing=colander.drop) order = OrderSchema(missing=colander.drop) tax = AmountItemSchema(missing=colander.drop) duty = AmountItemSchema(missing=colander.drop) shipping_and_handling = AmountItemSchema(missing=colander.drop) amount = colander.SchemaNode(colander.Decimal('0.01'), validator=colander.Range(min=0), required=True) currency_code = colander.SchemaNode(colander.String(), missing=colander.drop) split_tender_id = colander.SchemaNode(colander.String(), missing=colander.drop) tax_exempt = colander.SchemaNode(colander.Boolean(), missing=colander.drop) customer_ip = colander.SchemaNode(colander.String(), validator=colander.Length(max=39), missing=colander.drop) transaction_settings = TransactionSettings(missing=colander.drop)
class ClientItemSchema(colander.Schema): client_item_id = colander.SchemaNode(colander.Integer(), widget=HiddenWidget(), validator=client_item_id_validator, missing_msg=REQUIRED_FIELD) name = colander.SchemaNode( colander.String(), title=Item.NAME, widget=TextInputWidget( readonly=True, readonly_template=u'readonly/textinput_readonly'), missing_msg=REQUIRED_FIELD) price = colander.SchemaNode( colander.Decimal('0.01', decimal.ROUND_HALF_EVEN), title=Item.PRICE, missing_msg=REQUIRED_FIELD, widget=TextInputWidget(), validator=colander.Range( min=0, max=999999.99, min_err=string_constants.MIN_NUMBER_RANGE_ERROR.format(0), max_err=string_constants.MAX_NUMBER_RANGE_ERROR.format(999999.99)))
def get_schema_from_col(self, column, nullable=None): """ Build and return a Colander SchemaNode using information stored in the column. """ if hasattr(column, 'ca_registry'): params = column.ca_registry.copy() else: params = {} # support sqlalchemy.types.TypeDecorator column_type = getattr(column.type, 'impl', column.type) if 'type' in params: type_ = params.pop('type') elif isinstance(column_type, sqlalchemy.types.Boolean): type_ = colander.Boolean() elif isinstance(column_type, sqlalchemy.types.Date): type_ = colander.Date() elif isinstance(column_type, sqlalchemy.types.DateTime): type_ = colander.DateTime() elif isinstance(column_type, sqlalchemy.types.Enum): type_ = colander.String() elif isinstance(column_type, sqlalchemy.types.Float): type_ = colander.Float() elif isinstance(column_type, sqlalchemy.types.Integer): type_ = colander.Integer() elif isinstance(column_type, sqlalchemy.types.String): type_ = colander.String() elif isinstance(column_type, sqlalchemy.types.Numeric): type_ = colander.Decimal() elif isinstance(column_type, sqlalchemy.types.Time): type_ = colander.Time() else: raise NotImplementedError('Unknown type: %s' % column.type) if 'children' in params: children = params.pop('children') else: children = [] # Add a default value for missing parameters during serialization. if 'default' not in params and column.default is None: params['default'] = colander.null elif 'default' not in params and not column.default is None: params['default'] = column.default.arg # Add a default value for missing parameters during deserialization. if 'missing' not in params and not column.nullable: params['missing'] = colander.required elif 'missing' not in params and column.default is None: params['missing'] = None elif 'missing' not in params and not column.default is None: params['missing'] = column.default.arg # Overwrite default missing value when nullable is specified. if nullable is False: params['missing'] = colander.required elif nullable is True: params['missing'] = None if 'validator' not in params and \ isinstance(column_type, sqlalchemy.types.Enum): params['validator'] = colander.OneOf(column.type.enums) elif 'validator' not in params and \ isinstance(column_type, sqlalchemy.types.Enum): params['validator'] = colander.Length(0, column.type.length) if 'name' not in params: params['name'] = column.name return colander.SchemaNode(type_, *children, **params)
def decimal_property(**kwargs): return colander.SchemaNode(colander.Decimal(), **kwargs)
class AssetSchema(colander.Schema): #: Human readable name name = colander.SchemaNode(colander.String()) #: The network this asset is in network = colander.SchemaNode( UUIDForeignKeyValue(model=AssetNetwork, match_column="id"), widget=defer_widget_values(deform.widget.SelectWidget, available_networks), ) #: Symbol how this asset is presented in tickers symbol = colander.SchemaNode(colander.String()) description = colander.SchemaNode(colander.String(), missing="") #: Markdown page telling about this asset long_description = colander.SchemaNode(colander.String(), description="Markdown formatted", missing="", widget=deform.widget.TextAreaWidget( rows=20, cols=80)) #: Ethereum address external_id = colander.SchemaNode(colander.String(), title="Address", validator=validate_ethereum_address, missing=None, description="0x hex string format") #: Number of units avaialble supply = colander.SchemaNode(colander.Decimal(), missing=None) #: What kind of asset is this asset_class = colander.SchemaNode( EnumValue(AssetClass), widget=deform.widget.SelectWidget(values=enum_values(AssetClass))) #: Workflow state of this asset state = colander.SchemaNode( EnumValue(AssetState), widget=deform.widget.SelectWidget(values=enum_values(AssetState))) other_data = colander.SchemaNode( JSONValue(), widget=JSONWidget(), description="JSON bag of attributes of the object", missing=dict) def dictify(self, obj: Asset) -> dict: """Serialize SQLAlchemy model instance to nested dictionary appstruct presentation.""" appstruct = dictify(self, obj, excludes=("long_description", "external_id")) # Convert between binary storage and human readable hex presentation appstruct["long_description"] = obj.other_data.pop( "long_description", "") if obj.external_id: appstruct["external_id"] = bin_to_eth_address(obj.external_id) else: appstruct["external_id"] = "" return appstruct def objectify(self, appstruct: dict, obj: Asset): """Store the dictionary data from the form submission on the object.""" objectify(self, appstruct, obj, excludes=("long_description", "external_id")) if not obj.other_data: # When creating the object JSON value may be None # instead of empty dict obj.other_data = {} # Special case of field stored inside JSON bag obj.other_data["long_description"] = appstruct["long_description"] # Convert between binary storage and human readable hex presentation if appstruct["external_id"]: obj.external_id = eth_address_to_bin(appstruct["external_id"])
class CreditTransactionSchema(CIMBaseSchema): amount = colander.SchemaNode(colander.Decimal('0.01'), validator=colander.Range(0, 20000), required=True)
def get_schema_from_column(self, prop, overrides): """Extended to handle JSON/JSONB. """ # The name of the SchemaNode is the ColumnProperty key. name = prop.key kwargs = dict(name=name) column = prop.columns[0] typedecorator_overrides = getattr(column.type, self.ca_class_key, {}).copy() # print("colanderalchemy ", prop, typedecorator_overrides) declarative_overrides = column.info.get(self.sqla_info_key, {}).copy() self.declarative_overrides[name] = declarative_overrides.copy() key = 'exclude' if key not in itertools.chain(declarative_overrides, overrides) \ and typedecorator_overrides.pop(key, False): log.debug('Column %s skipped due to TypeDecorator overrides', name) return None if key not in overrides and declarative_overrides.pop(key, False): log.debug('Column %s skipped due to declarative overrides', name) return None if overrides.pop(key, False): log.debug('Column %s skipped due to imperative overrides', name) return None self.check_overrides(name, 'name', typedecorator_overrides, declarative_overrides, overrides) for key in ['missing', 'default']: self.check_overrides(name, key, typedecorator_overrides, {}, {}) # The SchemaNode built using the ColumnProperty has no children. children = [] # The type of the SchemaNode will be evaluated using the Column type. # User can overridden the default type via Column.info or # imperatively using overrides arg in SQLAlchemySchemaNode.__init__ # Support sqlalchemy.types.TypeDecorator dialect = self.dbsession.bind.engine.dialect column_type = column.type.dialect_impl(dialect) imperative_type = overrides.pop('typ', None) declarative_type = declarative_overrides.pop('typ', None) typedecorator_type = typedecorator_overrides.pop('typ', None) if self.type_overrides is not None: type_overrides_type, type_overrides_kwargs = self.type_overrides( self, name, column, column_type) if type_overrides_type == TypeOverridesHandling.drop: # This column should not appear on the form log.debug('Column %s: dropped by type overrides callback', name) return None elif type_overrides_type == TypeOverridesHandling.unknown: # type overrides callback doesn't know about this column type_overrides_type = None type_overrides_kwargs = {} else: type_overrides_type = None type_overrides_kwargs = {} if imperative_type is not None: if hasattr(imperative_type, '__call__'): type_ = imperative_type() else: type_ = imperative_type log.debug('Column %s: type overridden imperatively: %s.', name, type_) elif declarative_type is not None: if hasattr(declarative_type, '__call__'): type_ = declarative_type() else: type_ = declarative_type log.debug('Column %s: type overridden via declarative: %s.', name, type_) elif typedecorator_type is not None: if hasattr(typedecorator_type, '__call__'): type_ = typedecorator_type() else: type_ = typedecorator_type log.debug('Column %s: type overridden via TypeDecorator: %s.', name, type_) elif type_overrides_type is not None: if hasattr(type_overrides_type, '__call__'): type_ = type_overrides_type() else: type_ = type_overrides_type log.debug('Column %s: type overridden via type_overrides: %s.', name, type_) elif isinstance(column_type, Boolean): type_ = colander.Boolean() elif isinstance(column_type, Date): type_ = colander.Date() elif isinstance(column_type, DateTime): type_ = colander.DateTime(default_tzinfo=None) elif isinstance(column_type, Enum): type_ = colander.String() kwargs["validator"] = colander.OneOf(column.type.enums) elif isinstance(column_type, Float): type_ = colander.Float() elif isinstance(column_type, Integer): type_ = colander.Integer() elif isinstance(column_type, String): type_ = colander.String() kwargs["validator"] = colander.Length(0, column.type.length) elif isinstance(column_type, Numeric): type_ = colander.Decimal() elif isinstance(column_type, Time): type_ = colander.Time() else: raise NotImplementedError( 'Not able to derive a colander type from sqlalchemy ' 'type: %s Please explicitly provide a colander ' '`typ` for the "%s" Column.' % (repr(column_type), name)) """ Add default values possible values for default in SQLA: 1. plain non-callable Python value - give to Colander as a default 2. SQL expression (derived from ColumnElement) - leave default blank and allow SQLA to fill 3. Python callable with 0 or 1 args 1 arg version takes ExecutionContext - leave default blank and allow SQLA to fill all values for server_default should be ignored for Colander default """ if (isinstance(column.default, ColumnDefault) and column.default.is_scalar): kwargs["default"] = column.default.arg """ Add missing values possible values for default in SQLA: 1. plain non-callable Python value - give to Colander as a missing unless nullable 2. SQL expression (derived from ColumnElement) - set missing to 'drop' to allow SQLA to fill this in and make it an unrequired field 3. Python callable with 0 or 1 args 1 arg version takes ExecutionContext - set missing to 'drop' to allow SQLA to fill this in and make it an unrequired field if nullable, then missing = colander.null (this has to be the case since some colander types won't accept `None` as a value, but all accept `colander.null`) all values for server_default should result in 'drop' for Colander missing autoincrement results in drop """ if isinstance(column.default, ColumnDefault): if column.default.is_callable: kwargs["missing"] = drop elif column.default.is_clause_element: # SQL expression kwargs["missing"] = drop elif column.default.is_scalar: kwargs["missing"] = column.default.arg elif column.nullable: kwargs["missing"] = colander.null elif isinstance(column.server_default, FetchedValue): kwargs["missing"] = drop # value generated by SQLA backend elif (hasattr(column.table, "_autoincrement_column") and id(column.table._autoincrement_column) == id(column)): # this column is the autoincrement column, so we can drop # it if it's missing and let the database generate it kwargs["missing"] = drop kwargs.update(type_overrides_kwargs) kwargs.update(typedecorator_overrides) kwargs.update(declarative_overrides) kwargs.update(overrides) return colander.SchemaNode(type_, *children, **kwargs)
class Field(object): """Represents an individual form field (a visible object in a form rendering). A :class:`deform.form.Field` object instance is meant to last for the duration of a single web request. As a result, a field object is often used as a scratchpad by the widget associated with that field. Using a field as a scratchpad makes it possible to build implementations of state-retaining widgets while instances of those widget still only need to be constructed once instead of on each request. *Attributes* schema The schema node associated with this field. widget The widget associated with this field. When no widget is defined in the schema node, a default widget will be created. The default widget will have a generated item_css_class containing the normalized version of the ``name`` attribute (with ``item`` prepended, e.g. ``item-username``). NOTE: This behaviour is deprecated and will be removed in the future. Mapping and Sequence Widget templates simply render a css class on an item's container based on Field information. order An integer indicating the relative order of this field's construction to its children and parents. oid A string incorporating the ``order`` attribute that can be used as a unique identifier in HTML code (often for ``id`` attributes of field-related elements). A default oid is generated that looks like this: ``deformField0``. A custom oid can provided, but if the field is cloned, the clones will get unique default oids. name An alias for self.schema.name title An alias for self.schema.title description An alias for self.schema.description required An alias for self.schema.required typ An alias for self.schema.typ children Child fields of this field. parent The parent field of this field or ``None`` if this field is the root. This is actually a property that returns the result of ``weakref.ref(actualparent)()`` to avoid leaks due to circular references, but it can be treated like the field itself. error The exception raised by the last attempted validation of the schema element associated with this field. By default, this attribute is ``None``. If non-None, this attribute is usually an instance of the exception class :exc:`colander.Invalid`, which has a ``msg`` attribute providing a human-readable validation error message. errormsg The ``msg`` attribute of the ``error`` attached to this field or ``None`` if the ``error`` attached to this field is ``None``. renderer The template :term:`renderer` associated with the form. If a renderer is not passed to the constructor, the default deform renderer will be used (the :term:`default renderer`). counter ``None`` or an instance of ``itertools.counter`` which is used to generate sequential order-related attributes such as ``oid`` and ``order``. resource_registry The :term:`resource registry` associated with this field. autofocus If the field's parent form has its ``focus`` argument set to ``on``, the first field out of all fields in this form with ``autofocus`` set to a true-ish value (``on``, ``True``, or ``autofocus``) will receive focus on page load. Default: ``None``. *Constructor Arguments* ``renderer``, ``counter``, ``resource_registry`` and ``appstruct`` are accepted as explicit keyword arguments to the :class:`deform.Field`. These are also available as attribute values. ``renderer``, if passed, is a template renderer as described in :ref:`creating_a_renderer`. ``counter``, if passed, should be an :attr:`itertools.counter` object (useful when rendering multiple forms on the same page, see https://deformdemo.pylonsproject.org/multiple_forms/. ``resource_registry``, if passed should be a widget resource registry (see also :ref:`get_widget_resources`). If any of these values is not passed, a suitable default values is used in its place. The ``appstruct`` constructor argument is used to prepopulate field values related to this form's schema. If an appstruct is not supplied, the form's fields will be rendered with default values unless an appstruct is supplied to the ``render`` method explicitly. The :class:`deform.Field` constructor also accepts *arbitrary* keyword arguments. When an 'unknown' keyword argument is passed, it is attached unmodified to the form field as an attribute. All keyword arguments (explicit and unknown) are also attached to all *children* nodes of the field being constructed. """ error = None _cstruct = colander.null default_renderer = template.default_renderer default_resource_registry = widget.default_resource_registry # Allowable input types for automatic focusing focusable_input_types = ( type(colander.Boolean()), type(colander.Date()), type(colander.DateTime()), type(colander.Decimal()), type(colander.Float()), type(colander.Integer()), type(colander.Set()), type(colander.String()), type(colander.Time()), ) hidden_type = type(HiddenWidget()) def __init__(self, schema, renderer=None, counter=None, resource_registry=None, appstruct=colander.null, parent=None, autofocus=None, **kw): self.counter = counter or itertools.count() self.order = next(self.counter) self.oid = getattr(schema, "oid", "deformField%s" % self.order) self.schema = schema self.typ = schema.typ # required by Invalid exception self.name = schema.name self.title = schema.title self.description = schema.description self.required = schema.required if renderer is None: renderer = self.default_renderer if resource_registry is None: resource_registry = self.default_resource_registry self.renderer = renderer # Parameters passed from parent field to child if "focus" in kw: focus = kw["focus"] else: focus = "on" if "have_first_input" in kw: self.have_first_input = kw["have_first_input"] else: self.have_first_input = False if (focus == "off" or autofocus is None or autofocus is False or str(autofocus).lower() == "off"): self.autofocus = None else: self.autofocus = "autofocus" self.resource_registry = resource_registry self.children = [] if parent is not None: parent = weakref.ref(parent) self._parent = parent self.__dict__.update(kw) first_input_index = -1 child_count = 0 focused = False for child in schema.children: if (focus == "on" and not focused and type(child.typ) in Field.focusable_input_types and type(child.widget) != Field.hidden_type and not self.have_first_input): first_input_index = child_count self.found_first() # Notify ancestors autofocus = getattr(child, "autofocus", None) if autofocus is not None: focused = True kw["have_first_input"] = self.have_first_input self.children.append( Field(child, renderer=renderer, counter=self.counter, resource_registry=resource_registry, parent=self, autofocus=autofocus, **kw)) child_count += 1 if (focus == "on" and not focused and first_input_index != -1 and self.have_first_input): # User did not set autofocus. Focus on first valid input. self.children[first_input_index].autofocus = "autofocus" self.set_appstruct(appstruct) def found_first(self): """Set have_first_input of ancestors""" self.have_first_input = True if self.parent is not None: self.parent.found_first() @property def parent(self): if self._parent is None: return None return self._parent() def get_root(self): """Return the root field in the field hierarchy (the form field)""" node = self while True: parent = node.parent if parent is None: break node = parent return node @classmethod def set_zpt_renderer( cls, search_path, auto_reload=True, debug=True, encoding="utf-8", translator=None, ): """Create a :term:`Chameleon` ZPT renderer that will act as a :term:`default renderer` for instances of the associated class when no ``renderer`` argument is provided to the class' constructor. The arguments to this classmethod have the same meaning as the arguments provided to a :class:`deform.ZPTRendererFactory`. Calling this method resets the :term:`default renderer`. This method is effectively a shortcut for ``cls.set_default_renderer(ZPTRendererFactory(...))``.""" cls.default_renderer = template.ZPTRendererFactory( search_path, auto_reload=auto_reload, debug=debug, encoding=encoding, translator=translator, ) @classmethod def set_default_renderer(cls, renderer): """Set the callable that will act as a default renderer for instances of the associated class when no ``renderer`` argument is provided to the class' constructor. Useful when you'd like to use an alternate templating system. Calling this method resets the :term:`default renderer`. """ cls.default_renderer = staticmethod(renderer) @classmethod def set_default_resource_registry(cls, registry): """Set the callable that will act as a default :term:`resource registry` for instances of the associated class when no ``resource_registry`` argument is provided to the class' constructor. Useful when you'd like to use non-default requirement to resource path mappings for the entirety of a process. Calling this method resets the default :term:`resource registry`. """ cls.default_resource_registry = registry def translate(self, msgid): """Use the translator passed to the renderer of this field to translate the msgid into a term and return the term. If the renderer does not have a translator, this method will return the msgid.""" translate = getattr(self.renderer, "translate", None) if translate is not None: return translate(msgid) return msgid def __iter__(self): """Iterate over the children fields of this field.""" return iter(self.children) def __getitem__(self, name): """Return the subfield of this field named ``name`` or raise a :exc:`KeyError` if a subfield does not exist named ``name``.""" for child in self.children: if child.name == name: return child raise KeyError(name) def __contains__(self, name): for child in self.children: if child.name == name: return True return False def clone(self): """Clone the field and its subfields, retaining attribute information. Return the cloned field. The ``order`` attribute of the node is not cloned; instead the field receives a new order attribute; it will be a number larger than the last rendered field of this set. The parent of the cloned node will become ``None`` unconditionally.""" cloned = self.__class__(self.schema) cloned.__dict__.update(self.__dict__) cloned.order = next(cloned.counter) cloned.oid = "deformField%s" % cloned.order cloned._parent = None children = [] for field in self.children: cloned_child = field.clone() cloned_child._parent = weakref.ref(cloned) children.append(cloned_child) cloned.children = children return cloned @decorator.reify def widget(self): """If a widget is not assigned directly to a field, this function will be called to generate a default widget (only once). The result of this function will then be assigned as the ``widget`` attribute of the field for the rest of the lifetime of this field. If a widget is assigned to a field before form processing, this function will not be called.""" wdg = getattr(self.schema, "widget", None) if wdg is not None: return wdg widget_maker = getattr(self.schema.typ, "widget_maker", None) if widget_maker is None: widget_maker = schema.default_widget_makers.get( self.schema.typ.__class__) if widget_maker is None: for (cls, wgt) in schema.default_widget_makers.items(): if isinstance(self.schema.typ, cls): widget_maker = wgt break if widget_maker is None: widget_maker = widget.TextInputWidget return widget_maker(item_css_class=self.default_item_css_class()) def default_item_css_class(self): if not self.name: return None css_class = (unicodedata.normalize("NFKD", compat.text_type( self.name)).encode("ascii", "ignore").decode("ascii")) css_class = re.sub(r"[^\w\s-]", "", css_class).strip().lower() # noQA css_class = re.sub(r"[-\s]+", "-", css_class) # noQA return "item-%s" % css_class def get_widget_requirements(self): """Return a sequence of two tuples in the form [(``requirement_name``, ``version``), ..]. The first element in each two-tuple represents a requirement name. When a requirement name is returned as part of ``get_widget_requirements``, it means that one or more CSS or Javascript resources need to be loaded by the page performing the form rendering in order for some widget on the page to function properly. The second element in each two-tuple is the requested version of the library resource. It may be ``None``, in which case the version is unspecified. See also the ``requirements`` attribute of :class:`deform.Widget` and the explanation of widget requirements in :ref:`get_widget_requirements`. """ L = [] requirements = [req for req in self.widget.requirements] + [ req for child in self.children for req in child.get_widget_requirements() ] if requirements: for requirement in requirements: if isinstance(requirement, dict): L.append(requirement) else: reqt = tuple(requirement) if reqt not in L: L.append(reqt) return L def get_widget_resources(self, requirements=None): """Return a resources dictionary in the form ``{'js':[seq], 'css':[seq]}``. ``js`` represents Javascript resources, ``css`` represents CSS resources. ``seq`` represents a sequence of resource paths. Each path in ``seq`` represents a relative resource name, as defined by the mapping of a requirement to a set of resource specification by the :term:`resource registry` attached to this field or form. This method may raise a :exc:`ValueError` if the resource registry associated with this field or form cannot resolve a requirement to a set of resource paths. The ``requirements`` argument represents a set of requirements as returned by a manual call to :meth:`deform.Field.get_widget_requirements`. If ``requirements`` is not supplied, the requirement are implied by calling the :meth:`deform.Field.get_widget_requirements` method against this form field. See also :ref:`get_widget_resources`. """ if requirements is None: requirements = self.get_widget_requirements() resources = self.resource_registry( (req for req in requirements if not isinstance(req, dict))) for req in requirements: if not isinstance(req, dict): continue for key in {'js', 'css'}.intersection(req): value = req[key] if isinstance(value, str): resources[key].append(value) else: resources[key].extend(value) return resources def set_widgets(self, values, separator="."): """set widgets of the child fields of this field or form element. ``widgets`` should be a dictionary in the form:: {'dotted.field.name':Widget(), 'dotted.field.name2':Widget()} The keys of the dictionary are dotted names. Each dotted name refers to a single field in the tree of fields that are children of the field or form object upon which this method is called. The dotted name is split on its dots and the resulting list of names is used as a search path into the child fields of this field in order to find a field to which to assign the associated widget. Two special cases exist: - If the key is the empty string (``''``), the widget is assigned to the field upon which this method is called. - If the key contains an asterisk as an element name, the first child of the found element is traversed. This is most useful for sequence fields, because the first (and only) child of sequence fields is always the prototype field which is used to render all fields in the sequence within a form rendering. If the ``separator`` argument is passed, it is should be a string to be used as the dot character when splitting the dotted names (useful for supplying if one of your field object has a dot in its name, and you need to use a different separator). Examples follow. If the following form is used:: class Person(Schema): first_name = SchemaNode(String()) last_name = SchemaNode(String()) class People(SequenceSchema): person = Person() class Conference(Schema): people = People() name = SchemaNode(String()) schema = Conference() form = Form(schema) The following invocations will have the following results against the schema defined above: ``form.set_widgets({'people.person.first_name':TextAreaWidget()})`` Set the ``first_name`` field's widget to a ``TextAreaWidget``. ``form.set_widgets({'people.*.first_name':TextAreaWidget()})`` Set the ``first_name`` field's widget to a ``TextAreaWidget``. ``form.set_widgets({'people':MySequenceWidget()})`` Set the ``people`` sequence field's widget to a ``MySequenceWidget``. ``form.set_widgets({'people.*':MySequenceWidget()})`` Set the *person* field's widget to a ``MySequenceWidget``. ``form.set_widgets({'':MyMappingWidget()})`` Set *form* node's widget to a ``MyMappingWidget``. """ for k, v in values.items(): if not k: self.widget = v else: path = k.split(separator) field = self while path: element = path.pop(0) if element == "*": field = field.children[0] else: field = field[element] field.widget = v @property def errormsg(self): """Return the ``msg`` attribute of the ``error`` attached to this field. If the ``error`` attribute is ``None``, the return value will be ``None``.""" return getattr(self.error, "msg", None) def serialize(self, cstruct=_marker, **kw): """Serialize the cstruct into HTML and return the HTML string. This function just turns around and calls ``self.widget.serialize(**kw)``; therefore the field widget's ``serialize`` method should be expecting any values sent in ``kw``. If ``cstruct`` is not passed, the cstruct attached to this node will be injected into ``kw`` as ``cstruct``. If ``field`` is not passed in ``kw``, this field will be injected into ``kw`` as ``field``. .. note:: Deform versions before 0.9.8 only accepted a ``readonly`` keyword argument to this function. Version 0.9.8 and later accept arbitrary keyword arguments. It also required that ``cstruct`` was passed; it's broken out from ``kw`` in the method signature for backwards compatibility. """ if cstruct is _marker: cstruct = self.cstruct values = {"field": self, "cstruct": cstruct} values.update(kw) return self.widget.serialize(**values) def deserialize(self, pstruct): """Deserialize the pstruct into a cstruct and return the cstruct.""" return self.widget.deserialize(self, pstruct) def render(self, appstruct=_marker, **kw): """Render the field (or form) to HTML using ``appstruct`` as a set of default values and returns the HTML string. ``appstruct`` is typically a dictionary of application values matching the schema used by this form, or ``colander.null`` to render all defaults. If it is omitted, the rendering will use the ``appstruct`` passed to the constructor. Calling this method passing an appstruct is the same as calling:: cstruct = form.set_appstruct(appstruct) form.serialize(cstruct, **kw) Calling this method without passing an appstruct is the same as calling:: cstruct = form.cstruct form.serialize(cstruct, **kw) See the documentation for :meth:`colander.SchemaNode.serialize` and :meth:`deform.widget.Widget.serialize` . .. note:: Deform versions before 0.9.8 only accepted a ``readonly`` keyword argument to this function. Version 0.9.8 and later accept arbitrary keyword arguments. """ if appstruct is not _marker: self.set_appstruct(appstruct) cstruct = self.cstruct kw.pop("cstruct", None) # disallowed html = self.serialize(cstruct, **kw) return html def validate(self, controls, subcontrol=None): """ Validate the set of controls returned by a form submission against the schema associated with this field or form. ``controls`` should be a *document-ordered* sequence of two-tuples that represent the form submission data. Each two-tuple should be in the form ``(key, value)``. ``node`` should be the schema node associated with this widget. For example, using WebOb, you can compute a suitable value for ``controls`` via:: request.POST.items() Or, if you're using a ``cgi.FieldStorage`` object named ``fs``, you can compute a suitable value for ``controls`` via:: controls = [] if fs.list: for control in fs.list: if control.filename: controls.append((control.name, control)) else: controls.append((control.name, control.value)) Equivalent ways of computing ``controls`` should be available to any web framework. When the ``validate`` method is called: - if the fields are successfully validated, a data structure represented by the deserialization of the data as per the schema is returned. It will be a mapping. - If the fields cannot be successfully validated, a :exc:`deform.exception.ValidationFailure` exception is raised. The typical usage of ``validate`` in the wild is often something like this (at least in terms of code found within the body of a :mod:`pyramid` view function, the particulars will differ in your web framework):: from webob.exc import HTTPFound from deform.exception import ValidationFailure from deform import Form import colander from my_application import do_something class MySchema(colander.MappingSchema): color = colander.SchemaNode(colander.String()) schema = MySchema() def view(request): form = Form(schema, buttons=('submit',)) if 'submit' in request.POST: # form submission needs validation controls = request.POST.items() try: deserialized = form.validate(controls) do_something(deserialized) return HTTPFound(location='http://example.com/success') except ValidationFailure as e: return {'form':e.render()} else: return {'form':form.render()} # the form just needs rendering .. warning:: ``form.validate(controls)`` mutates the ``form`` instance, so the ``form`` instance should be constructed (and live) inside one request. If ``subcontrol`` is supplied, it represents a named subitem in the data returned by ``peppercorn.parse(controls)``. Use this subitem as the pstruct to validate instead of using the entire result of ``peppercorn.parse(controls)`` as the pstruct to validate. For example, if you've embedded a mapping in the form named ``user``, and you want to validate only the data contained in that mapping instead if all of the data in the form post, you might use ``form.validate(controls, subcontrol='user')``. """ try: pstruct = peppercorn.parse(controls) except ValueError as e: exc = colander.Invalid(self.schema, "Invalid peppercorn controls: %s" % e) self.widget.handle_error(self, exc) cstruct = colander.null raise exception.ValidationFailure(self, cstruct, exc) if subcontrol is not None: pstruct = pstruct.get(subcontrol, colander.null) return self.validate_pstruct(pstruct) def validate_pstruct(self, pstruct): """ Validate the pstruct passed. Works exactly like the :class:`deform.field.validate` method, except it accepts a pstruct instead of a set of form controls. A usage example follows:: if 'submit' in request.POST: # the form submission needs validation controls = request.POST.items() pstruct = peppercorn.parse(controls) substruct = pstruct['submapping'] try: deserialized = form.validate_pstruct(substruct) do_something(deserialized) return HTTPFound(location='http://example.com/success') except ValidationFailure, e: return {'form':e.render()} else: return {'form':form.render()} # the form just needs rendering """ exc = None try: cstruct = self.deserialize(pstruct) except colander.Invalid as e: # fill in errors raised by widgets self.widget.handle_error(self, e) cstruct = e.value exc = e self.cstruct = cstruct try: appstruct = self.schema.deserialize(cstruct) except colander.Invalid as e: # fill in errors raised by schema nodes self.widget.handle_error(self, e) exc = e if exc: raise exception.ValidationFailure(self, cstruct, exc) return appstruct def _get_cstruct(self): return self._cstruct def _set_cstruct(self, cstruct): self._cstruct = cstruct child_cstructs = self.schema.cstruct_children(cstruct) if not isinstance(child_cstructs, colander.SequenceItems): # If the schema's type returns SequenceItems, it means that the # node is a sequence node, which means it has one child # representing its prototype instead of a set of "real" children; # our widget handle cloning the prototype node. The prototype's # cstruct will already be set up with its default value by virtue # of set_appstruct having been called in its constructor, and we # needn't (and can't) do anything more. for n, child in enumerate(self.children): child.cstruct = child_cstructs[n] def _del_cstruct(self): if "_cstruct" in self.__dict__: # rely on class-scope _cstruct (null) del self._cstruct cstruct = property(_get_cstruct, _set_cstruct, _del_cstruct) def __repr__(self): return "<%s.%s object at %d (schemanode %r)>" % ( self.__module__, self.__class__.__name__, id(self), self.schema.name, ) def set_appstruct(self, appstruct): """Set the cstruct of this node (and its child nodes) using ``appstruct`` as input.""" cstruct = self.schema.serialize(appstruct) self.cstruct = cstruct return cstruct def set_pstruct(self, pstruct): """Set the cstruct of this node (and its child nodes) using ``pstruct`` as input.""" try: cstruct = self.deserialize(pstruct) except colander.Invalid as e: # explicitly don't set errors cstruct = e.value self.cstruct = cstruct def render_template(self, template, **kw): """Render the template named ``template`` using ``kw`` as the top-level keyword arguments (augmented with ``field`` and ``cstruct`` if necessary)""" values = {"field": self, "cstruct": self.cstruct} values.update(kw) # allow caller to override field and cstruct return self.renderer(template, **values) # retail API def start_mapping(self, name=None): """Create a start-mapping tag (a literal). If ``name`` is ``None``, the name of this node will be used to generate the name in the tag. See the :term:`Peppercorn` documentation for more information. """ if name is None: name = self.name tag = '<input type="hidden" name="__start__" value="%s:mapping"/>' return Markup(tag % (name, )) def end_mapping(self, name=None): """Create an end-mapping tag (a literal). If ``name`` is ``None``, the name of this node will be used to generate the name in the tag. See the :term:`Peppercorn` documentation for more information. """ if name is None: name = self.name tag = '<input type="hidden" name="__end__" value="%s:mapping"/>' return Markup(tag % (name, )) def start_sequence(self, name=None): """Create a start-sequence tag (a literal). If ``name`` is ``None``, the name of this node will be used to generate the name in the tag. See the :term:`Peppercorn` documentation for more information. """ if name is None: name = self.name tag = '<input type="hidden" name="__start__" value="%s:sequence"/>' return Markup(tag % (name, )) def end_sequence(self, name=None): """Create an end-sequence tag (a literal). If ``name`` is ``None``, the name of this node will be used to generate the name in the tag. See the :term:`Peppercorn` documentation for more information. """ if name is None: name = self.name tag = '<input type="hidden" name="__end__" value="%s:sequence"/>' return Markup(tag % (name, )) def start_rename(self, name=None): """Create a start-rename tag (a literal). If ``name`` is ``None``, the name of this node will be used to generate the name in the tag. See the :term:`Peppercorn` documentation for more information. """ if name is None: name = self.name tag = '<input type="hidden" name="__start__" value="%s:rename"/>' return Markup(tag % (name, )) def end_rename(self, name=None): """Create a start-rename tag (a literal). If ``name`` is ``None``, the name of this node will be used to generate the name in the tag. See the :term:`Peppercorn` documentation for more information. """ if name is None: name = self.name tag = '<input type="hidden" name="__end__" value="%s:rename"/>' return Markup(tag % (name, ))
class MenuSchema(colander.MappingSchema): name = colander.SchemaNode(colander.String()) price = colander.SchemaNode(colander.Decimal(quant='1.00', rounding=decimal.ROUND_UP)) # 2dp, rounded up currency = colander.SchemaNode(Currency(), validator=Currency.is_valid, missing='JPY') images = Images() tags = Tags()
def get_schema_from_column(self, prop, overrides): """ Build and return a :class:`colander.SchemaNode` for a given Column. This method uses information stored in the column within the ``info`` that was passed to the Column on creation. This means that ``Colander`` options can be specified declaratively in ``SQLAlchemy`` models using the ``info`` argument that you can pass to :class:`sqlalchemy.Column`. Arguments/Keywords prop A given :class:`sqlalchemy.orm.properties.ColumnProperty` instance that represents the column being mapped. overrides A dict-like structure that consists of schema attributes to override imperatively. Values provides as part of :attr:`overrides` will take precendence over all others. """ # The name of the SchemaNode is the ColumnProperty key. name = prop.key kwargs = dict(name=name) column = prop.columns[0] typedecorator_overrides = getattr(column.type, self.ca_class_key, {}).copy() declarative_overrides = column.info.get(self.sqla_info_key, {}).copy() self.declarative_overrides[name] = declarative_overrides.copy() key = 'exclude' if key not in itertools.chain(declarative_overrides, overrides) \ and typedecorator_overrides.pop(key, False): log.debug('Column %s skipped due to TypeDecorator overrides', name) return None if key not in overrides and declarative_overrides.pop(key, False): log.debug('Column %s skipped due to declarative overrides', name) return None if overrides.pop(key, False): log.debug('Column %s skipped due to imperative overrides', name) return None self.check_overrides(name, 'name', typedecorator_overrides, declarative_overrides, overrides) for key in ['missing', 'default']: self.check_overrides(name, key, typedecorator_overrides, {}, {}) # The SchemaNode built using the ColumnProperty has no children. children = [] # The type of the SchemaNode will be evaluated using the Column type. # User can overridden the default type via Column.info or # imperatively using overrides arg in SQLAlchemySchemaNode.__init__ # Support sqlalchemy.types.TypeDecorator column_type = getattr(column.type, 'impl', column.type) imperative_type = overrides.pop('typ', None) declarative_type = declarative_overrides.pop('typ', None) typedecorator_type = typedecorator_overrides.pop('typ', None) if imperative_type is not None: if hasattr(imperative_type, '__call__'): type_ = imperative_type() else: type_ = imperative_type log.debug('Column %s: type overridden imperatively: %s.', name, type_) elif declarative_type is not None: if hasattr(declarative_type, '__call__'): type_ = declarative_type() else: type_ = declarative_type log.debug('Column %s: type overridden via declarative: %s.', name, type_) elif typedecorator_type is not None: if hasattr(typedecorator_type, '__call__'): type_ = typedecorator_type() else: type_ = typedecorator_type log.debug('Column %s: type overridden via TypeDecorator: %s.', name, type_) elif isinstance(column_type, Boolean): type_ = colander.Boolean() elif isinstance(column_type, Date): type_ = colander.Date() elif isinstance(column_type, DateTime): type_ = colander.DateTime(default_tzinfo=None) elif isinstance(column_type, Enum): type_ = colander.String() kwargs["validator"] = colander.OneOf(column.type.enums) elif isinstance(column_type, Float): type_ = colander.Float() elif isinstance(column_type, Integer): type_ = colander.Integer() elif isinstance(column_type, String): type_ = colander.String() kwargs["validator"] = colander.Length(0, column.type.length) elif isinstance(column_type, Numeric): type_ = colander.Decimal() elif isinstance(column_type, Time): type_ = colander.Time() else: raise NotImplementedError( 'Not able to derive a colander type from sqlalchemy ' 'type: %s Please explicitly provide a colander ' '`typ` for the "%s" Column.' % (repr(column_type), name)) """ Add default values possible values for default in SQLA: 1. plain non-callable Python value - give to Colander as a default 2. SQL expression (derived from ColumnElement) - leave default blank and allow SQLA to fill 3. Python callable with 0 or 1 args 1 arg version takes ExecutionContext - leave default blank and allow SQLA to fill all values for server_default should be ignored for Colander default """ if (isinstance(column.default, ColumnDefault) and column.default.is_scalar): kwargs["default"] = column.default.arg """ Add missing values possible values for default in SQLA: 1. plain non-callable Python value - give to Colander as a missing unless nullable 2. SQL expression (derived from ColumnElement) - set missing to 'drop' to allow SQLA to fill this in and make it an unrequired field 3. Python callable with 0 or 1 args 1 arg version takes ExecutionContext - set missing to 'drop' to allow SQLA to fill this in and make it an unrequired field if nullable, then missing = colander.null (this has to be the case since some colander types won't accept `None` as a value, but all accept `colander.null`) all values for server_default should result in 'drop' for Colander missing autoincrement results in drop """ if isinstance(column.default, ColumnDefault): if column.default.is_callable: kwargs["missing"] = drop elif column.default.is_clause_element: # SQL expression kwargs["missing"] = drop elif column.default.is_scalar: kwargs["missing"] = column.default.arg elif column.nullable: kwargs["missing"] = colander.null elif isinstance(column.server_default, FetchedValue): kwargs["missing"] = drop # value generated by SQLA backend elif (hasattr(column.table, "_autoincrement_column") and id(column.table._autoincrement_column) == id(column)): # this column is the autoincrement column, so we can drop # it if it's missing and let the database generate it kwargs["missing"] = drop kwargs.update(typedecorator_overrides) kwargs.update(declarative_overrides) kwargs.update(overrides) return colander.SchemaNode(type_, *children, **kwargs)