FormAlchemy-1.4.2/000775 000765 000024 00000000000 11662023267 014175 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/CHANGELOG.txt000664 000765 000024 00000025450 11662023151 016223 0ustar00gawelstaff000000 000000 1.4.2 ----- * WebOb1.2 compat * Add some HTML5 renderer * Improve fsblob deletion. issue 16 * Add support for fanstatic in pytlons (thanks to Bruno Binet aka inneos) 1.4.1 ----- * Implemented WebOb-like request passing to FieldSet directly. * Also implemented request passing to Grid * Added support to set `.html_options` with Field.set(html={'some': 'thing'}) * Added support for set(validators=[validator1, validator2]) which adds the specified validators. * Fixed the set(null_as=...), was nul_as and badly wired in. * Improved documentation for the Field.set() method * Support zope.schema.Password * Fix issues 9, 10, 11, 12 1.4 ---- * Fix issue 5, 7 * Allow to binf form to a webob like request * Add Column wrapper to store some form options in models * Field label translation 1.3.9 ----- * fix unicode issue with non webob based framework 1.3.8 ----- * use webob.multidict as ``data``. This will improve unicode handling in the future (eg: py3k migration). WebOb is now a dependency. * add to_dict() method and .bind(with_prefix=True/False) to help to work with json data * improve ext.fsblob. files are wrote on the file system using ``shutil.copyfileobj`` from the ``cgi.FieldStorage`` field * Add a HiddenFieldRendererFactory and allow to hide Date/Time fields via ``.hidden()`` and ``.set(hidden=True)`` (Thanks to tarek to put this idea in my brain) * added german translation (thanks @disko for pull request) * fix issue 1, 2 (on github) 1.3.6 ----- * fix issues 150, 151, 153, 161, 162 * Added `field.label()` and `field.label_tags()` * Major refactoring. The `base` module no longer existe. 1.3.5 ----- * No longer use Binary type. Use LargeBinary instead. * fix issues 145, 147 1.3.4 ------ * renderer._value is deprecated. Use renderer.value * added renderer.raw_value * Model.__html__() (if any) is used to render model in read_only mode. 1.3.3 ----- * Added `.insert_after(field, new_field)` to the `FieldSet` object. Same as `.insert()`, except it adds it after the specified field. * Docs improvements * Fix 131 to 137 1.3.2 ----- * Added `.value_objects` to both Field and FieldRenderer objects. Returns the objects instead of list of primary keys when working with ForeignKeys. * add IntervalFieldRenderer * switch back to WebHelpers * add Hungarian translation (125) * fix bug with latest version of couchdbkit * update paster template to Pylons 1.0b1 * fix issues 123, 124, 127, 128 1.3.1 ------- * include css in MANIFEST.in 1.3 ----- * new controllers to generate CRUD interfaces based on pylons RESTController * couchdb support improvement (allow to use RESTController) * Experimental RDFAlchemy support * Add date formats to config module. * add fs.copy() * zope.schema.List and zope.schema.Choice support (thanks to Christophe Combelles) * fix issues 107, 113, 114, 117, 118 * css improvement for pylons admin interface 1.2.1 ----- * Added fs.append(field) fs.insert(field, new_field) and del fs.field to Fieldset. fs.add() is deprecated. * Added field.set() to modify the field inplace. * bug fixes: issues 70, 80, 82, 97 * added spanish tanslation (thanks to robarago) * added the `.with_html` method to `AbstractField` which will be passed to the renderers, allowing to add some HTML attributes to rendered HTML tags. Removed html_options from render method. (See issue #60) * validators are now passed as second argument the `field` being validated. WARN: this will mean adding the parameter to your functions to be backwards compatible. The validator function signature changed from `myfunc(value)` to `myfunc(value, field=None)`. * ext.couchdb now use couchdbkit instead of py-simplecouchdb * added the `.with_metadata` method to `AbstractField` which allows you to add metadata to your field. The difference with `.with_html()` is that the attributes passed in will not be rendered in the HTML element, but are there only to be used in your templates, to tweak the output according to those properties. See docs/forms.txt 1.2 --- * add a paster template to bootstrap a pylons project with FA support enabled * much sexier look for admin interface * performance improvements * non-SQLA Fields are no longer considered "experimental" * with_null_as feature (see issue #52) * prefix feature (see issue #59) * when auto-querying for option values, the order_by given on the relation is used, if any * synonym awareness (you don't have to manually exclude the shadowed attribute) * ext.couchdb (experimental) 1.1.1 ----- * bug fixes: issues 36, 37, 38, 39, 40, 41, 42, 43, 45, 46, 47, 49 * added EscapingReadonlyRenderer * add Date*Renderer translation 1.1 --- * formalchemy.ext.pylons.admin added; see http://docs.formalchemy.org/ext/pylons.html * formalchemy.ext.fsblob added; see http://docs.formalchemy.org/ext/fsblob.html * support for composite primary keys * support for composite foreign keys of primitive types * model argument now optional for FieldSet.bind * apply i8n to Grid labels * documentation improvement * bug fixes 1.0.1 ----- * Bug fixes 1.0 --- * i18n support (gael.pasgrimaud) * file upload support (gael.pasgrimaud) * mapper property alias support (gael.pasgrimaud) * add `kwargs` to FieldSet and Grid render methods, which are passed on to the template. this allows easy custom template use w/o having to subclass. (lbruno) * removed query_options. Just pass the query as the argument to the options parameter, and FA will turn it into (description, value) pairs. FA will also accept an iterable of objects as a value to the options parameter. * unicode(object) is used as the default option description, not str(object). (Before, unicode was only used if the engine had convert_unicode turned on.) This is more consistent with normal SA behavior. * added sanity checks to disallow getting into an inconsistent state. notably, binding to an object that belongs to a session but does NOT have a primary key set is not allowed. workaround: bind to the class, and FA will instantiate it and take it out of the session [until sync()]. Then you can pull that instance out as the .model attribute. * sync() will save model to session, if necessary * add Field.with_renderer * allow manually-added fields to pull their value from the bound model * fs.[field] returns the configured version of the field, not the unconfigured. fs.fields renamed to fs._fields. Added Field.reset() to deepcopy the unconfigured version. * explicit renderers required for custom types (FieldRenderer.render removed) * new documentation http://docs.formalchemy.org (gael.pasgrimaud) * bug fixes 0.5.1 ----- * Synonym support * Bug fixes 0.5 --- * Composite field and custom type support * Joined table support * Grid (companion to FieldSet) renders and edits multiple instances at once. * readonly support for FieldSet (replacing undocumented Table), Grid (replacing TableCollection) * FieldSet can render Fields from a non-mapped class (experimental) * Saner (backwards-incompatible, but easy port) widget (FieldRenderer) API * FieldSet.render_fields is now an OrderedDict like FieldSet.fields. Use render_fields.[iter]values() to get an iterable like the old render_fields. * Bug fixes 0.3.1 ----- * Bug fixes * Much better DateTime support * Extensible widget API (want to use your favorite date picker instead? No problem.) * `FieldRenderer` is now part of `from formalchemy import *` for use here * Minor changes to template API (details in documentation). Does not affect you unless you already wrote a custom template * order fields by declared order as much as possible, instead of alphabetical, when include= is absent * Validator suite fleshed out (minlength, maxlength, regex, email, currency) * Added doc sections on widget API and validation functions 0.3 --- * Completely new API, based on Fields instead of column names * Support manually added Fields, not just attributes from the SA model * Relations (a FK will be rendered with a dropdown of related objects) * Validation + sync * Template-based rendering for greater customizibility. Tempita is included; Mako is detected and used if present * WebHelpers is no longer a dependency; the small parts FA needs have been moved into helpers.py. (This was prompted by WebHelpers 0.6 breaking backwards compatibility in nontrivial ways.) * Pervasive docstrings * Preliminary SA 0.5 support * Regression test suite 0.2 --- * Added 'disable', 'disable_pk', 'disable_fk' options. * Fixed a bug where 'readonly*' options only worked for 'password' fields. * Added 'date', 'time' and 'datetime' options for date/time fields formatting. * Added 'bool_as_radio' option. * Added a hack to force browsers to POST unckecked checkboxes. * Fixed a bug where 'opts' from the 'dropdown' option is no longer rendered as an attribute of the """ _update_fa(options, name) if 'size' in options: options["cols"], options["rows"] = options["size"].split("x") del options['size'] return textarea(name, content=content, **options) def check_box(name, value="1", checked=False, **options): """ Creates a check box. """ _update_fa(options, name) if checked: options["checked"] = "checked" return tags.checkbox(name, value=value, **options) def hidden_field(name, value=None, **options): """ Creates a hidden field. Takes the same options as text_field """ _update_fa(options, name) return tags.hidden(name, value=value, **options) def file_field(name, value=None, **options): """ Creates a file upload field. If you are using file uploads then you will also need to set the multipart option for the form. Example:: >>> print file_field('myfile') """ _update_fa(options, name) return tags.file(name, value=value, type="file", **options) def radio_button(name, *args, **options): _update_fa(options, name) return radio(name, *args, **options) def tag(name, open=False, **options): """ Returns an XHTML compliant tag of type ``name``. ``open`` Set to True if the tag should remain open All additional keyword args become attribute/value's for the tag. To pass in Python reserved words, append _ to the name of the key. For attributes with no value (such as disabled and readonly), a value of True is permitted. Examples:: >>> print tag("br")
>>> print tag("br", True)
>>> print tag("input", type="text") >>> print tag("input", type='text', disabled='disabled') """ return HTML.tag(name, _closed=not open, **options) def label(value, **kwargs): """ Return a label tag >>> print label('My label', for_='fieldname') """ if 'for_' in kwargs: kwargs['for'] = kwargs.pop('for_') return tag('label', open=True, **kwargs) + literal(value) + literal('') def select(name, selected, select_options, **attrs): """ Creates a dropdown selection box:: """ if 'options' in attrs: del attrs['options'] if select_options and isinstance(select_options[0], (list, tuple)): select_options = [(v, k) for k, v in select_options] _update_fa(attrs, name) return tags.select(name, selected, select_options, **attrs) def _update_fa(attrs, name): if 'id' not in attrs: attrs['id'] = name if 'options' in attrs: del attrs['options'] if __name__=="__main__": import doctest doctest.testmod() FormAlchemy-1.4.2/formalchemy/i18n.py000664 000765 000024 00000006670 11603560632 017642 0ustar00gawelstaff000000 000000 # Copyright (C) 2007 Alexandre Conrad, alexandre (dot) conrad (at) gmail (dot) com # # This module is part of FormAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php import os from gettext import GNUTranslations i18n_path = os.path.join(os.path.dirname(__file__), 'i18n_resources') try: from pyramid.i18n import get_localizer from pyramid.i18n import TranslationStringFactory HAS_PYRAMID = True except ImportError: HAS_PYRAMID = False try: from pylons.i18n import get_lang HAS_PYLONS = True except: HAS_PYLONS = False if not HAS_PYLONS: def get_lang(): return [] class _Translator(object): """dummy translator""" def gettext(self, value): if isinstance(value, str): return unicode(value, 'utf-8') return value _translator = _Translator() def get_translator(lang=None, request=None): """ return a GNUTranslations instance for `lang`:: >>> translator = get_translator('fr') ... assert translate('Remove') == 'Supprimer' ... assert translate('month_01') == 'Janvier' >>> translator = get_translator('en') ... assert translate('Remove') == 'Remove' ... assert translate('month_01') == 'January' The correct gettext method is stored in request if possible:: >>> from webob import Request >>> req = Request.blank('/') >>> translator = get_translator('fr', request=req) ... assert translate('Remove') == 'Supprimer' >>> translator = get_translator('en', request=req) ... assert translate('Remove') == 'Supprimer' """ if request is not None: translate = request.environ.get('fa.translate') if translate: return translate if HAS_PYRAMID: translate = get_localizer(request).translate request.environ['fa.translate'] = translate return translate # get possible fallback languages try: langs = get_lang() or [] except TypeError: # this occurs when Pylons is available and we are not in a valid thread langs = [] # insert lang if provided if lang and lang not in langs: langs.insert(0, lang) if not langs: langs = ['en'] # get the first available catalog for lang in langs: filename = os.path.join(i18n_path, lang, 'LC_MESSAGES','formalchemy.mo') if os.path.isfile(filename): translations_path = os.path.join(i18n_path, lang, 'LC_MESSAGES','formalchemy.mo') tr = GNUTranslations(open(translations_path, 'rb')).gettext def translate(value): value = tr(value) if not isinstance(value, unicode): return unicode(value, 'utf-8') return value if request is not None: request.environ['fa.translate'] = translate return translate # dummy translator if request is not None: request.environ['fa.translate'] = _translator.gettext return _translator.gettext if HAS_PYRAMID: _ = TranslationStringFactory('formalchemy') else: def _(value): """dummy 'translator' to mark translation strings in python code""" return value # month translation _('Year') _('Month') _('Day') _('month_01') _('month_02') _('month_03') _('month_04') _('month_05') _('month_06') _('month_07') _('month_08') _('month_09') _('month_10') _('month_11') _('month_12') FormAlchemy-1.4.2/formalchemy/i18n_resources/000775 000765 000024 00000000000 11662023267 021354 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/msgfmt.py000775 000765 000024 00000013770 11557221745 020371 0ustar00gawelstaff000000 000000 #! /usr/bin/env python # -*- coding: iso-8859-1 -*- # Written by Martin v. Loewis # # Changed by Christian 'Tiran' Heimes for the placeless # translation service (PTS) of zope # # Slightly updated by Hanno Schlichting # # Included by Ingeniweb from PlacelessTranslationService 1.4.8 """Generate binary message catalog from textual translation description. This program converts a textual Uniforum-style message catalog (.po file) into a binary GNU catalog (.mo file). This is essentially the same function as the GNU msgfmt program, however, it is a simpler implementation. This file was taken from Python-2.3.2/Tools/i18n and altered in several ways. Now you can simply use it from another python module: from msgfmt import Msgfmt mo = Msgfmt(po).get() where po is path to a po file as string, an opened po file ready for reading or a list of strings (readlines of a po file) and mo is the compiled mo file as binary string. Exceptions: * IOError if the file couldn't be read * msgfmt.PoSyntaxError if the po file has syntax errors """ import struct import array import types from cStringIO import StringIO __version__ = "1.1pts" class PoSyntaxError(Exception): """ Syntax error in a po file """ def __init__(self, msg): self.msg = msg def __str__(self): return 'Po file syntax error: %s' % self.msg class Msgfmt: """ """ def __init__(self, po, name='unknown'): self.po = po self.name = name self.messages = {} def readPoData(self): """ read po data from self.po and store it in self.poLines """ output = [] if isinstance(self.po, types.FileType): self.po.seek(0) output = self.po.readlines() if isinstance(self.po, list): output = self.po if isinstance(self.po, str): output = open(self.po, 'rb').readlines() if not output: raise ValueError, "self.po is invalid! %s" % type(self.po) return output def add(self, id, str, fuzzy): "Add a non-empty and non-fuzzy translation to the dictionary." if str and not fuzzy: self.messages[id] = str def generate(self): "Return the generated output." keys = self.messages.keys() # the keys are sorted in the .mo file keys.sort() offsets = [] ids = strs = '' for id in keys: # For each string, we need size and file offset. Each string is NUL # terminated; the NUL does not count into the size. offsets.append((len(ids), len(id), len(strs), len(self.messages[id]))) ids += id + '\0' strs += self.messages[id] + '\0' output = '' # The header is 7 32-bit unsigned integers. We don't use hash tables, so # the keys start right after the index tables. # translated string. keystart = 7*4+16*len(keys) # and the values start after the keys valuestart = keystart + len(ids) koffsets = [] voffsets = [] # The string table first has the list of keys, then the list of values. # Each entry has first the size of the string, then the file offset. for o1, l1, o2, l2 in offsets: koffsets += [l1, o1+keystart] voffsets += [l2, o2+valuestart] offsets = koffsets + voffsets output = struct.pack("Iiiiiii", 0x950412deL, # Magic 0, # Version len(keys), # # of entries 7*4, # start of key index 7*4+len(keys)*8, # start of value index 0, 0) # size and offset of hash table output += array.array("i", offsets).tostring() output += ids output += strs return output def get(self): """ """ ID = 1 STR = 2 section = None fuzzy = 0 lines = self.readPoData() # Parse the catalog lno = 0 for l in lines: lno += 1 # If we get a comment line after a msgstr or a line starting with # msgid, this is a new entry # XXX: l.startswith('msgid') is needed because not all msgid/msgstr # pairs in the plone pos have a leading comment if (l[0] == '#' or l.startswith('msgid')) and section == STR: self.add(msgid, msgstr, fuzzy) section = None fuzzy = 0 # Record a fuzzy mark if l[:2] == '#,' and 'fuzzy' in l: fuzzy = 1 # Skip comments if l[0] == '#': continue # Now we are in a msgid section, output previous section if l.startswith('msgid'): section = ID l = l[5:] msgid = msgstr = '' # Now we are in a msgstr section elif l.startswith('msgstr'): section = STR l = l[6:] # Skip empty lines l = l.strip() if not l: continue # XXX: Does this always follow Python escape semantics? # XXX: eval is evil because it could be abused try: l = eval(l, globals()) except Exception, msg: raise PoSyntaxError('%s (line %d of po file %s): \n%s' % (msg, lno, self.name, l)) if section == ID: msgid += l elif section == STR: msgstr += l else: raise PoSyntaxError('error in line %d of po file %s' % (lno, self.name)) # Add last entry if section == STR: self.add(msgid, msgstr, fuzzy) # Compute output return self.generate() def getAsFile(self): return StringIO(self.get()) def __call__(self): return self.getAsFile() FormAlchemy-1.4.2/formalchemy/multidict.py000664 000765 000024 00000014431 11654560570 021062 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- import cgi import copy from UserDict import DictMixin from webob.multidict import MultiDict class UnicodeMultiDict(DictMixin): """ A MultiDict wrapper that decodes returned values to unicode on the fly. Decoding is not applied to assigned values. The key/value contents are assumed to be ``str``/``strs`` or ``str``/``FieldStorages`` (as is returned by the ``paste.request.parse_`` functions). Can optionally also decode keys when the ``decode_keys`` argument is True. ``FieldStorage`` instances are cloned, and the clone's ``filename`` variable is decoded. Its ``name`` variable is decoded when ``decode_keys`` is enabled. """ def __init__(self, multi, encoding=None, errors='strict', decode_keys=False): self.multi = multi if encoding is None: encoding = sys.getdefaultencoding() self.encoding = encoding self.errors = errors self.decode_keys = decode_keys def _decode_key(self, key): if self.decode_keys: try: key = key.decode(self.encoding, self.errors) except AttributeError: pass return key def _encode_key(self, key): if self.decode_keys and isinstance(key, unicode): return key.encode(self.encoding, self.errors) return key def _decode_value(self, value): """ Decode the specified value to unicode. Assumes value is a ``str`` or `FieldStorage`` object. ``FieldStorage`` objects are specially handled. """ if isinstance(value, cgi.FieldStorage): # decode FieldStorage's field name and filename value = copy.copy(value) if self.decode_keys: if not isinstance(value.name, unicode): value.name = value.name.decode(self.encoding, self.errors) if value.filename: if not isinstance(value.filename, unicode): value.filename = value.filename.decode(self.encoding, self.errors) elif not isinstance(value, unicode): try: value = value.decode(self.encoding, self.errors) except AttributeError: pass return value def _encode_value(self, value): if isinstance(value, unicode): value = value.encode(self.encoding, self.errors) return value def __getitem__(self, key): return self._decode_value(self.multi.__getitem__(self._encode_key(key))) def __setitem__(self, key, value): self.multi.__setitem__(self._encode_key(key), self._encode_value(value)) def add(self, key, value): """ Add the key and value, not overwriting any previous value. """ self.multi.add(self._encode_key(key), self._encode_value(value)) def getall(self, key): """ Return a list of all values matching the key (may be an empty list) """ return map(self._decode_value, self.multi.getall(self._encode_key(key))) def getone(self, key): """ Get one value matching the key, raising a KeyError if multiple values were found. """ return self._decode_value(self.multi.getone(self._encode_key(key))) def mixed(self): """ Returns a dictionary where the values are either single values, or a list of values when a key/value appears more than once in this dictionary. This is similar to the kind of dictionary often used to represent the variables in a web request. """ unicode_mixed = {} for key, value in self.multi.mixed().iteritems(): if isinstance(value, list): value = [self._decode_value(value) for value in value] else: value = self._decode_value(value) unicode_mixed[self._decode_key(key)] = value return unicode_mixed def dict_of_lists(self): """ Returns a dictionary where each key is associated with a list of values. """ unicode_dict = {} for key, value in self.multi.dict_of_lists().iteritems(): value = [self._decode_value(value) for value in value] unicode_dict[self._decode_key(key)] = value return unicode_dict def __delitem__(self, key): self.multi.__delitem__(self._encode_key(key)) def __contains__(self, key): return self.multi.__contains__(self._encode_key(key)) has_key = __contains__ def clear(self): self.multi.clear() def copy(self): return UnicodeMultiDict(self.multi.copy(), self.encoding, self.errors) def setdefault(self, key, default=None): return self._decode_value( self.multi.setdefault(self._encode_key(key), self._encode_value(default))) def pop(self, key, *args): return self._decode_value(self.multi.pop(self._encode_key(key), *args)) def popitem(self): k, v = self.multi.popitem() return (self._decode_key(k), self._decode_value(v)) def __repr__(self): items = map('(%r, %r)'.__mod__, _hide_passwd(self.iteritems())) return '%s([%s])' % (self.__class__.__name__, ', '.join(items)) def __len__(self): return self.multi.__len__() ## ## All the iteration: ## def keys(self): return [self._decode_key(k) for k in self.multi.iterkeys()] def iterkeys(self): for k in self.multi.iterkeys(): yield self._decode_key(k) __iter__ = iterkeys def items(self): return [(self._decode_key(k), self._decode_value(v)) for k, v in self.multi.iteritems()] def iteritems(self): for k, v in self.multi.iteritems(): yield (self._decode_key(k), self._decode_value(v)) def values(self): return [self._decode_value(v) for v in self.multi.itervalues()] def itervalues(self): for v in self.multi.itervalues(): yield self._decode_value(v) def _hide_passwd(items): for k, v in items: if ('password' in k or 'passwd' in k or 'pwd' in k ): yield k, '******' else: yield k, v FormAlchemy-1.4.2/formalchemy/paster_templates/000775 000765 000024 00000000000 11662023267 022057 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/tables.py000664 000765 000024 00000014764 11603557766 020356 0ustar00gawelstaff000000 000000 # Copyright (C) 2007 Alexandre Conrad, alexandre (dot) conrad (at) gmail (dot) com # # This module is part of FormAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php import helpers as h from formalchemy import config from formalchemy.forms import FieldSet from tempita import Template as TempitaTemplate # must import after base __all__ = ["Grid"] def _validate_iterable(o): try: iter(o) except: raise Exception('instances must be an iterable, not %s' % o) class Grid(FieldSet): """ Besides `FieldSet`, `FormAlchemy` provides `Grid` for editing and rendering multiple instances at once. Most of what you know about `FieldSet` applies to `Grid`, with the following differences to accomodate being bound to multiple objects: The `Grid` constructor takes the following arguments: * `cls`: the class type that the `Grid` will render (NOT an instance) * `instances=[]`: the instances to render as grid rows * `session=None`: as in `FieldSet` * `data=None`: as in `FieldSet` * `request=None`: as in `FieldSet` `bind` and `rebind` take the last 3 arguments (`instances`, `session`, and `data`); you may not specify a different class type than the one given to the constructor. The `Grid` `errors` attribute is a dictionary keyed by bound instance, whose value is similar to the `errors` from a `FieldSet`, that is, a dictionary whose keys are `Field`s, and whose values are `ValidationError` instances. """ engine = _render = _render_readonly = None def __init__(self, cls, instances=[], session=None, data=None, request=None, prefix=None): if self.__sa__: from sqlalchemy.orm import class_mapper if not class_mapper(cls): raise Exception('Grid must be bound to an SA mapped class') FieldSet.__init__(self, model=cls, session=session, data=data, request=request, prefix=prefix) self.rows = instances self.readonly = False self._errors = {} def configure(self, **kwargs): """ The `Grid` `configure` method takes the same arguments as `FieldSet` (`pk`, `exclude`, `include`, `options`, `readonly`), except there is no `focus` argument. """ if 'focus' in kwargs: del kwargs['focus'] FieldSet.configure(self, **kwargs) def bind(self, instances, session=None, data=None, request=None): """bind to instances""" _validate_iterable(instances) if not session: i = iter(instances) try: instance = i.next() except StopIteration: pass else: from sqlalchemy.orm import object_session session = object_session(instance) mr = FieldSet.bind(self, self.model, session, data, request) mr.rows = instances mr._request = request return mr def rebind(self, instances=None, session=None, data=None, request=None): """rebind to instances""" if instances is not None: _validate_iterable(instances) FieldSet.rebind(self, self.model, session, data, request) if instances is not None: self.rows = instances def copy(self, *args): """return a copy of the fieldset. args is a list of field names or field objects to render in the new fieldset""" mr = FieldSet.bind(self, self.model, self.session) mr.rows = [] mr.readonly = self.readonly mr._errors = {} _fields = self._render_fields or self._fields _new_fields = [] if args: for field in args: if isinstance(field, basestring): if field in _fields: field = _fields.get(field) else: raise AttributeError('%r as not field named %s' % (self, field)) assert isinstance(field, fields.AbstractField), field field.bind(mr) _new_fields.append(field) mr._render_fields = OrderedDict([(field.key, field) for field in _new_fields]) return mr def render(self, **kwargs): engine = self.engine or config.engine if self._render or self._render_readonly: import warnings warnings.warn(DeprecationWarning('_render and _render_readonly are deprecated and will be removed in 1.5. Use a TemplateEngine instead')) if self.readonly: if self._render_readonly is not None: engine._update_args(kwargs) return self._render_readonly(collection=self, **kwargs) return engine('grid_readonly', collection=self, **kwargs) if 'request' not in kwargs: kwargs['request'] = self._request if self._render is not None: engine._update_args(kwargs) return self._render(collection=self, **kwargs) return engine('grid', collection=self, **kwargs) def _set_active(self, instance, session=None): FieldSet.rebind(self, instance, session or self.session, self.data) def get_errors(self, row): if self._errors: return self._errors.get(row, {}) return {} @property def errors(self): return self._errors def validate(self): """These are the same as in `FieldSet`""" if self.data is None: raise Exception('Cannot validate without binding data') if self.readonly: raise Exception('Cannot validate a read-only Grid') self._errors.clear() success = True for row in self.rows: self._set_active(row) row_errors = {} for field in self.render_fields.itervalues(): success = field._validate() and success if field.errors: row_errors[field] = field.errors self._errors[row] = row_errors return success def sync_one(self, row): """ Use to sync a single one of the instances that are bound to the `Grid`. """ # we want to allow the user to sync just rows w/o errors, so this is public if self.readonly: raise Exception('Cannot sync a read-only Grid') self._set_active(row) FieldSet.sync(self) def sync(self): """These are the same as in `FieldSet`""" for row in self.rows: self.sync_one(row) FormAlchemy-1.4.2/formalchemy/templates.py000664 000765 000024 00000010416 11577125234 021057 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- import os import sys from formalchemy.i18n import get_translator from formalchemy import helpers from tempita import Template as TempitaTemplate try: from mako.lookup import TemplateLookup from mako.template import Template as MakoTemplate from mako.exceptions import TopLevelLookupException HAS_MAKO = True except ImportError: HAS_MAKO = False try: from genshi.template import TemplateLoader as GenshiTemplateLoader HAS_GENSHI = True except ImportError: HAS_GENSHI = False MAKO_TEMPLATES = os.path.join( os.path.dirname(__file__), 'paster_templates','pylons_fa','+package+','templates', 'forms') class TemplateEngine(object): """Base class for templates engines """ directories = [] extension = None _templates = ['fieldset', 'fieldset_readonly', 'grid', 'grid_readonly'] def __init__(self, **kw): self.templates = {} if 'extension' in kw: self.extension = kw.pop('extension') if 'directories' in kw: self.directories = list(kw.pop('directories')) for name in self._templates: self.templates[name] = self.get_template(name, **kw) def get_template(self, name, **kw): """return the template object for `name`. Must be override by engines""" return None def get_filename(self, name): """return the filename for template `name`""" for dirname in self.directories + [os.path.dirname(__file__)]: filename = os.path.join(dirname, '%s.%s' % (name, self.extension)) if os.path.isfile(filename): return filename def render(self, template_name, **kwargs): """render the template. Must be override by engines""" return '' def _update_args(cls, kw): kw['F_'] = get_translator(lang=kw.get('lang', None), request=kw.get('request', None)) kw['html'] = helpers return kw _update_args = classmethod(_update_args) def __call__(self, template_name, **kw): """update kw to extend the namespace with some FA's utils then call `render`""" self._update_args(kw) return self.render(template_name, **kw) class TempitaEngine(TemplateEngine): """Template engine for tempita. File extension is `.tmpl`. """ extension = 'tmpl' def get_template(self, name, **kw): filename = self.get_filename(name) if filename: return TempitaTemplate.from_filename(filename, **kw) def render(self, template_name, **kwargs): template = self.templates.get(template_name, None) return template.substitute(**kwargs) class MakoEngine(TemplateEngine): """Template engine for mako. File extension is `.mako`. """ extension = 'mako' _lookup = None def get_template(self, name, **kw): if self._lookup is None: self._lookup = TemplateLookup(directories=self.directories, **kw) try: return self._lookup.get_template('%s.%s' % (name, self.extension)) except TopLevelLookupException: filename = os.path.join(MAKO_TEMPLATES, '%s.mako_tmpl' % name) if os.path.isfile(filename): template = TempitaTemplate.from_filename(filename) return MakoTemplate(template.substitute(template_engine='mako'), **kw) def render(self, template_name, **kwargs): template = self.templates.get(template_name, None) return template.render_unicode(**kwargs) class GenshiEngine(TemplateEngine): """Template engine for genshi. File extension is `.html`. """ extension = 'html' def get_template(self, name, **kw): filename = self.get_filename(name) if filename: loader = GenshiTemplateLoader(os.path.dirname(filename), **kw) return loader.load(os.path.basename(filename)) def render(self, template_name, **kwargs): template = self.templates.get(template_name, None) return template.generate(**kwargs).render('html', doctype=None) if HAS_MAKO: default_engine = MakoEngine(input_encoding='utf-8', output_encoding='utf-8') engines = dict(mako=default_engine, tempita=TempitaEngine()) else: default_engine = TempitaEngine() engines = dict(tempita=TempitaEngine()) FormAlchemy-1.4.2/formalchemy/tests/000775 000765 000024 00000000000 11662023267 017645 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/validators.py000664 000765 000024 00000015270 11560200655 021225 0ustar00gawelstaff000000 000000 # Copyright (C) 2007 Alexandre Conrad, alexandre (dot) conrad (at) gmail (dot) com # # This module is part of FormAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # todo 2.0 pass field and value (so exception can refer to field name, for instance) from exceptions import ValidationError from i18n import _ if 'any' not in locals(): # pre-2.5 support def any(seq): """ >>> any(xrange(10)) True >>> any([0, 0, 0]) False """ for o in seq: if o: return True return False def accepts_none(func): """validator decorator to validate None value""" func.accepts_none = True return func def required(value, field=None): """Successful if value is neither None nor the empty string (yes, including empty lists)""" if value is None or value == '': msg = isinstance(value, list) and _('Please select a value') or _('Please enter a value') raise ValidationError(msg) required = accepts_none(required) # other validators will not be called for empty values def integer(value, field=None): """Successful if value is an int""" # the validator contract says you don't have to worry about "value is None", # but this is called from deserialize as well as validation if isinstance(value, int): return value if value is None or not value.strip(): return None try: return int(value) except: raise ValidationError(_('Value is not an integer')) def float_(value, field=None): """Successful if value is a float""" # the validator contract says you don't have to worry about "value is None", # but this is called from deserialize as well as validation if value is None or not value.strip(): return None try: return float(value) except: raise ValidationError(_('Value is not a number')) from decimal import Decimal def decimal_(value, field=None): """Successful if value can represent a decimal""" # the validator contract says you don't have to worry about "value is None", # but this is called from deserialize as well as validation if value is None or not value.strip(): return None try: return Decimal(value) except: raise ValidationError(_('Value is not a number')) def currency(value, field=None): """Successful if value looks like a currency amount (has exactly two digits after a decimal point)""" if '%.2f' % float_(value) != value: raise ValidationError('Please specify full currency value, including cents (e.g., 12.34)') def email(value, field=None): """ Successful if value is a valid RFC 822 email address. Ignores the more subtle intricacies of what is legal inside a quoted region, and thus may accept some technically invalid addresses, but will never reject a valid address (which is a much worse problem). """ if not value.strip(): return None reserved = r'()<>@,;:\"[]' try: recipient, domain = value.split('@', 1) except ValueError: raise ValidationError(_('Missing @ sign')) if any([ord(ch) < 32 for ch in value]): raise ValidationError(_('Control characters present')) if any([ord(ch) > 127 for ch in value]): raise ValidationError(_('Non-ASCII characters present')) # validate recipient if not recipient: raise ValidationError(_('Recipient must be non-empty')) if recipient.endswith('.'): raise ValidationError(_("Recipient must not end with '.'")) # quoted regions, aka the reason any regexp-based validator is wrong i = 0 while i < len(recipient): if recipient[i] == '"' and (i == 0 or recipient[i - 1] == '.' or recipient[i - 1] == '"'): # begin quoted region -- reserved characters are allowed here. # (this implementation allows a few addresses not strictly allowed by rfc 822 -- # for instance, a quoted region that ends with '\' appears to be illegal.) i += 1 while i < len(recipient): if recipient[i] == '"': break # end of quoted region i += 1 else: raise ValidationError(_("Unterminated quoted section in recipient")) i += 1 if i < len(recipient) and recipient[i] != '.': raise ValidationError(_("Quoted section must be followed by '@' or '.'")) continue if recipient[i] in reserved: raise ValidationError(_("Reserved character present in recipient")) i += 1 # validate domain if not domain: raise ValidationError(_('Domain must be non-empty')) if domain.endswith('.'): raise ValidationError(_("Domain must not end with '.'")) if '..' in domain: raise ValidationError(_("Domain must not contain '..'")) if any([ch in reserved for ch in domain]): raise ValidationError(_("Reserved character present in domain")) # parameterized validators return the validation function def length(min=0, max=None): """Returns a validator that is successful if the input's length is between min and max.""" min_ = min max_ = max def f(value, field=None): if len(value) < min_: raise ValidationError(_('Value must be at least %d characters long') % min_) if max_ is not None and len(value) > max_: raise ValidationError(_('Value must be no more than %d characters long') % max_) return f def maxlength(max): """Returns a validator that is successful if the input's length is at most the given one.""" if max <= 0: raise ValueError('Invalid maximum length') return length(max=max) def minlength(min): """Returns a validator that is successful if the input's length is at least the given one.""" if min <= 0: raise ValueError('Invalid minimum length') return length(min=min) def regex(exp, errormsg=_('Invalid input')): """ Returns a validator that is successful if the input matches (that is, fulfils the semantics of re.match) the given expression. Expressions may be either a string or a Pattern object of the sort returned by re.compile. """ import re if type(exp) != type(re.compile('')): exp = re.compile(exp) def f(value, field=None): if not exp.match(value): raise ValidationError(errormsg) return f # possible others: # oneof raises if input is not one of [or a subset of for multivalues] the given list of possibilities # url(check_exists=False) # address parts # cidr # creditcard number/securitycode (/expires?) # whole-form validators # fieldsmatch # requiredipresent/missing FormAlchemy-1.4.2/formalchemy/tests/__init__.py000664 000765 000024 00000035751 11662017206 021765 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- import os import glob import logging import unittest logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) from BeautifulSoup import BeautifulSoup # required for html prettification from webob import Request, Response from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.orm import mapper as sqla_mapper from sqlalchemy.ext.declarative import declarative_base logging.getLogger('sqlalchemy').setLevel(logging.ERROR) from formalchemy.fields import Field, SelectFieldRenderer, FieldRenderer, TextFieldRenderer, EscapingReadonlyRenderer import formalchemy.fatypes as types from formalchemy import Column def ls(*args): dirname = os.path.dirname(__file__) args = list(args) args.append('*') files = glob.glob(os.path.join(dirname, *args)) files.sort() for f in files: if os.path.isdir(f): print 'D %s' % os.path.basename(f) else: print '- %s' % os.path.basename(f) def cat(*args): filename = os.path.join(os.path.dirname(__file__), *args) print open(filename).read() def session_mapper(scoped_session): def mapper(cls, *arg, **kw): cls.query = scoped_session.query_property() return sqla_mapper(cls, *arg, **kw) return mapper def application(model, fieldset=None): def app(environ, start_response): tmpl = '''
%s
''' req = Request(environ) resp = Response() if fieldset is None: fs = FieldSet(model) else: fs = fieldset.bind(model) if req.method == 'POST': if fieldset is None: fs = fs.bind(request=req) else: fs = fs.bind(model=model, request=req) if fs.validate(): fs.sync() fs.readonly = True resp.body = tmpl % ('OK' + fs.render(),) else: resp.body = tmpl % fs.render() else: resp.body = tmpl % fs.render() return resp(environ, start_response) return app engine = create_engine('sqlite://') Session = scoped_session(sessionmaker(autoflush=False, bind=engine)) mapper = session_mapper(Session) Base = declarative_base(engine, mapper=mapper) class One(Base): __tablename__ = 'ones' id = Column(Integer, primary_key=True) class Two(Base): __tablename__ = 'twos' id = Column(Integer, primary_key=True) foo = Column(Integer, default='133', nullable=True) class TwoInterval(Base): __tablename__ = 'two_interval' id = Column(Integer, primary_key=True) foo = Column(Interval, nullable=False) class TwoFloat(Base): __tablename__ = 'two_floats' id = Column(Integer, primary_key=True) foo = Column(Float, nullable=False) from decimal import Decimal class TwoNumeric(Base): __tablename__ = 'two_numerics' id = Column(Integer, primary_key=True) foo = Column(Numeric, nullable=True) class Three(Base): __tablename__ = 'threes' id = Column(Integer, primary_key=True) foo = Column(Text, nullable=True) bar = Column(Text, nullable=True) class CheckBox(Base): __tablename__ = 'checkboxes' id = Column(Integer, primary_key=True) field = Column(Boolean, nullable=False) class PrimaryKeys(Base): __tablename__ = 'primary_keys' id = Column(Integer, primary_key=True) id2 = Column(String(10), primary_key=True) field = Column(String(10), nullable=False) class Binaries(Base): __tablename__ = 'binaries' id = Column(Integer, primary_key=True) file = Column(LargeBinary, nullable=True) class ConflictNames(Base): __tablename__ = 'conflict_names' id = Column(Integer, primary_key=True) model = Column(String, nullable=True) data = Column(String, nullable=True) session = Column(String, nullable=True) vertices = Table('vertices', Base.metadata, Column('id', Integer, primary_key=True), Column('x1', Integer), Column('y1', Integer), Column('x2', Integer), Column('y2', Integer), ) class Point(object): def __init__(self, x, y): self.x = x self.y = y def __composite_values__(self): return [self.x, self.y] def __eq__(self, other): return other.x == self.x and other.y == self.y def __ne__(self, other): return not self.__eq__(other) class Vertex(object): pass mapper(Vertex, vertices, properties={ 'start':composite(Point, vertices.c.x1, vertices.c.y1), 'end':composite(Point, vertices.c.x2, vertices.c.y2) }) class PointFieldRenderer(FieldRenderer): def render(self, **kwargs): from formalchemy import helpers as h data = self.field.parent.data x_name = self.name + '-x' y_name = self.name + '-y' x_value = (data is not None and x_name in data) and data[x_name] or str(self.field.value and self.field.value.x or '') y_value = (data is not None and y_name in data) and data[y_name] or str(self.field.value and self.field.value.y or '') return h.text_field(x_name, value=x_value) + h.text_field(y_name, value=y_value) def deserialize(self): data = self.field.parent.data.getone(self.name + '-x'), self.field.parent.data.getone(self.name + '-y') return Point(*[int(i) for i in data]) # todo? test a CustomBoolean, using a TypeDecorator -- # http://www.sqlalchemy.org/docs/04/types.html#types_custom # probably need to add _renderer attr and check # isinstance(getattr(myclass, '_renderer', type(myclass)), Boolean) # since the custom class shouldn't really inherit from Boolean properties = Table('properties', Base.metadata, Column('id', Integer, primary_key=True), Column('a', Integer)) class Property(Base): __table__ = properties foo = column_property(properties.c.a.label('foo')) # bar = column_property(properties.c.a) # TODO class Recursive(Base): __tablename__ = 'recursives' id = Column(Integer, primary_key=True) foo = Column(Text, nullable=True) parent_id = Column(Integer, ForeignKey("recursives.id")) parent = relation('Recursive', primaryjoin=parent_id==id, uselist=False, remote_side=parent_id) class Synonym(Base): __tablename__ = 'synonyms' id = Column(Integer, primary_key=True) _foo = Column(Text, nullable=True) def _set_foo(self, foo): self._foo = "SOMEFOO " + foo def _get_foo(self): return self._foo foo = synonym('_foo', descriptor=property(_get_foo, _set_foo)) class OTOChild(Base): __tablename__ = 'one_to_one_child' id = Column(Integer, primary_key=True) baz = Column(Text, nullable=False) def __unicode__(self): return self.baz def __repr__(self): return '' % self.baz class OTOParent(Base): __tablename__ = 'one_to_one_parent' id = Column(Integer, primary_key=True) oto_child_id = Column(Integer, ForeignKey('one_to_one_child.id'), nullable=False) child = relation(OTOChild, uselist=False) class Order(Base): __tablename__ = 'orders' id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey('users.id'), nullable=False) quantity = Column(Integer, nullable=False) def __unicode__(self): return 'Quantity: %s' % self.quantity def __repr__(self): return '' % (self.user_id, self.quantity) class OptionalOrder(Base): # the user is optional, not the order __tablename__ = 'optional_orders' id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey('users.id')) quantity = Column(Integer) user = relation('User') def __unicode__(self): return 'Quantity: %s' % self.quantity def __repr__(self): return '' % (self.user_id, self.quantity) class User(Base): __label__ = 'User' __tablename__ = 'users' id = Column(Integer, primary_key=True) email = Column(Unicode(40), unique=True, nullable=False) password = Column(Unicode(20), nullable=False) name = Column(Unicode(30)) orders = relation(Order, backref='user', order_by='Order.quantity') orders_dl = dynamic_loader(Order) def __unicode__(self): return self.name def __repr__(self): return '' % self.name def __html__(self): return '%s' % (self.email, self.name) class NaturalOrder(Base): __tablename__ = 'natural_orders' id = Column(Integer, primary_key=True) user_email = Column(String, ForeignKey('natural_users.email'), nullable=False) quantity = Column(Integer, nullable=False) def __repr__(self): return 'Quantity: %s' % self.quantity class NaturalUser(Base): __tablename__ = 'natural_users' email = Column(Unicode(40), primary_key=True) password = Column(Unicode(20), nullable=False) name = Column(Unicode(30)) orders = relation(NaturalOrder, backref='user') def __repr__(self): return self.name class Function(Base): __tablename__ = 'functions' foo = Column(TIMESTAMP, primary_key=True, default=func.current_timestamp()) # test property order for non-declarative mapper addresses = Table('email_addresses', Base.metadata, Column('address_id', Integer, Sequence('address_id_seq', optional=True), primary_key = True), Column('address', String(40)), ) users2 = Table('users2', Base.metadata, Column('user_id', Integer, Sequence('user_id_seq', optional=True), primary_key = True), Column('address_id', Integer, ForeignKey(addresses.c.address_id)), Column('name', String(40), nullable=False) ) class Address(object): pass class User2(object): pass mapper(Address, addresses) mapper(User2, users2, properties={'address': relation(Address)}) class OrderUser(Base): __tablename__ = 'order_users' user_id = Column(Integer, ForeignKey('users.id'), primary_key=True) order_id = Column(Integer, ForeignKey('orders.id'), primary_key=True) user = relation(User) order = relation(Order) def __repr__(self): return 'OrderUser(%s, %s)' % (self.user_id, self.order_id) class OrderUserTag(Base): __table__ = Table('order_user_tags', Base.metadata, Column('id', Integer, primary_key=True), Column('user_id', Integer, nullable=False), Column('order_id', Integer, nullable=False), Column('tag', String, nullable=False), ForeignKeyConstraint(['user_id', 'order_id'], ['order_users.user_id', 'order_users.order_id'])) order_user = relation(OrderUser) class Order__User(Base): __table__ = join(Order.__table__, User.__table__).alias('__orders__users') class Aliases(Base): __tablename__ = 'table_with_aliases' id = Column(Integer, primary_key=True) text = Column('row_text', Text) Base.metadata.create_all() session = Session() primary1 = PrimaryKeys(id=1, id2='22', field='value1') session.add(primary1) primary2 = PrimaryKeys(id=1, id2='33', field='value2') session.add(primary2) parent = OTOParent() session.add(parent) parent.child = OTOChild(baz='baz') bill = User(email='bill@example.com', password='1234', name='Bill') session.add(bill) john = User(email='john@example.com', password='5678', name='John') order1 = Order(user=bill, quantity=10) session.add(order1) order2 = Order(user=john, quantity=5) session.add(order2) order3 = Order(user=john, quantity=6) session.add(order3) nbill = NaturalUser(email='nbill@example.com', password='1234', name='Natural Bill') session.add(nbill) njohn = NaturalUser(email='njohn@example.com', password='5678', name='Natural John') session.add(njohn) norder1 = NaturalOrder(user=nbill, quantity=10) session.add(norder1) norder2 = NaturalOrder(user=njohn, quantity=5) session.add(norder2) orderuser1 = OrderUser(user_id=1, order_id=1) session.add(orderuser1) orderuser2 = OrderUser(user_id=1, order_id=2) session.add(orderuser2) conflict_names = ConflictNames(data='data', model='model', session='session') session.commit() from formalchemy import config from formalchemy.forms import FieldSet as DefaultFieldSet from formalchemy.tables import Grid as DefaultGrid from formalchemy.fields import Field from formalchemy import templates from formalchemy.validators import ValidationError if templates.HAS_MAKO: if not isinstance(config.engine, templates.MakoEngine): raise ValueError('MakoEngine is not the default engine: %s' % config.engine) else: raise ImportError('mako is required for testing') def pretty_html(html): if isinstance(html, unicode): html = html.encode('utf-8') soup = BeautifulSoup(str(html)) return soup.prettify().strip() class FieldSet(DefaultFieldSet): def render(self, lang=None): if self.readonly: html = pretty_html(DefaultFieldSet.render(self)) for name, engine in templates.engines.items(): if isinstance(engine, config.engine.__class__): continue html_engine = pretty_html(engine('fieldset_readonly', fieldset=self)) assert html == html_engine, (name, html, html_engine) return html html = pretty_html(DefaultFieldSet.render(self)) for name, engine in templates.engines.items(): if isinstance(engine, config.engine.__class__): continue html_engine = pretty_html(engine('fieldset', fieldset=self)) assert html == html_engine, (name, html, html_engine) return html class Grid(DefaultGrid): def render(self, lang=None): if self.readonly: html = pretty_html(DefaultGrid.render(self)) for name, engine in templates.engines.items(): if isinstance(engine, config.engine.__class__): continue html_engine = pretty_html(engine('grid_readonly', collection=self)) assert html == html_engine, (name, html, html_engine) return html html = pretty_html(DefaultGrid.render(self)) for name, engine in templates.engines.items(): if isinstance(engine, config.engine.__class__): continue html_engine = pretty_html(engine('grid', collection=self)) assert html == html_engine, (name, html, html_engine) return html original_renderers = FieldSet.default_renderers.copy() def configure_and_render(fs, **options): fs.configure(**options) return fs.render() if not hasattr(__builtins__, 'sorted'): # 2.3 support def sorted(L, key=lambda a: a): L = list(L) L.sort(lambda a, b: cmp(key(a), key(b))) return L class ImgRenderer(TextFieldRenderer): def render(self, *args, **kwargs): return '' % self.value import fake_module fake_module.__dict__.update({ 'fs': FieldSet(User, session=session), }) import sys sys.modules['library'] = fake_module FormAlchemy-1.4.2/formalchemy/tests/data/000775 000765 000024 00000000000 11662023267 020556 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/tests/fake_module.py000664 000765 000024 00000000117 11514360146 022465 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- #used to simulate the library module used in doctests FormAlchemy-1.4.2/formalchemy/tests/test_aliases.py000664 000765 000024 00000001146 11514360146 022675 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * def test_aliases(): fs = FieldSet(Aliases) fs.bind(Aliases) assert fs.id.name == 'id' def test_render_aliases(): """ >>> alias = session.query(Aliases).first() >>> alias >>> fs = FieldSet(Aliases) >>> print fs.render()
""" FormAlchemy-1.4.2/formalchemy/tests/test_binary.py000664 000765 000024 00000013630 11662021440 022534 0ustar00gawelstaff000000 000000 import cgi import shutil import tempfile from StringIO import StringIO from formalchemy.fields import FileFieldRenderer from formalchemy.ext import fsblob from formalchemy.tests import * from webtest import TestApp, selenium from webob import multidict BOUNDARY='testdata' ENVIRON = { 'REQUEST_METHOD':'POST', 'CONTENT_TYPE': 'multipart/form-data;boundary="%s"' % BOUNDARY } TEST_DATA = '''--%s Content-Disposition: form-data; name="Binaries--file"; filename="test.js" Content-Type: application/x-javascript var test = null; --%s-- ''' % (BOUNDARY, BOUNDARY) EMPTY_DATA = '''--%s Content-Disposition: form-data; name="Binaries--file"; filename="" Content-Type: application/x-javascript --%s-- ''' % (BOUNDARY, BOUNDARY) REMOVE_DATA = '''--%s Content-Disposition: form-data; name="Binaries--file--remove" 1 --%s Content-Disposition: form-data; name="Binaries--file"; filename="" Content-Type: application/x-javascript --%s-- ''' % (BOUNDARY, BOUNDARY, BOUNDARY) def get_fields(data): return multidict.MultiDict.from_fieldstorage(cgi.FieldStorage(fp=StringIO(data), environ=ENVIRON)) def test_binary(): r""" Notice that those tests assume that the FileFieldRenderer work with LargeBinary type *and* String type if you only want to store file path in your DB. Configure a fieldset with a file field >>> fs = FieldSet(Three) >>> record = fs.model >>> fs.configure(include=[fs.bar.with_renderer(FileFieldRenderer)]) >>> isinstance(fs.bar.renderer, FileFieldRenderer) True At creation time only the input field is rendered >>> print fs.render()
If the field has a value then we add a check box to remove it >>> record.bar = '/path/to/file' >>> print fs.render()
Now submit form with empty value >>> fs.rebind(data={'Three--bar':''}) >>> fs.validate() True >>> fs.sync() The field value does not change >>> print record.bar /path/to/file Try to remove it by checking the checkbox >>> fs.rebind(data={'Three--bar':'', 'Three--bar--remove':'1'}) >>> fs.validate() True >>> fs.sync() The field value is removed >>> print record.bar Also check that this work with cgi.FieldStorage >>> fs = FieldSet(Binaries) >>> record = fs.model We need test data >>> data = get_fields(TEST_DATA) >>> print data.getone('Binaries--file') FieldStorage(u'Binaries--file', u'test.js') >>> fs.rebind(data=data) >>> if fs.validate(): fs.sync() We get the file, yeah. >>> print record.file var test = null; Now submit form with empty value >>> data = get_fields(EMPTY_DATA) >>> fs.rebind(data=data) >>> if fs.validate(): fs.sync() The field value dos not change >>> print record.file var test = null; Remove file >>> data = get_fields(REMOVE_DATA) >>> fs.rebind(data=data) >>> if fs.validate(): fs.sync() The field value is now empty >>> print record.file See what append in read only mode >>> record.file = 'e'*1000 >>> print fs.file.render_readonly() 1 KB >>> record.file = 'e'*1000*1024 >>> print fs.file.render_readonly() 1000.00 KB >>> record.file = 'e'*2*1024*1024 >>> print fs.file.render_readonly() 2.00 MB """ class BlobTestCase(unittest.TestCase): renderer = fsblob.FileFieldRenderer def setUp(self): self.wd = tempfile.mkdtemp() self.binary = Three() self.fs = FieldSet(Three) self.fs.configure(include=[self.fs.bar, self.fs.foo]) self.fs.foo.set(renderer=self.renderer.new( storage_path=self.wd, url_prefix='/media')) self.app = TestApp(application(self.binary, self.fs)) def test_file(self): resp = self.app.get('/') resp.mustcontain('type="file"') resp = self.app.post('/', {"Three--bar":'bar'}, upload_files=[('Three--foo', 'foo.txt', 'data')]) resp = self.app.get('/') resp.mustcontain('', 'foo.txt (1 KB)') self.assert_(self.binary.foo.endswith('foo.txt'), repr(self.binary.foo)) resp = self.app.get('/') resp.mustcontain('name="Three--foo--remove"', '', 'foo.txt (1 KB)') # no change form = resp.form resp = form.submit() resp.mustcontain('', 'foo.txt (1 KB)') self.assert_(self.binary.foo.endswith('foo.txt'), repr(self.binary.foo)) # remove file resp = self.app.get('/') form = resp.form form['Three--foo--remove'] = '1' resp = form.submit() self.assert_(self.binary.foo == '', repr(self.binary.foo)) resp.mustcontain(no='foo.txt (1 KB)') def tearDown(self): shutil.rmtree(self.wd) FormAlchemy-1.4.2/formalchemy/tests/test_column.py000664 000765 000024 00000001063 11601326406 022545 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * class Label(Base): __tablename__ = 'label' id = Column(Integer, primary_key=True) label = Column(String, label='My label') def test_label(): """ >>> Label.__table__.c.label.info {'label': 'My label'} >>> fs = FieldSet(Label) >>> print fs.label.label_text My label >>> print fs.label.label() My label """ def test_fk_label(self): """ >>> fs = FieldSet(Order) >>> print fs.user.label_text User >>> print fs.user.label() User """ FormAlchemy-1.4.2/formalchemy/tests/test_dates.py000664 000765 000024 00000026165 11560015572 022365 0ustar00gawelstaff000000 000000 from formalchemy.tests import * from formalchemy.fields import DateTimeFieldRenderer import datetime class Dt(Base): __tablename__ = 'dts' id = Column('id', Integer, primary_key=True) foo = Column('foo', Date, nullable=True) bar = Column('bar', Time, nullable=True) foobar = Column('foobar', DateTime, nullable=True) class DateTimeFieldRendererFr(DateTimeFieldRenderer): edit_format = 'd-m-y' def test_dt_hang_up(): """ >>> class MyClass(object): ... td = Field(type=types.DateTime, value=datetime.datetime.now()) ... t = Field().required() >>> MyFS = FieldSet(MyClass) >>> fs = MyFS.bind(model=MyClass, data={ ... 'MyClass--td__year': '2011', ... 'MyClass--td__month': '12', ... 'MyClass--td__day': '12', ... 'MyClass--td__hour': '17', ... 'MyClass--td__minute': '28', ... 'MyClass--td__second': '49', ... 'MyClass--t': ""}) >>> fs.validate() False >>> print pretty_html(fs.td.render()) #doctest: +ELLIPSIS : : >>> fs.td.value datetime.datetime(2011, 12, 12, 17, 28, 49) """ def test_hidden(): """ >>> fs = FieldSet(Dt) >>> _ = fs.foo.set(hidden=True) >>> print pretty_html(fs.foo.render()) #doctest: +ELLIPSIS
... >>> _ = fs.bar.set(hidden=True) >>> print pretty_html(fs.bar.render()) #doctest: +ELLIPSIS
... >>> _ = fs.foobar.set(hidden=True) >>> print pretty_html(fs.foobar.render()) #doctest: +ELLIPSIS
... """ __doc__ = r""" >>> fs = FieldSet(Dt) >>> fs.configure(options=[fs.foobar.with_renderer(DateTimeFieldRendererFr)]) >>> print pretty_html(fs.foobar.with_html(lang='fr').render()) #doctest: +ELLIPSIS ... >>> fs = FieldSet(Dt) >>> print pretty_html(fs.foobar.render()) #doctest: +ELLIPSIS : : >>> fs = FieldSet(Dt) >>> dt = fs.model >>> dt.foo = datetime.date(2008, 6, 3); dt.bar=datetime.time(14, 16, 18); dt.foobar=datetime.datetime(2008, 6, 3, 14, 16, 18) >>> print pretty_html(fs.foo.render()) #doctest: +ELLIPSIS >>> print pretty_html(fs.bar.render()) #doctest: +ELLIPSIS : : >>> print pretty_html(fs.foobar.render()) #doctest: +ELLIPSIS : : >>> fs.rebind(dt, data={'Dt--foo__day': 'DD', 'Dt--foo__month': '2', 'Dt--foo__year': '', 'Dt--bar__hour': 'HH', 'Dt--bar__minute': '6', 'Dt--bar__second': '8'}) >>> print pretty_html(fs.foo.render()) #doctest: +ELLIPSIS >>> print pretty_html(fs.bar.render()) #doctest: +ELLIPSIS : : >>> fs.rebind(dt, data={'Dt--foo__day': '11', 'Dt--foo__month': '2', 'Dt--foo__year': '1951', 'Dt--bar__hour': '4', 'Dt--bar__minute': '6', 'Dt--bar__second': '8', 'Dt--foobar__day': '11', 'Dt--foobar__month': '2', 'Dt--foobar__year': '1951', 'Dt--foobar__hour': '4', 'Dt--foobar__minute': '6', 'Dt--foobar__second': '8'}) >>> fs.sync() >>> dt.foo datetime.date(1951, 2, 11) >>> dt.bar datetime.time(4, 6, 8) >>> dt.foobar datetime.datetime(1951, 2, 11, 4, 6, 8) >>> session.rollback() >>> fs.rebind(dt, data={'Dt--foo__day': 'DD', 'Dt--foo__month': 'MM', 'Dt--foo__year': 'YYYY', 'Dt--bar__hour': 'HH', 'Dt--bar__minute': 'MM', 'Dt--bar__second': 'SS', 'Dt--foobar__day': 'DD', 'Dt--foobar__month': 'MM', 'Dt--foobar__year': '', 'Dt--foobar__hour': 'HH', 'Dt--foobar__minute': 'MM', 'Dt--foobar__second': 'SS'}) >>> fs.validate() True >>> fs.sync() >>> dt.foo is None True >>> dt.bar is None True >>> dt.foobar is None True >>> session.rollback() >>> fs.rebind(dt, data={'Dt--foo__day': '1', 'Dt--foo__month': 'MM', 'Dt--foo__year': 'YYYY', 'Dt--bar__hour': 'HH', 'Dt--bar__minute': 'MM', 'Dt--bar__second': 'SS', 'Dt--foobar__day': 'DD', 'Dt--foobar__month': 'MM', 'Dt--foobar__year': '', 'Dt--foobar__hour': 'HH', 'Dt--foobar__minute': 'MM', 'Dt--foobar__second': 'SS'}) >>> fs.validate() False >>> fs.errors {AttributeField(foo): ['Invalid date']} >>> fs.rebind(dt, data={'Dt--foo__day': 'DD', 'Dt--foo__month': 'MM', 'Dt--foo__year': 'YYYY', 'Dt--bar__hour': 'HH', 'Dt--bar__minute': '1', 'Dt--bar__second': 'SS', 'Dt--foobar__day': 'DD', 'Dt--foobar__month': 'MM', 'Dt--foobar__year': '', 'Dt--foobar__hour': 'HH', 'Dt--foobar__minute': 'MM', 'Dt--foobar__second': 'SS'}) >>> fs.validate() False >>> fs.errors {AttributeField(bar): ['Invalid time']} >>> fs.rebind(dt, data={'Dt--foo__day': 'DD', 'Dt--foo__month': 'MM', 'Dt--foo__year': 'YYYY', 'Dt--bar__hour': 'HH', 'Dt--bar__minute': 'MM', 'Dt--bar__second': 'SS', 'Dt--foobar__day': '11', 'Dt--foobar__month': '2', 'Dt--foobar__year': '1951', 'Dt--foobar__hour': 'HH', 'Dt--foobar__minute': 'MM', 'Dt--foobar__second': 'SS'}) >>> fs.validate() False >>> fs.errors {AttributeField(foobar): ['Incomplete datetime']} >>> fs.rebind(dt) >>> dt.bar = datetime.time(0) >>> print fs.bar.render() #doctest: +ELLIPSIS ... """ if __name__ == '__main__': import doctest doctest.testmod() FormAlchemy-1.4.2/formalchemy/tests/test_fieldset.py000664 000765 000024 00000066764 11661724635 023106 0ustar00gawelstaff000000 000000 __doc__ = r""" >>> from formalchemy.tests import * >>> FieldSet.default_renderers = original_renderers.copy() # some low-level testing first >>> fs = FieldSet(order1) >>> fs._raw_fields() [AttributeField(id), AttributeField(user_id), AttributeField(quantity), AttributeField(user)] >>> fs.user.name 'user_id' >>> fs = FieldSet(bill) >>> fs._raw_fields() [AttributeField(id), AttributeField(email), AttributeField(password), AttributeField(name), AttributeField(orders)] >>> fs.orders.name 'orders' binding should not change attribute order: >>> fs = FieldSet(User) >>> fs_bound = fs.bind(User) >>> fs_bound._fields.values() [AttributeField(id), AttributeField(email), AttributeField(password), AttributeField(name), AttributeField(orders)] >>> fs = FieldSet(User2) >>> fs._raw_fields() [AttributeField(user_id), AttributeField(address_id), AttributeField(name), AttributeField(address)] >>> fs.render() #doctest: +ELLIPSIS Traceback (most recent call last): ... Exception: No session found... >>> fs = FieldSet(One) >>> fs.configure(pk=True, focus=None) >>> fs.id.is_required() True >>> print fs.render()
>>> fs = FieldSet(Two) >>> fs
>>> fs.configure(pk=True) >>> fs
>>> print fs.render()
>>> fs = FieldSet(Two) >>> print fs.render()
>>> fs = FieldSet(Two) >>> fs.configure(options=[fs.foo.label('A custom label')]) >>> print fs.render()
>>> fs.configure(options=[fs.foo.label('')]) >>> print fs.render()
>>> fs = FieldSet(Two) >>> assert fs.render() == configure_and_render(fs, include=[fs.foo]) >>> assert fs.render() == configure_and_render(fs, exclude=[fs.id]) >>> fs = FieldSet(Two) >>> fs.configure(include=[fs.foo.hidden()]) >>> print fs.render() >>> fs = FieldSet(Two) >>> fs.configure(include=[fs.foo.dropdown([('option1', 'value1'), ('option2', 'value2')])]) >>> print fs.render()
>>> fs = FieldSet(Two) >>> assert configure_and_render(fs, include=[fs.foo.dropdown([('option1', 'value1'), ('option2', 'value2')])]) == configure_and_render(fs, options=[fs.foo.dropdown([('option1', 'value1'), ('option2', 'value2')])]) >>> print pretty_html(fs.foo.with_html(onblur='test()').render()) >>> print fs.foo.reset().with_html(onblur='test').render() # Test with_metadata() >>> fs = FieldSet(Three) >>> fs.configure(include=[fs.foo.with_metadata(instructions=u'Answer well')]) >>> print fs.render()
Answer well
# test sync >>> print session.query(One).count() 0 >>> fs_1 = FieldSet(One, data={}, session=session) >>> fs_1.sync() >>> session.flush() >>> print session.query(One).count() 1 >>> session.rollback() >>> twof = TwoFloat(id=1, foo=32.2) >>> fs_twof = FieldSet(twof) >>> print '%.1f' % fs_twof.foo.value 32.2 >>> print pretty_html(fs_twof.foo.render()) >>> import datetime >>> twoi = TwoInterval(id=1, foo=datetime.timedelta(2.2)) >>> fs_twoi = FieldSet(twoi) >>> fs_twoi.foo.renderer >>> fs_twoi.foo.value datetime.timedelta(2, 17280) >>> print pretty_html(fs_twoi.foo.render()) >>> fs_twoi.rebind(data={"TwoInterval-1-foo": "3.1"}) >>> fs_twoi.sync() >>> new_twoi = fs_twoi.model >>> new_twoi.foo == datetime.timedelta(3.1) True # test render and sync fatypes.Numeric # http://code.google.com/p/formalchemy/issues/detail?id=41 >>> twon = TwoNumeric(id=1, foo=Decimal('2.3')) >>> fs_twon = FieldSet(twon) >>> print pretty_html(fs_twon.foo.render()) >>> fs_twon.rebind(data={"TwoNumeric-1-foo": "6.7"}) >>> fs_twon.sync() >>> new_twon = fs_twon.model >>> new_twon.foo == Decimal("6.7") True # test sync when TwoNumeric-1-foo is empty >>> fs_twon.rebind(data={"TwoNumeric-1-foo": ""}) >>> fs_twon.sync() >>> new_twon = fs_twon.model >>> str(new_twon.foo) 'None' >>> fs_cb = FieldSet(CheckBox) >>> fs_cb.field.value is None True >>> print pretty_html(fs_cb.field.dropdown().render()) # test no checkbox/radio submitted >>> fs_cb.rebind(data={}) >>> fs_cb.field.raw_value is None True >>> fs_cb.field.value False >>> fs_cb.field.renderer.value is None True >>> print fs_cb.field.render() >>> fs_cb.field.renderer #doctest: +ELLIPSIS >>> fs_cb.field.renderer._serialized_value() is None True >>> print pretty_html(fs_cb.field.radio().render())
>>> fs_cb.validate() True >>> fs_cb.errors {} >>> fs_cb.sync() >>> cb = fs_cb.model >>> cb.field False >>> fs_cb.rebind(data={'CheckBox--field': 'True'}) >>> fs_cb.validate() True >>> fs_cb.sync() >>> cb.field True >>> fs_cb.configure(options=[fs_cb.field.dropdown()]) >>> fs_cb.rebind(data={'CheckBox--field': 'False'}) >>> fs_cb.sync() >>> cb.field False >>> fs = FieldSet(Two) >>> print pretty_html(fs.foo.dropdown(options=['one', 'two']).radio().render())
>>> assert fs.foo.radio(options=['one', 'two']).render() == fs.foo.dropdown(options=['one', 'two']).radio().render() >>> print fs.foo.radio(options=['one', 'two']).dropdown().render() >>> assert fs.foo.dropdown(options=['one', 'two']).render() == fs.foo.radio(options=['one', 'two']).dropdown().render() >>> print pretty_html(fs.foo.dropdown(options=['one', 'two'], multiple=True).checkbox().render())
>>> fs = FieldSet(User, session=session) >>> print fs.render()
>>> fs = FieldSet(bill) >>> print pretty_html(fs.orders.render()) >>> print pretty_html(fs.orders.checkbox().render())

>>> print fs.orders.checkbox(options=session.query(Order).filter_by(id=1)).render() >>> fs = FieldSet(bill, data={}) >>> fs.configure(include=[fs.orders.checkbox()]) >>> fs.validate() True >>> fs = FieldSet(bill, data={'User-1-orders': ['2', '3']}) >>> print pretty_html(fs.orders.render()) >>> fs.orders.model_value [1] >>> fs.orders.raw_value [] >>> fs = FieldSet(Two) >>> print fs.foo.render() >>> fs = FieldSet(Two) >>> print fs.foo.dropdown([('option1', 'value1'), ('option2', 'value2')]).render() >>> fs = FieldSet(Order, session) >>> print fs.render()
# this seems particularly prone to errors; break it out in its own test >>> fs = FieldSet(order1) >>> fs.user.value 1 # test re-binding >>> fs = FieldSet(Order) >>> fs.configure(pk=True, options=[fs.quantity.hidden()]) >>> fs.rebind(order1) >>> fs.quantity.value 10 >>> fs.session == object_session(order1) True >>> print fs.render()
>>> fs = FieldSet(One) >>> fs.configure(pk=True) >>> print fs.render()
>>> fs.configure(include=[]) >>> print fs.render() >>> fs.configure(pk=True, focus=None) >>> print fs.render()
>>> fs = FieldSet(One) >>> fs.rebind(Two) #doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: ... >>> fs = FieldSet(Two) >>> fs.configure() >>> fs2 = fs.bind(Two) >>> [fs2 == field.parent for field in fs2._render_fields.itervalues()] [True] >>> fs = FieldSet(OTOParent, session) >>> print fs.render()
>>> fs.rebind(parent) >>> fs.child.raw_value # validation + sync >>> fs_2 = FieldSet(Two, session=session, data={'Two--foo': ''}) >>> fs_2.foo.value # '' is deserialized to None, so default of 133 is used '133' >>> fs_2.validate() True >>> fs_2.configure(options=[fs_2.foo.required()], focus=None) >>> fs_2.validate() False >>> fs_2.errors {AttributeField(foo): ['Please enter a value']} >>> print fs_2.render()
Please enter a value
>>> fs_2.rebind(data={'Two--foo': 'asdf'}) >>> fs_2.data SimpleMultiDict([('Two--foo', u'asdf')]) >>> fs_2.validate() False >>> fs_2.errors {AttributeField(foo): ['Value is not an integer']} >>> print fs_2.render()
Value is not an integer
>>> fs_2.rebind(data={'Two--foo': '2'}) >>> fs_2.data SimpleMultiDict([('Two--foo', u'2')]) >>> fs_2.validate() True >>> fs_2.errors {} >>> fs_2.sync() >>> fs_2.model.foo 2 >>> session.flush() >>> print fs_2.render() #doctest: +ELLIPSIS Traceback (most recent call last): ... PkError: Primary key of model has changed since binding, probably due to sync()ing a new instance (from None to 1)... >>> session.rollback() >>> fs_1 = FieldSet(One, session=session, data={'One--id': '1'}) >>> fs_1.configure(pk=True) >>> fs_1.validate() True >>> fs_1.sync() >>> fs_1.model.id 1 >>> fs_1.rebind(data={'One--id': 'asdf'}) >>> fs_1.id.renderer.name u'One--id' >>> fs_1.validate() False >>> fs_1.errors {AttributeField(id): ['Value is not an integer']} # test updating _bound_pk copy >>> one = One(id=1) >>> fs_11 = FieldSet(one) >>> fs_11.id.renderer.name u'One-1-id' >>> one.id = 2 >>> fs_11.rebind(one) >>> fs_11.id.renderer.name u'One-2-id' >>> fs_u = FieldSet(User, session=session, data={}) >>> fs_u.configure(include=[fs_u.orders]) >>> fs_u.validate() True >>> fs_u.sync() >>> fs_u.model.orders [] >>> fs_u.rebind(User, session, data={'User--orders': [str(order1.id), str(order2.id)]}) >>> fs_u.validate() True >>> fs_u.sync() >>> fs_u.model.orders == [order1, order2] True >>> session.rollback() >>> fs_3 = FieldSet(Three, data={'Three--foo': 'asdf', 'Three--bar': 'fdsa'}) >>> fs_3.foo.value u'asdf' >>> print fs_3.foo.textarea().render() >>> print fs_3.foo.textarea("3x4").render() >>> print fs_3.foo.textarea((3,4)).render() >>> fs_3.bar.value u'fdsa' >>> def custom_validator(fs): ... if fs.foo.value != fs.bar.value: ... fs.foo.errors.append('does not match bar') ... raise ValidationError('foo and bar do not match') >>> fs_3.configure(global_validator=custom_validator, focus=None) >>> fs_3.validate() False >>> sorted(fs_3.errors.items()) [(None, ('foo and bar do not match',)), (AttributeField(foo), ['does not match bar'])] >>> print fs_3.render()
foo and bar do not match
does not match bar
# custom renderer >>> fs_3 = FieldSet(Three, data={'Three--foo': 'http://example.com/image.png'}) >>> fs_3.configure(include=[fs_3.foo.with_renderer(ImgRenderer)]) >>> print fs_3.foo.render() # natural PKs >>> fs_npk = FieldSet(NaturalOrder, session) >>> print fs_npk.render()
>>> fs_npk.rebind(norder2, session, data={'NaturalOrder-2-user_email': nbill.email, 'NaturalOrder-2-quantity': str(norder2.quantity)}) >>> fs_npk.user_email.renderer.name u'NaturalOrder-2-user_email' >>> fs_npk.sync() >>> fs_npk.model.user_email == nbill.email True >>> session.rollback() # allow attaching custom attributes to wrappers >>> fs = FieldSet(User) >>> fs.name.baz = 'asdf' >>> fs2 = fs.bind(bill) >>> fs2.name.baz 'asdf' # equality can tell an field bound to an instance is the same as one bound to a type >>> fs.name == fs2.name True # Field >>> fs = FieldSet(One) >>> fs.add(Field('foo')) >>> print configure_and_render(fs, focus=None)
>>> fs = FieldSet(One) >>> fs.add(Field('foo', types.Integer, value=2)) >>> fs.foo.value 2 >>> print configure_and_render(fs, focus=None)
>>> fs.rebind(One, data={'One--foo': '4'}) >>> fs.sync() >>> fs.foo.value 4 >>> fs = FieldSet(One) >>> fs.add(Field('foo', types.Integer, value=2).dropdown(options=[('1', 1), ('2', 2)])) >>> print configure_and_render(fs, focus=None)
# test Field __hash__, __eq__ >>> fs.foo == fs.foo.dropdown(options=[('1', 1), ('2', 2)]) True >>> fs2 = FieldSet(One) >>> fs2.add(Field('foo', types.Integer, value=2)) >>> fs2.configure(options=[fs2.foo.dropdown(options=[('1', 1), ('2', 2)])], focus=None) >>> fs.render() == fs2.render() True >>> fs_1 = FieldSet(One) >>> fs_1.add(Field('foo', types.Integer, value=[2, 3]).dropdown(options=[('1', 1), ('2', 2), ('3', 3)], multiple=True)) >>> print configure_and_render(fs_1, focus=None)
>>> fs_1.rebind(One, data={'One--foo': ['1', '2']}) >>> fs_1.sync() >>> fs_1.foo.value [1, 2] # test attribute names >>> fs = FieldSet(One) >>> fs.add(Field('foo')) >>> fs.foo == fs['foo'] True >>> fs.add(Field('add')) >>> fs.add == fs['add'] False # change default renderer >>> class BooleanSelectRenderer(SelectFieldRenderer): ... def render(self, **kwargs): ... kwargs['options'] = [('Yes', True), ('No', False)] ... return SelectFieldRenderer.render(self, **kwargs) >>> d = dict(FieldSet.default_renderers) >>> d[types.Boolean] = BooleanSelectRenderer >>> fs = FieldSet(CheckBox) >>> fs.default_renderers = d >>> print fs.field.render() # test setter rejection >>> fs = FieldSet(One) >>> fs.id = fs.id.required() Traceback (most recent call last): ... AttributeError: Do not set field attributes manually. Use append() or configure() instead # join >>> fs = FieldSet(Order__User) >>> fs._fields.values() [AttributeField(orders_id), AttributeField(orders_user_id), AttributeField(orders_quantity), AttributeField(users_id), AttributeField(users_email), AttributeField(users_password), AttributeField(users_name)] >>> fs.rebind(session.query(Order__User).filter_by(orders_id=1).one()) >>> print configure_and_render(fs, focus=None)
>>> fs.rebind(session.query(Order__User).filter_by(orders_id=1).one(), data={'Order__User-1_1-orders_quantity': '5', 'Order__User-1_1-users_email': bill.email, 'Order__User-1_1-users_password': '5678', 'Order__User-1_1-users_name': 'Bill'}) >>> fs.validate() True >>> fs.sync() >>> session.flush() >>> session.refresh(bill) >>> bill.password == '5678' True >>> session.rollback() >>> FieldSet.default_renderers[Point] = PointFieldRenderer >>> fs = FieldSet(Vertex) >>> print pretty_html(fs.start.render()) >>> fs.rebind(Vertex) >>> v = fs.model >>> v.start = Point(1,2) >>> v.end = Point(3,4) >>> print pretty_html(fs.start.render()) >>> fs.rebind(v) >>> fs.rebind(data={'Vertex--start-x': '10', 'Vertex--start-y': '20', 'Vertex--end-x': '30', 'Vertex--end-y': '40'}) >>> fs.validate() True >>> fs.sync() >>> session.add(v) >>> session.flush() >>> v.id 1 >>> session.refresh(v) >>> v.start.x 10 >>> v.end.y 40 >>> session.rollback() # readonly tests >>> t = FieldSet(john) >>> john.name = None >>> t.configure(readonly=True) >>> t.readonly True >>> print t.render() Email: john@example.com Password: 5678 Name: Orders: Quantity: 5, Quantity: 6 >>> session.rollback() >>> session.refresh(john) >>> fs_or = FieldSet(order1) >>> print fs_or.user.render_readonly()
Bill >>> out = FieldSet(OrderUserTag, session=session) >>> list(sorted(out._fields)) ['id', 'order_id', 'order_user', 'tag', 'user_id'] >>> print out.order_user.name order_user >>> out.order_user.is_raw_foreign_key False >>> out.order_user.is_composite_foreign_key True >>> list(sorted(out.render_fields)) ['order_user', 'tag'] >>> print pretty_html(out.order_user.render()) >>> out.rebind(data={'OrderUserTag--order_user': '(1, 2)', 'OrderUserTag--tag': 'asdf'}) >>> out.validate() True >>> out.sync() >>> print out.model.order_user OrderUser(1, 2) >>> fs = FieldSet(Function) >>> fs.configure(pk=True) >>> fs.foo.render().startswith('>> fs_r = FieldSet(Recursive) >>> fs_r.parent_id.is_raw_foreign_key True >>> fs_r.rebind(data={'Recursive--foo': 'asdf'}) >>> fs_r.validate() True >>> fs_oo = FieldSet(OptionalOrder, session=session) >>> fs_oo.configure(options=[fs_oo.user.with_null_as(('No user', ''))]) >>> fs_oo.user._null_option ('No user', '') >>> print pretty_html(fs_oo.user.render()) >>> fs_oo = FieldSet(OptionalOrder) >>> fs_oo.rebind(data={'OptionalOrder--user_id': fs_oo.user_id._null_option[1], 'OptionalOrder--quantity': ''}) >>> fs_oo.validate() True >>> fs_oo.user_id.value is None True >>> fs_bad = FieldSet(One) >>> fs_bad.configure(include=[Field('invalid')]) Traceback (most recent call last): ... ValueError: Unrecognized Field `AttributeField(invalid)` in `include` -- did you mean to call append() first? >>> fs_s = FieldSet(Synonym) >>> fs_s._fields {'foo': AttributeField(foo), 'id': AttributeField(id)} >>> fs_prefix = FieldSet(Two, prefix="myprefix") >>> print(fs_prefix.render())
>>> fs_prefix.rebind(data={"myprefix-Two--foo": "42"}) >>> fs_prefix.validate() True >>> fs_prefix.sync() >>> fs_prefix.model.foo 42 >>> fs_two = FieldSet(Two) >>> fs_two.configure(options=[fs_two.foo.label('1 < 2')]) >>> print fs_two.render()
>>> fs_prop = FieldSet(Property) >>> fs_prop.foo.is_readonly() True >>> fs_conflict = FieldSet(ConflictNames) >>> fs_conflict.rebind(conflict_names) >>> print fs_conflict.render() #doctest: +ELLIPSIS
... """ if __name__ == '__main__': import doctest doctest.testmod() FormAlchemy-1.4.2/formalchemy/tests/test_fieldset_api.py000664 000765 000024 00000013652 11603557766 023730 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * from formalchemy.fields import PasswordFieldRenderer def copy(): """ >>> fs = FieldSet(User) >>> fs1 = fs.copy(fs.id, fs.email) >>> fs1._render_fields.keys() ['id', 'email'] >>> fs2 = fs.copy(fs.name, fs.email) >>> fs2._render_fields.keys() ['name', 'email'] """ def append(): """ >>> fs = FieldSet(User) >>> fs.append(Field('added')) >>> fs._fields.keys() ['id', 'email', 'password', 'name', 'orders', 'added'] >>> fs = FieldSet(User) >>> fs.configure() >>> fs.append(Field('added')) >>> fs._fields.keys() ['id', 'email', 'password', 'name', 'orders'] >>> fs._render_fields.keys() ['email', 'password', 'name', 'orders', 'added'] """ def extend(): """ >>> fs = FieldSet(User) >>> fs.extend([Field('added')]) >>> fs._fields.keys() ['id', 'email', 'password', 'name', 'orders', 'added'] >>> fs._render_fields.keys() [] >>> fs.added AttributeField(added) >>> fs = FieldSet(User) >>> fs.configure() >>> fs.extend([Field('added')]) >>> fs._fields.keys() ['id', 'email', 'password', 'name', 'orders'] >>> fs._render_fields.keys() ['email', 'password', 'name', 'orders', 'added'] >>> fs.added AttributeField(added) """ def insert(): """ >>> fs = FieldSet(User) >>> fs.insert(fs.password, Field('login')) >>> fs._fields.keys() ['id', 'email', 'login', 'password', 'name', 'orders'] >>> fs._render_fields.keys() [] >>> fs.login AttributeField(login) >>> fs = FieldSet(User) >>> fs.configure() >>> fs.insert(fs.password, Field('login')) >>> fs._fields.keys() ['id', 'email', 'password', 'name', 'orders'] >>> fs._render_fields.keys() ['email', 'login', 'password', 'name', 'orders'] >>> fs.login AttributeField(login) """ def test_insert_after_relation(): """ >>> fs = FieldSet(OTOParent) >>> fs.configure() >>> fs.insert(fs.child, Field('foo')) >>> fs.insert_after(fs.child, Field('bar')) >>> fs._render_fields.keys() ['foo', 'child', 'bar'] """ def test_insert_after_alias(): """ >>> fs = FieldSet(Aliases) >>> fs.configure() >>> fs.insert(fs.text, Field('foo')) >>> fs.insert_after(fs.text, Field('bar')) >>> fs._render_fields.keys() ['foo', 'text', 'bar'] """ def insert_after(): """ >>> fs = FieldSet(User) >>> fs.insert_after(fs.password, Field('login')) >>> fs._fields.keys() ['id', 'email', 'password', 'login', 'name', 'orders'] >>> fs._render_fields.keys() [] >>> fs.login AttributeField(login) >>> fs = FieldSet(User) >>> fs.configure() >>> fs.insert_after(fs.password, Field('login')) >>> fs._fields.keys() ['id', 'email', 'password', 'name', 'orders'] >>> fs._render_fields.keys() ['email', 'password', 'login', 'name', 'orders'] >>> fs.login AttributeField(login) >>> fs.insert_after('somethingbad', Field('login')) #doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: field must be a Field. Got 'somethingbad' >>> fs.insert_after(fs.password, ['some', 'random', 'objects']) #doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: Can only add Field objects; got AttributeField(password) instead """ def delete(): """ >>> fs = FieldSet(User) >>> del fs.name #doctest: +ELLIPSIS Traceback (most recent call last): ... RuntimeError: You try to delete a field but your form is not configured >>> del fs.notexist #doctest: +ELLIPSIS Traceback (most recent call last): ... AttributeError: field notexist does not exist >>> fs.configure() >>> del fs.name >>> fs._fields.keys() ['id', 'email', 'password', 'name', 'orders'] >>> fs._render_fields.keys() ['email', 'password', 'orders'] """ def test_delete_relation(): """ >>> fs = FieldSet(OTOParent) >>> fs.configure() >>> del fs.child """ def field_set(): """ >>> fs = FieldSet(User) >>> fs.insert(fs.password, Field('login')) >>> def validate(value, field): ... if len(value) < 2: raise ValidationError('Need more than 2 chars') >>> fs.password.set(renderer=PasswordFieldRenderer, validate=validate) AttributeField(password) >>> fs.password.renderer >>> fs.password.validators # doctest: +ELLIPSIS [, ] >>> fs.password.set(instructions='Put a password here') AttributeField(password) >>> fs.password.metadata {'instructions': 'Put a password here'} >>> field = Field('password', value='passwd', renderer=PasswordFieldRenderer) >>> field.renderer >>> field.raw_value 'passwd' >>> field.set(value='new_passwd') AttributeField(password) >>> field.raw_value 'new_passwd' >>> field.set(required=True) AttributeField(password) >>> field.validators #doctest: +ELLIPSIS [] >>> field.set(required=False) AttributeField(password) >>> field.validators [] >>> field.set(html={'this': 'that'}) AttributeField(password) >>> field.html_options {'this': 'that'} >>> field.set(html={'some': 'thing'}) AttributeField(password) >>> field.html_options {'this': 'that', 'some': 'thing'} >>> bob = lambda x: x >>> field.set(validators=[bob]) AttributeField(password) >>> field.validators #doctest: +ELLIPSIS [ at ...>] >>> field.set(validators=[bob]) AttributeField(password) >>> field.validators #doctest: +ELLIPSIS [ at ...>, at ...>] >>> field.set(non_exist=True) Traceback (most recent call last): ... ValueError: Invalid argument non_exist """ FormAlchemy-1.4.2/formalchemy/tests/test_fsblob.py000664 000765 000024 00000005240 11514360146 022522 0ustar00gawelstaff000000 000000 import os import cgi import shutil import tempfile from StringIO import StringIO from nose import with_setup from formalchemy.tests import * from formalchemy.tests.test_binary import * from formalchemy.ext.fsblob import FileFieldRenderer as BaseFile from formalchemy.ext.fsblob import ImageFieldRenderer as BaseImage from formalchemy.ext.fsblob import file_extension TEMPDIR = tempfile.mkdtemp() class FileFieldRenderer(BaseFile): storage_path = TEMPDIR class ImageFieldRenderer(BaseImage): storage_path = TEMPDIR def setup_tempdir(): if not os.path.isdir(TEMPDIR): os.makedirs(TEMPDIR) def teardown_tempdir(): if os.path.isdir(TEMPDIR): shutil.rmtree(TEMPDIR) @with_setup(setup_tempdir, teardown_tempdir) def test_file_storage(): fs = FieldSet(Binaries) record = fs.model fs.configure(include=[fs.file.with_renderer(FileFieldRenderer)]) assert 'test.js' not in fs.render() data = get_fields(TEST_DATA) fs.rebind(data=data) assert fs.validate() is True assert fs.file.value.endswith('/test.js') fs.sync() filepath = os.path.join(TEMPDIR, fs.file.value) assert os.path.isfile(filepath), filepath view = fs.file.render_readonly() value = 'test.js (1 KB)' % fs.file.value assert value in view, '%s != %s' % (value, view) assert value in fs.file.render(), fs.render() @with_setup(setup_tempdir, teardown_tempdir) def test_image_storage(): fs = FieldSet(Binaries) record = fs.model fs.configure(include=[fs.file.with_renderer(ImageFieldRenderer)]) assert 'test.js' not in fs.render() data = get_fields(TEST_DATA) fs.rebind(data=data) assert fs.validate() is True fs.sync() assert fs.file.value.endswith('/test.js') filepath = os.path.join(TEMPDIR, fs.file.value) assert os.path.isfile(filepath), filepath view = fs.file.render_readonly() v = fs.file.value value = 'test.js (1 KB)' % (v, v) assert value in view, '%s != %s' % (value, view) assert value in fs.file.render(), fs.render() @with_setup(setup_tempdir, teardown_tempdir) def test_file_validation(): fs = FieldSet(Binaries) record = fs.model fs.configure(include=[ fs.file.with_renderer( FileFieldRenderer ).validate(file_extension(['js']))]) data = get_fields(TEST_DATA) fs.rebind(data=data) assert fs.validate() is True fs.configure(include=[ fs.file.with_renderer( FileFieldRenderer ).validate(file_extension(['txt']))]) data = get_fields(TEST_DATA) fs.rebind(data=data) assert fs.validate() is False FormAlchemy-1.4.2/formalchemy/tests/test_html5.py000664 000765 000024 00000004630 11661736710 022315 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * from formalchemy.fatypes import * from formalchemy import tests from webtest import SeleniumApp, selenium def test_render(): """ >>> html5_test_fieldset = FieldSet(Three) >>> print html5_test_fieldset.foo.url().render() >>> print html5_test_fieldset.foo.email().render() >>> print html5_test_fieldset.foo.range(min_=2, max_=10, step=5).render() >>> print html5_test_fieldset.foo.number(min_=2, max_=10, step=5).render() >>> print html5_test_fieldset.foo.time().render() >>> print html5_test_fieldset.foo.date().render() >>> print html5_test_fieldset.foo.datetime().render() >>> print html5_test_fieldset.foo.datetime_local().render() >>> print html5_test_fieldset.foo.week().render() >>> print html5_test_fieldset.foo.month().render() >>> print html5_test_fieldset.foo.color().render() """ class HTML5(Base): __tablename__ = 'html5' id = Column('id', Integer, primary_key=True) date = Column(HTML5Date, nullable=True) time = Column(HTML5Time, nullable=True) datetime = Column(HTML5DateTime, nullable=True) color = Column(HTML5Color, nullable=True) @selenium class TestDateTime(unittest.TestCase): def setUp(self): self.app = SeleniumApp(application(HTML5)) def test_render(self): resp = self.app.get('/') form = resp.form form['HTML5--date'] = '2011-01-1' form['HTML5--time'] = '12:10' form['HTML5--datetime'] = '2011-01-1T10:11Z' form['HTML5--color'] = '#fff' resp = form.submit() resp.mustcontain('OK') resp.mustcontain('2011-01-01 10:11:00') def tearDown(self): self.app.close() FormAlchemy-1.4.2/formalchemy/tests/test_json.py000664 000765 000024 00000003055 11605646160 022232 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * from formalchemy.fields import PasswordFieldRenderer try: import json except ImportError: import simplejson as json def to_dict(): """ >>> fs = FieldSet(User, session=session) >>> _ = fs.password.set(renderer=PasswordFieldRenderer) >>> fs.to_dict() {u'User--id': None, u'User--name': None, u'User--email': None, u'User--orders': []} >>> fs = FieldSet(bill) >>> _ = fs.password.set(renderer=PasswordFieldRenderer) >>> fs.to_dict() {u'User-1-email': u'bill@example.com', u'User-1-id': 1, u'User-1-orders': [1], u'User-1-name': u'Bill'} >>> fs.to_dict(with_prefix=False) {'orders': [1], 'id': 1, 'name': u'Bill', 'email': u'bill@example.com'} >>> print json.dumps(fs.to_dict(with_prefix=False, as_string=True)) {"orders": "Quantity: 10", "password": "******", "id": "1", "name": "Bill", "email": "bill@example.com"} """ def bind_without_prefix(): """ >>> data = {u'password': u'1', u'id': 1, u'orders': [1], u'email': u'bill@example.com', u'name': u'Bill'} >>> fs = FieldSet(User) >>> fs = fs.bind(data=data, session=session, with_prefix=False) >>> fs.validate() True >>> fs.rebind(bill, data=data, with_prefix=False) >>> fs.validate() True >>> fs.password.value u'1' >>> data = {u'password': u'2', u'id': 1, u'orders': [1], u'email': u'bill@example.com', u'name': u'Bill'} >>> fs = fs.bind(bill, data=data, with_prefix=False) >>> fs.validate() True >>> fs.password.value u'2' """ FormAlchemy-1.4.2/formalchemy/tests/test_manual.py000644 000765 000024 00000004556 11515111004 022523 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import FieldSet, Field, EscapingReadonlyRenderer, types, configure_and_render, pretty_html class Manual(object): a = Field() b = Field(type=types.Integer).dropdown([('one', 1), ('two', 2)], multiple=True) d = Field().textarea((80, 10)) class ReportByUserForm(object): user_id = Field(type=types.Integer) from_date = Field(type=types.Date).required() to_date = Field(type=types.Date).required() def test_manual(self): """ >>> fs = FieldSet(Manual) >>> print configure_and_render(fs, focus=None)
>>> fs.rebind(data={'Manual--a': 'asdf'}) >>> print pretty_html(fs.a.render()) >>> t = FieldSet(Manual) >>> t.configure(include=[t.a, t.b], readonly=True) >>> t.model.b = [1, 2] >>> print t.render() A: B: one, two >>> t.model.a = 'test' >>> print t.a.render_readonly() test >>> t.configure(readonly=True, options=[t.a.with_renderer(EscapingReadonlyRenderer)]) >>> t.model.a = '' >>> print t.a.render_readonly() <test> """ def test_manual2(): """ >>> fs = FieldSet(ReportByUserForm) >>> print fs.render() #doctest: +ELLIPSIS
...
""" FormAlchemy-1.4.2/formalchemy/tests/test_misc.py000664 000765 000024 00000002674 11542746522 022225 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- import unittest from formalchemy.tests import * from formalchemy.fields import AbstractField, FieldRenderer from formalchemy.fields import _htmlify, deserialize_once class TestAbstractField(unittest.TestCase): def setUp(self): self.fs = FieldSet(User) self.f = AbstractField(self.fs, name="field", type=types.String) self.f.set(renderer=FieldRenderer) self.fs.append(self.f) def test_not_implemented(self): f = self.f self.assertRaises(NotImplementedError, lambda: f.model_value) self.assertRaises(NotImplementedError, lambda: f.raw_value) self.assertRaises(NotImplementedError, f.render) def test_errors(self): f = self.f self.assertEqual(f.errors, []) class TestUtils(unittest.TestCase): def test_htmlify(self): class H(object): __html__ = '' def __repr__(self): return '-' self.assertEqual(_htmlify(H()), '-') class H(object): def __html__(self): return 'html' def __repr__(self): return '-' self.assertEqual(_htmlify(H()), 'html') def test_deserialize_once(self): class H(object): value = 'foo' @deserialize_once def deserialize(self): return self.value h = H() self.assertEqual(h.deserialize(), 'foo') h.value = 'bar' self.assertEqual(h.deserialize(), 'foo') FormAlchemy-1.4.2/formalchemy/tests/test_multiple_keys.py000664 000765 000024 00000005777 11514360146 024160 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * def test_renderer_names(): """ Check that the input name take care of multiple primary keys:: >>> fs = FieldSet(primary1) >>> print fs.field.render() >>> fs = FieldSet(primary2) >>> print fs.field.render() Check form rendering with keys:: >>> fs = FieldSet(primary2) >>> fs.configure(pk=True) >>> print fs.render()
""" def test_foreign_keys(): """ Assume that we can have more than one ForeignKey as primary key:: >>> fs = FieldSet(orderuser2) >>> fs.configure(pk=True) >>> print pretty_html(fs.user.render()) >>> print pretty_html(fs.order.render()) """ def test_deserialize(): """ Assume that we can deserialize a value """ fs = FieldSet(primary1, data={'PrimaryKeys-1_22-field':'new_value'}) assert fs.validate() is True assert fs.field.value == 'new_value' fs.sync() session.rollback() def test_deserialize_new_record(): """ Assume that we can deserialize a value """ fs = FieldSet(PrimaryKeys(), data={'PrimaryKeys-_-id':'8', 'PrimaryKeys-_-id2':'9'}) fs.configure(include=[fs.id, fs.id2]) assert fs.validate() is True fs.sync() assert fs.model.id == 8, fs.model.id assert fs.model.id2 == '9', fs.model.id2 session.rollback() FormAlchemy-1.4.2/formalchemy/tests/test_options.py000664 000765 000024 00000004047 11542761512 022755 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * def test_dropdown(): """ >>> fs = FieldSet(bill) >>> print pretty_html(fs.orders.render()) """ def test_lazy_filtered_dropdown(): """ >>> fs = FieldSet(bill) >>> def available_orders(fs_): ... return fs_.session.query(Order).filter_by(quantity=10) >>> fs.configure(include=[fs.orders.dropdown(options=available_orders)]) >>> print pretty_html(fs.orders.render()) """ def test_lazy_record(): """ >>> fs = FieldSet(bill) >>> r = engine.execute('select quantity, user_id from orders').fetchall() >>> r = engine.execute("select 'Swedish' as name, 'sv_SE' as iso_code union all select 'English', 'en_US'").fetchall() >>> fs.configure(include=[fs.orders.dropdown(options=r)]) >>> print pretty_html(fs.orders.render()) """ def test_manual_options(): """ >>> fs = FieldSet(bill) >>> fs.append(Field(name="cb").checkbox(options=[('one', 1), ('two', 2)])) >>> print fs.render() #doctest: +ELLIPSIS
...
""" FormAlchemy-1.4.2/formalchemy/tests/test_readonly.py000664 000765 000024 00000003473 11605651731 023102 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * def test_readonly_mode(): """ Assume that the field value is render in readonly mode:: >>> fs = FieldSet(Two) >>> fs.configure(options=[fs.foo.readonly()]) >>> print fs.render()
133
""" def test_focus_with_readonly_mode(): """ Assume that the field value is render in readonly mode and that the focus is set to the correct field:: >>> fs = FieldSet(Three) >>> fs.configure(options=[fs.foo.readonly()]) >>> print fs.render()
""" def test_ignore_request_in_readonly(): fs = FieldSet(bill) value = bill.name assert fs.name.value == value, '%s != %s' % (fs.name.value, value) fs.configure(options=[fs.name.readonly()]) assert value in fs.render(), fs.render() data = {'User-1-password':bill.password, 'User-1-email': bill.email, 'User-1-name': 'new name', 'User-1-orders': [o.id for o in bill.orders]} fs.rebind(bill, data=data) fs.configure(options=[fs.name.readonly()]) assert fs.name.value == value, '%s != %s' % (fs.name.value, value) assert fs.name.is_readonly() fs.sync() assert bill.name == value, '%s != %s' % (bill.name, value) bill.name = value FormAlchemy-1.4.2/formalchemy/tests/test_request.py000664 000765 000024 00000003263 11603557766 022765 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * from webob import Request def test_get(): """ >>> fs = FieldSet(User) >>> request = Request.blank('/') >>> fs = fs.bind(User, request=request) >>> fs.id.renderer.request is request True """ def test_post(): """ >>> fs = FieldSet(User) >>> request = Request.blank('/') >>> request.method = 'POST' >>> request.POST['User--id'] = '1' >>> request.POST['User--name'] = 'bill' >>> request.POST['User--email'] = 'a@a.com' >>> request.POST['User--password'] = 'xx' >>> fs = fs.bind(request=request) >>> fs.id.renderer.request is request True >>> fs.validate() True """ def test_post_on_fieldset(): """ >>> request = Request.blank('/') >>> request.method = 'POST' >>> request.POST['User--id'] = '1' >>> request.POST['User--name'] = 'bill' >>> request.POST['User--email'] = 'a@a.com' >>> request.POST['User--password'] = 'xx' >>> fs = FieldSet(User, request=request) >>> fs.id.renderer.request is request True >>> fs.validate() True """ def test_post_on_grid(): """ >>> request = Request.blank('/') >>> request.method = 'POST' >>> request.POST['User-1-id'] = '1' >>> request.POST['User-1-name'] = 'bill' >>> request.POST['User-1-email'] = 'a@a.com' >>> request.POST['User-1-password'] = 'xx' >>> g = Grid(User, [bill], request=request) >>> g.id.renderer.request is request True >>> g.validate() True >>> print g.render() #doctest: +ELLIPSIS ...... """ FormAlchemy-1.4.2/formalchemy/tests/test_tables.py000664 000765 000024 00000014562 11605651336 022541 0ustar00gawelstaff000000 000000 from formalchemy.tests import * def test_rebind_and_render(self): """Explicitly test rebind + render: >>> g = Grid(User, session=session) >>> g.rebind([bill, john]) >>> print pretty_html(g.render()) Email Password Name Orders """ def test_extra_field(): """ Test rendering extra field: >>> g = Grid(User, session=session) >>> g.add(Field('edit', types.String, 'fake edit link')) >>> g._set_active(john) >>> print g.edit.render() And extra field w/ callable value: >>> g = Grid(User, session=session) >>> g.add(Field('edit', types.String, lambda o: 'fake edit link for %s' % o.id)) >>> g._set_active(john) >>> print g.edit.render() Text syncing: >>> g = Grid(User, [john, bill], session=session) >>> g.rebind(data={'User-1-email': '', 'User-1-password': '1234_', 'User-1-name': 'Bill_', 'User-1-orders': '1', 'User-2-email': 'john_@example.com', 'User-2-password': '5678_', 'User-2-name': 'John_', 'User-2-orders': ['2', '3'], }) >>> g.validate() False >>> g.errors[bill] {AttributeField(email): ['Please enter a value']} >>> g.errors[john] {} >>> g.sync_one(john) >>> session.flush() >>> session.refresh(john) >>> john.email == 'john_@example.com' True >>> session.rollback() Test preventing user from binding to the wrong kind of object: >>> g = g.bind([john]) >>> g.rows == [john] True >>> g.rebind(User) Traceback (most recent call last): ... Exception: instances must be an iterable, not >>> g = g.bind(User) Traceback (most recent call last): ... Exception: instances must be an iterable, not Simulate creating a grid in a different thread than it's used in: >>> _Session = sessionmaker(bind=engine) >>> _old_session = _Session() >>> assert _old_session != object_session(john) >>> g = Grid(User, session=_old_session) >>> g2 = g.bind([john]) >>> _ = g2.render() """ def test_rebind_render(): """ Explicitly test rebind + render: >>> g = Grid(User, session=session, prefix="myprefix") >>> g.rebind([bill, john]) >>> print pretty_html(g.render()) Email Password Name Orders >>> g.rebind(data={'myprefix-User-1-email': 'updatebill_@example.com', 'myprefix-User-1-password': '1234_', 'myprefix-User-1-name': 'Bill_', 'myprefix-User-1-orders': '1', 'myprefix-User-2-email': 'john_@example.com', 'myprefix-User-2-password': '5678_', 'myprefix-User-2-name': 'John_', 'myprefix-User-2-orders': ['2', '3'], }) >>> g.validate() True >>> g.sync() >>> bill.email u'updatebill_@example.com' """ if __name__ == '__main__': import doctest doctest.testmod() FormAlchemy-1.4.2/formalchemy/tests/test_unicode.py000664 000765 000024 00000004107 11654560671 022714 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * from formalchemy.multidict import UnicodeMultiDict from formalchemy.multidict import MultiDict def test_unicode(): """ >>> jose = User(email='jose@example.com', ... password='6565', ... name=u'Jos\xe9') >>> order4 = Order(user=jose, quantity=4) >>> session.add(jose) >>> session.add(order4) >>> session.flush() >>> FieldSet.default_renderers = original_renderers.copy() >>> fs = FieldSet(jose) >>> print fs.render() #doctest: +ELLIPSIS
...... >>> fs.readonly = True >>> print fs.render() #doctest: +ELLIPSIS ...José... >>> fs = FieldSet(order4) >>> print fs.render() #doctest: +ELLIPSIS
...José... >>> fs.readonly = True >>> print fs.render() #doctest: +ELLIPSIS ...José... >>> session.rollback() """ def test_unicode_data(self): """ >>> fs = FieldSet(User, session=session) >>> data = UnicodeMultiDict(MultiDict({'User--name': 'José', 'User--email': 'j@jose.com', 'User--password': 'pwd'}), encoding='utf-8') >>> data.encoding 'utf-8' >>> fs.rebind(data=data) >>> fs.data is data True >>> print(fs.render()) # doctest: +ELLIPSIS
......
>>> data = UnicodeMultiDict(MultiDict({'name': 'José', 'email': 'j@jose.com', 'password': 'pwd'}), encoding='utf-8') >>> fs.rebind(data=data, with_prefix=False) >>> print(fs.render()) # doctest: +ELLIPSIS
......
>>> fs.rebind(data={'User--name': 'José', 'User--email': 'j@jose.com', 'User--password': 'pwd'}) >>> isinstance(fs.data, UnicodeMultiDict) True >>> print(fs.render()) # doctest: +ELLIPSIS
......
""" FormAlchemy-1.4.2/formalchemy/tests/test_validate.py000664 000765 000024 00000000775 11601323767 023061 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * def validate_empty(): """ >>> fs = FieldSet(bill) >>> fs.validate() Traceback (most recent call last): ... ValidationError: Cannot validate without binding data >>> fs.render() #doctest: +ELLIPSIS '
...
' """ def validate_no_field_in_data(): """ >>> fs = FieldSet(bill) >>> fs.rebind(data={}) >>> fs.validate() False >>> fs.render() #doctest: +ELLIPSIS '
...
' """ FormAlchemy-1.4.2/formalchemy/tests/test_validators.py000664 000765 000024 00000001261 11514360146 023422 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- from formalchemy.tests import * from formalchemy import validators def validator1(value, field): if not value: raise ValidationError('Must have a value') @validators.accepts_none def validator2(value, field): if not value: raise ValidationError('Must have a value') def accepts_none(): """ >>> fs = FieldSet(bill) >>> fs.configure(include=[fs.name.validate(validator1)]) >>> fs = fs.bind(data={'User-1-name':''}) >>> fs.validate() True >>> fs = FieldSet(bill) >>> fs.configure(include=[fs.name.validate(validator2)]) >>> fs = fs.bind(data={'User-1-name':''}) >>> fs.validate() False """ FormAlchemy-1.4.2/formalchemy/tests/data/genshi/000775 000765 000024 00000000000 11662023267 022033 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/tests/data/mako/000775 000765 000024 00000000000 11662023267 021505 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/tests/data/mako/fieldset.mako000664 000765 000024 00000000135 11514360146 024150 0ustar00gawelstaff000000 000000
    %for field in fieldset.render_fields.itervalues():
  • ${field.name}
  • %endfor
FormAlchemy-1.4.2/formalchemy/tests/data/genshi/fieldset.html000664 000765 000024 00000000177 11514360146 024521 0ustar00gawelstaff000000 000000
  • ${field.name}
FormAlchemy-1.4.2/formalchemy/paster_templates/pylons_fa/000775 000765 000024 00000000000 11662023267 024051 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/paster_templates/pylons_fa/+package+/000775 000765 000024 00000000000 11662023267 025572 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/paster_templates/pylons_fa/+package+/config/000775 000765 000024 00000000000 11662023267 027037 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/paster_templates/pylons_fa/+package+/controllers/000775 000765 000024 00000000000 11662023267 030140 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/paster_templates/pylons_fa/+package+/forms/000775 000765 000024 00000000000 11662023267 026720 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/paster_templates/pylons_fa/+package+/templates/000775 000765 000024 00000000000 11662023267 027570 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/paster_templates/pylons_fa/+package+/templates/forms/000775 000765 000024 00000000000 11662023267 030716 5ustar00gawelstaff000000 000000 formalchemy/paster_templates/pylons_fa/+package+/templates/forms/fieldset.mako_tmpl000664 000765 000024 00000001572 11542705570 034351 0ustar00gawelstaff000000 000000 FormAlchemy-1.4.2{{if template_engine == 'mako'}} # -*- coding: utf-8 -*- <% _ = F_ _focus_rendered = False %>\ % for error in fieldset.errors.get(None, []):
${_(error)}
% endfor % for field in fieldset.render_fields.itervalues(): % if field.requires_label:
${field.label_tag()|n} ${field.render()|n} % if 'instructions' in field.metadata: ${field.metadata['instructions']} % endif % for error in field.errors: ${_(error)} % endfor
% if (fieldset.focus == field or fieldset.focus is True) and not _focus_rendered: % if not field.is_readonly(): <% _focus_rendered = True %>\ % endif % endif % else: ${field.render()|n} % endif % endfor {{endif}} formalchemy/paster_templates/pylons_fa/+package+/templates/forms/fieldset_readonly.mako_tmpl000664 000765 000024 00000000441 11542733722 036240 0ustar00gawelstaff000000 000000 FormAlchemy-1.4.2{{if template_engine == 'mako'}} # -*- coding: utf-8 -*- %for field in fieldset.render_fields.itervalues(): %if field.requires_label: ${field.label()|h}: ${field.render_readonly()|n} %endif %endfor {{endif}} FormAlchemy-1.4.2/formalchemy/paster_templates/pylons_fa/+package+/templates/forms/grid.mako_tmpl000664 000765 000024 00000001161 11542707155 033551 0ustar00gawelstaff000000 000000 {{if template_engine == 'mako'}} # -*- coding: utf-8 -*- %for field in collection.render_fields.itervalues(): ${field.label()|h} %endfor %for i, row in enumerate(collection.rows): <% collection._set_active(row) %> <% row_errors = collection.get_errors(row) %> %for field in collection.render_fields.itervalues(): ${field.render()|n} %for error in row_errors.get(field, []): ${error} %endfor %endfor %endfor {{endif}} formalchemy/paster_templates/pylons_fa/+package+/templates/forms/grid_readonly.mako_tmpl000664 000765 000024 00000000722 11542706732 035371 0ustar00gawelstaff000000 000000 FormAlchemy-1.4.2{{if template_engine == 'mako'}} # -*- coding: utf-8 -*- %for field in collection.render_fields.itervalues(): ${field.label()|h} %endfor %for i, row in enumerate(collection.rows): <% collection._set_active(row) %> %for field in collection.render_fields.itervalues(): ${field.render_readonly()|n} %endfor %endfor {{endif}} formalchemy/paster_templates/pylons_fa/+package+/templates/forms/restfieldset.mako_tmpl000664 000765 000024 00000005543 11514360146 035244 0ustar00gawelstaff000000 000000 FormAlchemy-1.4.2{{if template_engine == 'mako'}} # -*- coding: utf-8 -*- <%! from formalchemy.ext.pylons.controller import model_url from pylons import url %> <%def name="h1(title, href=None)">

%if breadcrumb: %endif %if href: ${title.title()} %else: ${title.title()} %endif

<%def name="buttons()">

${F_('Cancel')}

${collection_name.title()}
%if isinstance(models, dict):

${F_('Models')}

%for name in sorted(models):

${name}

%endfor %elif is_grid: ${h1(model_name)}
${pager|n}
${fs.render()|n}

${F_('New')} ${model_name}

%else: ${h1(model_name, href=model_url(collection_name))} %if action == 'show': ${fs.render()|n}

${F_('Edit')}

%elif action == 'edit':
${fs.render()|n} ${buttons()}
%else:
${fs.render()|n} ${buttons()}
%endif %endif
{{endif}} FormAlchemy-1.4.2/formalchemy/paster_templates/pylons_fa/+package+/forms/__init__.py_tmpl000664 000765 000024 00000002240 11514360146 032057 0ustar00gawelstaff000000 000000 from pylons import config from {{package}} import model from {{package}}.lib.base import render from formalchemy import config as fa_config from formalchemy import templates from formalchemy import validators from formalchemy import fields from formalchemy import forms from formalchemy import tables from formalchemy.ext.fsblob import FileFieldRenderer from formalchemy.ext.fsblob import ImageFieldRenderer fa_config.encoding = 'utf-8' {{if template_engine == 'mako'}} class TemplateEngine(templates.TemplateEngine): def render(self, name, **kwargs): return render('/forms/%s.mako' % name, extra_vars=kwargs) fa_config.engine = TemplateEngine() {{else}} ## You can use this class to override the default template engine #class TemplateEngine(templates.TemplateEngine): # def render(self, name, **kwargs): # return render('/forms/%s.mako' % name, extra_vars=kwargs) #fa_config.engine = TemplateEngine() {{endif}} class FieldSet(forms.FieldSet): pass class Grid(tables.Grid): pass ## Initialize fieldsets #Foo = FieldSet(model.Foo) #Reflected = FieldSet(Reflected) ## Initialize grids #FooGrid = Grid(model.Foo) #ReflectedGrid = Grid(Reflected) FormAlchemy-1.4.2/formalchemy/paster_templates/pylons_fa/+package+/controllers/admin.py_tmpl000664 000765 000024 00000002107 11514360146 032632 0ustar00gawelstaff000000 000000 {{if admin_controller}} import logging from formalchemy.ext.pylons.controller import ModelsController from webhelpers.paginate import Page from {{package}}.lib.base import BaseController, render from {{package}} import model from {{package}} import forms from {{package}}.model import meta log = logging.getLogger(__name__) class AdminControllerBase(BaseController): model = model # where your SQLAlchemy mappers are forms = forms # module containing FormAlchemy fieldsets definitions def Session(self): # Session factory return meta.Session ## customize the query for a model listing # def get_page(self): # if self.model_name == 'Foo': # return Page(meta.Session.query(model.Foo).order_by(model.Foo.bar) # return super(AdminControllerBase, self).get_page() AdminController = ModelsController(AdminControllerBase, prefix_name='admin', member_name='model', collection_name='models', ) {{endif}} FormAlchemy-1.4.2/formalchemy/paster_templates/pylons_fa/+package+/config/routing.py_tmpl000664 000765 000024 00000002706 11514360146 032135 0ustar00gawelstaff000000 000000 """Routes configuration The more specific and detailed routes should be defined first so they may take precedent over the more generic routes. For more information refer to the routes manual at http://routes.groovie.org/docs/ """ from routes import Mapper def make_map(config): """Create, configure and return the routes Mapper""" map = Mapper(directory=config['pylons.paths']['controllers'], always_scan=config['debug']) map.minimization = False # The ErrorController route (handles 404/500 error pages); it should # likely stay at the top, ensuring it can always be resolved map.connect('/error/{action}', controller='error') map.connect('/error/{action}/{id}', controller='error') # CUSTOM ROUTES HERE {{if admin_controller}} # Map the /admin url to FA's AdminController # Map static files map.connect('fa_static', '/admin/_static/{path_info:.*}', controller='admin', action='static') # Index page map.connect('admin', '/admin', controller='admin', action='models') map.connect('formatted_admin', '/admin.json', controller='admin', action='models', format='json') # Models map.resource('model', 'models', path_prefix='/admin/{model_name}', controller='admin') {{endif}} {{if package == 'pylonsapp'}} # serve Owner Model as resource map.resource('owner', 'owners') {{endif}} map.connect('/{controller}/{action}') map.connect('/{controller}/{action}/{id}') return map FormAlchemy-1.4.2/formalchemy/i18n_resources/de/000775 000765 000024 00000000000 11662023267 021744 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/en/000775 000765 000024 00000000000 11662023267 021756 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/es/000775 000765 000024 00000000000 11662023267 021763 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/formalchemy.pot000664 000765 000024 00000010522 11600123411 024366 0ustar00gawelstaff000000 000000 # Translations template for FormAlchemy. # Copyright (C) 2011 ORGANIZATION # This file is distributed under the same license as the FormAlchemy project. # FIRST AUTHOR , 2011. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: FormAlchemy 1.3.9\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2011-06-21 16:13+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: formalchemy/fields.py:474 msgid "Remove" msgstr "" #: formalchemy/i18n.py:85 msgid "Year" msgstr "" #: formalchemy/i18n.py:86 msgid "Month" msgstr "" #: formalchemy/i18n.py:87 msgid "Day" msgstr "" #: formalchemy/i18n.py:88 msgid "month_01" msgstr "" #: formalchemy/i18n.py:89 msgid "month_02" msgstr "" #: formalchemy/i18n.py:90 msgid "month_03" msgstr "" #: formalchemy/i18n.py:91 msgid "month_04" msgstr "" #: formalchemy/i18n.py:92 msgid "month_05" msgstr "" #: formalchemy/i18n.py:93 msgid "month_06" msgstr "" #: formalchemy/i18n.py:94 msgid "month_07" msgstr "" #: formalchemy/i18n.py:95 msgid "month_08" msgstr "" #: formalchemy/i18n.py:96 msgid "month_09" msgstr "" #: formalchemy/i18n.py:97 msgid "month_10" msgstr "" #: formalchemy/i18n.py:98 msgid "month_11" msgstr "" #: formalchemy/i18n.py:99 msgid "month_12" msgstr "" #: formalchemy/validators.py:32 msgid "Please select a value" msgstr "" #: formalchemy/validators.py:32 msgid "Please enter a value" msgstr "" #: formalchemy/validators.py:49 msgid "Value is not an integer" msgstr "" #: formalchemy/validators.py:60 formalchemy/validators.py:72 msgid "Value is not a number" msgstr "" #: formalchemy/validators.py:95 msgid "Missing @ sign" msgstr "" #: formalchemy/validators.py:98 msgid "Control characters present" msgstr "" #: formalchemy/validators.py:100 msgid "Non-ASCII characters present" msgstr "" #: formalchemy/validators.py:104 msgid "Recipient must be non-empty" msgstr "" #: formalchemy/validators.py:106 msgid "Recipient must not end with '.'" msgstr "" #: formalchemy/validators.py:121 msgid "Unterminated quoted section in recipient" msgstr "" #: formalchemy/validators.py:124 msgid "Quoted section must be followed by '@' or '.'" msgstr "" #: formalchemy/validators.py:127 msgid "Reserved character present in recipient" msgstr "" #: formalchemy/validators.py:132 msgid "Domain must be non-empty" msgstr "" #: formalchemy/validators.py:134 msgid "Domain must not end with '.'" msgstr "" #: formalchemy/validators.py:136 msgid "Domain must not contain '..'" msgstr "" #: formalchemy/validators.py:138 msgid "Reserved character present in domain" msgstr "" #: formalchemy/validators.py:148 #, python-format msgid "Value must be at least %d characters long" msgstr "" #: formalchemy/validators.py:150 #, python-format msgid "Value must be no more than %d characters long" msgstr "" #: formalchemy/validators.py:165 msgid "Invalid input" msgstr "" #: formalchemy/ext/fsblob.py:26 #, python-format msgid "Invalid file extension. Must be %s, " msgstr "" #: formalchemy/ext/fsblob.py:33 #, python-format msgid "Invalid image file. Must be %s, " msgstr "" #: formalchemy/ext/pylons/admin.py:25 msgid "Add" msgstr "" #: formalchemy/ext/pylons/admin.py:26 msgid "Edit" msgstr "" #: formalchemy/ext/pylons/admin.py:27 msgid "New" msgstr "" #: formalchemy/ext/pylons/admin.py:28 msgid "Save" msgstr "" #: formalchemy/ext/pylons/admin.py:29 msgid "Delete" msgstr "" #: formalchemy/ext/pylons/admin.py:30 msgid "Cancel" msgstr "" #: formalchemy/ext/pylons/admin.py:31 msgid "Models" msgstr "" #: formalchemy/ext/pylons/admin.py:32 formalchemy/ext/pylons/admin.py:35 msgid "Existing objects" msgstr "" #: formalchemy/ext/pylons/admin.py:33 msgid "New object" msgstr "" #: formalchemy/ext/pylons/admin.py:34 msgid "Related types" msgstr "" #: formalchemy/ext/pylons/admin.py:36 msgid "Create form" msgstr "" #: formalchemy/ext/pylons/admin.py:81 msgid "edit" msgstr "" #: formalchemy/ext/pylons/admin.py:90 msgid "delete" msgstr "" #: formalchemy/ext/pylons/admin.py:201 #, python-format msgid "Created %s %s" msgstr "" #: formalchemy/ext/pylons/admin.py:204 #, python-format msgid "Modified %s %s" msgstr "" #: formalchemy/ext/pylons/admin.py:236 #, python-format msgid "Deleted %s %s" msgstr "" FormAlchemy-1.4.2/formalchemy/i18n_resources/fr/000775 000765 000024 00000000000 11662023267 021763 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/hu/000775 000765 000024 00000000000 11662023267 021770 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/pl/000775 000765 000024 00000000000 11662023267 021767 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/pt_BR/000775 000765 000024 00000000000 11662023267 022362 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/pt_BR/LC_MESSAGES/000775 000765 000024 00000000000 11662023267 024147 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/pt_BR/LC_MESSAGES/formalchemy.mo000664 000765 000024 00000006171 11662023267 027017 0ustar00gawelstaff000000 000000 6|}    ).$? d  -C_ $'(%)=-g  "    5 Q r y , )     $ ) . !: \ v %    & &6 ] !d   ' &     ! ) 1 9 A I Q Y a i q AddCancelControl characters presentCreate formCreated %s %sDayDeleteDeleted %s %sDomain must be non-emptyDomain must not contain '..'Domain must not end with '.'EditExisting objectsInvalid file extension. Must be %s, Invalid image file. Must be %s, Invalid inputMissing @ signModelsModified %s %sMonthNewNew objectNon-ASCII characters presentPlease enter a valuePlease select a valueQuoted section must be followed by '@' or '.'Recipient must be non-emptyRecipient must not end with '.'Related typesRemoveReserved character present in domainReserved character present in recipientSaveUnterminated quoted section in recipientValue is not a numberValue is not an integerValue must be at least %d characters longValue must be no more than %d characters longYeardeleteeditmonth_01month_02month_03month_04month_05month_06month_07month_08month_09month_10month_11month_12Project-Id-Version: FormAlchemy 1.3.9 Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2011-06-21 16:13+0200 PO-Revision-Date: 2011-08-01 20:45-0300 Last-Translator: Rodrigo Ferreira de Souza Language-Team: pt_BR Plural-Forms: nplurals=2; plural=(n > 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.6 AdicionarCancelarCaracteres de controle encontradosCriar formulárioCriado %s %sDiaApagarApagado %s %sCampo deve ser preenchidoCampo não pode conter '..'Campo não deve terminar com '.'EditarObjetos existentesExtensão de arquivo inválida. Deve ser %s,Arquivo de imagem inválido. Deve ser %s.Entrada inválidaFaltando o símbolo @ModelosModificado %s %sMêsNovoNovo objetoEncontrados caracteres não-ASCIIPor favor digite um valorPor favor selecione um valorCampo deve ser seguido por '@' ou '.'Campo deve ser preenchidoCampo não pode terminar com '.'Tipos relacionadosRemoverCaractere proibido encontrado no campoCaractere proibido encontrado no campoSalvarCampo com com aspas não fechadasValor não é um númeroValor não é um inteiroValor deve ter pelo menos %d caracteresValor deve ser menor que %d caracteresAnoapagareditarmês_01mês_02mês_03mês_04mês_05mês_06mês_07mês_09mês_09mês_10mês_11mês_12FormAlchemy-1.4.2/formalchemy/i18n_resources/pt_BR/LC_MESSAGES/formalchemy.po000664 000765 000024 00000012463 11621712601 027013 0ustar00gawelstaff000000 000000 # Portuguese (Brazil) translations for FormAlchemy. # Copyright (C) 2011 ORGANIZATION # This file is distributed under the same license as the FormAlchemy # project. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: FormAlchemy 1.3.9\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2011-06-21 16:13+0200\n" "PO-Revision-Date: 2011-08-01 20:45-0300\n" "Last-Translator: Rodrigo Ferreira de Souza \n" "Language-Team: pt_BR \n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" #: formalchemy/fields.py:474 msgid "Remove" msgstr "Remover" #: formalchemy/i18n.py:85 msgid "Year" msgstr "Ano" #: formalchemy/i18n.py:86 msgid "Month" msgstr "Mês" #: formalchemy/i18n.py:87 msgid "Day" msgstr "Dia" #: formalchemy/i18n.py:88 msgid "month_01" msgstr "mês_01" #: formalchemy/i18n.py:89 msgid "month_02" msgstr "mês_02" #: formalchemy/i18n.py:90 msgid "month_03" msgstr "mês_03" #: formalchemy/i18n.py:91 msgid "month_04" msgstr "mês_04" #: formalchemy/i18n.py:92 msgid "month_05" msgstr "mês_05" #: formalchemy/i18n.py:93 msgid "month_06" msgstr "mês_06" #: formalchemy/i18n.py:94 msgid "month_07" msgstr "mês_07" #: formalchemy/i18n.py:95 msgid "month_08" msgstr "mês_09" #: formalchemy/i18n.py:96 msgid "month_09" msgstr "mês_09" #: formalchemy/i18n.py:97 msgid "month_10" msgstr "mês_10" #: formalchemy/i18n.py:98 msgid "month_11" msgstr "mês_11" #: formalchemy/i18n.py:99 msgid "month_12" msgstr "mês_12" #: formalchemy/validators.py:32 msgid "Please select a value" msgstr "Por favor selecione um valor" #: formalchemy/validators.py:32 msgid "Please enter a value" msgstr "Por favor digite um valor" #: formalchemy/validators.py:49 msgid "Value is not an integer" msgstr "Valor não é um inteiro" #: formalchemy/validators.py:60 #: formalchemy/validators.py:72 msgid "Value is not a number" msgstr "Valor não é um número" #: formalchemy/validators.py:95 msgid "Missing @ sign" msgstr "Faltando o símbolo @" #: formalchemy/validators.py:98 msgid "Control characters present" msgstr "Caracteres de controle encontrados" #: formalchemy/validators.py:100 msgid "Non-ASCII characters present" msgstr "Encontrados caracteres não-ASCII" #: formalchemy/validators.py:104 msgid "Recipient must be non-empty" msgstr "Campo deve ser preenchido" #: formalchemy/validators.py:106 msgid "Recipient must not end with '.'" msgstr "Campo não pode terminar com '.'" #: formalchemy/validators.py:121 msgid "Unterminated quoted section in recipient" msgstr "Campo com com aspas não fechadas" #: formalchemy/validators.py:124 msgid "Quoted section must be followed by '@' or '.'" msgstr "Campo deve ser seguido por '@' ou '.'" #: formalchemy/validators.py:127 msgid "Reserved character present in recipient" msgstr "Caractere proibido encontrado no campo" #: formalchemy/validators.py:132 msgid "Domain must be non-empty" msgstr "Campo deve ser preenchido" #: formalchemy/validators.py:134 msgid "Domain must not end with '.'" msgstr "Campo não deve terminar com '.'" #: formalchemy/validators.py:136 msgid "Domain must not contain '..'" msgstr "Campo não pode conter '..'" #: formalchemy/validators.py:138 msgid "Reserved character present in domain" msgstr "Caractere proibido encontrado no campo" #: formalchemy/validators.py:148 #, python-format msgid "Value must be at least %d characters long" msgstr "Valor deve ter pelo menos %d caracteres" #: formalchemy/validators.py:150 #, python-format msgid "Value must be no more than %d characters long" msgstr "Valor deve ser menor que %d caracteres" #: formalchemy/validators.py:165 msgid "Invalid input" msgstr "Entrada inválida" #: formalchemy/ext/fsblob.py:26 #, python-format msgid "Invalid file extension. Must be %s, " msgstr "Extensão de arquivo inválida. Deve ser %s," #: formalchemy/ext/fsblob.py:33 #, python-format msgid "Invalid image file. Must be %s, " msgstr "Arquivo de imagem inválido. Deve ser %s." #: formalchemy/ext/pylons/admin.py:25 msgid "Add" msgstr "Adicionar" #: formalchemy/ext/pylons/admin.py:26 msgid "Edit" msgstr "Editar" #: formalchemy/ext/pylons/admin.py:27 msgid "New" msgstr "Novo" #: formalchemy/ext/pylons/admin.py:28 msgid "Save" msgstr "Salvar" #: formalchemy/ext/pylons/admin.py:29 msgid "Delete" msgstr "Apagar" #: formalchemy/ext/pylons/admin.py:30 msgid "Cancel" msgstr "Cancelar" #: formalchemy/ext/pylons/admin.py:31 msgid "Models" msgstr "Modelos" #: formalchemy/ext/pylons/admin.py:32 #: formalchemy/ext/pylons/admin.py:35 msgid "Existing objects" msgstr "Objetos existentes" #: formalchemy/ext/pylons/admin.py:33 msgid "New object" msgstr "Novo objeto" #: formalchemy/ext/pylons/admin.py:34 msgid "Related types" msgstr "Tipos relacionados" #: formalchemy/ext/pylons/admin.py:36 msgid "Create form" msgstr "Criar formulário" #: formalchemy/ext/pylons/admin.py:81 msgid "edit" msgstr "editar" #: formalchemy/ext/pylons/admin.py:90 msgid "delete" msgstr "apagar" #: formalchemy/ext/pylons/admin.py:201 #, python-format msgid "Created %s %s" msgstr "Criado %s %s" #: formalchemy/ext/pylons/admin.py:204 #, python-format msgid "Modified %s %s" msgstr "Modificado %s %s" #: formalchemy/ext/pylons/admin.py:236 #, python-format msgid "Deleted %s %s" msgstr "Apagado %s %s" FormAlchemy-1.4.2/formalchemy/i18n_resources/pl/LC_MESSAGES/000775 000765 000024 00000000000 11662023267 023554 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/pl/LC_MESSAGES/formalchemy.mo000664 000765 000024 00000006232 11662023267 026422 0ustar00gawelstaff000000 000000 6|}    ).$? d  -C_ $'(%)=-g     - ; &[   1 -    ' ; D I U g  +  (    ; Y $`   1 2 " & , 3 < A H R V _ f p z  AddCancelControl characters presentCreate formCreated %s %sDayDeleteDeleted %s %sDomain must be non-emptyDomain must not contain '..'Domain must not end with '.'EditExisting objectsInvalid file extension. Must be %s, Invalid image file. Must be %s, Invalid inputMissing @ signModelsModified %s %sMonthNewNew objectNon-ASCII characters presentPlease enter a valuePlease select a valueQuoted section must be followed by '@' or '.'Recipient must be non-emptyRecipient must not end with '.'Related typesRemoveReserved character present in domainReserved character present in recipientSaveUnterminated quoted section in recipientValue is not a numberValue is not an integerValue must be at least %d characters longValue must be no more than %d characters longYeardeleteeditmonth_01month_02month_03month_04month_05month_06month_07month_08month_09month_10month_11month_12Project-Id-Version: FormAlchemy 1.3.1 Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2009-11-08 12:02+0100 PO-Revision-Date: 2011-06-21 16:13+0200 Last-Translator: FULL NAME Language-Team: pl Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.6 DodajAnulujNiepoprawne znakiStwórz formularzStworzono %s %sDzieńUsuńUsunięto %s %sPodaj domenęDomena nie może zawierać '..'Domena nie może kończyć się na '.'EdytujIstniejące obiektyNiepoprawne rozszerzenie pliku. Powinno być %s, Niepoprawny plik z obrazem. Powinno być %s, Niepoprawna wartośćBrak znaku @ModeleZmodyfikowano %s %sMiesiącNowyNowy obiektNiepoprawne znakiProszę wprowadzić wartośćProszę wybrać wartośćZa cudzysłowem musi być znak '@' lub '.' Nie podano odbiorcyOdbiorca nie może kończyć się na '.'Powiązane typyUsuńZarezerwowany znak w domenieZarezerwowany znak w odbiorcyZapiszNiezamknięty cudzysłów w odbiorcyWartość musi być liczbąWartość musi być liczbąWartość nie może być krótsza niż %d znakówWartość nie może być dłuższa niż %d znakówRokusuńedytujstyczeńlutymarzeckwiecieńmajczerwieclipiecsierpieńwrzesieńpaździerniklistopadgrudzieńFormAlchemy-1.4.2/formalchemy/i18n_resources/pl/LC_MESSAGES/formalchemy.po000664 000765 000024 00000012504 11600123411 026404 0ustar00gawelstaff000000 000000 # Polish translations for FormAlchemy. # Copyright (C) 2009 ORGANIZATION # This file is distributed under the same license as the FormAlchemy # project. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: FormAlchemy 1.3.1\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2009-11-08 12:02+0100\n" "PO-Revision-Date: 2011-06-21 16:13+0200\n" "Last-Translator: FULL NAME \n" "Language-Team: pl \n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && " "(n%100<10 || n%100>=20) ? 1 : 2)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: formalchemy/fields.py:474 msgid "Remove" msgstr "Usuń" #: formalchemy/i18n.py:85 msgid "Year" msgstr "Rok" #: formalchemy/i18n.py:86 msgid "Month" msgstr "Miesiąc" #: formalchemy/i18n.py:87 msgid "Day" msgstr "Dzień" #: formalchemy/i18n.py:88 msgid "month_01" msgstr "styczeń" #: formalchemy/i18n.py:89 msgid "month_02" msgstr "luty" #: formalchemy/i18n.py:90 msgid "month_03" msgstr "marzec" #: formalchemy/i18n.py:91 msgid "month_04" msgstr "kwiecień" #: formalchemy/i18n.py:92 msgid "month_05" msgstr "maj" #: formalchemy/i18n.py:93 msgid "month_06" msgstr "czerwiec" #: formalchemy/i18n.py:94 msgid "month_07" msgstr "lipiec" #: formalchemy/i18n.py:95 msgid "month_08" msgstr "sierpień" #: formalchemy/i18n.py:96 msgid "month_09" msgstr "wrzesień" #: formalchemy/i18n.py:97 msgid "month_10" msgstr "październik" #: formalchemy/i18n.py:98 msgid "month_11" msgstr "listopad" #: formalchemy/i18n.py:99 msgid "month_12" msgstr "grudzień" #: formalchemy/validators.py:32 msgid "Please select a value" msgstr "Proszę wybrać wartość" #: formalchemy/validators.py:32 msgid "Please enter a value" msgstr "Proszę wprowadzić wartość" #: formalchemy/validators.py:49 msgid "Value is not an integer" msgstr "Wartość musi być liczbą" #: formalchemy/validators.py:60 formalchemy/validators.py:72 msgid "Value is not a number" msgstr "Wartość musi być liczbą" #: formalchemy/validators.py:95 msgid "Missing @ sign" msgstr "Brak znaku @" #: formalchemy/validators.py:98 msgid "Control characters present" msgstr "Niepoprawne znaki" #: formalchemy/validators.py:100 msgid "Non-ASCII characters present" msgstr "Niepoprawne znaki" #: formalchemy/validators.py:104 msgid "Recipient must be non-empty" msgstr "Nie podano odbiorcy" #: formalchemy/validators.py:106 msgid "Recipient must not end with '.'" msgstr "Odbiorca nie może kończyć się na '.'" #: formalchemy/validators.py:121 msgid "Unterminated quoted section in recipient" msgstr "Niezamknięty cudzysłów w odbiorcy" #: formalchemy/validators.py:124 msgid "Quoted section must be followed by '@' or '.'" msgstr "Za cudzysłowem musi być znak '@' lub '.' " #: formalchemy/validators.py:127 msgid "Reserved character present in recipient" msgstr "Zarezerwowany znak w odbiorcy" #: formalchemy/validators.py:132 msgid "Domain must be non-empty" msgstr "Podaj domenę" #: formalchemy/validators.py:134 msgid "Domain must not end with '.'" msgstr "Domena nie może kończyć się na '.'" #: formalchemy/validators.py:136 msgid "Domain must not contain '..'" msgstr "Domena nie może zawierać '..'" #: formalchemy/validators.py:138 msgid "Reserved character present in domain" msgstr "Zarezerwowany znak w domenie" #: formalchemy/validators.py:148 #, python-format msgid "Value must be at least %d characters long" msgstr "Wartość nie może być krótsza niż %d znaków" #: formalchemy/validators.py:150 #, python-format msgid "Value must be no more than %d characters long" msgstr "Wartość nie może być dłuższa niż %d znaków" #: formalchemy/validators.py:165 msgid "Invalid input" msgstr "Niepoprawna wartość" #: formalchemy/ext/fsblob.py:26 #, python-format msgid "Invalid file extension. Must be %s, " msgstr "Niepoprawne rozszerzenie pliku. Powinno być %s, " #: formalchemy/ext/fsblob.py:33 #, python-format msgid "Invalid image file. Must be %s, " msgstr "Niepoprawny plik z obrazem. Powinno być %s, " #: formalchemy/ext/pylons/admin.py:25 msgid "Add" msgstr "Dodaj" #: formalchemy/ext/pylons/admin.py:26 msgid "Edit" msgstr "Edytuj" #: formalchemy/ext/pylons/admin.py:27 msgid "New" msgstr "Nowy" #: formalchemy/ext/pylons/admin.py:28 msgid "Save" msgstr "Zapisz" #: formalchemy/ext/pylons/admin.py:29 msgid "Delete" msgstr "Usuń" #: formalchemy/ext/pylons/admin.py:30 msgid "Cancel" msgstr "Anuluj" #: formalchemy/ext/pylons/admin.py:31 msgid "Models" msgstr "Modele" #: formalchemy/ext/pylons/admin.py:32 formalchemy/ext/pylons/admin.py:35 msgid "Existing objects" msgstr "Istniejące obiekty" #: formalchemy/ext/pylons/admin.py:33 msgid "New object" msgstr "Nowy obiekt" #: formalchemy/ext/pylons/admin.py:34 msgid "Related types" msgstr "Powiązane typy" #: formalchemy/ext/pylons/admin.py:36 msgid "Create form" msgstr "Stwórz formularz" #: formalchemy/ext/pylons/admin.py:81 msgid "edit" msgstr "edytuj" #: formalchemy/ext/pylons/admin.py:90 msgid "delete" msgstr "usuń" #: formalchemy/ext/pylons/admin.py:201 #, python-format msgid "Created %s %s" msgstr "Stworzono %s %s" #: formalchemy/ext/pylons/admin.py:204 #, python-format msgid "Modified %s %s" msgstr "Zmodyfikowano %s %s" #: formalchemy/ext/pylons/admin.py:236 #, python-format msgid "Deleted %s %s" msgstr "Usunięto %s %s" FormAlchemy-1.4.2/formalchemy/i18n_resources/hu/LC_MESSAGES/000775 000765 000024 00000000000 11662023267 023555 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/hu/LC_MESSAGES/formalchemy.mo000664 000765 000024 00000005346 11662023266 026427 0ustar00gawelstaff000000 000000 5lpw   $. S t -2N n|$'(),-V  1FVZcs!  &# 1@PYko s/! ; S \ z  -   1 -4 b f o |          AddCancelControl characters presentCreate formCreated %s %sDayDeleteDeleted %s %sDomain must be non-emptyDomain must not contain '..'Domain must not end with '.'EditExisting objectsInvalid file extension. Must be %s, Invalid image file. Must be %s, Invalid inputMissing @ signModelsModified %s %sMonthNewNew objectNon-ASCII characters presentPlease enter a valuePlease select a valueQuoted section must be followed by '@' or '.'Recipient must be non-emptyRecipient must not end with '.'Related typesRemoveReserved character present in domainReserved character present in recipientSaveUnterminated quoted section in recipientValue is not a numberValue is not an integerValue must be at least %d characters longValue must be no more than %d characters longYeardeleteeditmonth_01month_02month_03month_04month_05month_06month_07month_08month_09month_10month_11month_12HozzáadásMégseKontrol karakterek is vannakŰrlap létrehozásaKészült %s %sNapTörlésTörölve %s %sA domain nem lehet üresA domain nem tartalmazhat '..'-otA domain nem végződhet '.'-raMódosításObjektumokHibás kiterjesztés. %s kell legyen, Hibás kép fájl. %s kell legyen, Hibás értékHiányzó @ jelModellekMódosítva %s %sHóÚjÚj objektumNem ASCII karakterek is vannakRögzíts egy értéketVálassz egy értéketAz első rész után '@' vagy '.' karakter kellA címzett nem lehet üresA címzett nem végződhet '.'-raÖsszetartozó típusokTörlésFoglalt karakter a domain-benFoglalt karakterek a címzettbenMentésMeghatározhatatlan első rész a címzettbenAz érték nem számAz érték nem egész számAz értéknek legalább %d hosszúnak kell lennieAz érték nem lehet hosszabb %d karakternélÉvtörlésmódosításJanuárFebruárMárciusÁprilisMájusJúniusJúliusAugusztusSzeptemberOktóberNovemberDecemberFormAlchemy-1.4.2/formalchemy/i18n_resources/hu/LC_MESSAGES/formalchemy.po000664 000765 000024 00000012500 11600123411 026401 0ustar00gawelstaff000000 000000 # Hungarian translations for FormAlchemy. # Copyright (C) 2009 ORGANIZATION # This file is distributed under the same license as the FormAlchemy # project. # FIRST AUTHOR , 2009. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: FormAlchemy 1.2\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2009-10-11 17:10+0200\n" "PO-Revision-Date: 2011-06-21 16:13+0200\n" "Last-Translator: Bajusz Tamás \n" "Language-Team: Hungarian \n" "Plural-Forms: nplurals=2; plural=(n!=1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: formalchemy/fields.py:474 msgid "Remove" msgstr "Törlés" #: formalchemy/i18n.py:85 msgid "Year" msgstr "Év" #: formalchemy/i18n.py:86 msgid "Month" msgstr "Hó" #: formalchemy/i18n.py:87 msgid "Day" msgstr "Nap" #: formalchemy/i18n.py:88 msgid "month_01" msgstr "Január" #: formalchemy/i18n.py:89 msgid "month_02" msgstr "Február" #: formalchemy/i18n.py:90 msgid "month_03" msgstr "Március" #: formalchemy/i18n.py:91 msgid "month_04" msgstr "Április" #: formalchemy/i18n.py:92 msgid "month_05" msgstr "Május" #: formalchemy/i18n.py:93 msgid "month_06" msgstr "Június" #: formalchemy/i18n.py:94 msgid "month_07" msgstr "Július" #: formalchemy/i18n.py:95 msgid "month_08" msgstr "Augusztus" #: formalchemy/i18n.py:96 msgid "month_09" msgstr "Szeptember" #: formalchemy/i18n.py:97 msgid "month_10" msgstr "Október" #: formalchemy/i18n.py:98 msgid "month_11" msgstr "November" #: formalchemy/i18n.py:99 msgid "month_12" msgstr "December" #: formalchemy/validators.py:32 msgid "Please select a value" msgstr "Válassz egy értéket" #: formalchemy/validators.py:32 msgid "Please enter a value" msgstr "Rögzíts egy értéket" #: formalchemy/validators.py:49 msgid "Value is not an integer" msgstr "Az érték nem egész szám" #: formalchemy/validators.py:60 formalchemy/validators.py:72 msgid "Value is not a number" msgstr "Az érték nem szám" #: formalchemy/validators.py:95 msgid "Missing @ sign" msgstr "Hiányzó @ jel" #: formalchemy/validators.py:98 msgid "Control characters present" msgstr "Kontrol karakterek is vannak" #: formalchemy/validators.py:100 msgid "Non-ASCII characters present" msgstr "Nem ASCII karakterek is vannak" #: formalchemy/validators.py:104 msgid "Recipient must be non-empty" msgstr "A címzett nem lehet üres" #: formalchemy/validators.py:106 msgid "Recipient must not end with '.'" msgstr "A címzett nem végződhet '.'-ra" #: formalchemy/validators.py:121 msgid "Unterminated quoted section in recipient" msgstr "Meghatározhatatlan első rész a címzettben" #: formalchemy/validators.py:124 msgid "Quoted section must be followed by '@' or '.'" msgstr "Az első rész után '@' vagy '.' karakter kell" #: formalchemy/validators.py:127 msgid "Reserved character present in recipient" msgstr "Foglalt karakterek a címzettben" #: formalchemy/validators.py:132 msgid "Domain must be non-empty" msgstr "A domain nem lehet üres" #: formalchemy/validators.py:134 msgid "Domain must not end with '.'" msgstr "A domain nem végződhet '.'-ra" #: formalchemy/validators.py:136 msgid "Domain must not contain '..'" msgstr "A domain nem tartalmazhat '..'-ot" #: formalchemy/validators.py:138 msgid "Reserved character present in domain" msgstr "Foglalt karakter a domain-ben" #: formalchemy/validators.py:148 #, python-format msgid "Value must be at least %d characters long" msgstr "Az értéknek legalább %d hosszúnak kell lennie" #: formalchemy/validators.py:150 #, python-format msgid "Value must be no more than %d characters long" msgstr "Az érték nem lehet hosszabb %d karakternél" #: formalchemy/validators.py:165 msgid "Invalid input" msgstr "Hibás érték" #: formalchemy/ext/fsblob.py:26 #, python-format msgid "Invalid file extension. Must be %s, " msgstr "Hibás kiterjesztés. %s kell legyen, " #: formalchemy/ext/fsblob.py:33 #, python-format msgid "Invalid image file. Must be %s, " msgstr "Hibás kép fájl. %s kell legyen, " #: formalchemy/ext/pylons/admin.py:25 msgid "Add" msgstr "Hozzáadás" #: formalchemy/ext/pylons/admin.py:26 msgid "Edit" msgstr "Módosítás" #: formalchemy/ext/pylons/admin.py:27 msgid "New" msgstr "Új" #: formalchemy/ext/pylons/admin.py:28 msgid "Save" msgstr "Mentés" #: formalchemy/ext/pylons/admin.py:29 msgid "Delete" msgstr "Törlés" #: formalchemy/ext/pylons/admin.py:30 msgid "Cancel" msgstr "Mégse" #: formalchemy/ext/pylons/admin.py:31 msgid "Models" msgstr "Modellek" #: formalchemy/ext/pylons/admin.py:32 formalchemy/ext/pylons/admin.py:35 msgid "Existing objects" msgstr "Objektumok" #: formalchemy/ext/pylons/admin.py:33 msgid "New object" msgstr "Új objektum" #: formalchemy/ext/pylons/admin.py:34 msgid "Related types" msgstr "Összetartozó típusok" #: formalchemy/ext/pylons/admin.py:36 msgid "Create form" msgstr "Űrlap létrehozása" #: formalchemy/ext/pylons/admin.py:81 msgid "edit" msgstr "módosítás" #: formalchemy/ext/pylons/admin.py:90 msgid "delete" msgstr "törlés" #: formalchemy/ext/pylons/admin.py:201 #, python-format msgid "Created %s %s" msgstr "Készült %s %s" #: formalchemy/ext/pylons/admin.py:204 #, python-format msgid "Modified %s %s" msgstr "Módosítva %s %s" #: formalchemy/ext/pylons/admin.py:236 #, python-format msgid "Deleted %s %s" msgstr "Törölve %s %s" FormAlchemy-1.4.2/formalchemy/i18n_resources/fr/LC_MESSAGES/000775 000765 000024 00000000000 11662023267 023550 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/fr/LC_MESSAGES/formalchemy.mo000664 000765 000024 00000006426 11662023267 026423 0ustar00gawelstaff000000 000000 6|}    ).$? d  -C_ $'(%)=-g /     . L $m   , '   0 9 H M U 7b  # <  &8 _ q 8{ 6    $ /C 1s             AddCancelControl characters presentCreate formCreated %s %sDayDeleteDeleted %s %sDomain must be non-emptyDomain must not contain '..'Domain must not end with '.'EditExisting objectsInvalid file extension. Must be %s, Invalid image file. Must be %s, Invalid inputMissing @ signModelsModified %s %sMonthNewNew objectNon-ASCII characters presentPlease enter a valuePlease select a valueQuoted section must be followed by '@' or '.'Recipient must be non-emptyRecipient must not end with '.'Related typesRemoveReserved character present in domainReserved character present in recipientSaveUnterminated quoted section in recipientValue is not a numberValue is not an integerValue must be at least %d characters longValue must be no more than %d characters longYeardeleteeditmonth_01month_02month_03month_04month_05month_06month_07month_08month_09month_10month_11month_12Project-Id-Version: FormAlchemy 0.5.1 Report-Msgid-Bugs-To: formalchemy@googlegroups.com POT-Creation-Date: 2008-08-27 12:33+0200 PO-Revision-Date: 2011-06-21 16:13+0200 Last-Translator: gael@gawel.org Language-Team: fr Plural-Forms: nplurals=2; plural=(n > 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.6 AjouterAnnulerL'adresse contient des caractères de contrôleFormulaire de création%s %s créeJourSupprimer%s %s suppriméLe domaine ne peut être videLe domaine ne peut contenir '..'Le domaine ne doit pas finir par '.'ÉditerObjets existantExtension de fichier invalide. Doit être %sImage invalide. Doit être au format %sValeur non valideL'adresse doit contenir un '@'Modèles%s %s modifiéMoisNouveauNouvel objetLa valeur ne doit pas contenir de caractères non ASCIIVous devez saisir une valeurVous devez sélectionner une valeurUne section entre guillemets doit être suivie de '@' ou '.'Vous devez renseigner l'adresseL'adresse ne doit pas finir par un '.'Types en relationSupprimerDes caractères réservés sont présent dans le domaineDes caractères réservé sont présent dans l'adresseSauverGuillemet non closLa valeur doit être un nombreLa valeur doit être un entierLa valeur doit contenir au moins %d caractèresLa valeur ne peut contenir plus de %d caractèresAnnéesupprimeréditerJanvierFévrierMarsAvrilMaiJuinJuilletAoütSeptembreOctobreNovembreDécembreFormAlchemy-1.4.2/formalchemy/i18n_resources/fr/LC_MESSAGES/formalchemy.po000664 000765 000024 00000012675 11600123411 026411 0ustar00gawelstaff000000 000000 # French translations for FormAlchemy. # Copyright (C) 2008 ORGANIZATION # This file is distributed under the same license as the FormAlchemy # project. # FIRST AUTHOR , 2008. # msgid "" msgstr "" "Project-Id-Version: FormAlchemy 0.5.1\n" "Report-Msgid-Bugs-To: formalchemy@googlegroups.com\n" "POT-Creation-Date: 2008-08-27 12:33+0200\n" "PO-Revision-Date: 2011-06-21 16:13+0200\n" "Last-Translator: gael@gawel.org\n" "Language-Team: fr \n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: formalchemy/fields.py:474 msgid "Remove" msgstr "Supprimer" #: formalchemy/i18n.py:85 msgid "Year" msgstr "Année" #: formalchemy/i18n.py:86 msgid "Month" msgstr "Mois" #: formalchemy/i18n.py:87 msgid "Day" msgstr "Jour" #: formalchemy/i18n.py:88 msgid "month_01" msgstr "Janvier" #: formalchemy/i18n.py:89 msgid "month_02" msgstr "Février" #: formalchemy/i18n.py:90 msgid "month_03" msgstr "Mars" #: formalchemy/i18n.py:91 msgid "month_04" msgstr "Avril" #: formalchemy/i18n.py:92 msgid "month_05" msgstr "Mai" #: formalchemy/i18n.py:93 msgid "month_06" msgstr "Juin" #: formalchemy/i18n.py:94 msgid "month_07" msgstr "Juillet" #: formalchemy/i18n.py:95 msgid "month_08" msgstr "Aoüt" #: formalchemy/i18n.py:96 msgid "month_09" msgstr "Septembre" #: formalchemy/i18n.py:97 msgid "month_10" msgstr "Octobre" #: formalchemy/i18n.py:98 msgid "month_11" msgstr "Novembre" #: formalchemy/i18n.py:99 msgid "month_12" msgstr "Décembre" #: formalchemy/validators.py:32 msgid "Please select a value" msgstr "Vous devez sélectionner une valeur" #: formalchemy/validators.py:32 msgid "Please enter a value" msgstr "Vous devez saisir une valeur" #: formalchemy/validators.py:49 msgid "Value is not an integer" msgstr "La valeur doit être un entier" #: formalchemy/validators.py:60 formalchemy/validators.py:72 msgid "Value is not a number" msgstr "La valeur doit être un nombre" #: formalchemy/validators.py:95 msgid "Missing @ sign" msgstr "L'adresse doit contenir un '@'" #: formalchemy/validators.py:98 msgid "Control characters present" msgstr "L'adresse contient des caractères de contrôle" #: formalchemy/validators.py:100 msgid "Non-ASCII characters present" msgstr "La valeur ne doit pas contenir de caractères non ASCII" #: formalchemy/validators.py:104 msgid "Recipient must be non-empty" msgstr "Vous devez renseigner l'adresse" #: formalchemy/validators.py:106 msgid "Recipient must not end with '.'" msgstr "L'adresse ne doit pas finir par un '.'" #: formalchemy/validators.py:121 msgid "Unterminated quoted section in recipient" msgstr "Guillemet non clos" #: formalchemy/validators.py:124 msgid "Quoted section must be followed by '@' or '.'" msgstr "Une section entre guillemets doit être suivie de '@' ou '.'" #: formalchemy/validators.py:127 msgid "Reserved character present in recipient" msgstr "Des caractères réservé sont présent dans l'adresse" #: formalchemy/validators.py:132 msgid "Domain must be non-empty" msgstr "Le domaine ne peut être vide" #: formalchemy/validators.py:134 msgid "Domain must not end with '.'" msgstr "Le domaine ne doit pas finir par '.'" #: formalchemy/validators.py:136 msgid "Domain must not contain '..'" msgstr "Le domaine ne peut contenir '..'" #: formalchemy/validators.py:138 msgid "Reserved character present in domain" msgstr "Des caractères réservés sont présent dans le domaine" #: formalchemy/validators.py:148 #, python-format msgid "Value must be at least %d characters long" msgstr "La valeur doit contenir au moins %d caractères" #: formalchemy/validators.py:150 #, python-format msgid "Value must be no more than %d characters long" msgstr "La valeur ne peut contenir plus de %d caractères" #: formalchemy/validators.py:165 msgid "Invalid input" msgstr "Valeur non valide" #: formalchemy/ext/fsblob.py:26 #, python-format msgid "Invalid file extension. Must be %s, " msgstr "Extension de fichier invalide. Doit être %s" #: formalchemy/ext/fsblob.py:33 #, python-format msgid "Invalid image file. Must be %s, " msgstr "Image invalide. Doit être au format %s" #: formalchemy/ext/pylons/admin.py:25 msgid "Add" msgstr "Ajouter" #: formalchemy/ext/pylons/admin.py:26 msgid "Edit" msgstr "Éditer" #: formalchemy/ext/pylons/admin.py:27 msgid "New" msgstr "Nouveau" #: formalchemy/ext/pylons/admin.py:28 msgid "Save" msgstr "Sauver" #: formalchemy/ext/pylons/admin.py:29 msgid "Delete" msgstr "Supprimer" #: formalchemy/ext/pylons/admin.py:30 msgid "Cancel" msgstr "Annuler" #: formalchemy/ext/pylons/admin.py:31 msgid "Models" msgstr "Modèles" #: formalchemy/ext/pylons/admin.py:32 formalchemy/ext/pylons/admin.py:35 msgid "Existing objects" msgstr "Objets existant" #: formalchemy/ext/pylons/admin.py:33 msgid "New object" msgstr "Nouvel objet" #: formalchemy/ext/pylons/admin.py:34 msgid "Related types" msgstr "Types en relation" #: formalchemy/ext/pylons/admin.py:36 msgid "Create form" msgstr "Formulaire de création" #: formalchemy/ext/pylons/admin.py:81 msgid "edit" msgstr "éditer" #: formalchemy/ext/pylons/admin.py:90 msgid "delete" msgstr "supprimer" #: formalchemy/ext/pylons/admin.py:201 #, python-format msgid "Created %s %s" msgstr "%s %s crée" #: formalchemy/ext/pylons/admin.py:204 #, python-format msgid "Modified %s %s" msgstr "%s %s modifié" #: formalchemy/ext/pylons/admin.py:236 #, python-format msgid "Deleted %s %s" msgstr "%s %s supprimé" FormAlchemy-1.4.2/formalchemy/i18n_resources/es/LC_MESSAGES/000775 000765 000024 00000000000 11662023267 023550 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/es/LC_MESSAGES/formalchemy.mo000664 000765 000024 00000006271 11662023267 026421 0ustar00gawelstaff000000 000000 6|}    ).$? d  -C_ $'(%)=-g   !- $O t { / +     * . 4 A Y x 2 % )  ( (1 -Z  /   * - K P Y ` f n t z     AddCancelControl characters presentCreate formCreated %s %sDayDeleteDeleted %s %sDomain must be non-emptyDomain must not contain '..'Domain must not end with '.'EditExisting objectsInvalid file extension. Must be %s, Invalid image file. Must be %s, Invalid inputMissing @ signModelsModified %s %sMonthNewNew objectNon-ASCII characters presentPlease enter a valuePlease select a valueQuoted section must be followed by '@' or '.'Recipient must be non-emptyRecipient must not end with '.'Related typesRemoveReserved character present in domainReserved character present in recipientSaveUnterminated quoted section in recipientValue is not a numberValue is not an integerValue must be at least %d characters longValue must be no more than %d characters longYeardeleteeditmonth_01month_02month_03month_04month_05month_06month_07month_08month_09month_10month_11month_12Project-Id-Version: FormAlchemy 0.5.1 Report-Msgid-Bugs-To: robarago@gmail.com POT-Creation-Date: 2008-08-27 17:18+0200 PO-Revision-Date: 2011-06-21 16:13+0200 Last-Translator: Roberto Aragón Language-Team: es Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.6 AgrerarCancelarHay caracteres de controlCrear formularioCreado %s %sDíaEliminarEliminado %s %sEl dominio no puede estar vacíoEl dominio no puede contener '..'El dominio no puede terminar con '.'EditarObjetos existentesExtensión de fichero no válida. Debe ser %s, Fichero de imagen no válido. Debe ser %s, Entrada no válidaFalta el símbolo @ModelosModificado %s %sMesNuevoNuevo objetoHay caracteres no ASCIIPor favor, introduzca un valorPor favor, elija un valorLa sección citada debe estar seguida de '@' o '.'El destinatario no puede estar vacíoEl destinatario no puede terminar con '.'Tipos relacionadosEliminarHay un carácter reservado en el dominioHay un carácter reservado en el destinatarioSalvarSección citada no terminada en el destinatarioEl valor no es un númeroEl valor no es un enteroEl valor debe tener al menos %d caracteresEl valor no puede tener más de %d caracteresAñoeliminareditarEneroFebreroMarzoAbrilMayoJunioJulioAgostoSeptiembreOctubreNoviembreDiciembreFormAlchemy-1.4.2/formalchemy/i18n_resources/es/LC_MESSAGES/formalchemy.po000664 000765 000024 00000012541 11600123411 026401 0ustar00gawelstaff000000 000000 # Spanish translations for FormAlchemy. # Copyright (C) 2008 ORGANIZATION # This file is distributed under the same license as the FormAlchemy # project. # FIRST AUTHOR , 2008. # msgid "" msgstr "" "Project-Id-Version: FormAlchemy 0.5.1\n" "Report-Msgid-Bugs-To: robarago@gmail.com\n" "POT-Creation-Date: 2008-08-27 17:18+0200\n" "PO-Revision-Date: 2011-06-21 16:13+0200\n" "Last-Translator: Roberto Aragón \n" "Language-Team: es \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: formalchemy/fields.py:474 msgid "Remove" msgstr "Eliminar" #: formalchemy/i18n.py:85 msgid "Year" msgstr "Año" #: formalchemy/i18n.py:86 msgid "Month" msgstr "Mes" #: formalchemy/i18n.py:87 msgid "Day" msgstr "Día" #: formalchemy/i18n.py:88 msgid "month_01" msgstr "Enero" #: formalchemy/i18n.py:89 msgid "month_02" msgstr "Febrero" #: formalchemy/i18n.py:90 msgid "month_03" msgstr "Marzo" #: formalchemy/i18n.py:91 msgid "month_04" msgstr "Abril" #: formalchemy/i18n.py:92 msgid "month_05" msgstr "Mayo" #: formalchemy/i18n.py:93 msgid "month_06" msgstr "Junio" #: formalchemy/i18n.py:94 msgid "month_07" msgstr "Julio" #: formalchemy/i18n.py:95 msgid "month_08" msgstr "Agosto" #: formalchemy/i18n.py:96 msgid "month_09" msgstr "Septiembre" #: formalchemy/i18n.py:97 msgid "month_10" msgstr "Octubre" #: formalchemy/i18n.py:98 msgid "month_11" msgstr "Noviembre" #: formalchemy/i18n.py:99 msgid "month_12" msgstr "Diciembre" #: formalchemy/validators.py:32 msgid "Please select a value" msgstr "Por favor, elija un valor" #: formalchemy/validators.py:32 msgid "Please enter a value" msgstr "Por favor, introduzca un valor" #: formalchemy/validators.py:49 msgid "Value is not an integer" msgstr "El valor no es un entero" #: formalchemy/validators.py:60 formalchemy/validators.py:72 msgid "Value is not a number" msgstr "El valor no es un número" #: formalchemy/validators.py:95 msgid "Missing @ sign" msgstr "Falta el símbolo @" #: formalchemy/validators.py:98 msgid "Control characters present" msgstr "Hay caracteres de control" #: formalchemy/validators.py:100 msgid "Non-ASCII characters present" msgstr "Hay caracteres no ASCII" #: formalchemy/validators.py:104 msgid "Recipient must be non-empty" msgstr "El destinatario no puede estar vacío" #: formalchemy/validators.py:106 msgid "Recipient must not end with '.'" msgstr "El destinatario no puede terminar con '.'" #: formalchemy/validators.py:121 msgid "Unterminated quoted section in recipient" msgstr "Sección citada no terminada en el destinatario" #: formalchemy/validators.py:124 msgid "Quoted section must be followed by '@' or '.'" msgstr "La sección citada debe estar seguida de '@' o '.'" #: formalchemy/validators.py:127 msgid "Reserved character present in recipient" msgstr "Hay un carácter reservado en el destinatario" #: formalchemy/validators.py:132 msgid "Domain must be non-empty" msgstr "El dominio no puede estar vacío" #: formalchemy/validators.py:134 msgid "Domain must not end with '.'" msgstr "El dominio no puede terminar con '.'" #: formalchemy/validators.py:136 msgid "Domain must not contain '..'" msgstr "El dominio no puede contener '..'" #: formalchemy/validators.py:138 msgid "Reserved character present in domain" msgstr "Hay un carácter reservado en el dominio" #: formalchemy/validators.py:148 #, python-format msgid "Value must be at least %d characters long" msgstr "El valor debe tener al menos %d caracteres" #: formalchemy/validators.py:150 #, python-format msgid "Value must be no more than %d characters long" msgstr "El valor no puede tener más de %d caracteres" #: formalchemy/validators.py:165 msgid "Invalid input" msgstr "Entrada no válida" #: formalchemy/ext/fsblob.py:26 #, python-format msgid "Invalid file extension. Must be %s, " msgstr "Extensión de fichero no válida. Debe ser %s, " #: formalchemy/ext/fsblob.py:33 #, python-format msgid "Invalid image file. Must be %s, " msgstr "Fichero de imagen no válido. Debe ser %s, " #: formalchemy/ext/pylons/admin.py:25 msgid "Add" msgstr "Agrerar" #: formalchemy/ext/pylons/admin.py:26 msgid "Edit" msgstr "Editar" #: formalchemy/ext/pylons/admin.py:27 msgid "New" msgstr "Nuevo" #: formalchemy/ext/pylons/admin.py:28 msgid "Save" msgstr "Salvar" #: formalchemy/ext/pylons/admin.py:29 msgid "Delete" msgstr "Eliminar" #: formalchemy/ext/pylons/admin.py:30 msgid "Cancel" msgstr "Cancelar" #: formalchemy/ext/pylons/admin.py:31 msgid "Models" msgstr "Modelos" #: formalchemy/ext/pylons/admin.py:32 formalchemy/ext/pylons/admin.py:35 msgid "Existing objects" msgstr "Objetos existentes" #: formalchemy/ext/pylons/admin.py:33 msgid "New object" msgstr "Nuevo objeto" #: formalchemy/ext/pylons/admin.py:34 msgid "Related types" msgstr "Tipos relacionados" #: formalchemy/ext/pylons/admin.py:36 msgid "Create form" msgstr "Crear formulario" #: formalchemy/ext/pylons/admin.py:81 msgid "edit" msgstr "editar" #: formalchemy/ext/pylons/admin.py:90 msgid "delete" msgstr "eliminar" #: formalchemy/ext/pylons/admin.py:201 #, python-format msgid "Created %s %s" msgstr "Creado %s %s" #: formalchemy/ext/pylons/admin.py:204 #, python-format msgid "Modified %s %s" msgstr "Modificado %s %s" #: formalchemy/ext/pylons/admin.py:236 #, python-format msgid "Deleted %s %s" msgstr "Eliminado %s %s" FormAlchemy-1.4.2/formalchemy/i18n_resources/en/LC_MESSAGES/000775 000765 000024 00000000000 11662023267 023543 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/en/LC_MESSAGES/formalchemy.mo000664 000765 000024 00000006051 11662023267 026410 0ustar00gawelstaff000000 000000 6|}    ).$? d  -C_ $'(%)=-g      9 V [ $l        , -B p   $ '  ( < R )j -                AddCancelControl characters presentCreate formCreated %s %sDayDeleteDeleted %s %sDomain must be non-emptyDomain must not contain '..'Domain must not end with '.'EditExisting objectsInvalid file extension. Must be %s, Invalid image file. Must be %s, Invalid inputMissing @ signModelsModified %s %sMonthNewNew objectNon-ASCII characters presentPlease enter a valuePlease select a valueQuoted section must be followed by '@' or '.'Recipient must be non-emptyRecipient must not end with '.'Related typesRemoveReserved character present in domainReserved character present in recipientSaveUnterminated quoted section in recipientValue is not a numberValue is not an integerValue must be at least %d characters longValue must be no more than %d characters longYeardeleteeditmonth_01month_02month_03month_04month_05month_06month_07month_08month_09month_10month_11month_12Project-Id-Version: FormAlchemy 1.1 Report-Msgid-Bugs-To: formalchemy@googlegroups.com POT-Creation-Date: 2009-01-18 12:51+0100 PO-Revision-Date: 2011-06-21 16:13+0200 Last-Translator: gael@gawel.org Language-Team: fr Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.6 AddCancelControl characters presentCreate formCreated %s %sDayDeleteDeleted %s %sDomain must be non-emptyDomain must not contain '..'Domain must not end with '.'EditExisting objectsInvalid file extension. Must be %s, Invalid image file. Must be %s, Invalid inputMissing @ signModelsModified %s %sMonthNewNew objectNon-ASCII characters presentPlease enter a valuePlease select a valueQuoted section must be followed by '@' or '.'Recipient must be non-emptyRecipient must not end with '.'Related typesRemoveReserved character present in domainReserved character present in recipientSaveUnterminated quoted section in recipientValue is not a numberValue is not an integerValue must be at least %d characters longValue must be no more than %d characters longYeardeleteeditJanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecemberFormAlchemy-1.4.2/formalchemy/i18n_resources/en/LC_MESSAGES/formalchemy.po000664 000765 000024 00000010721 11600123411 026372 0ustar00gawelstaff000000 000000 # English translations for FormAlchemy. # Copyright (C) 2009 ORGANIZATION # This file is distributed under the same license as the FormAlchemy # project. # FIRST AUTHOR , 2009. # msgid "" msgstr "" "Project-Id-Version: FormAlchemy 1.1\n" "Report-Msgid-Bugs-To: formalchemy@googlegroups.com\n" "POT-Creation-Date: 2009-01-18 12:51+0100\n" "PO-Revision-Date: 2011-06-21 16:13+0200\n" "Last-Translator: gael@gawel.org\n" "Language-Team: fr \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: formalchemy/fields.py:474 msgid "Remove" msgstr "" #: formalchemy/i18n.py:85 msgid "Year" msgstr "" #: formalchemy/i18n.py:86 msgid "Month" msgstr "" #: formalchemy/i18n.py:87 msgid "Day" msgstr "" #: formalchemy/i18n.py:88 msgid "month_01" msgstr "January" #: formalchemy/i18n.py:89 msgid "month_02" msgstr "February" #: formalchemy/i18n.py:90 msgid "month_03" msgstr "March" #: formalchemy/i18n.py:91 msgid "month_04" msgstr "April" #: formalchemy/i18n.py:92 msgid "month_05" msgstr "May" #: formalchemy/i18n.py:93 msgid "month_06" msgstr "June" #: formalchemy/i18n.py:94 msgid "month_07" msgstr "July" #: formalchemy/i18n.py:95 msgid "month_08" msgstr "August" #: formalchemy/i18n.py:96 msgid "month_09" msgstr "September" #: formalchemy/i18n.py:97 msgid "month_10" msgstr "October" #: formalchemy/i18n.py:98 msgid "month_11" msgstr "November" #: formalchemy/i18n.py:99 msgid "month_12" msgstr "December" #: formalchemy/validators.py:32 msgid "Please select a value" msgstr "" #: formalchemy/validators.py:32 msgid "Please enter a value" msgstr "" #: formalchemy/validators.py:49 msgid "Value is not an integer" msgstr "" #: formalchemy/validators.py:60 formalchemy/validators.py:72 msgid "Value is not a number" msgstr "" #: formalchemy/validators.py:95 msgid "Missing @ sign" msgstr "" #: formalchemy/validators.py:98 msgid "Control characters present" msgstr "" #: formalchemy/validators.py:100 msgid "Non-ASCII characters present" msgstr "" #: formalchemy/validators.py:104 msgid "Recipient must be non-empty" msgstr "" #: formalchemy/validators.py:106 msgid "Recipient must not end with '.'" msgstr "" #: formalchemy/validators.py:121 msgid "Unterminated quoted section in recipient" msgstr "" #: formalchemy/validators.py:124 msgid "Quoted section must be followed by '@' or '.'" msgstr "" #: formalchemy/validators.py:127 msgid "Reserved character present in recipient" msgstr "" #: formalchemy/validators.py:132 msgid "Domain must be non-empty" msgstr "" #: formalchemy/validators.py:134 msgid "Domain must not end with '.'" msgstr "" #: formalchemy/validators.py:136 msgid "Domain must not contain '..'" msgstr "" #: formalchemy/validators.py:138 msgid "Reserved character present in domain" msgstr "" #: formalchemy/validators.py:148 #, python-format msgid "Value must be at least %d characters long" msgstr "" #: formalchemy/validators.py:150 #, python-format msgid "Value must be no more than %d characters long" msgstr "" #: formalchemy/validators.py:165 msgid "Invalid input" msgstr "" #: formalchemy/ext/fsblob.py:26 #, python-format msgid "Invalid file extension. Must be %s, " msgstr "" #: formalchemy/ext/fsblob.py:33 #, python-format msgid "Invalid image file. Must be %s, " msgstr "" #: formalchemy/ext/pylons/admin.py:25 msgid "Add" msgstr "" #: formalchemy/ext/pylons/admin.py:26 msgid "Edit" msgstr "" #: formalchemy/ext/pylons/admin.py:27 msgid "New" msgstr "" #: formalchemy/ext/pylons/admin.py:28 msgid "Save" msgstr "" #: formalchemy/ext/pylons/admin.py:29 msgid "Delete" msgstr "" #: formalchemy/ext/pylons/admin.py:30 msgid "Cancel" msgstr "" #: formalchemy/ext/pylons/admin.py:31 msgid "Models" msgstr "" #: formalchemy/ext/pylons/admin.py:32 formalchemy/ext/pylons/admin.py:35 msgid "Existing objects" msgstr "" #: formalchemy/ext/pylons/admin.py:33 msgid "New object" msgstr "" #: formalchemy/ext/pylons/admin.py:34 msgid "Related types" msgstr "" #: formalchemy/ext/pylons/admin.py:36 msgid "Create form" msgstr "" #: formalchemy/ext/pylons/admin.py:81 msgid "edit" msgstr "" #: formalchemy/ext/pylons/admin.py:90 msgid "delete" msgstr "" #: formalchemy/ext/pylons/admin.py:201 #, python-format msgid "Created %s %s" msgstr "" #: formalchemy/ext/pylons/admin.py:204 #, python-format msgid "Modified %s %s" msgstr "" #: formalchemy/ext/pylons/admin.py:236 #, python-format msgid "Deleted %s %s" msgstr "" FormAlchemy-1.4.2/formalchemy/i18n_resources/de/LC_MESSAGES/000775 000765 000024 00000000000 11662023267 023531 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/i18n_resources/de/LC_MESSAGES/formalchemy.mo000664 000765 000024 00000005666 11662023267 026411 0ustar00gawelstaff000000 000000 4\]ah    $ D es  1?$F'k)-#*/8AJS\enw ( 4>Vix|  $ "5 X k         #* N o y "   ) + I N W b i q w }       AddCancelControl characters presentCreate formCreated %s %sDayDeleteDeleted %s %sDomain must be non-emptyDomain must not contain '..'Domain must not end with '.'EditExisting objectsInvalid file extension. Must be %s, Invalid image file. Must be %s, Invalid inputMissing @ signModelsModified %s %sMonthNewNew objectNon-ASCII characters presentPlease enter a valuePlease select a valueRecipient must be non-emptyRecipient must not end with '.'Related typesRemoveReserved character present in domainReserved character present in recipientSaveValue is not a numberValue is not an integerValue must be at least %d characters longValue must be no more than %d characters longYeardeleteeditmonth_01month_02month_03month_04month_05month_06month_07month_08month_09month_10month_11month_12Project-Id-Version: FormAlchemy 1.3.1 Report-Msgid-Bugs-To: support@xo7.de POT-Creation-Date: 2011-05-02 12:35+0100 PO-Revision-Date: 2011-06-21 16:13+0200 Last-Translator: Andreas Kaiser Language-Team: German Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.6 HinzufügenAbbrechenSteuerzeichen enthaltenFormular erstellenErstellt %s %sTagLöschenGelöscht %s %sDomain darf nicht leer seinDomain darf nicht '..' enthaltenDomain darf nicht mit '.' endenBearbeitenVorhandene ObjekteUngültige Dateiendung. Gültig: %s,Ungültige Bilddatei. Gültig: %s,Ungültige EingabeFehlendes @ ZeichenVorlagenBearbeitet %s %sMonatNeuNeues ObjektNicht-ASCII Zeichen enthaltenBitte geben Sie einen Wert einBitte wählen Sie einen WertEmpfänger darf nicht leer seinEmpfänger darf nicht auf '.' endenIn Zusammenhang stehende ObjekteEntfernenReserviertes Zeichen in DomainReserviertes Zeichen in EmpfängerSpeichernWert ist keine ZahlWert ist keine ganze ZahlWert muss mindestens %d Zeichen lang seinWert darf nicht länger als %d Zeichen seinJahrlöschenbearbeitenJanuarFebruarMärzAprilMaiJuniJuliAugustSeptemberOktoberNovemberDezemberFormAlchemy-1.4.2/formalchemy/i18n_resources/de/LC_MESSAGES/formalchemy.po000664 000765 000024 00000012536 11600123411 026366 0ustar00gawelstaff000000 000000 # German Translations for FormAlchemy. # Copyright (C) 2009 Xo7 GmbH # This file is distributed under the same license as the FormAlchemy # project. # Andreas Kaiser , 2011. # msgid "" msgstr "" "Project-Id-Version: FormAlchemy 1.3.1\n" "Report-Msgid-Bugs-To: support@xo7.de\n" "POT-Creation-Date: 2011-05-02 12:35+0100\n" "PO-Revision-Date: 2011-06-21 16:13+0200\n" "Last-Translator: Andreas Kaiser \n" "Language-Team: German \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: formalchemy/fields.py:474 msgid "Remove" msgstr "Entfernen" #: formalchemy/i18n.py:85 msgid "Year" msgstr "Jahr" #: formalchemy/i18n.py:86 msgid "Month" msgstr "Monat" #: formalchemy/i18n.py:87 msgid "Day" msgstr "Tag" #: formalchemy/i18n.py:88 msgid "month_01" msgstr "Januar" #: formalchemy/i18n.py:89 msgid "month_02" msgstr "Februar" #: formalchemy/i18n.py:90 msgid "month_03" msgstr "März" #: formalchemy/i18n.py:91 msgid "month_04" msgstr "April" #: formalchemy/i18n.py:92 msgid "month_05" msgstr "Mai" #: formalchemy/i18n.py:93 msgid "month_06" msgstr "Juni" #: formalchemy/i18n.py:94 msgid "month_07" msgstr "Juli" #: formalchemy/i18n.py:95 msgid "month_08" msgstr "August" #: formalchemy/i18n.py:96 msgid "month_09" msgstr "September" #: formalchemy/i18n.py:97 msgid "month_10" msgstr "Oktober" #: formalchemy/i18n.py:98 msgid "month_11" msgstr "November" #: formalchemy/i18n.py:99 msgid "month_12" msgstr "Dezember" #: formalchemy/validators.py:32 msgid "Please select a value" msgstr "Bitte wählen Sie einen Wert" #: formalchemy/validators.py:32 msgid "Please enter a value" msgstr "Bitte geben Sie einen Wert ein" #: formalchemy/validators.py:49 msgid "Value is not an integer" msgstr "Wert ist keine ganze Zahl" #: formalchemy/validators.py:60 formalchemy/validators.py:72 msgid "Value is not a number" msgstr "Wert ist keine Zahl" #: formalchemy/validators.py:95 msgid "Missing @ sign" msgstr "Fehlendes @ Zeichen" #: formalchemy/validators.py:98 msgid "Control characters present" msgstr "Steuerzeichen enthalten" #: formalchemy/validators.py:100 msgid "Non-ASCII characters present" msgstr "Nicht-ASCII Zeichen enthalten" #: formalchemy/validators.py:104 msgid "Recipient must be non-empty" msgstr "Empfänger darf nicht leer sein" #: formalchemy/validators.py:106 msgid "Recipient must not end with '.'" msgstr "Empfänger darf nicht auf '.' enden" #: formalchemy/validators.py:121 #, fuzzy msgid "Unterminated quoted section in recipient" msgstr "Empfänger enthält eine nicht terminierte \"quoted\" Sektion" #: formalchemy/validators.py:124 #, fuzzy msgid "Quoted section must be followed by '@' or '.'" msgstr "\"Quoted\" Sektion muss mit '@' oder '.' fortgesetzt werden" #: formalchemy/validators.py:127 msgid "Reserved character present in recipient" msgstr "Reserviertes Zeichen in Empfänger" #: formalchemy/validators.py:132 msgid "Domain must be non-empty" msgstr "Domain darf nicht leer sein" #: formalchemy/validators.py:134 msgid "Domain must not end with '.'" msgstr "Domain darf nicht mit '.' enden" #: formalchemy/validators.py:136 msgid "Domain must not contain '..'" msgstr "Domain darf nicht '..' enthalten" #: formalchemy/validators.py:138 msgid "Reserved character present in domain" msgstr "Reserviertes Zeichen in Domain" #: formalchemy/validators.py:148 #, python-format msgid "Value must be at least %d characters long" msgstr "Wert muss mindestens %d Zeichen lang sein" #: formalchemy/validators.py:150 #, python-format msgid "Value must be no more than %d characters long" msgstr "Wert darf nicht länger als %d Zeichen sein" #: formalchemy/validators.py:165 msgid "Invalid input" msgstr "Ungültige Eingabe" #: formalchemy/ext/fsblob.py:26 #, python-format msgid "Invalid file extension. Must be %s, " msgstr "Ungültige Dateiendung. Gültig: %s," #: formalchemy/ext/fsblob.py:33 #, python-format msgid "Invalid image file. Must be %s, " msgstr "Ungültige Bilddatei. Gültig: %s," #: formalchemy/ext/pylons/admin.py:25 msgid "Add" msgstr "Hinzufügen" #: formalchemy/ext/pylons/admin.py:26 msgid "Edit" msgstr "Bearbeiten" #: formalchemy/ext/pylons/admin.py:27 msgid "New" msgstr "Neu" #: formalchemy/ext/pylons/admin.py:28 msgid "Save" msgstr "Speichern" #: formalchemy/ext/pylons/admin.py:29 msgid "Delete" msgstr "Löschen" #: formalchemy/ext/pylons/admin.py:30 msgid "Cancel" msgstr "Abbrechen" #: formalchemy/ext/pylons/admin.py:31 msgid "Models" msgstr "Vorlagen" #: formalchemy/ext/pylons/admin.py:32 formalchemy/ext/pylons/admin.py:35 msgid "Existing objects" msgstr "Vorhandene Objekte" #: formalchemy/ext/pylons/admin.py:33 msgid "New object" msgstr "Neues Objekt" #: formalchemy/ext/pylons/admin.py:34 msgid "Related types" msgstr "In Zusammenhang stehende Objekte" #: formalchemy/ext/pylons/admin.py:36 msgid "Create form" msgstr "Formular erstellen" #: formalchemy/ext/pylons/admin.py:81 msgid "edit" msgstr "bearbeiten" #: formalchemy/ext/pylons/admin.py:90 msgid "delete" msgstr "löschen" #: formalchemy/ext/pylons/admin.py:201 #, python-format msgid "Created %s %s" msgstr "Erstellt %s %s" #: formalchemy/ext/pylons/admin.py:204 #, python-format msgid "Modified %s %s" msgstr "Bearbeitet %s %s" #: formalchemy/ext/pylons/admin.py:236 #, python-format msgid "Deleted %s %s" msgstr "Gelöscht %s %s" FormAlchemy-1.4.2/formalchemy/ext/__init__.py000664 000765 000024 00000000031 11514360146 021402 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- FormAlchemy-1.4.2/formalchemy/ext/couchdb.py000664 000765 000024 00000030346 11601334711 021262 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- __doc__ = """ Define a couchdbkit schema:: >>> from couchdbkit import schema >>> from formalchemy.ext import couchdb >>> class Person(couchdb.Document): ... name = schema.StringProperty(required=True) ... @classmethod ... def _render_options(self, fs): ... return [(gawel, gawel._id), (benoitc, benoitc._id)] ... def __unicode__(self): return getattr(self, 'name', None) or u'' >>> gawel = Person(name='gawel') >>> gawel._id = '123' >>> benoitc = Person(name='benoitc') >>> benoitc._id = '456' >>> class Pet(couchdb.Document): ... name = schema.StringProperty(required=True) ... type = schema.StringProperty(required=True) ... birthdate = schema.DateProperty(auto_now=True) ... weight_in_pounds = schema.IntegerProperty() ... spayed_or_neutered = schema.BooleanProperty() ... owner = schema.SchemaProperty(Person) ... friends = schema.SchemaListProperty(Person) Configure your FieldSet:: >>> fs = couchdb.FieldSet(Pet) >>> fs.configure(include=[fs.name, fs.type, fs.birthdate, fs.weight_in_pounds]) >>> p = Pet(name='dewey') >>> p.name = 'dewey' >>> p.type = 'cat' >>> p.owner = gawel >>> p.friends = [benoitc] >>> fs = fs.bind(p) Render it:: >>> # rendering >>> fs.name.is_required() True >>> print fs.render() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
... ... """ from formalchemy.forms import FieldSet as BaseFieldSet from formalchemy.tables import Grid as BaseGrid from formalchemy.fields import Field as BaseField from formalchemy.forms import SimpleMultiDict from formalchemy import fields from formalchemy import validators from formalchemy import fatypes from sqlalchemy.util import OrderedDict from couchdbkit.schema.properties_proxy import LazySchemaList from couchdbkit import schema from datetime import datetime __all__ = ['Field', 'FieldSet', 'Session', 'Document'] class Pk(property): def __init__(self, attr='_id'): self.attr = attr def __get__(self, instance, cls): if not instance: return self return getattr(instance, self.attr, None) or None def __set__(self, instance, value): setattr(instance, self.attr, value) class Document(schema.Document): _pk = Pk() class Query(list): """A list like object to emulate SQLAlchemy's Query. This mostly exist to work with ``webhelpers.paginate.Page``""" def __init__(self, model, **options): self.model = model self._init = False self.options = options def get(self, id): """Get a record by id""" return self.model.get(id) def view(self, view_name, **kwargs): """set self to a list of record returned by view named ``{model_name}/{view_name}``""" kwargs = kwargs or self.options if not self._init: self.extend([r for r in self.model.view('%s/%s' % (self.model.__name__.lower(), view_name), **kwargs)]) self._init = True return self def all(self, **kwargs): """set self to a list of record returned by view named ``{model_name}/all``""" kwargs = kwargs or self.options return self.view('all', **kwargs) def __len__(self): if not self._init: self.all() return list.__len__(self) class Session(object): """A SA like Session to implement couchdb""" def __init__(self, db): self.db = db def add(self, record): """add a record""" record.save() def update(self, record): """update a record""" record.save() def delete(self, record): """delete a record""" del self.db[record._id] def query(self, model, *args, **kwargs): """return a :class:`~formalchemy.ext.couchdb.Query` bound to model object""" return Query(model, *args, **kwargs) def commit(self): """do nothing since there is no transaction in couchdb""" remove = commit def _stringify(value): if isinstance(value, (list, LazySchemaList)): return [_stringify(v) for v in value] if isinstance(value, schema.Document): return value._id return value class Field(BaseField): """Field for CouchDB FieldSet""" def __init__(self, *args, **kwargs): self.schema = kwargs.pop('schema') if self.schema and 'renderer' not in kwargs: kwargs['renderer'] = fields.SelectFieldRenderer if self.schema and 'options' not in kwargs: if hasattr(self.schema, '_render_options'): kwargs['options'] = self.schema._render_options else: kwargs['options'] = lambda fs: [(d, d._id) for d in Query(self.schema).all()] if kwargs.get('type') == fatypes.List: kwargs['multiple'] = True BaseField.__init__(self, *args, **kwargs) @property def value(self): if not self.is_readonly() and self.parent.data is not None: v = self._deserialize() if v is not None: return v value = getattr(self.model, self.name) return _stringify(value) @property def raw_value(self): try: value = getattr(self.model, self.name) return _stringify(value) except (KeyError, AttributeError): pass if callable(self._value): return self._value(self.model) return self._value @property def model_value(self): return self.raw_value def sync(self): """Set the attribute's value in `model` to the value given in `data`""" if not self.is_readonly(): value = self._deserialize() if self.schema: if isinstance(value, list): value = [self.schema.get(v) for v in value] else: value = self.schema.get(value) setattr(self.model, self.name, value) class FieldSet(BaseFieldSet): """See :class:`~formalchemy.forms.FieldSet`""" __sa__ = False def __init__(self, model, **kwargs): BaseFieldSet.__init__(self, model, **kwargs) if model is not None and isinstance(model, schema.Document): BaseFieldSet.rebind(self, model.__class__, data=kwargs.get('data', None)) self.doc = model.__class__ self.model = model self._bound_pk = fields._pk(model) else: BaseFieldSet.rebind(self, model, data=kwargs.get('data', None)) self.doc = model values = self.doc._properties.values() values.sort(lambda a, b: cmp(a.creation_counter, b.creation_counter)) for v in values: if getattr(v, 'name'): k = v.name sch = None if isinstance(v, schema.SchemaListProperty): t = fatypes.List sch = v._schema elif isinstance(v, schema.SchemaProperty): t = fatypes.String sch = v._schema else: try: t = getattr(fatypes, v.__class__.__name__.replace('Property','')) except AttributeError: raise NotImplementedError('%s is not mapped to a type for field %s (%s)' % (v.__class__, k, v.__class__.__name__)) self.append(Field(name=k, type=t, schema=sch)) if v.required: self._fields[k].validators.append(validators.required) def bind(self, model=None, session=None, data=None): """Bind to an instance""" if not (model or session or data): raise Exception('must specify at least one of {model, session, data}') if not model: if not self.model: raise Exception('model must be specified when none is already set') model = fields._pk(self.model) is None and self.doc() or self.model # copy.copy causes a stacktrace on python 2.5.2/OSX + pylons. unable to reproduce w/ simpler sample. mr = object.__new__(self.__class__) mr.__dict__ = dict(self.__dict__) # two steps so bind's error checking can work mr.rebind(model, session, data) mr._fields = OrderedDict([(key, renderer.bind(mr)) for key, renderer in self._fields.iteritems()]) if self._render_fields: mr._render_fields = OrderedDict([(field.key, field) for field in [field.bind(mr) for field in self._render_fields.itervalues()]]) return mr def rebind(self, model=None, session=None, data=None): if model is not None and model is not self.doc: if not isinstance(model, self.doc): try: model = model() except Exception, e: raise Exception('''%s appears to be a class, not an instance, but FormAlchemy cannot instantiate it. (Make sure all constructor parameters are optional!) %r - %s''' % ( model, self.doc, e)) else: model = self.doc() self.model = model self._bound_pk = fields._pk(model) if data is None: self.data = None elif hasattr(data, 'getall') and hasattr(data, 'getone'): self.data = data else: try: self.data = SimpleMultiDict(data) except: raise Exception('unsupported data object %s. currently only dicts and Paste multidicts are supported' % self.data) def jsonify(self): if isinstance(self.model, schema.Document): return self.model.to_json() return self.doc().to_json() class Grid(BaseGrid, FieldSet): """See :class:`~formalchemy.tables.Grid`""" def __init__(self, cls, instances=None, **kwargs): FieldSet.__init__(self, cls, **kwargs) self.rows = instances or [] self.readonly = False self._errors = {} def _get_errors(self): return self._errors def _set_errors(self, value): self._errors = value errors = property(_get_errors, _set_errors) def rebind(self, instances=None, session=None, data=None): FieldSet.rebind(self, self.model, data=data) if instances is not None: self.rows = instances def bind(self, instances=None, session=None, data=None): mr = FieldSet.bind(self, self.model, session, data) mr.rows = instances return mr def _set_active(self, instance, session=None): FieldSet.rebind(self, instance, session or self.session, self.data) FormAlchemy-1.4.2/formalchemy/ext/fsblob.py000664 000765 000024 00000014766 11662015161 021134 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- import os import stat import cgi import string import random import shutil import formalchemy.helpers as h from formalchemy.fields import FileFieldRenderer as Base from formalchemy.fields import FieldRenderer from formalchemy.validators import regex from formalchemy.i18n import _ try: from pylons import config except ImportError: config = {} __all__ = ['file_extension', 'image_extension', 'FileFieldRenderer', 'ImageFieldRenderer'] def file_extension(extensions=[], errormsg=None): """Validate a file extension. """ if errormsg is None: errormsg = _('Invalid file extension. Must be %s'%', '.join(extensions)) return regex(r'^.+\.(%s)$' % '|'.join(extensions), errormsg=errormsg) def image_extension(extensions=['jpeg', 'jpg', 'gif', 'png']): """Validate an image extension. default valid extensions are jpeg, jpg, gif, png. """ errormsg = _('Invalid image file. Must be %s'%', '.join(extensions)) return file_extension(extensions, errormsg=errormsg) def normalized_basename(path): """ >>> print normalized_basename(u'c:\\Prog files\My fil\xe9.jpg') My_fil.jpg >>> print normalized_basename('c:\\Prog files\My fil\xc3\xa9.jpg') My_fil.jpg """ if isinstance(path, str): path = path.decode('utf-8', 'ignore').encode('ascii', 'ignore') if isinstance(path, unicode): path = path.encode('ascii', 'ignore') filename = path.split('/')[-1] filename = filename.split('\\')[-1] return filename.replace(' ', '_') class FileFieldRenderer(Base): """render a file input field stored on file system """ url_prefix = '/' @property def storage_path(self): if 'app_conf' in config: config['app_conf'].get('storage_path', '') def __init__(self, *args, **kwargs): if not self.storage_path or not os.path.isdir(self.storage_path): raise ValueError( 'storage_path must be set to a valid path. Got %r' % self.storage_path) Base.__init__(self, *args, **kwargs) self._path = None def relative_path(self, filename): """return the file path relative to root """ rdir = lambda: ''.join(random.sample(string.ascii_lowercase, 3)) path = '/'.join([rdir(), rdir(), rdir(), filename]) return path def get_url(self, relative_path): """return the file url. by default return the relative path stored in the DB """ return self.url_prefix + relative_path def get_size(self): relative_path = self.field.value if relative_path: filepath = os.path.join(self.storage_path, relative_path.replace('/', os.sep)) if os.path.isfile(filepath): return os.stat(filepath)[stat.ST_SIZE] return 0 def render(self, **kwargs): """render a file field and the file preview """ html = Base.render(self, **kwargs) value = self.field.value if value: html += self.render_readonly() # add the old value for objects not yet stored old_value = '%s--old' % self.name html += h.hidden_field(old_value, value=value) return html def render_readonly(self, **kwargs): """render the filename and the binary size in a human readable with a link to the file itself. """ value = self.field.value if value: content = '%s (%s)' % (normalized_basename(value), self.readable_size()) return h.content_tag('a', content, href=self.get_url(value), **kwargs) return '' def _serialized_value(self): name = self.name if '%s--remove' % self.name in self.params: self._path = None return None elif name in self.params: return self.params.getone(self.name) old_value = '%s--old' % self.name if old_value in self.params: self._path = self.params.getone(old_value) return self._path raise RuntimeError('This should never occurs') def deserialize(self): if self._path: return self._path data = FieldRenderer.deserialize(self) if isinstance(data, cgi.FieldStorage): filename = normalized_basename(data.filename) self._path = self.relative_path(filename) filepath = os.path.join(self.storage_path, self._path.replace('/', os.sep)) dirname = os.path.dirname(filepath) if not os.path.isdir(dirname): os.makedirs(dirname) fd = open(filepath, 'wb') shutil.copyfileobj(data.file, fd) fd.close() return self._path checkbox_name = '%s--remove' % self.name if not data and not self.params.has_key(checkbox_name): data = getattr(self.field.model, self.field.name) # get value from old_value if needed old_value = '%s--old' % self.name checkbox_name = '%s--remove' % self.name if not data and not self.params.has_key(checkbox_name) \ and self.params.has_key(old_value): return self.params[old_value] return data is not None and data or '' @classmethod def new(cls, storage_path, url_prefix='/'): """Return a new class:: >>> FileFieldRenderer.new(storage_path='/') # doctest: +ELLIPSIS >>> ImageFieldRenderer.new(storage_path='/') # doctest: +ELLIPSIS """ if url_prefix[-1] != '/': url_prefix += '/' name = 'Configured%s_%s' % (cls.__name__, str(random.random())[2:]) return type(name, (cls,), dict(storage_path=storage_path, url_prefix=url_prefix)) class ImageFieldRenderer(FileFieldRenderer): def render_readonly(self, **kwargs): """render the image tag with a link to the image itself. """ value = self.field.value if value: url = self.get_url(value) content = '%s (%s)' % (normalized_basename(value), self.readable_size()) tag = h.tag('img', src=url, alt=content) return h.content_tag('a', tag, href=url, **kwargs) return '' FormAlchemy-1.4.2/formalchemy/ext/pylons/000775 000765 000024 00000000000 11662023267 020627 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/ext/rdf.py000664 000765 000024 00000015524 11543610506 020433 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- __doc__ = """This module provides an experimental subclass of :class:`~formalchemy.forms.FieldSet` to support RDFAlchemy_. .. _RDFAlchemy: http://www.openvest.com/trac/wiki/RDFAlchemy Usage ===== >>> from rdfalchemy.samples.company import Company >>> c = Company(stockDescription='description', symbol='FA', ... cik='cik', companyName='fa corp', ... stock=['value1']) >>> fs = FieldSet(Company) >>> fs.configure(options=[fs.stock.set(options=['value1', 'value2'])]) >>> fs = fs.bind(c) >>> fs.stock.value ['value1'] >>> print fs.render().strip() # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
...
>>> fs = Grid(Company, [c]) >>> fs.configure(options=[fs.stock.set(options=['value1', 'value2'])], readonly=True) >>> print fs.render().strip() #doctest: +NORMALIZE_WHITESPACE Stockdescription Companyname Cik Symbol Stock description fa corp cik FA value1 """ from formalchemy.forms import FieldSet as BaseFieldSet from formalchemy.tables import Grid as BaseGrid from formalchemy.fields import Field as BaseField from formalchemy.forms import SimpleMultiDict from formalchemy import fields from formalchemy import validators from formalchemy import fatypes from sqlalchemy.util import OrderedDict from rdfalchemy import descriptors from datetime import datetime __all__ = ['Field', 'FieldSet'] class Session(object): """A SA like Session to implement rdf""" def add(self, record): """add a record""" record.save() def update(self, record): """update a record""" record.save() def delete(self, record): """delete a record""" def query(self, model, *args, **kwargs): raise NotImplementedError() def commit(self): """do nothing since there is no transaction in couchdb""" remove = commit class Field(BaseField): """""" @property def value(self): if not self.is_readonly() and self.parent.data is not None: v = self._deserialize() if v is not None: return v return getattr(self.model, self.name) @property def model_value(self): return getattr(self.model, self.name) raw_value = model_value def sync(self): """Set the attribute's value in `model` to the value given in `data`""" if not self.is_readonly(): setattr(self.model, self.name, self._deserialize()) class FieldSet(BaseFieldSet): __sa__ = False _mapping = { descriptors.rdfSingle: fatypes.String, descriptors.rdfMultiple: fatypes.List, descriptors.rdfList: fatypes.List, } def __init__(self, model, **kwargs): BaseFieldSet.__init__(self, model, **kwargs) BaseFieldSet.rebind(self, model, data=kwargs.get('data', None)) for k, v in model.__dict__.iteritems(): if not k.startswith('_'): descriptor = type(v) t = self._mapping.get(descriptor) if t: self.append(Field(name=k, type=t)) def bind(self, model=None, session=None, data=None): """Bind to an instance""" if not (model or session or data): raise Exception('must specify at least one of {model, session, data}') if not model: if not self.model: raise Exception('model must be specified when none is already set') else: model = self.model() # copy.copy causes a stacktrace on python 2.5.2/OSX + pylons. unable to reproduce w/ simpler sample. mr = object.__new__(self.__class__) mr.__dict__ = dict(self.__dict__) # two steps so bind's error checking can work mr.rebind(model, session, data) mr._fields = OrderedDict([(key, renderer.bind(mr)) for key, renderer in self._fields.iteritems()]) if self._render_fields: mr._render_fields = OrderedDict([(field.key, field) for field in [field.bind(mr) for field in self._render_fields.itervalues()]]) return mr def rebind(self, model, session=None, data=None): if model: if isinstance(model, type): try: model = model() except: raise Exception('%s appears to be a class, not an instance, but FormAlchemy cannot instantiate it. (Make sure all constructor parameters are optional!)' % model) self.model = model self._bound_pk = None if data is None: self.data = None elif hasattr(data, 'getall') and hasattr(data, 'getone'): self.data = data else: try: self.data = SimpleMultiDict(data) except: raise Exception('unsupported data object %s. currently only dicts and Paste multidicts are supported' % self.data) class Grid(BaseGrid, FieldSet): def __init__(self, cls, instances=None, **kwargs): FieldSet.__init__(self, cls, **kwargs) self.rows = instances or [] self.readonly = False self._errors = {} def _get_errors(self): return self._errors def _set_errors(self, value): self._errors = value errors = property(_get_errors, _set_errors) def rebind(self, instances=None, session=None, data=None): FieldSet.rebind(data=data) if instances is not None: self.rows = instances def _set_active(self, instance, session=None): FieldSet.rebind(self, instance, session or self.session, self.data) def test_sync(): from rdfalchemy.samples.company import Company c = Company(stockDescription='description', symbol='FA', cik='cik', companyName='fa corp', stock=['value1']) fs = FieldSet(Company) fs.configure(include=[fs.companyName, fs.stock.set(options=['value1', 'value2'])]) fs = fs.bind(c, data={'Company--companyName':'new name', 'Company--stock':'value2'}) assert fs.stock.raw_value == ['value1'] fs.validate() fs.sync() assert fs.stock.raw_value == ['value2'] FormAlchemy-1.4.2/formalchemy/ext/zope/000775 000765 000024 00000000000 11662023267 020260 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/ext/zope/__init__.py000664 000765 000024 00000046064 11612066543 022402 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- __doc__ = """This module provides an experimental subclass of :class:`~formalchemy.forms.FieldSet` to support zope.schema_'s schema. `Simple validation`_ is supported. `Invariant`_ is not supported. .. _zope.schema: http://pypi.python.org/pypi/zope.schema .. _simple validation: http://pypi.python.org/pypi/zope.schema#simple-usage .. _invariant: http://pypi.python.org/pypi/zope.schema#schema-validation Available fields ================ Not all fields are supported. You can use TextLine, Text, Int, Bool, Float, Date, Datetime, Time, and Choice. Usage ===== Here is a simple example. First we need a schema:: >>> class IPet(interface.Interface): ... name = schema.Text(title=u'Name', required=True) ... type = schema.TextLine(title=u'Type', required=True) ... age = schema.Int(min=1) ... owner = schema.TextLine(title=u'Owner') ... birthdate = schema.Date(title=u'Birth date') ... colour = schema.Choice(title=u'Colour', ... values=['Brown', 'Black']) ... friends = schema.List(title=u'Friends', value_type=schema.Choice(['cat', 'dog', 'bulldog'])) Initialize FieldSet with schema:: >>> fs = FieldSet(IPet) Create a class to store values. If your class does not implement the form interface the FieldSet will generate an adapter for you: >>> class Pet(FlexibleDict):pass >>> p = Pet(name='dewey', type='cat', owner='gawel', friends=['cat', 'dog']) >>> fs = fs.bind(p) Fields are aware of schema attributes: >>> fs.name.is_required() True We can use the form:: >>> print fs.render().strip() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Ok, let's assume that validation and syncing works: >>> fs.configure(include=[fs.name]) >>> fs.rebind(p, data={'Pet--name':'minou'}) >>> fs.validate() True >>> fs.sync() >>> fs.name.value u'minou' >>> p.name u'minou' >>> fs.configure(include=[fs.age]) >>> fs.rebind(p, data={'Pet--age':'-1'}) >>> fs.validate() False >>> fs.age.errors [u'Value is too small'] >>> fs.configure(include=[fs.colour]) >>> fs.rebind(p, data={'Pet--colour':'Yellow'}) >>> fs.validate() False >>> fs.colour.errors [u'Constraint not satisfied'] >>> fs.rebind(p, data={'Pet--colour':'Brown'}) >>> fs.validate() True >>> fs.sync() >>> fs.colour.value u'Brown' >>> p.colour u'Brown' Looks nice ! Let's use the grid: >>> grid = Grid(IPet) >>> grid = grid.bind([p]) >>> print grid.render().strip() #doctest: +ELLIPSIS Name Type age Owner Birth date Colour Friends ... """ from formalchemy.forms import FieldSet as BaseFieldSet from formalchemy.tables import Grid as BaseGrid from formalchemy.fields import Field as BaseField from formalchemy.forms import SimpleMultiDict from formalchemy.fields import _stringify from formalchemy import fields from formalchemy import validators from formalchemy import fatypes from sqlalchemy.util import OrderedDict from datetime import datetime from uuid import UUID from zope import schema from zope.schema import interfaces from zope import interface class Pk(property): """FormAlchemy use a ``_pk`` attribute to identify objects. You can use this property to bind another attribute as a primary key:: >>> class Content(object): ... _pk = Pk() ... __name__ = 'primary_key' >>> content = Content() >>> content._pk 'primary_key' >>> content._pk = 'another_key' >>> content.__name__ 'another_key' >>> class Content(object): ... _pk = Pk('uid') ... uid = 'primary_key' >>> content = Content() >>> content._pk 'primary_key' >>> fields._pk(content) 'primary_key' """ def __init__(self, attr='__name__'): self.attr = attr def __get__(self, instance, cls): return getattr(instance, self.attr, None) or None def __set__(self, instance, value): setattr(instance, self.attr, value) class FlexibleModel(object): """A flexible object to easy adapt most python classes: .. sourcecode:: python >>> obj = FlexibleModel(owner='gawel') >>> obj.owner == obj.get('owner') == obj['owner'] == 'gawel' True >>> obj._pk is None True If your object provide an uuid attribute then FormAlchemy will use it has primary key: .. sourcecode:: python >>> import uuid >>> obj = FlexibleModel(uuid=uuid.uuid4()) >>> obj._pk is None False """ interface.implements(interface.Interface) _is_dict = False def __init__(self, context=None, **kwargs): if context is None: self.context = dict(**kwargs) else: self.context = context if getattr(self, 'uuid', None): uuid = self.uuid if isinstance(uuid, UUID): self._pk = str(int(uuid)) else: self._pk = _stringify(self.uuid) else: self._pk = None def __getattr__(self, attr): """flexible __getattr__. also used for __getitem__ and get""" if hasattr(self.context, attr): return getattr(self.context, attr, None) elif self._is_dict or isinstance(self.context, dict): return self.context.get(attr) raise AttributeError('%r as no attribute %s' % (self.context, attr)) __getitem__ = get = __getattr__ def __setattr__(self, attr, value): """flexible __getattr__""" if attr.startswith('_') or attr == 'context': object.__setattr__(self, attr, value) elif not self._is_dict and hasattr(self.context, attr): setattr(self.context, attr, value) elif self._is_dict or isinstance(self.context, dict): self.context[attr] = value else: raise AttributeError('%r as no attribute %s' % (self.context, attr)) def __repr__(self): return '<%s adapter for %s>' % (self.__class__.__name__, repr(self.context)) class FlexibleDict(FlexibleModel, dict): """like FlexibleModel but inherit from dict: .. sourcecode:: python >>> obj = FlexibleDict(owner='gawel') >>> obj.owner == obj.get('owner') == obj['owner'] == 'gawel' True >>> isinstance(obj, dict) True >>> 'owner' in obj True """ interface.implements(interface.Interface) _is_dict = True def keys(self): return self.context.keys() def values(self): return self.context.values() def items(self): return self.context.items() def copy(self): return self.context.copy() def __iter__(self): return iter(self.context) def __contains__(self, value): return value in self.context _model_registry = {} def gen_model(iface, klass=None, dict_like=False): """return a new FlexibleModel or FlexibleDict factory who provide iface: .. sourcecode:: python >>> class ITitle(interfaces.Interface): ... title = schema.TextLine(title=u'title') >>> factory = gen_model(ITitle) >>> adapted = factory() >>> ITitle.providedBy(adapted) True >>> class Title(object): ... title = None >>> obj = Title() >>> adapted = factory(obj) >>> adapted.context is obj True >>> adapted.title = 'my title' >>> obj.title 'my title' >>> obj = dict() >>> adapted = factory(obj) >>> adapted.context is obj True >>> adapted.title = 'my title' >>> obj['title'] 'my title' """ if klass: if not hasattr(klass, '__name__'): klass = klass.__class__ name = class_name = klass.__name__ else: class_name = None name = iface.__name__[1:] adapter = _model_registry.get((iface.__name__, class_name), None) if adapter is not None: return adapter new_klass = type(name, (dict_like and FlexibleDict or FlexibleModel,), {}) def adapter(context=None, **kwargs): adapted = new_klass(context=context, **kwargs) interface.directlyProvides(adapted, [iface]) return adapted _model_registry[(iface.__name__, class_name)] = adapter return adapter class Field(BaseField): """Field aware of zope schema. See :class:`formalchemy.fields.AbstractField` for full api.""" def set(self, options=[], **kwargs): if isinstance(options, schema.Choice): sourcelist = options.source.by_value.values() if sourcelist[0].title is None: options = [term.value for term in sourcelist] else: options = [(term.title, term.value) for term in sourcelist] return BaseField.set(self, options=options, **kwargs) @property def value(self): if not self.is_readonly() and self.parent.data is not None: v = self._deserialize() if v is not None: return v return getattr(self.model, self.name) @property def raw_value(self): try: return getattr(self.model, self.name) except (KeyError, AttributeError): pass if callable(self._value): return self._value(self.model) return self._value def _validate(self): if self.is_readonly(): return True valide = BaseField._validate(self) if not valide: return False value = self._deserialize() if isinstance(self.type, fatypes.Unicode) and not isinstance(value, unicode): value = _stringify(value) field = self.parent.iface[self.name] bound = field.bind(self.model) try: bound.validate(value) except schema.ValidationError, e: self.errors.append(e.doc()) except schema._bootstrapinterfaces.ConstraintNotSatisfied, e: self.errors.append(e.doc()) return not self.errors def sync(self): """Set the attribute's value in `model` to the value given in `data`""" if not self.is_readonly(): setattr(self.model, self.name, self._deserialize()) class FieldSet(BaseFieldSet): """FieldSet aware of zope schema. See :class:`formalchemy.forms.FieldSet` for full api.""" __sa__ = False _fields_mapping = { schema.TextLine: fatypes.Unicode, schema.Text: fatypes.Unicode, schema.Int: fatypes.Integer, schema.Bool: fatypes.Boolean, schema.Float: fatypes.Float, schema.Date: fatypes.Date, schema.Datetime: fatypes.DateTime, schema.Time: fatypes.Time, schema.Choice: fatypes.Unicode, schema.List: fatypes.List, schema.Password: fatypes.Unicode, } def __init__(self, model, **kwargs): BaseFieldSet.__init__(self, model, **kwargs) self.iface = model self.rebind(model) self._fields = OrderedDict() self._render_fields = OrderedDict() self._bound_pk = None for name, field in schema.getFieldsInOrder(self.iface): klass = field.__class__ try: t = self._fields_mapping[klass] except KeyError: raise NotImplementedError('%s is not mapped to a type' % klass) else: self.append(Field(name=name, type=t)) self._fields[name].label_text = field.title or name if field.description: self._fields[name].set(instructions=field.description) if field.required: self._fields[name].validators.append(validators.required) if klass is schema.Password: self._fields[name].set(renderer=fields.PasswordFieldRenderer) if klass is schema.Text: self._fields[name].set(renderer=fields.TextAreaFieldRenderer) if klass is schema.List: value_type = self.iface[name].value_type if isinstance(value_type, schema.Choice): self._fields[name].set(options=value_type, multiple=True) else: self._fields[name].set(multiple=True) elif klass is schema.Choice: self._fields[name].set(renderer=fields.SelectFieldRenderer, options=self.iface[name]) def bind(self, model, session=None, data=None, request=None): if not (model is not None or session or data): raise Exception('must specify at least one of {model, session, data}') # copy.copy causes a stacktrace on python 2.5.2/OSX + pylons. unable to reproduce w/ simpler sample. mr = object.__new__(self.__class__) mr.__dict__ = dict(self.__dict__) # two steps so bind's error checking can work mr.rebind(model, session, data) mr._request = request mr._fields = OrderedDict([(key, renderer.bind(mr)) for key, renderer in self._fields.iteritems()]) if self._render_fields: mr._render_fields = OrderedDict([(field.key, field) for field in [field.bind(mr) for field in self._render_fields.itervalues()]]) return mr def gen_model(self, model=None, dict_like=False, **kwargs): if model and self.iface.providedBy(model): return model factory = gen_model(self.iface, model, dict_like=dict_like) model = factory(context=model, **kwargs) return model def rebind(self, model, session=None, data=None): if model is not self.iface: if model and not self.iface.providedBy(model): if getattr(model, '__implemented__', None) is not None: raise ValueError('%r does not provide %r' % (model, self.iface)) model = self.gen_model(model) self.model = model self._bound_pk = fields._pk(model) if data is None: self.data = None elif hasattr(data, 'getall') and hasattr(data, 'getone'): self.data = data else: try: self.data = SimpleMultiDict(data) except: raise Exception('unsupported data object %s. currently only dicts and Paste multidicts are supported' % self.data) class Grid(BaseGrid, FieldSet): """Grid aware of zope schema. See :class:`formalchemy.tables.Grid` for full api.""" __sa__ = False def __init__(self, cls, instances=[], **kwargs): FieldSet.__init__(self, cls, **kwargs) self.rows = instances self.readonly = False self._errors = {} def _get_errors(self): return self._errors def _set_errors(self, value): self._errors = value errors = property(_get_errors, _set_errors) def rebind(self, instances=None, session=None, data=None): self.session = session self.data = data if instances is not None: self.rows = instances def bind(self, instances=None, session=None, data=None): mr = FieldSet.bind(self, self.iface, session, data) mr.rows = instances return mr def _set_active(self, instance, session=None): instance = self.gen_model(instance) FieldSet.rebind(self, instance, session or self.session, self.data) FormAlchemy-1.4.2/formalchemy/ext/pylons/__init__.py000664 000765 000024 00000000031 11514360146 022726 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- FormAlchemy-1.4.2/formalchemy/ext/pylons/admin.py000664 000765 000024 00000024422 11514360146 022271 0ustar00gawelstaff000000 000000 # standard pylons controller imports import os import logging log = logging.getLogger(__name__) from pylons import request, response, session, config from pylons import tmpl_context as c from pylons import url from pylons.controllers.util import redirect import pylons.controllers.util as h from webhelpers.paginate import Page from sqlalchemy.orm import class_mapper, object_session from formalchemy import * from formalchemy.i18n import _, get_translator from formalchemy.fields import _pk from formalchemy.templates import MakoEngine import simplejson as json __all__ = ['FormAlchemyAdminController'] # misc labels _('Add') _('Edit') _('New') _('Save') _('Delete') _('Cancel') _('Models') _('Existing objects') _('New object') _('Related types') _('Existing objects') _('Create form') # templates template_dir = os.path.dirname(__file__) static_dir = os.path.join(template_dir, 'resources') def flash(msg): """Add 'msg' to the users flashest list in the users session""" flashes = session.setdefault('_admin_flashes', []) flashes.append(msg) session.save() def get_forms(model_module, forms): """scan model and forms""" if forms is not None: model_fieldsets = dict((form.model.__class__.__name__, form) for form in forms.__dict__.itervalues() if isinstance(form, FieldSet)) model_grids = dict((form.model.__class__.__name__, form) for form in forms.__dict__.itervalues() if isinstance(form, Grid)) else: model_fieldsets = dict() model_grids = dict() # generate missing forms, grids for key, obj in model_module.__dict__.iteritems(): try: class_mapper(obj) except: continue if not isinstance(obj, type): continue if key not in model_fieldsets: model_fieldsets[key] = FieldSet(obj) if key not in model_grids: model_grids[key] = Grid(obj) # add Edit + Delete link to grids for modelname, grid in model_grids.iteritems(): def edit_link(): model_url = url('models', modelname=modelname) return lambda item: '%(label)s' % dict( url=model_url, id=_pk(item), label=get_translator().gettext('edit')) def delete_link(): model_url = url('models', modelname=modelname) return lambda item: '''
''' % dict( url=model_url, id=_pk(item), label=get_translator().gettext('delete')) grid.append(Field('edit', types.String, edit_link())) grid.append(Field('delete', types.String, delete_link())) grid.readonly = True return {'_model_fieldsets':model_fieldsets, '_model_grids':model_grids} class AdminController(object): """Base class to generate administration interface in Pylons""" _custom_css = _custom_js = '' def render_json(self, fs=None, **kwargs): response.content_type = 'text/javascript' if fs: fields = dict([(field.key, field.model_value) for field in fs.render_fields.values()]) data = dict(fields=fields) pk = _pk(fs.model) if pk: data['url'] = url('view_model', modelname=fs.model.__class__.__name__, id=pk) else: data = {} data.update(kwargs) return json.dumps(data) def index(self, format='html'): """List model types""" modelnames = sorted(self._model_grids.keys()) if format == 'json': return self.render_json(**dict([(m, url('models', modelname=m)) for m in modelnames])) return self._engine('admin_index', c=c, modelname=None, modelnames=modelnames, custom_css = self._custom_css, custom_js = self._custom_js) def list(self, modelname, format='html'): """List instances of a model type""" S = self.Session() grid = self._model_grids[modelname] query = S.query(grid.model.__class__) page = Page(query, page=int(request.GET.get('page', '1')), **self._paginate) if format == 'json': values = [] for item in page: pk = _pk(item) values.append((pk, url('view_model', pk))) return self.render_json(records=dict(values), page_count=page.page_count, page=page.page) grid = grid.bind(instances=page, session=None) clsnames = [f.relation_type().__name__ for f in grid._fields.itervalues() if f.is_relation] return self._engine('admin_list', c=c, grid=grid, page=page, clsnames=clsnames, modelname=modelname, custom_css = self._custom_css, custom_js = self._custom_js) def edit(self, modelname, id=None, format='html'): """Edit (or create, if `id` is None) an instance of the given model type""" saved = 1 if id and id.endswith('.json'): id = id[:-5] format = 'json' if request.method == 'POST' or format == 'json': if id: prefix = '%s-%s' % (modelname, id) else: prefix = '%s-' % modelname if request.method == 'PUT': items = json.load(request.body_file).items() request.method = 'POST' elif '_method' not in request.POST: items = request.POST.items() format = 'json' else: items = None if items: for k, v in items: if not k.startswith(prefix): if isinstance(v, list): for val in v: request.POST.add('%s-%s' % (prefix, k), val) else: request.POST.add('%s-%s' % (prefix, k), v) fs = self._model_fieldsets[modelname] S = self.Session() if id: instance = S.query(fs.model.__class__).get(id) assert instance, id title = 'Edit' else: instance = fs.model.__class__ title = 'New object' if request.method == 'POST': F_ = get_translator().gettext c.fs = fs.bind(instance, data=request.POST, session=not id and S or None) if c.fs.validate(): c.fs.sync() S.flush() if not id: # needed if the object does not exist in db if not object_session(c.fs.model): S.add(c.fs.model) message = _('Created %s %s') else: S.refresh(c.fs.model) message = _('Modified %s %s') S.commit() saved = 0 if format == 'html': message = F_(message) % (modelname.encode('utf-8', 'ignore'), _pk(c.fs.model)) flash(message) redirect(url('models', modelname=modelname)) else: c.fs = fs.bind(instance, session=not id and S or None) if format == 'html': return self._engine('admin_edit', c=c, action=title, id=id, modelname=modelname, custom_css = self._custom_css, custom_js = self._custom_js) else: return self.render_json(fs=c.fs, status=saved, model=modelname) def delete(self, modelname, id, format='html'): """Delete an instance of the given model type""" F_ = get_translator().gettext fs = self._model_fieldsets[modelname] S = self.Session() instance = S.query(fs.model.__class__).get(id) key = _pk(instance) S.delete(instance) S.commit() if format == 'html': message = F_(_('Deleted %s %s')) % (modelname.encode('utf-8', 'ignore'), key) flash(message) redirect(url('models', modelname=modelname)) else: return self.render_json(status=0) def static(self, id): filename = os.path.basename(id) if filename not in os.listdir(static_dir): raise IOError('Invalid filename: %s' % filename) filepath = os.path.join(static_dir, filename) if filename.endswith('.css'): response.headers['Content-type'] = "text/css" elif filename.endswith('.js'): response.headers['Content-type'] = "text/javascript" elif filename.endswith('.png'): response.headers['Content-type'] = "image/png" else: raise IOError('Invalid filename: %s' % filename) fd = open(filepath, 'rb') data = fd.read() fd.close() return data class TemplateEngine(MakoEngine): directories = [os.path.join(p, 'fa_admin') for p in config['pylons.paths']['templates']] + [template_dir] _templates = ['base', 'admin_index', 'admin_list', 'admin_edit'] def FormAlchemyAdminController(cls, engine=None, paginate=dict(), **kwargs): """ Generate a controller that is a subclass of `AdminController` and the Pylons BaseController `cls` """ kwargs = get_forms(cls.model, cls.forms) log.info('creating admin controller with args %s' % kwargs) kwargs['_paginate'] = paginate if engine is not None: kwargs['_engine'] = engine else: kwargs['_engine'] = TemplateEngine(input_encoding='utf-8', output_encoding='utf-8') return type(cls.__name__, (cls, AdminController), kwargs) FormAlchemy-1.4.2/formalchemy/ext/pylons/admin_edit.mako000664 000765 000024 00000001411 11514360146 023566 0ustar00gawelstaff000000 000000 <%inherit file="base.mako"/>\ <% from pylons import url %>\ <%def name="title()"> ${F_(action)} ${modelname} ${id and id or ''}
${F_(action)} ${modelname} ${id and id or ''}
${c.fs.render()}
%if id: %else: %endif
FormAlchemy-1.4.2/formalchemy/ext/pylons/admin_index.mako000664 000765 000024 00000000451 11514360146 023753 0ustar00gawelstaff000000 000000 <%inherit file="base.mako"/>\ <% from pylons import url %>\ <%def name="title()"> ${F_('Models')} %for i, modelname in enumerate(modelnames): %endfor
${modelname}
FormAlchemy-1.4.2/formalchemy/ext/pylons/admin_list.mako000664 000765 000024 00000001206 11514360146 023616 0ustar00gawelstaff000000 000000 <%inherit file="base.mako"/>\ <%! from pylons import url %>\ <%def name="title()"> ${modelname} <%def name="sidebar()"> %if clsnames: %endif

${F_('Existing objects')}

${page.pager()}
${grid.render()}
${F_('New object')}
FormAlchemy-1.4.2/formalchemy/ext/pylons/base.mako000664 000765 000024 00000002437 11514360146 022414 0ustar00gawelstaff000000 000000 <% from pylons import url, request %>\ ${self.title()} ${custom_css} ${custom_js} <%def name="sidebar()"> ${self.sidebar()}
<% from pylons import session flashes = session.get('_admin_flashes', []) if flashes: session['_admin_flashes'] = [] session.save() %> %for flash in flashes:
${flash}
%endfor ${self.body()}
FormAlchemy-1.4.2/formalchemy/ext/pylons/controller.py000664 000765 000024 00000041606 11654560772 023403 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- import os from paste.urlparser import StaticURLParser from pylons import request, response, session, tmpl_context as c from pylons.controllers.util import abort, redirect from pylons.templating import render_mako as render from pylons import url from webhelpers.paginate import Page from sqlalchemy.orm import class_mapper, object_session from formalchemy.fields import _pk from formalchemy.fields import _stringify from formalchemy import Grid, FieldSet from formalchemy.i18n import get_translator from formalchemy.fields import Field from formalchemy import fatypes try: from formalchemy.ext.couchdb import Document except ImportError: Document = None import simplejson as json def model_url(*args, **kwargs): """wrap ``pylons.url`` and take care about ``model_name`` in ``pylons.routes_dict`` if any""" if 'model_name' in request.environ['pylons.routes_dict'] and 'model_name' not in kwargs: kwargs['model_name'] = request.environ['pylons.routes_dict']['model_name'] return url(*args, **kwargs) class Session(object): """A abstract class to implement other backend than SA""" def add(self, record): """add a record""" def update(self, record): """update a record""" def delete(self, record): """delete a record""" def commit(self): """commit transaction""" class _RESTController(object): """A RESTful Controller bound to a model""" template = '/forms/restfieldset.mako' engine = prefix_name = None FieldSet = FieldSet Grid = Grid pager_args = dict(link_attr={'class': 'ui-pager-link ui-state-default ui-corner-all'}, curpage_attr={'class': 'ui-pager-curpage ui-state-highlight ui-corner-all'}) @property def model_name(self): """return ``model_name`` from ``pylons.routes_dict``""" return request.environ['pylons.routes_dict'].get('model_name', None) def Session(self): """return a Session object. You **must** override this.""" return Session() def get_model(self): """return SA mapper class. You **must** override this.""" raise NotImplementedError() def sync(self, fs, id=None): """sync a record. If ``id`` is None add a new record else save current one. Default is:: S = self.Session() if id: S.merge(fs.model) else: S.add(fs.model) S.commit() """ S = self.Session() if id: try: S.merge(fs.model) except AttributeError: # SA <= 0.5.6 S.update(fs.model) else: S.add(fs.model) S.commit() def breadcrumb(self, action=None, fs=None, id=None, **kwargs): """return items to build the breadcrumb""" items = [] if self.prefix_name: items.append((url(self.prefix_name), self.prefix_name)) if self.model_name: items.append((model_url(self.collection_name), self.model_name)) elif not self.prefix_name and 'is_grid' not in kwargs: items.append((model_url(self.collection_name), self.collection_name)) if id and hasattr(fs.model, '__unicode__'): items.append((model_url(self.member_name, id=id), u'%s' % fs.model)) elif id: items.append((model_url(self.member_name, id=id), id)) if action in ('edit', 'new'): items.append((None, action)) return items def render(self, format='html', **kwargs): """render the form as html or json""" if format != 'html': meth = getattr(self, 'render_%s_format' % format, None) if meth is not None: return meth(**kwargs) else: abort(404) kwargs.update(model_name=self.model_name or self.member_name, prefix_name=self.prefix_name, collection_name=self.collection_name, member_name=self.member_name, breadcrumb=self.breadcrumb(**kwargs), F_=get_translator()) self.update_resources() if self.engine: return self.engine.render(self.template, **kwargs) else: return render(self.template, extra_vars=kwargs) def render_grid(self, format='html', **kwargs): """render the grid as html or json""" return self.render(format=format, is_grid=True, **kwargs) def render_json_format(self, fs=None, **kwargs): response.content_type = 'text/javascript' if fs: try: fields = fs.jsonify() except AttributeError: fields = dict([(field.renderer.name, field.model_value) for field in fs.render_fields.values()]) data = dict(fields=fields) pk = _pk(fs.model) if pk: data['item_url'] = model_url(self.member_name, id=pk) else: data = {} data.update(kwargs) return json.dumps(data) def render_xhr_format(self, fs=None, **kwargs): response.content_type = 'text/html' if fs is not None: if 'field' in request.GET: field_name = request.GET.get('field') fields = fs.render_fields if field_name in fields: field = fields[field_name] return field.render() else: abort(404) return fs.render() return '' def get_page(self, **kwargs): """return a ``webhelpers.paginate.Page`` used to display ``Grid``. Default is:: S = self.Session() query = S.query(self.get_model()) kwargs = request.environ.get('pylons.routes_dict', {}) return Page(query, page=int(request.GET.get('page', '1')), **kwargs) """ S = self.Session() options = dict(collection=S.query(self.get_model()), page=int(request.GET.get('page', '1'))) options.update(request.environ.get('pylons.routes_dict', {})) options.update(kwargs) collection = options.pop('collection') return Page(collection, **options) def get(self, id=None): """return correct record for ``id`` or a new instance. Default is:: S = self.Session() model = self.get_model() if id: model = S.query(model).get(id) else: model = model() return model or abort(404) """ S = self.Session() model = self.get_model() if id: model = S.query(model).get(id) return model or abort(404) def get_fieldset(self, id=None): """return a ``FieldSet`` object bound to the correct record for ``id``. Default is:: fs = self.FieldSet(self.get(id)) fs.engine = fs.engine or self.engine return fs """ fs = self.FieldSet(self.get(id)) fs.engine = fs.engine or self.engine return fs def get_add_fieldset(self): """return a ``FieldSet`` used for add form. Default is:: fs = self.get_fieldset() for field in fs.render_fields.itervalues(): if field.is_readonly(): del fs[field.name] return fs """ fs = self.get_fieldset() for field in fs.render_fields.itervalues(): if field.is_readonly(): del fs[field.name] return fs def get_grid(self): """return a Grid object Default is:: grid = self.Grid(self.get_model()) grid.engine = self.engine self.update_grid(grid) return grid """ grid = self.Grid(self.get_model()) grid.engine = self.engine self.update_grid(grid) return grid def update_grid(self, grid): """Add edit and delete buttons to ``Grid``""" try: grid.edit except AttributeError: def edit_link(): return lambda item: '''
''' % dict(url=model_url('edit_%s' % self.member_name, id=_pk(item)), label=get_translator()('edit')) def delete_link(): return lambda item: '''
''' % dict(url=model_url(self.member_name, id=_pk(item)), label=get_translator()('delete')) grid.append(Field('edit', fatypes.String, edit_link())) grid.append(Field('delete', fatypes.String, delete_link())) grid.readonly = True def update_resources(self): """A hook to add some fanstatic resources""" pass def index(self, format='html', **kwargs): """REST api""" page = self.get_page() fs = self.get_grid() fs = fs.bind(instances=page) fs.readonly = True if format == 'json': values = [] for item in page: pk = _pk(item) fs._set_active(item) value = dict(id=pk, item_url=model_url(self.member_name, id=pk)) if 'jqgrid' in request.GET: fields = [_stringify(field.render_readonly()) for field in fs.render_fields.values()] value['cell'] = [pk] + fields else: value.update(dict([(field.key, field.model_value) for field in fs.render_fields.values()])) values.append(value) return self.render_json_format(rows=values, records=len(values), total=page.page_count, page=page.page) if 'pager' not in kwargs: pager = page.pager(**self.pager_args) else: pager = kwargs.pop('pager') return self.render_grid(format=format, fs=fs, id=None, pager=pager) def create(self, format='html', **kwargs): """REST api""" fs = self.get_add_fieldset() if format == 'json' and request.method == 'PUT': data = json.load(request.body_file) else: data = request.POST try: fs = fs.bind(data=data, session=self.Session()) except: # non SA forms fs = fs.bind(self.get_model(), data=data, session=self.Session()) if fs.validate(): fs.sync() self.sync(fs) if format == 'html': if request.is_xhr: response.content_type = 'text/plain' return '' redirect(model_url(self.collection_name)) else: fs.rebind(fs.model, data=None) return self.render(format=format, fs=fs) return self.render(format=format, fs=fs, action='new', id=None) def delete(self, id, format='html', **kwargs): """REST api""" record = self.get(id) if record: S = self.Session() S.delete(record) S.commit() if format == 'html': if request.is_xhr: response.content_type = 'text/plain' return '' redirect(model_url(self.collection_name)) return self.render(format=format, id=id) def show(self, id=None, format='html', **kwargs): """REST api""" fs = self.get_fieldset(id=id) fs.readonly = True return self.render(format=format, fs=fs, action='show', id=id) def new(self, format='html', **kwargs): """REST api""" fs = self.get_add_fieldset() fs = fs.bind(session=self.Session()) return self.render(format=format, fs=fs, action='new', id=None) def edit(self, id=None, format='html', **kwargs): """REST api""" fs = self.get_fieldset(id) return self.render(format=format, fs=fs, action='edit', id=id) def update(self, id, format='html', **kwargs): """REST api""" fs = self.get_fieldset(id) if format == 'json' and request.method == 'PUT' and '_method' not in request.GET: data = json.load(request.body_file) else: data = request.POST fs = fs.bind(data=data) if fs.validate(): fs.sync() self.sync(fs, id) if format == 'html': if request.is_xhr: response.content_type = 'text/plain' return '' redirect(model_url(self.member_name, id=id)) else: return self.render(format=format, fs=fs, status=0) if format == 'html': return self.render(format=format, fs=fs, action='edit', id=id) else: return self.render(format=format, fs=fs, status=1) def RESTController(cls, member_name, collection_name): """wrap a controller with :class:`~formalchemy.ext.pylons.controller._RESTController`""" return type(cls.__name__, (cls, _RESTController), dict(member_name=member_name, collection_name=collection_name)) class _ModelsController(_RESTController): """A RESTful Controller bound to more tha one model. The ``model`` and ``forms`` attribute can be a list of object or a module""" engine = None model = forms = None _static_app = StaticURLParser(os.path.join(os.path.dirname(__file__), 'resources')) def Session(self): return meta.Session def models(self, format='html', **kwargs): """Models index page""" models = self.get_models() return self.render(models=models, format=format) def static(self): """Serve static files from the formalchemy package""" return self._static_app(request.environ, self.start_response) def get_models(self): """return a dict containing all model names as key and url as value""" models = {} if isinstance(self.model, list): for model in self.model: key = model.__name__ models[key] = model_url(self.collection_name, model_name=key) else: for key, obj in self.model.__dict__.iteritems(): if not key.startswith('_'): if Document is not None: try: if issubclass(obj, Document): models[key] = model_url(self.collection_name, model_name=key) continue except: pass try: class_mapper(obj) except: continue if not isinstance(obj, type): continue models[key] = model_url(self.collection_name, model_name=key) return models def get_model(self): if isinstance(self.model, list): for model in self.model: if model.__name__ == self.model_name: return model elif hasattr(self.model, self.model_name): return getattr(self.model, self.model_name) abort(404) def get_fieldset(self, id): if self.forms and hasattr(self.forms, self.model_name): fs = getattr(self.forms, self.model_name) fs.engine = fs.engine or self.engine return id and fs.bind(self.get(id)) or fs return _RESTController.get_fieldset(self, id) def get_add_fieldset(self): if self.forms and hasattr(self.forms, '%sAdd' % self.model_name): fs = getattr(self.forms, '%sAdd' % self.model_name) fs.engine = fs.engine or self.engine return fs return self.get_fieldset(id=None) def get_grid(self): model_name = self.model_name if self.forms and hasattr(self.forms, '%sGrid' % model_name): g = getattr(self.forms, '%sGrid' % model_name) g.engine = g.engine or self.engine g.readonly = True self.update_grid(g) return g return _RESTController.get_grid(self) def ModelsController(cls, prefix_name, member_name, collection_name): """wrap a controller with :class:`~formalchemy.ext.pylons.controller._ModelsController`""" return type(cls.__name__, (cls, _ModelsController), dict(prefix_name=prefix_name, member_name=member_name, collection_name=collection_name)) FormAlchemy-1.4.2/formalchemy/ext/pylons/maps.py000664 000765 000024 00000005115 11514360146 022137 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- import pylons import logging log = logging.getLogger(__name__) try: version = pylons.__version__.split('.') except AttributeError: version = ['0', '6'] def format(environ, result): if environ.get('HTTP_ACCEPT', '') == 'application/json': result['format'] = 'json' return True elif 'format' not in result: result['format'] = 'html' return True def admin_map(map, controller, url='%s'): """connect the admin controller `cls` under the given `url`""" log.info('connecting %s to %s' % (url, controller)) map.connect('static_contents', '%s/static_contents/{id}' % url, controller=controller, action='static') map.connect('admin', '%s' % url, controller=controller, action='index') map.connect('formatted_admin', '%s.{format}' % url, controller=controller, action='index') map.connect("models", "%s/{modelname}" % url, controller=controller, action="edit", id=None, format='html', conditions=dict(method=["POST"], function=format)) map.connect("models", "%s/{modelname}" % url, controller=controller, action="list", conditions=dict(method=["GET"], function=format)) map.connect("formatted_models", "%s/{modelname}.{format}" % url, controller=controller, action="list", conditions=dict(method=["GET"])) map.connect("new_model", "%s/{modelname}/new" % url, controller=controller, action="edit", id=None, conditions=dict(method=["GET"])) map.connect("formatted_new_model", "%s/{modelname}/new.{format}" % url, controller=controller, action="edit", id=None, conditions=dict(method=["GET"])) map.connect("%s/{modelname}/{id}" % url, controller=controller, action="edit", conditions=dict(method=["PUT"], function=format)) map.connect("%s/{modelname}/{id}" % url, controller=controller, action="delete", conditions=dict(method=["DELETE"])) map.connect("edit_model", "%s/{modelname}/{id}/edit" % url, controller=controller, action="edit", conditions=dict(method=["GET"])) map.connect("formatted_edit_model", "%s/{modelname}/{id}.{format}/edit" % url, controller=controller, action="edit", conditions=dict(method=["GET"])) map.connect("view_model", "%s/{modelname}/{id}" % url, controller=controller, action="edit", conditions=dict(method=["GET"], function=format)) map.connect("formatted_view_model", "%s/{modelname}/{id}.{format}" % url, controller=controller, action="edit", conditions=dict(method=["GET"])) FormAlchemy-1.4.2/formalchemy/ext/pylons/pastertemplate.py000664 000765 000024 00000001201 11514360146 024221 0ustar00gawelstaff000000 000000 # -*- coding: utf-8 -*- try: from tempita import paste_script_template_renderer from paste.script.templates import Template, var except ImportError: class PylonsTemplate(object): pass else: class PylonsTemplate(Template): _template_dir = ('formalchemy', 'paster_templates/pylons_fa') summary = 'Pylons application template with formalchemy support' required_templates = ['pylons'] template_renderer = staticmethod(paste_script_template_renderer) vars = [ var('admin_controller', 'Add formalchemy\'s admin controller', default=False), ] FormAlchemy-1.4.2/formalchemy/ext/pylons/resources/000775 000765 000024 00000000000 11662023267 022641 5ustar00gawelstaff000000 000000 FormAlchemy-1.4.2/formalchemy/ext/pylons/resources/add.png000664 000765 000024 00000000503 11514360146 024071 0ustar00gawelstaff000000 000000 PNG  IHDRabKGDC pHYs B(xtIME DxIDAT8͑``rҺ`6[;tqDdZ &g7Z,Vi1v`9=0+n/\ϵUR>.009xrbD["[-$mx\9XwV2@@PiAkeRjt&Rȿ&ROXNOӅ43kgO=TrK wIENDB`FormAlchemy-1.4.2/formalchemy/ext/pylons/resources/admin.css000664 000765 000024 00000014707 11514360146 024450 0ustar00gawelstaff000000 000000 /** * Blue Box main CSS file * @version 1.0.0 * @author Aaron D. Campbell http://xavisys.com/ */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { border: 0; font-family: inherit; font-size: 100%; font-style: inherit; font-weight: inherit; margin: 0; outline: 0; padding: 0; vertical-align: baseline; } h1,h2,h3,h4,h5,h6 { font-weight:bold; } h1 { font-size:2em; } h2 { font-size:1.5em; } h3 { font-size:1.3em; } h4 { font-size:1.1em; } h5 { font-size:1em; } h6 { font-size:.8em; } body { background-color:#D5D6D7; color:#333; font:80% Verdana,"Trebuchet MS",Georgia,"Times New Roman",Times,serif; margin:20px; min-width:675px; padding:0pt; } table { margin:1em; } table, table tr { border-collapse:collapse; padding:0; } table th, table td { border:thin solid #D5D6D7; border-collapse:collapse; margin:0; padding:0.2em 0.5em; } table th { font-weight:bold; background:#EFEFEF; } td a { display:block; } tr.odd { background:#EFEFEF; } p { line-height:1.5em; margin:1em 0; } #header { background-color:#73A0C5; border:1px solid #666; border-bottom:none; height:70px; position:relative; } #title { float:left; margin:5px 0 0 1em; } #header h1 { font-size:2em; padding:0pt; } #header #tagline { color:#DDD; font-size:0.9em; font-style:italic; margin:0; text-align:right; } #header h1, #header h1 a, #header h1 a:hover, #header h1 a:visited { color:#FFF; text-decoration:none; } h1#header { color: white; height: 1.5em; padding: 0.3em 1em; } h1#header a { color: white; } .breadcrumb { float:right; font-size: 0.7em;} #nav { bottom:0; position:absolute; right:0; } #nav a { background-color:#EFEFEF; border:1px solid #666; border-bottom:0; color:#259; font-size:1.2em; font-weight:bold; margin:0 .1em; padding:.2em; padding-bottom:0; text-decoration:none; } #nav a:hover, #nav a.current { background-color:#FFF; } #content { background-color:#FFF; border:1px solid #666; border-width:0 1px; padding:1em; } #content ol { margin-left:2em; } #content ul { margin-left:1.5em; } #sidebar { float:right; margin:1em; width:260px; } #sidebar .box p { background-color:#F2F2F2; margin:.5em; } #sidebar .box ul li { border-bottom:1px solid #73A0C5; list-style-type:none; } #sidebar .box ul li a { display:block; padding:.5em; } #sidebar .box label { display:block; float:left; height:21px; margin-right:10px; width:70px; } #footer { background-color:#EFEFEF; border:1px solid #666; border-top:none; font-size:.8em; padding:1em; text-align:center; } #footer p { margin:0; } a, a:link { color:#06C; text-decoration:none; } a:visited { color:#147; } a:hover, a:active { color:#147; text-decoration:underline; } blockquote, code { background-color:#F2F2F2; border-left:4px solid #73A0C5; display:block; font-style:oblique; line-height:20px; margin:0 1em; padding:0 1em; } code { white-space:pre; } .box{ border:1px solid #999; margin:0 0 1em 0; overflow:auto; } .box p, .box ul, .box ol, .box div.cont, .box form { margin:.5em; } .box h1, .box h2, .box h3, .box h4, .box h5, .box h6 { background-color:#73A0C5; display:block; padding:0 5px; color:white; } .more { display:block; font-size:.8em; text-align:right; } /********* * Forms * *********/ .admin-flash, fieldset { color:#777; margin-top:15px; padding:10px; } .admin-flash { background:#EFEFEF; margin-bottom:15px; font-weight:bold; } .message, fieldset, input, button, fieldset textarea, fieldset select { border:1px solid #F5F5F5; border-left-color:#DDD; border-top-color:#DDD; } legend { color:#73A0C5; font-weight:bold; padding:5px 10px; } input, button, fieldset textarea, fieldset select { color:#777; font:90% Verdana; padding:4px; } fieldset textarea { width:430px; } option { padding:0 10px 0 5px; } fieldset label, fieldset p.label { color:#777; text-align:right; width:145px; } fieldset label { float:left; margin:5px 0; margin-right:10px; } fieldset p { margin:0; } fieldset div { padding:5px 0; position:relative; } fieldset div div { margin:0; } fieldset p.label { left:0; position:absolute; } .radio { margin-left:160px; } .radio label, .radio input { background:none; border:none; display:inline; float:none; vertical-align:middle; width:auto; } .radio div { clear:none; white-space:nowrap; } #sidebar form { margin:0 0 1em 0; } .submit, #sidebar .submit { text-align:right; } .ui-widget-link, .submit input, #sidebar .submit input { background-color:#F9F9F9; border:1px solid #F5F5F5; border-left-color:#DDD; border-top-color:#DDD; cursor:pointer; padding:0 21px; text-transform:lowercase; width:auto; } .ui-widget-link input { border:0px; background:transparent; } a.ui-widget-link { color:#777777; font-family:Verdana; } .ui-widget-link { cursor:pointer; margin-right:0.3em; } td form { text-align:center;} td input.ui-icon { background-color:transparent; border:0px; width:16px; height: 0px; overflow:hidden; padding-bottom:13px; cursor:pointer; } .ui-icon-pencil { background-image: url(./edit.png); } .ui-icon-circle-close { background-image: url(./delete.png); } #sidebar input { border:1px solid #DDD; border-bottom-color:#F5F5F5; border-right-color:#F5F5F5; width:100%; } #sidebar label { height:auto; margin-bottom:0; width:auto; } .button { width:auto; } .form_controls { margin-left:155px; } .icon { display:block; height:0px; padding-top:16px; width:16px; overflow:hidden; } input.icon { border:none; width:16px; height:16px; padding:0; cursor:pointer; } #pager { text-align: center; margin-top: 0.5em; } #pager a, #pager .pager_curpage { padding: 0 0.2em; border:thin solid #114477; background: #73A0C5; color: white; } #pager a:hover, #pager .pager_curpage { text-decoration:none; background:white; color:#114477; } /*****************************/ FormAlchemy-1.4.2/formalchemy/ext/pylons/resources/delete.png000664 000765 000024 00000000367 11514360146 024613 0ustar00gawelstaff000000 000000 PNG  IHDRabKGDC pHYs B(xtIME ;"\IDAT8푱 Pi:+#mF stԶ"DO!ʳ3w&H6暺`?S-FIENDB`FormAlchemy-1.4.2/formalchemy/ext/pylons/resources/edit.png000664 000765 000024 00000001076 11514360146 024274 0ustar00gawelstaff000000 000000 PNG  IHDRabKGD pHYs  tIME 7);RHIDAT8˥MhA3ݣH"-)X+xEж'< "~1[M\bӍLlZx0Cmj+ ofރ a]VzxG/fw؎XM9WOJݬ;-T@YXݓ zV_LKNJ7qĉ?}MeM}'O2t3@zz}KAI{|-Mu=۶m;R>n&X^Q@dct| UsEΟ0 M ҽ \y7}7FdY~C;>Yz=iv4nK!?y%;nL&q]7\OPV<G;+H%H`YeExA,CkMVCk5Bl:ud})0oZy IENDB`FormAlchemy-1.4.2/docs/config.txt000664 000765 000024 00000000210 11514367006 017122 0ustar00gawelstaff000000 000000 :mod:`formalchemy.config` -- Global configuration ================================================= .. automodule:: formalchemy.config FormAlchemy-1.4.2/docs/customisation.txt000664 000765 000024 00000001572 11514367006 020572 0ustar00gawelstaff000000 000000 Other customizations ==================== Customization: CSS ------------------ `FormAlchemy` uses the following CSS classes: - `fieldset_error`: class for a div containing a "global" error - `field_error`: class for a span containing an error from a single `Field` - `field_req`: class for a label for a required field - `field_opt`: class for a label for an optional field - `field_readonly`: class for the td of the 'label' for a field in a readonly `FieldSet` table - `grid_error`: class for a span containing an error from a single `Field` in a `Grid` Here is some basic CSS for aligning your forms nicely:: label { float: left; text-align: right; margin-right: 1em; width: 10em; } form div { margin: 0.5em; float: left; width: 100%; } form input[type="submit"] { margin-top: 1em; margin-left: 9em; } FormAlchemy-1.4.2/docs/doc.txt000664 000765 000024 00000000511 11514367006 016426 0ustar00gawelstaff000000 000000 Modules contents ================== .. toctree:: :maxdepth: 2 :glob: formalchemy models fields forms tables validators internationalisation config templates customisation pylons_sample ext/* Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` FormAlchemy-1.4.2/docs/fields.txt000664 000765 000024 00000027614 11560020765 017143 0ustar00gawelstaff000000 000000 :mod:`formalchemy.fields` -- `Fields` and `Renderers` ===================================================== .. automodule:: formalchemy.fields .. Commented imports >>> from formalchemy.fields import * >>> from formalchemy.tests import * >>> from datetime import datetime Fields ------ .. autoclass:: AbstractField :members: .. autoclass:: Field :members: .. autoclass:: AttributeField :members: Renderers --------- It is important to note that althought these objects are called `renderers`, they are also responsible for deserialization of data received from the web and insertion of those (possibly mangled) values back to the SQLALchemy object, if any. They also have to take into consideration that the data used when displaying `can` come either from the `self.params` (the dict-like object received from the web) or from the model. The latter case happens when first displaying a form, and the former when validation triggered an error, and the form is to be re-displayed (and still contain the values you entered). FieldRenderer ************* .. autoclass:: FieldRenderer :members: TextFieldRenderer ***************** .. autoclass:: TextFieldRenderer :members: Render a string field:: >>> fs = FieldSet(One) >>> fs.append(Field(name='text', type=types.String, value='a value')) Edit mode:: >>> print fs.text.render() Read only mode:: >>> print fs.text.render_readonly() a value IntegerFieldRenderer ******************** .. autoclass:: IntegerFieldRenderer :members: PasswordFieldRenderer ********************* .. autoclass:: PasswordFieldRenderer :members: Render a string field:: >>> fs = FieldSet(One) >>> fs.append(Field(name='passwd').with_renderer(PasswordFieldRenderer)) Edit mode:: >>> print fs.passwd.render() Read only mode:: >>> print fs.passwd.render_readonly() ****** TextAreaFieldRenderer ********************* .. autoclass:: TextAreaFieldRenderer :members: Render a string field:: >>> fs = FieldSet(One) >>> fs.append(Field(name='text',value='a value').with_renderer(TextAreaFieldRenderer)) Edit mode:: >>> print fs.text.render() Read only mode:: >>> print fs.text.render_readonly() a value HiddenFieldRenderer ******************* .. autoclass:: HiddenFieldRenderer :members: Render a string field:: >>> fs = FieldSet(One) >>> fs.append(Field(name='text', value='h').with_renderer(HiddenFieldRenderer)) Edit mode:: >>> print fs.render() Read only mode:: >>> print fs.text.render_readonly() HiddenFieldRendererFactory *************************** .. autofunction:: HiddenFieldRendererFactory CheckBoxFieldRenderer ********************* .. autoclass:: CheckBoxFieldRenderer :members: FileFieldRenderer ***************** .. autoclass:: FileFieldRenderer :members: DateFieldRenderer ***************** .. autoclass:: DateFieldRenderer :members: Render a date field:: >>> date = datetime(2000, 12, 31, 9, 00) >>> fs = FieldSet(One) >>> fs.append(Field(name='date', type=types.Date, value=date)) Edit mode:: >>> print pretty_html(fs.date.render()) #doctest: +ELLIPSIS Read only mode:: >>> print fs.date.render_readonly() 2000-12-31 TimeFieldRenderer ***************** .. autoclass:: TimeFieldRenderer :members: Render a time field:: >>> time = datetime(2000, 12, 31, 9, 03, 30).time() >>> fs = FieldSet(One) >>> fs.append(Field(name='time', type=types.Time, value=time)) Edit mode:: >>> print pretty_html(fs.time.render()) #doctest: +ELLIPSIS : : Read only mode:: >>> print fs.time.render_readonly() 09:03:30 DateTimeFieldRenderer ********************* .. autoclass:: DateTimeFieldRenderer :members: Render a datetime field:: >>> datetime = datetime(2000, 12, 31, 9, 03, 30) >>> fs = FieldSet(One) >>> fs.append(Field(name='datetime', type=types.DateTime, value=datetime)) Edit mode:: >>> print pretty_html(fs.datetime.render()) #doctest: +ELLIPSIS : : Read only mode:: >>> print fs.datetime.render_readonly() 2000-12-31 09:03:30 HiddenDateFieldRenderer *********************** .. autoclass:: HiddenDateFieldRenderer HiddenTimeFieldRenderer *********************** .. autoclass:: HiddenTimeFieldRenderer HiddenDateTimeFieldRenderer *************************** .. autoclass:: HiddenDateTimeFieldRenderer RadioSet ******** .. autoclass:: RadioSet :members: CheckBoxSet *********** .. autoclass:: CheckBoxSet :members: SelectFieldRenderer ******************* .. autoclass:: SelectFieldRenderer :members: EscapingReadonlyRenderer ************************ .. autoclass:: EscapingReadonlyRenderer :members: Custom renderer --------------- You can write your own `FieldRenderer` s to customize the widget (input element[s]) used to edit different types of fields... 1. Subclass `FieldRenderer`. 1. Override `render` to return a string containing the HTML input elements desired. Use `self.name` to get a unique name and id for the input element. `self._value` may also be useful if you are not rendering multiple input elements. 2. If you are rendering a custom type (any class you defined yourself), you will need to override `deserialize` as well. `render` turns the user-submitted data into a Python value. (The raw data will be available in self.field.parent.data, or you can use `_serialized_value` if it is convenient.) For SQLAlchemy collections, return a list of primary keys, and `FormAlchemy` will take care of turning that into a list of objects. For manually added collections, return a list of values. 3. If you are rendering a builtin type with multiple input elements, override `_serialized_value` to return a single string combining the multiple input pieces. See the source for DateFieldRenderer for an example. 2. Update `FieldSet.default_renderers`. `default_renderers` is a dict of FieldRenderer subclasses. The default contents of `default_renderers` is:: .. literalinclude:: ../formalchemy/forms.py :pyobject: DefaultRenderers For instance, to make `Boolean` s render as select fields with Yes/No options by default, you could write:: >>> from formalchemy.fields import SelectFieldRenderer >>> class BooleanSelectRenderer(SelectFieldRenderer): ... def render(self, **kwargs): ... kwargs['options'] = [('Yes', True), ('No', False)] ... return SelectFieldRenderer.render(self, **kwargs) >>> FieldSet.default_renderers[types.Boolean] = BooleanSelectRenderer Of course, you can subclass `FieldSet` if you don't want to change the defaults globally. One more example, this one to use the `JQuery UI DatePicker `_ to render `Date` objects:: >>> from formalchemy.fields import FieldRenderer >>> class DatePickerFieldRenderer(FieldRenderer): ... def render(self): ... value= self.value and self.value or '' ... vars = dict(name=self.name, value=value) ... return """ ... ... ... """ % vars (Obviously the page template will need to add references to the jquery library and css.) Another example to render a link field:: >>> class LinkFieldRenderer(FieldRenderer): ... def render(self, **kwargs): ... """render html for edit mode""" ... from formalchemy import helpers as h ... return h.text_field(self.name, value=self._value, **kwargs) ... def render_readonly(self, **kwargs): ... """render html for read only mode""" ... kwargs = {'value':self.field.raw_value} ... return '%(value)s' % kwargs Then bind it to a specific field:: >>> from formalchemy.tests import * >>> fs = FieldSet(One) >>> fs.append(Field('link', value='http://www.formalchemy.org')) >>> fs.configure(include=[fs.link.with_renderer(LinkFieldRenderer)]) Here is the result for edit mode:: >>> print fs.render()
And for read only mode:: >>> fs.readonly = True >>> print fs.render() Link: http://www.formalchemy.org FormAlchemy-1.4.2/docs/formalchemy.txt000664 000765 000024 00000001236 11542716535 020202 0ustar00gawelstaff000000 000000 :mod:`formalchemy` -- Imports ============================= .. automodule:: formalchemy All `FormAlchemy`'s objects live under the `formalchemy` package :mod:`~formalchemy.forms` related classes:: >>> from formalchemy import FieldSet, Field :mod:`~formalchemy.validators`:: >>> from formalchemy import validators, ValidationError For manual Field definition:: >>> from formalchemy import types :mod:`~formalchemy.tables` for collection rendering:: >>> from formalchemy import Grid Advanced :mod:`~formalchemy.fields` customization:: >>> from formalchemy import FieldRenderer The above imports are equivalent to:: >>> from formalchemy import * FormAlchemy-1.4.2/docs/forms.txt000664 000765 000024 00000035613 11542765145 017031 0ustar00gawelstaff000000 000000 :mod:`formalchemy.forms` -- `FieldSet`: Form generation ******************************************************* .. Commented imports >>> from formalchemy.tests import * .. automodule:: formalchemy.forms Configuring and rendering forms =============================== In FormAlchemy, forms are rendered using the `FieldSet` object. There are several operations that can be made on a FieldSet. They can be `bound`, `configured`, `validated`, and `sync'd`. * `Binding` attaches a model object to the `FieldSet`. * `Configuration` tells the `FieldSet` which fields to include, in which order, etc. * `Validation` checks the form-submitted parameters for correctness against the FieldSet's validators and field definitions. * `Synchronization` fills the model object with values taken from the web form submission. Binding ------- Binding occurs at first on :class:`FieldSet` object creation. The :class:`~formalchemy.forms.FieldSet` object constructor takes it's parameters and calls it's base class's constructor. Fields ------ Each :class:`~formalchemy.forms.FieldSet` will have a :mod:`Field ` created for each attribute of the bound model. Additional :mod:`Field `s may be added manually; see below. A :mod:`Field ` knows how to render itself, and most customization is done by telling a :mod:`Field ` to modify itself appropriately. :mod:`Field `-s are accessed simply as attributes of the :class:`~formalchemy.forms.FieldSet`:: >>> fs = FieldSet(bill) >>> print fs.name.value Bill If you have an attribute name that conflicts with a built-in :class:`~formalchemy.forms.FieldSet` attribute, you can use `fs[fieldname]` instead. So these are equivalent:: >>> fs.name == fs['name'] True Field Modification ------------------ :mod:`Field ` rendering can be modified with the following methods: - `validate(self, validator)`: Add the `validator` function to the list of validation routines to run when the :class:`~formalchemy.forms.FieldSet`'s `validate` method is run. Validator functions take one parameter: the value to validate. This value will have already been turned into the appropriate data type for the given :mod:`Field ` (string, int, float, etc.). It should raise `ValidationException` if validation fails with a message explaining the cause of failure. - `required(self)`: Convenience method for `validate(validators.required)`. By default, NOT NULL columns are required. You can only add required-ness, not remove it. - `label(self)`: Change the label associated with this field. By default, the field name is used, modified for readability (e.g., 'user_name' -> 'User name'). - `with_null_as(self, option)`: For optional foreign key fields, render null as the given option tuple of text, value. - `with_renderer(self, renderer)`: Change the renderer class used to render this field. Used for one-off renderer changes; if you want to change the renderer for all instances of a Field type, modify FieldSet.default_renderers instead. - `with_metadata(self, **attrs)`: Add/modify some metadata for the Field. Use this to attach any metadata to your field. By default, the the `instructions` property is used to show additional text below or beside your rendered Field. - `disabled(self)`: Render the field disabled. - `readonly(self)`: Render the field readonly. - `hidden(self)`: Render the field hidden. (Value only, no label.) - `password(self)`: Render the field as a password input, hiding its value. - `textarea(self, size=None)`: Render the field as a textarea. - `radio(self, options=None)`: Render the field as a set of radio buttons. - `checkbox(self, options=None)`: Render the field as a set of checkboxes. - `dropdown(self, options=None, multiple=False, size=5)`: Render the field as an HTML select field. (With the `multiple` option this is not really a 'dropdown'.) Methods taking an `options` parameter will accept several ways of specifying those options: - an iterable of SQLAlchemy objects; `str()` of each object will be the description, and the primary key the value - a SQLAlchemy query; the query will be executed with `all()` and the objects returned evaluated as above - an iterable of (description, value) pairs - a dictionary of {description: value} pairs - a callable that return one of those cases. Used to evaluate options each time. Options can be "chained" indefinitely because each modification returns a new :mod:`Field ` instance, so you can write:: >>> fs.append(Field('foo').dropdown(options=[('one', 1), ('two', 2)]).radio()) or:: >>> fs.configure(options=[fs.name.label('Username').readonly()]) Here is a callable exemple:: >>> def custom_query(fs): ... return fs.session.query(User).filter(User.name=='Bill') >>> fs3 = FieldSet(bill) >>> fs3.configure(options=[fs3.name.dropdown(options=custom_query)]) >>> print fs3.name.render() Manipulating Fields -------------------- You can add additional fields not in your SQLAlchemy model with the `append` method, which takes a :mod:`Field ` object as parameter:: >>> fs3 = FieldSet(bill) >>> fs3.configure(include=[fs3.name, fs3.email]) >>> fs3.append(Field('password', renderer='password')) >>> fs3.render_fields.keys() ['name', 'email', 'password'] You can also `insert` fields. Here we add a country before the password field:: >>> fs3.insert(fs3.password, Field('country')) >>> fs3.render_fields.keys() ['name', 'email', 'country', 'password'] And finally, you can `delete` fields:: >>> del fs3.country >>> fs3.render_fields.keys() ['name', 'email', 'password'] >>> del fs3['password'] >>> fs3.render_fields.keys() ['name', 'email'] Here is `Field`'s constructor: .. automethod:: formalchemy.fields.Field.__init__ Fields to render ---------------- The `configure` method specifies a set of attributes to be rendered. By default, all attributes are rendered except primary keys and foreign keys. But, relations **based on** foreign keys **will** be rendered. For example, if an `Order` has a `user_id` FK and a `user` relation based on it, `user` will be rendered (as a select box of `User`'s, by default) but `user_id` will not. See parameters in :meth:`FieldSet.configure`. Examples: given a :class:`~formalchemy.forms.FieldSet` `fs` bound to a `User` instance as a model with primary key `id` and attributes `name` and `email`, and a relation `orders` of related Order objects, the default will be to render `name`, `email`, and `orders`. To render the orders list as checkboxes instead of a select, you could specify:: >>> fs2 = fs.bind(bill) >>> fs2.configure(options=[fs.orders.checkbox()]) To render only name and email:: >>> fs2 = fs.bind(bill) >>> fs2.configure(include=[fs.name, fs.email]) or:: >>> fs2 = fs.bind(bill) >>> fs2.configure(exclude=[fs.orders]) Of course, you can include modifications to a field in the `include` parameter, such as here, to render name and options-as-checkboxes:: >>> fs2 = fs.bind(bill) >>> fs2.configure(include=[fs.name, fs.orders.checkbox()]) Rendering --------- Once you've configured your :class:`~formalchemy.forms.FieldSet`, just call the `render` method to get an HTML string suitable for including in your page:: >>> fs = FieldSet(bill) >>> print fs.render()
Note that there is no `form` element! You must provide that yourself. You can also render individual fields for more fine-grained control:: >>> fs = FieldSet(bill) >>> print fs.name.render() Custom FieldSet =============== You can customize your FieldSet, and create a ready-made derived version for when you need it in your application. For example, you could create one FieldSet per model object in your application. In this example, we create a FieldSet to edit the `User` model object: .. sourcecode:: py from formalchemy import validators class UserFieldSet(FieldSet): """Used to edit users""" def __init__(self): """Pre-configuration""" FieldSet.__init__(self, model.User) self.add(Field('passwd1')) self.add(Field('passwd2')) inc = [self.username, self.passwd1.password().label(u'Password'), self.passwd2.password().label(u'Confirm') \ .validate(validators.passwords_match('passwd1')), self.email, self.firstname, self.lastname, ] self.configure(include=inc) Then you could use it in your framework controllers as: .. sourcecode:: py fs = UserFieldSet().bind(my_user_object, data=request.POST or None) if request.POST and fs.validate(): fs.sync() fs.model.password = fs.passwd1.value ... Another option would be to create a function that generates your FieldSet, perhaps at the top of your controller if it's not to be reused anywhere, otherwise in a central lib for your application. Then you would call your function instead of the `forms.UserFieldSet()` above. You can use the `.insert`, `.insert_after`, `.append`, `.extend` functions to tweak your FieldSet's composition afterwards. You can also use the `del` keyword on ``Field`` attributes (like `fs.passwd`) to remove them from the FieldSet. You'll probably want to modify the default behavior for fields using the `.set` function on the ``Field`` attributes directly. This will tweak the objects in-place. Including data from more than one class ======================================= `FormAlchemy` only supports binding to a single class, but a single class can itself include data from multiple tables. Example:: >>> class Order__User(Base): ... __table__ = join(Order.__table__, User.__table__).alias('__orders__users') Such a class can then be used normally in a :class:`~formalchemy.forms.FieldSet`. See http://www.sqlalchemy.org/docs/05/mappers.html#advdatamapping_mapper_joins for full details on mapping multiple tables to a single class. Non-SQLAlchemy forms ==================== You can create a :class:`~formalchemy.forms.FieldSet` from non-SQLAlchemy, new-style (inheriting from `object`) classes, like this:: >>> class Manual(object): ... a = Field() ... b = Field(type=types.Integer).dropdown([('one', 1), ('two', 2)]) >>> fs = FieldSet(Manual) :mod:`Field ` declaration is the same as for adding fields to a SQLAlchemy-based :class:`~formalchemy.forms.FieldSet`, except that you do not give the Field a name (the attribute name is used). You can still validate and sync a non-SQLAlchemy class instance, but obviously persisting any data post-sync is up to you. You can also have a look at :mod:`formalchemy.ext.zope`. A note on Sessions ================== `FormAlchemy` can save you the most time if you use contextual Sessions: http://www.sqlalchemy.org/docs/05/session.html#contextual-thread-local-sessions. Otherwise, you will have to manually pass Session objects when you bind :class:`~formalchemy.forms.FieldSet` and :class:`~formalchemy.tables.Grid` instances to your data. Advanced Customization: Form Templates ====================================== There are three parts you can customize in a `FieldSet` subclass short of writing your own render method. These are `default_renderers`, and `prettify`. As in:: >>> from formalchemy import fields >>> def myprettify(value): ... return value >>> def myrender(**kwargs): ... return template % kwargs >>> class MyFieldSet(FieldSet): ... default_renderers = { ... types.String: fields.TextFieldRenderer, ... types.Integer: fields.IntegerFieldRenderer, ... # ... ... } ... prettify = staticmethod(myprettify) ... _render = staticmethod(myrender) `default_renderers` is a dict of callables returning a FieldRenderer. Usually these will be FieldRenderer subclasses, but this is not required. For instance, to make Booleans render as select fields with Yes/No options by default, you could write:: >>> class BooleanSelectRenderer(fields.SelectFieldRenderer): ... def render(self, **kwargs): ... kwargs['options'] = [('Yes', True), ('No', False)] ... return fields.SelectFieldRenderer.render(self, **kwargs) >>> FieldSet.default_renderers[types.Boolean] = BooleanSelectRenderer `prettify` is a function that, given an attribute name ('user_name') turns it into something usable as an HTML label ('User name'). `_render` should be a template rendering method, such as `Template.render` from a mako Template or `Template.substitute` from a Tempita Template. `_render` should take as parameters: - `fieldset` the :class:`~formalchemy.forms.FieldSet` object to render Your template will be particularly interested in these :class:`~formalchemy.forms.FieldSet` attributes: - `render_fields`: the list of fields the user has configured for rendering - `errors`: a dictionary of validation failures, keyed on field. `errors[None]` are errors applying to the form as a whole rather than a specific field. - `prettify`: as above - `focus`: the field to focus You can also override `prettify` and `_render` on a per-:class:`~formalchemy.forms.FieldSet` basis:: fs = FieldSet(...) fs.prettify = myprettify fs._render = ... The default template is `formalchemy.forms.template_text_tempita`. Classes definitions =================== FieldSet -------- .. autoclass:: formalchemy.forms.FieldSet :members: FormAlchemy-1.4.2/docs/index.txt000664 000765 000024 00000000666 11514367006 017003 0ustar00gawelstaff000000 000000 Welcome to FormAlchemy's documentation! ======================================= .. seealso:: If you use the trunk you may look at a more up to date version of the documentation at `http://docs.formalchemy.org/current/ `_. .. toctree:: :maxdepth: 3 doc Changes ======= .. include:: ../CHANGELOG.txt Copyright and License ===================== .. include:: ../COPYRIGHT.txt FormAlchemy-1.4.2/docs/internationalisation.txt000664 000765 000024 00000002414 11514367006 022123 0ustar00gawelstaff000000 000000 :mod:`formalchemy.i18n` -- Internationalisation ================================================ .. automodule:: formalchemy.i18n .. Commented imports >>> from library import fs `FormAlchemy` is able to render error messages in your own language. You just need to provide a `lang` attribute to the render method:: >>> html_fr = fs.render(lang='fr') If you use `Pylons` the language is retrieved from `pylons.i18n.get_lang()` so the `lang` attribute become optional. At the moment only the french translation is available. You have to checkout the source (http://code.google.com/p/formalchemy/source/checkout) and install `FormAlchemy` in develop mode to add a new translation:: $ cd FormAlchemy && python setup.py develop Then install Babel with easy_install:: $ easy_install Babel You are now able to initialize a new catalog $ python setup.py init_catalog -l Where `` is your language code. This will generate a new file named `formalchemy/i18n//LC_MESSAGES/formalchemy.po` Replace all the `msgstr` in the new `.po` file with your translated messages and compile the catalogs:: $ python setup.py compile_catalog Now the new language is avalaible. Last step, send your `.po` to the [http://groups.google.com/group/formalchemy project list] ! FormAlchemy-1.4.2/docs/models.txt000664 000765 000024 00000002000 11577472546 017156 0ustar00gawelstaff000000 000000 Models API ================== FormAlchemy is aware of the ``__unicode__`` and ``__html__`` methods: .. sourcecode:: python class User(Base): """A User model""" __tablename__ = 'users' id = Column(Integer, primary_key=True) email = Column(Unicode(40), unique=True, nullable=False) password = Column(Unicode(20), nullable=False) name = Column(Unicode(30)) def __unicode__(self): """This is used to render the model in a relation field. Must return an unicode string.""" return self.name def __html__(self): """This is used to render the model in relation field (readonly mode). You need need to clean up the html yourself. Use it at your own risk.""" return '%s' % (self.email, self.name) def __repr__(self): return '' % self.name You can also use the :func:`formalchemy.Column` wrapper to set some extra options: .. autofunction:: formalchemy.Column FormAlchemy-1.4.2/docs/pylons_sample.txt000664 000765 000024 00000002402 11514367006 020547 0ustar00gawelstaff000000 000000 Pylons integration ================== Bootstrap your project ---------------------- `FormAlchemy` come with a subclass of the Pylons_ template. If you have Pylons_ and `FormAlchemy` installed you should see that:: $ paster create --list-templates Available templates: basic_package: A basic setuptools-enabled package paste_deploy: A web application deployed through paste.deploy pylons: Pylons application template pylons_fa: Pylons application template with formalchemy support pylons_minimal: Pylons minimal application template To bootstrap a new Pylons_ project with `FormAlchemy` support enable just run:: $ paster create -t pylons_fa pylonsapp Using forms in controllers -------------------------- Imagine you have a `Foo` model in your `model/__init__.py` then your controller can look like this: .. literalinclude:: ../pylonsapp/pylonsapp/controllers/basic.py If you have a lot of fieldset and configuration stuff and want to use them in different controller, then you can use the `forms/` module to put your fieldsets. This is a standard and allow you to use the :mod:`formalchemy.ext.pylons` extension .. _Pylons: http://pylonshq.com/ You can also have a look at the :ref:`RESTful Controller ` FormAlchemy-1.4.2/docs/tables.txt000664 000765 000024 00000010757 11514367006 017150 0ustar00gawelstaff000000 000000 :mod:`formalchemy.tables` -- `Grid`: Rendering collections ========================================================== .. automodule:: formalchemy.tables Besides :class:`~formalchemy.forms.FieldSet`, `FormAlchemy` provides `Grid` for editing and rendering multiple instances at once. Most of what you know about :class:`~formalchemy.forms.FieldSet` applies to `Grid`, with the following differences to accomodate being bound to multiple objects: The Grid class -------------- .. autoclass:: Grid :members: Creating -------- The `Grid` constructor takes parameters (`cls`, `instances=[]`, `session=None`, `data=None`). A significant difference from :class:`~formalchemy.forms.FieldSet` is that the first argument must _always_ be a mapped class, e.g., `User`. `instances` is the objects to render, which must all be of the given type. The other parameters are the same as in :class:`~formalchemy.forms.FieldSet`. Binding ------- `Grid` `bind` and `rebind` methods are similar to those methods in :class:`~formalchemy.forms.FieldSet`, except they take an iterable `instances` instead of an instance `model`. Thus, the full signature is (`instances`, `session=None`, `data=None`). Configuration ------------- The `Grid` `configure` method takes the same arguments as :class:`~formalchemy.forms.FieldSet` (`pk`, `exclude`, `include`, `options`, `readonly`), except there is no `focus` argument. Validation and Sync ------------------- These are the same as in :class:`~formalchemy.forms.FieldSet`, except that you can also call `sync_one(instance)` to sync a single one of the instances that are bound to the `Grid`. The `Grid` `errors` attribute is a dictionary keyed by bound instance, whose value is similar to the `errors` from a :class:`~formalchemy.forms.FieldSet`, that is, a dictionary whose keys are `Field`s, and whose values are `ValidationError` instances. Customizing Grid ---------------- Overriding `Grid` rendering is similar to :class:`~formalchemy.forms.FieldSet`. The differences are: * The default templates take a `collection` parameter instead of `fieldset`, which is the instance of `Grid` to render * The instances given to the collection are available in `collection.rows`; to access the fields of each single row, call `_set_active(row)`, then access `render_fields`. The default templates are `formalchemy.tables.template_grid_readonly` and `formalchemy.tables.template_grid`. Usage ----- You need some imports:: >>> from formalchemy.tables import * .. Hidden code needed for testing >>> from formalchemy.tests import * Then you can initialize a `Grid` and bind it to a list of row instance:: >>> tc = Grid(User, [bill]) >>> tc.configure(readonly=True) This will render instances as an html table:: >>> print tc.render() Email Password Name Orders bill@example.com 1234 Bill Quantity: 10 You can also add a field to the `Grid` manually:: >>> tc2 = Grid(User, [bill, john]) >>> tc2.append(Field('link', type=types.String, value=lambda item: 'link' % item.id)) >>> tc2.configure(readonly=True) >>> print tc2.render() Email Password Name Orders Link bill@example.com 1234 Bill Quantity: 10 link john@example.com 5678 John Quantity: 5, Quantity: 6 link You can provide a dict as new values:: >>> g = Grid(User, [bill, john], data={'User-1-email': 'bill_@example.com', 'User-1-password': '1234_', 'User-1-name': 'Bill_', 'User-1-orders': '1', 'User-2-email': 'john_@example.com', 'User-2-password': '5678_', 'User-2-name': 'John_', 'User-2-orders': ['2', '3'], }) Validation work like `Fieldset`:: >>> g.validate() True Sync too: >>> g.sync() >>> session.flush() >>> session.refresh(john) >>> john.email == 'john_@example.com' True >>> session.rollback() FormAlchemy-1.4.2/docs/templates.txt000664 000765 000024 00000006263 11514367006 017671 0ustar00gawelstaff000000 000000 :mod:`formalchemy.templates` -- Template engines ================================================= .. automodule:: formalchemy.templates .. Commented imports >>> from formalchemy.tests import User >>> from formalchemy.forms import FieldSet >>> from formalchemy.templates import TemplateEngine >>> from formalchemy import config >>> old_engine = config.engine Available engines ----------------- .. autoclass:: MakoEngine :members: .. autoclass:: GenshiEngine :members: .. autoclass:: TempitaEngine :members: Base class ---------- .. autoclass:: TemplateEngine :members: Customize templates ------------------- You can override the default template by adding a directory for your project which will contain the templates. The engine will scan the directory and try to load templates from it. If he can't, the default templates are used. .. set templates dirs >>> import os >>> import formalchemy.tests >>> from formalchemy. tests import ls, cat >>> mako_templates_dir = os.path.join(os.path.dirname(formalchemy.tests.__file__), 'data', 'mako') >>> genshi_templates_dir = os.path.join(os.path.dirname(formalchemy.tests.__file__), 'data', 'genshi') Here is an example:: >>> ls(mako_templates_dir) - fieldset.mako >>> cat(mako_templates_dir, 'fieldset.mako')
    %for field in fieldset.render_fields.itervalues():
  • ${field.name}
  • %endfor
Then you can override the default mako templates:: >>> from formalchemy import config >>> from formalchemy import templates >>> config.engine = templates.MakoEngine( ... directories=[mako_templates_dir], ... input_encoding='utf-8', output_encoding='utf-8') And see the result:: >>> print FieldSet(User).render()
  • email
  • password
  • name
  • orders
Same with genshi except that :mod:`formalchemy` don't provide default templates:: >>> cat(genshi_templates_dir, 'fieldset.html')
  • ${field.name}
>>> config.engine = templates.GenshiEngine(directories=[genshi_templates_dir]) And same the result of course:: >>> print FieldSet(User).render()
  • email
  • password
  • name
  • orders
Write your own engine ---------------------- You need to subclass the :class:`~formalchemy.templates.TemplateEngine`:: >>> class MyEngine(TemplateEngine): ... def render(self, template_name, **kw): ... return 'It works !' You can use it for a specific :class:`~formalchemy.forms.FieldSet`:: >>> fs = FieldSet(User) >>> fs.engine = MyEngine() >>> print fs.render() It works ! You can also override the engine in a subclass:: >>> class MyFieldSet(FieldSet): ... engine = MyEngine() Or set it as the global engine with :mod:`formalchemy`'s :mod:`~formalchemy.config`:: >>> from formalchemy import config >>> config.engine = MyEngine() It should be available for all :class:`~formalchemy.forms.FieldSet`:: >>> print FieldSet(User).render() It works ! .. restore config >>> config.engine = old_engine FormAlchemy-1.4.2/docs/validators.txt000664 000765 000024 00000017221 11560200544 020031 0ustar00gawelstaff000000 000000 :mod:`formalchemy.validators` -- Validation stuff ================================================= .. Commented imports >>> from formalchemy.tests import * .. automodule:: formalchemy.validators To validate data, you must bind it to your :class:`~formalchemy.forms.FieldSet` along with the SQLAlchemy model. Normally, you will retrieve `data` from a dict:: >>> from formalchemy.tests import User, bill >>> from formalchemy.forms import FieldSet >>> fs = FieldSet(User) >>> fs.configure(include=[fs.name]) # we only use the name field here >>> fs.rebind(bill, data={'User-1-name': 'Sam'}) Validation is performed simply by invoking `fs.validate()`, which returns True if validation was successful, and False otherwise. Validation functions are added with the `validate` method, described above. If validation fails, `fs.errors` will be populated. `errors` is a dictionary of validation failures, and is always empty before `validate()` is run. Dictionary keys are attributes; values are lists of messages given to `ValidationException`. Global errors (not specific to a single attribute) are under the key `None`. Rendering a :class:`~formalchemy.forms.FieldSet` with errors will result in error messages being displayed inline. Here's what this looks like for a required field that was not supplied with a value: .. sourcecode:: html
Please enter a value
If validation succeeds, you can have `FormAlchemy` put the submitted data back into the bound model object with `fs.sync`. (If you bound to a class instead of an object, the class will be instantiated for you.) The object will be placed into the current session, if one exists:: >>> if fs.validate(): fs.sync() >>> print bill.name Sam Exception --------- All validators raise a `ValidationError` if the validation failed. .. exception:: ValidationError Validators ---------- `formalchemy.validators` contains two types of functions: validation functions that can be used directly, and validation function _generators_ that _return_ a validation function satisfying some conditon. E.g., `validators.maxlength(30)` will return a validation function that can then be passed to `validate`. >>> from formalchemy.validators import * Validation Functions ******************** A validation function is simply a function that, given a value, raises a ValidationError if it is invalid. .. >>> field = fs.name .. autofunction:: integer >>> integer('1', field) 1 >>> integer('1.2', field) Traceback (most recent call last): ... ValidationError: Value is not an integer .. autofunction:: float_ >>> float_('1', field) 1.0 >>> float_('1.2', field) 1.2 >>> float_('asdf', field) Traceback (most recent call last): ... ValidationError: Value is not a number .. autofunction:: currency >>> currency('asdf', field) Traceback (most recent call last): ... ValidationError: Value is not a number >>> currency('1', field) Traceback (most recent call last): ... ValidationError: Please specify full currency value, including cents (e.g., 12.34) >>> currency('1.0', field) Traceback (most recent call last): ... ValidationError: Please specify full currency value, including cents (e.g., 12.34) >>> currency('1.00', field) .. autofunction:: required >>> required('asdf', field) >>> required('', field) Traceback (most recent call last): ... ValidationError: Please enter a value .. autofunction:: email >>> email('a+formalchemy@gmail.com', field) >>> email('a+."<>"@gmail.com', field) >>> email('a+."<>"."[]"@gmail.com', field) >>> email('a+."<>@gmail.com', field) Traceback (most recent call last): ... ValidationError: Unterminated quoted section in recipient >>> email('a+."<>""[]"@gmail.com', field) Traceback (most recent call last): ... ValidationError: Quoted section must be followed by '@' or '.' >>> email('<>@gmail.com', field) Traceback (most recent call last): ... ValidationError: Reserved character present in recipient >>> email(chr(0) + '@gmail.com', field) Traceback (most recent call last): ... ValidationError: Control characters present >>> email(chr(129) + '@gmail.com', field) Traceback (most recent call last): ... ValidationError: Non-ASCII characters present >>> email('', field) >>> email('asdf', field) Traceback (most recent call last): ... ValidationError: Missing @ sign >>> email('@', field) Traceback (most recent call last): ... ValidationError: Recipient must be non-empty >>> email('a@', field) Traceback (most recent call last): ... ValidationError: Domain must be non-empty >>> email('a@gmail.com.', field) Traceback (most recent call last): ... ValidationError: Domain must not end with '.' >>> email('a@gmail..com', field) Traceback (most recent call last): ... ValidationError: Domain must not contain '..' >>> email('a@gmail>com', field) Traceback (most recent call last): ... ValidationError: Reserved character present in domain Function generators ******************* .. autofunction:: length >>> length(min=2, max=5)('a', field) Traceback (most recent call last): ... ValidationError: Value must be at least 2 characters long >>> length(min=2, max=5)('abcdef', field) Traceback (most recent call last): ... ValidationError: Value must be no more than 5 characters long >>> length(min=2, max=5)('abcde', field) .. autofunction:: minlength >>> minlength(0)('a', field) Traceback (most recent call last): ... ValueError: Invalid minimum length >>> minlength(2)('a', field) Traceback (most recent call last): ... ValidationError: Value must be at least 2 characters long >>> minlength(2)('ab', field) .. autofunction:: maxlength >>> maxlength(0)('a', field) Traceback (most recent call last): ... ValueError: Invalid maximum length >>> maxlength(1)('a', field) >>> maxlength(1)('ab', field) Traceback (most recent call last): ... ValidationError: Value must be no more than 1 characters long .. autofunction:: regex >>> regex('[A-Z]+$')('ASDF', field) >>> regex('[A-Z]+$')('abc', field) Traceback (most recent call last): ... ValidationError: Invalid input >>> import re >>> pattern = re.compile('[A-Z]+$', re.I) >>> regex(pattern)('abc') Write your own validator ------------------------ You can write your own validator, with the following function signature. The `field` parameter will be the `Field` object being validated (and though its `.parent` attribute, the `FieldSet`:: >>> def negative(value, field): ... if not (isinstance(value, int) and value < 0): ... raise ValidationError('Value must be less than 0') Then bind it to a field:: >>> from formalchemy import types >>> fs = FieldSet(One) >>> fs.append(Field('number', type=types.Integer)) >>> fs.configure(include=[fs.number.validate(negative)]) Then it should work:: >>> fs.rebind(One, data={'One--number': '-2'}) >>> fs.validate() True >>> fs.rebind(One, data={'One--number': '2'}) >>> fs.validate() False You can also use the `field` positional argument to compare with some other fields in the same FieldSet if you know this will be contained in a FieldSet, for example: >>> def passwd2_validator(value, field): ... if field.parent.passwd1.value != value: ... raise validators.ValidationError('Password do not match') The `FieldSet.errors` and `Field.errors` attributes contain your custom error message:: >>> fs.errors {AttributeField(number): ['Value must be less than 0']} >>> fs.number.errors ['Value must be less than 0']