--- a/appobject.py Wed Sep 23 09:35:36 2009 +0200
+++ b/appobject.py Wed Sep 23 09:54:25 2009 +0200
@@ -11,6 +11,7 @@
import types
from logging import getLogger
+from warnings import warn
from logilab.common.decorators import classproperty
from logilab.common.deprecation import deprecated
@@ -250,7 +251,13 @@
the right hook to create an instance for example). By default the
appobject is returned without any transformation.
"""
- pdefs = getattr(cls, 'cw_property_defs', {})
+ try:
+ pdefs = cls.property_defs
+ except AttributeError:
+ pdefs = getattr(cls, 'cw_property_defs', {})
+ else:
+ warn('property_defs is deprecated, use cw_property_defs in %s'
+ % cls, DeprecationWarning)
for propid, pdef in pdefs.items():
pdef = pdef.copy() # may be shared
pdef['default'] = getattr(cls, propid, pdef['default'])
--- a/misc/migration/bootstrapmigration_repository.py Wed Sep 23 09:35:36 2009 +0200
+++ b/misc/migration/bootstrapmigration_repository.py Wed Sep 23 09:54:25 2009 +0200
@@ -35,14 +35,22 @@
# drop explicit 'State allowed_transition Transition' since it should be
# infered due to yams inheritance. However we've to disable the schema
# sync hook first to avoid to destroy existing data...
- from cubicweb.server.schemahooks import after_del_relation_type
- repo.hm.unregister_hook(after_del_relation_type,
- 'after_delete_relation', 'relation_type')
try:
- drop_relation_definition('State', 'allowed_transition', 'Transition')
- finally:
- repo.hm.register_hook(after_del_relation_type,
- 'after_delete_relation', 'relation_type')
+ from cubicweb.hooks import syncschema
+ repo.vreg.unregister(syncschema.AfterDelRelationTypeHook)
+ try:
+ drop_relation_definition('State', 'allowed_transition', 'Transition')
+ finally:
+ repo.vreg.register(syncschema.AfterDelRelationTypeHook)
+ except ImportError: # syncschema is in CW >= 3.6 only
+ from cubicweb.server.schemahooks import after_del_relation_type
+ repo.hm.unregister_hook(after_del_relation_type,
+ 'after_delete_relation', 'relation_type')
+ try:
+ drop_relation_definition('State', 'allowed_transition', 'Transition')
+ finally:
+ repo.hm.register_hook(after_del_relation_type,
+ 'after_delete_relation', 'relation_type')
schema.rebuild_infered_relations() # need to be explicitly called once everything is in place
for et in rql('DISTINCT Any ET,ETN WHERE S state_of ET, ET name ETN',
--- a/schemas/base.py Wed Sep 23 09:35:36 2009 +0200
+++ b/schemas/base.py Wed Sep 23 09:54:25 2009 +0200
@@ -134,31 +134,7 @@
subject = '*'
object = 'String'
-
-class CWProperty(EntityType):
- """used for cubicweb configuration. Once a property has been created you
- can't change the key.
- """
- permissions = {
- 'read': ('managers', 'users', 'guests'),
- 'add': ('managers', 'users',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', 'owners',),
- }
- # key is a reserved word for mysql
- pkey = String(required=True, internationalizable=True, maxsize=256,
- description=_('defines what\'s the property is applied for. '
- 'You must select this first to be able to set '
- 'value'))
- value = String(internationalizable=True, maxsize=256)
-
- for_user = SubjectRelation('CWUser', cardinality='?*', composite='object',
- description=_('user for which this property is '
- 'applying. If this relation is not '
- 'set, the property is considered as'
- ' a global property'))
-
-
+# XXX find a better relation name
class for_user(RelationType):
"""link a property to the user which want this property customization. Unless
you're a site manager, this relation will be handled automatically.
@@ -169,7 +145,10 @@
'delete': ('managers',),
}
inlined = True
-
+ subject = 'CWProperty'
+ object = 'CWUser'
+ composite = 'object'
+ cardinality = '?*'
class CWPermission(EntityType):
"""entity type that may be used to construct some advanced security configuration
--- a/schemas/bootstrap.py Wed Sep 23 09:35:36 2009 +0200
+++ b/schemas/bootstrap.py Wed Sep 23 09:54:25 2009 +0200
@@ -173,6 +173,23 @@
+class CWProperty(EntityType):
+ """used for cubicweb configuration. Once a property has been created you
+ can't change the key.
+ """
+ permissions = {
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers', 'users',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners',),
+ }
+ # key is a reserved word for mysql
+ pkey = String(required=True, internationalizable=True, maxsize=256,
+ description=_('defines what\'s the property is applied for. '
+ 'You must select this first to be able to set '
+ 'value'))
+ value = String(internationalizable=True, maxsize=256)
+
class relation_type(RelationType):
"""link a relation definition to its relation type"""
permissions = META_RTYPE_PERMS
--- a/selectors.py Wed Sep 23 09:35:36 2009 +0200
+++ b/selectors.py Wed Sep 23 09:54:25 2009 +0200
@@ -614,11 +614,11 @@
# only check this is a known type if etype comes from req.form,
# else we want the error to propagate
try:
- etype = cls.vreg.case_insensitive_etypes[etype.lower()]
+ etype = req.vreg.case_insensitive_etypes[etype.lower()]
req.form['etype'] = etype
except KeyError:
return 0
- return self.score_class(cls.vreg['etypes'].etype_class(etype), req)
+ return self.score_class(req.vreg['etypes'].etype_class(etype), req)
class entity_implements(ImplementsMixIn, EntitySelector):
--- a/server/hook.py Wed Sep 23 09:35:36 2009 +0200
+++ b/server/hook.py Wed Sep 23 09:54:25 2009 +0200
@@ -64,8 +64,10 @@
def register(self, obj, **kwargs):
try:
iter(obj.events)
+ except AttributeError:
+ raise
except:
- raise Exception('bad .events attribute %s on %s' % (obj.event, obj))
+ raise Exception('bad .events attribute %s on %s' % (obj.events, obj))
for event in obj.events:
if event not in ALL_HOOKS:
raise Exception('bad event %s on %s' % (event, obj))
--- a/server/hooksmanager.py Wed Sep 23 09:35:36 2009 +0200
+++ b/server/hooksmanager.py Wed Sep 23 09:54:25 2009 +0200
@@ -1,8 +1,10 @@
from logilab.common.deprecation import class_renamed, class_moved
-from cubicweb.server.hook import Hook
-SystemHook = class_renamed('SystemHook', Hook)
-PropagateSubjectRelationHook = class_renamed('PropagateSubjectRelationHook', Hook)
-PropagateSubjectRelationAddHook = class_renamed('PropagateSubjectRelationAddHook', Hook)
-PropagateSubjectRelationDelHook = class_renamed('PropagateSubjectRelationDelHook', Hook)
-Hook = class_moved(Hook)
-
+from cubicweb.server import hook
+SystemHook = class_renamed('SystemHook', hook.Hook)
+PropagateSubjectRelationHook = class_renamed('PropagateSubjectRelationHook',
+ hook.PropagateSubjectRelationHook)
+PropagateSubjectRelationAddHook = class_renamed('PropagateSubjectRelationAddHook',
+ hook.PropagateSubjectRelationAddHook)
+PropagateSubjectRelationDelHook = class_renamed('PropagateSubjectRelationDelHook',
+ hook.PropagateSubjectRelationDelHook)
+Hook = class_moved(hook.Hook)
--- a/web/_exceptions.py Wed Sep 23 09:35:36 2009 +0200
+++ b/web/_exceptions.py Wed Sep 23 09:54:25 2009 +0200
@@ -19,6 +19,10 @@
class NothingToEdit(RequestError):
"""raised when an edit request doesn't specify any eid to edit"""
+class ProcessFormError(RequestError):
+ """raised when posted data can't be processed by the corresponding field
+ """
+
class NotFound(RequestError):
"""raised when a 404 error should be returned"""
--- a/web/formfields.py Wed Sep 23 09:35:36 2009 +0200
+++ b/web/formfields.py Wed Sep 23 09:54:25 2009 +0200
@@ -214,6 +214,21 @@
"""
pass
+ def process_form_value(self, form):
+ """process posted form and return correctly typed value"""
+ widget = self.get_widget(form)
+ return widget.process_field_data(form, self)
+
+ def process_posted(self, form):
+ for field in self.actual_fields(form):
+ if field is self:
+ yield field.name, field.process_form_value(form)
+ else:
+ # recursive function: we might have compound fields
+ # of compound fields (of compound fields of ...)
+ for fieldname, value in field.process_posted(form):
+ yield fieldname, value
+
class StringField(Field):
widget = TextArea
@@ -286,6 +301,7 @@
fkwargs['choices'] = fcstr.vocabulary(form=form)
fkwargs['internationalizable'] = True
fkwargs['initial'] = lambda f: f.form_field_format(self)
+ fkwargs['eidparam'] = self.eidparam
field = StringField(name=self.name + '_format', **fkwargs)
req.data[self] = field
return field
@@ -361,6 +377,25 @@
+ renderer.render_help(form, field)
+ u'<br/>')
+ def process_form_value(self, form):
+ posted = form.req.form
+ value = posted.get(form.form_field_name(self))
+ formkey = form.form_field_name(self)
+ if ('%s__detach' % form.context[self]['name']) in posted:
+ # drop current file value
+ value = None
+ # no need to check value when nor explicit detach nor new file
+ # submitted, since it will think the attribute is not modified
+ elif value:
+ filename, _, stream = value
+ # value is a 3-uple (filename, mimetype, stream)
+ value = Binary(stream.read())
+ if not val.getvalue(): # usually an unexistant file
+ value = None
+ else:
+ value.filename = filename
+ return value
+
class EditableFileField(FileField):
editable_formats = ('text/plain', 'text/html', 'text/rest')
@@ -391,6 +426,14 @@
# XXX restore form context?
return '\n'.join(wdgs)
+ def process_form_value(self, form):
+ value = form.req.form.get(form.form_field_name(self))
+ if isinstance(value, unicode):
+ # file modified using a text widget
+ encoding = form.form_field_encoding(self)
+ return Binary(value.encode(encoding))
+ return super(EditableFileField, self).process_form_value(form)
+
class IntField(Field):
def __init__(self, min=None, max=None, **kwargs):
@@ -401,6 +444,8 @@
self.widget.attrs.setdefault('size', 5)
self.widget.attrs.setdefault('maxlength', 15)
+ def process_form_value(self, form):
+ return int(Field.process_form_value(self, form))
class BooleanField(Field):
widget = Radio
@@ -410,6 +455,8 @@
return self.choices
return [(form.req._('yes'), '1'), (form.req._('no'), '')]
+ def process_form_value(self, form):
+ return bool(Field.process_form_value(self, form))
class FloatField(IntField):
def format_single_value(self, req, value):
@@ -421,6 +468,8 @@
def render_example(self, req):
return self.format_single_value(req, 1.234)
+ def process_form_value(self, form):
+ return float(Field.process_form_value(self, form))
class DateField(StringField):
format_prop = 'ui.date-format'
@@ -432,26 +481,39 @@
def render_example(self, req):
return self.format_single_value(req, datetime.now())
+ def process_form_value(self, form):
+ # widget is supposed to return a date as a correctly formatted string
+ date = Field.process_form_value(self, form)
+ # but for some widgets, it might be simpler to return date objects
+ # directly, so handle that case :
+ if isinstance(date, basestring):
+ date = form.parse_date(wdgdate, 'Date')
+ return date
class DateTimeField(DateField):
format_prop = 'ui.datetime-format'
+ def process_form_value(self, form):
+ # widget is supposed to return a date as a correctly formatted string
+ date = Field.process_form_value(self, form)
+ # but for some widgets, it might be simpler to return date objects
+ # directly, so handle that case :
+ if isinstance(date, basestring):
+ date = form.parse_datetime(wdgdate, 'Datetime')
+ return date
class TimeField(DateField):
format_prop = 'ui.time-format'
widget = TextInput
-
-class HiddenInitialValueField(Field):
- def __init__(self, visible_field):
- name = 'edit%s-%s' % (visible_field.role[0], visible_field.name)
- super(HiddenInitialValueField, self).__init__(
- name=name, widget=HiddenInput, eidparam=True)
- self.visible_field = visible_field
-
- def format_single_value(self, req, value):
- return self.visible_field.format_single_value(req, value)
-
+ def process_form_value(self, form):
+ # widget is supposed to return a date as a correctly formatted string
+ time = Field.process_form_value(self, form)
+ # but for some widgets, it might be simpler to return time objects
+ # directly, so handle that case :
+ if isinstance(time, basestring):
+ time = form.parse_time(wdgdate, 'Time')
+ return time
class RelationField(Field):
# XXX (syt): iirc, we originaly don't sort relation vocabulary since we want
@@ -533,6 +595,7 @@
kwargs['label'] = (eschema.type + '_object', rschema.type)
else:
kwargs['label'] = (eschema.type, rschema.type)
+ kwargs['eidparam'] = True
kwargs.setdefault('help', help)
if rschema.is_final():
if skip_meta_attr and rschema in eschema.meta_attributes():
--- a/web/formwidgets.py Wed Sep 23 09:35:36 2009 +0200
+++ b/web/formwidgets.py Wed Sep 23 09:54:25 2009 +0200
@@ -11,7 +11,7 @@
from warnings import warn
from cubicweb.common import tags, uilib
-from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE
+from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError
class FieldWidget(object):
@@ -63,6 +63,10 @@
attrs['tabindex'] = form.req.next_tabindex()
return name, values, attrs
+ def process_field_data(self, form, field):
+ formkey = form.form_field_name(field)
+ posted = form.req.form
+ return posted.get(formkey)
class Input(FieldWidget):
"""abstract widget class for <input> tag based widgets"""
@@ -114,11 +118,25 @@
**{'class': 'emphasis'})]
return u'\n'.join(inputs)
+ def process_field_data(self, form, field):
+ passwd1 = super(PasswordInput, self).process_field_data(form, field)
+ fieldname = form.form_field_name(field)
+ passwd2 = form.req.form[fieldname+'-confirm']
+ if passwd1 == passwd2:
+ if passwd1 is None:
+ return None
+ return passwd1.encode('utf-8')
+ raise ProcessFormError(form.req._("password and confirmation don't match"))
class PasswordSingleInput(Input):
"""<input type='password'> without a confirmation field"""
type = 'password'
+ def process_field_data(self, form, field):
+ value = super(PasswordSingleInput, self).process_field_data(form, field)
+ if value is not None:
+ return value.encode('utf-8')
+ return value
class FileInput(Input):
"""<input type='file'>"""
--- a/web/views/editcontroller.py Wed Sep 23 09:35:36 2009 +0200
+++ b/web/views/editcontroller.py Wed Sep 23 09:54:25 2009 +0200
@@ -12,7 +12,7 @@
from rql.utils import rqlvar_maker
from cubicweb import Binary, ValidationError, typed_eid
-from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, NothingToEdit
+from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, NothingToEdit, ProcessFormError
from cubicweb.web.controller import parse_relations_descr
from cubicweb.web.views.basecontrollers import ViewController
@@ -22,6 +22,32 @@
can't be handled right now and have to be handled later
"""
+class RqlQuery(object):
+ def __init__(self):
+ self.edited = []
+ self.restrictions = []
+ self.kwargs = {}
+
+ def insert_query(self, etype):
+ if self.edited:
+ rql = 'INSERT %s X: %s' % (etype, ','.join(self.edited))
+ else:
+ rql = 'INSERT %s X' % etype
+ if self.restrictions:
+ rql += ' WHERE %s' % ','.join(self.restrictions)
+ return rql
+
+ def update_query(self, eid):
+ varmaker = rqlvar_maker()
+ var = varmaker.next()
+ while var in self.kwargs:
+ var = varmaker.next()
+ rql = 'SET %s WHERE X eid %%(%s)s' % (','.join(self.edited), var)
+ if self.restrictions:
+ rql += ', %s' % ','.join(self.restrictions)
+ self.kwargs[var] = eid
+ return rql
+
class EditController(ViewController):
__regid__ = 'edit'
@@ -42,14 +68,15 @@
def _default_publish(self):
req = self.req
- form = req.form
+ self.errors = []
+ self.relations_rql = []
# no specific action, generic edition
self._to_create = req.data['eidmap'] = {}
self._pending_relations = []
todelete = self.req.get_pending_deletes()
toinsert = self.req.get_pending_inserts()
try:
- methodname = form.pop('__method', None)
+ methodname = req.form.pop('__method', None)
for eid in req.edited_eids():
# __type and eid
formparams = req.extract_entity_params(eid, minparams=2)
@@ -59,84 +86,72 @@
method(formparams)
eid = self.edit_entity(formparams)
except (RequestError, NothingToEdit):
- if '__linkto' in form and 'eid' in form:
+ if '__linkto' in req.form and 'eid' in req.form:
self.execute_linkto()
- elif not ('__delete' in form or '__insert' in form or todelete or toinsert):
+ elif not ('__delete' in req.form or '__insert' in req.form or todelete or toinsert):
raise ValidationError(None, {None: req._('nothing to edit')})
+ for querydef in self.relations_rql:
+ self.req.execute(*querydef)
# handle relations in newly created entities
+ # XXX find a way to merge _pending_relations and relations_rql
if self._pending_relations:
- for rschema, formparams, x, entity in self._pending_relations:
- self.handle_relation(rschema, formparams, x, entity, True)
-
+ for form, field, entity in self._pending_relations:
+ for querydef in self.handle_relation(form, field, entity, True):
+ self.req.execute(*querydef)
# XXX this processes *all* pending operations of *all* entities
- if form.has_key('__delete'):
- todelete += req.list_form_param('__delete', form, pop=True)
+ if req.form.has_key('__delete'):
+ todelete += req.list_form_param('__delete', req.form, pop=True)
if todelete:
self.delete_relations(parse_relations_descr(todelete))
- if form.has_key('__insert'):
- toinsert = req.list_form_param('__insert', form, pop=True)
+ if req.form.has_key('__insert'):
+ toinsert = req.list_form_param('__insert', req.form, pop=True)
if toinsert:
self.insert_relations(parse_relations_descr(toinsert))
self.req.remove_pending_operations()
+ def _insert_entity(self, etype, eid, rqlquery):
+ rql = rqlquery.insert_query(etype)
+ try:
+ # get the new entity (in some cases, the type might have
+ # changed as for the File --> Image mutation)
+ entity = self.req.execute(rql, rqlquery.kwargs).get_entity(0, 0)
+ neweid = entity.eid
+ except ValidationError, ex:
+ self._to_create[eid] = ex.entity
+ if self.req.json_request: # XXX (syt) why?
+ ex.entity = eid
+ raise
+ self._to_create[eid] = neweid
+ return neweid
+
+ def _update_entity(self, eid, rqlquery):
+ rql = rqlquery.update_query(eid)
+ self.req.execute(rql, rqlquery.kwargs)
+
def edit_entity(self, formparams, multiple=False):
"""edit / create / copy an entity and return its eid"""
etype = formparams['__type']
entity = self.vreg['etypes'].etype_class(etype)(self.req)
- entity.eid = eid = self._get_eid(formparams['eid'])
- edited = self.req.form.get('__maineid') == formparams['eid']
- # let a chance to do some entity specific stuff.
+ entity.eid = formparams['eid']
+ eid = self._get_eid(entity.eid)
+ is_main_entity = self.req.form.get('__maineid') == formparams['eid']
+ # let a chance to do some entity specific stuff.tn
entity.pre_web_edit()
# create a rql query from parameters
- self.relations = []
- self.restrictions = []
+ rqlquery = RqlQuery()
# process inlined relations at the same time as attributes
- # this is required by some external source such as the svn source which
- # needs some information provided by those inlined relation. Moreover
- # this will generate less write queries.
- for rschema in entity.e_schema.subject_relations():
- if rschema.is_final():
- self.handle_attribute(entity, rschema, formparams)
- elif rschema.inlined:
- self.handle_inlined_relation(rschema, formparams, entity)
- execute = self.req.execute
+ # this will generate less rql queries and might be useful in
+ # a few dark corners
+ formid = self.req.form.get('__form_id', 'edition')
+ form = self.vreg['forms'].select(formid, self.req, entity=entity)
+ for field in form.fields:
+ if form.form_field_modified(field):
+ self.handle_formfield(form, field, entity, rqlquery)
if eid is None: # creation or copy
- if self.relations:
- rql = 'INSERT %s X: %s' % (etype, ','.join(self.relations))
- else:
- rql = 'INSERT %s X' % etype
- if self.restrictions:
- rql += ' WHERE %s' % ','.join(self.restrictions)
- try:
- # get the new entity (in some cases, the type might have
- # changed as for the File --> Image mutation)
- entity = execute(rql, formparams).get_entity(0, 0)
- eid = entity.eid
- except ValidationError, ex:
- self._to_create[formparams['eid']] = ex.entity
- if self.req.json_request: # XXX (syt) why?
- ex.entity = formparams['eid']
- raise
- self._to_create[formparams['eid']] = eid
- elif self.relations: # edition of an existant entity
- varmaker = rqlvar_maker()
- var = varmaker.next()
- while var in formparams:
- var = varmaker.next()
- rql = 'SET %s WHERE X eid %%(%s)s' % (','.join(self.relations), var)
- if self.restrictions:
- rql += ', %s' % ','.join(self.restrictions)
- formparams[var] = eid
- execute(rql, formparams)
- for rschema in entity.e_schema.subject_relations():
- if rschema.is_final() or rschema.inlined:
- continue
- self.handle_relation(rschema, formparams, 'subject', entity)
- for rschema in entity.e_schema.object_relations():
- if rschema.is_final():
- continue
- self.handle_relation(rschema, formparams, 'object', entity)
- if edited:
+ entity.eid = self._insert_entity(etype, formparams['eid'], rqlquery)
+ elif rqlquery.edited: # edition of an existant entity
+ self._update_entity(eid, rqlquery)
+ if is_main_entity:
self.notify_edited(entity)
if formparams.has_key('__delete'):
todelete = self.req.list_form_param('__delete', formparams, pop=True)
@@ -146,10 +161,31 @@
if formparams.has_key('__insert'):
toinsert = self.req.list_form_param('__insert', formparams, pop=True)
self.insert_relations(parse_relations_descr(toinsert))
- if edited: # only execute linkto for the main entity
+ if is_main_entity: # only execute linkto for the main entity
self.execute_linkto(eid)
return eid
+ def handle_formfield(self, form, field, entity, rqlquery):
+ eschema = entity.e_schema
+ try:
+ for attr, value in field.process_posted(form):
+ if not (
+ (field.role == 'subject' and eschema.has_subject_relation(field.name))
+ or
+ (field.role == 'object' and eschema.has_object_relation(field.name))):
+ continue
+ rschema = self.schema.rschema(field.name)
+ if rschema.is_final():
+ rqlquery.kwargs[attr] = value
+ rqlquery.edited.append('X %s %%(%s)s' % (attr, attr))
+ elif rschema.inlined:
+ self.handle_inlined_relation(form, field, entity, rqlquery)
+ else:
+ self.relations_rql += self.handle_relation(
+ form, field, entity)
+ except ProcessFormError, exc:
+ self.errors.append((field, exc))
+
def _action_apply(self):
self._default_publish()
self.reset()
@@ -165,158 +201,49 @@
self.delete_entities(self.req.edited_eids(withtype=True))
return self.reset()
- def _needs_edition(self, rtype, formparams, entity):
- """returns True and and the new value if `rtype` was edited"""
- editkey = 'edits-%s' % rtype
- if not editkey in formparams:
- return False, None # not edited
- value = formparams.get(rtype) or None
- if entity.has_eid() and (formparams.get(editkey) or None) == value:
- return False, None # not modified
- if value == INTERNAL_FIELD_VALUE:
- value = None
- return True, value
-
- def handle_attribute(self, entity, rschema, formparams):
- """append to `relations` part of the rql query to edit the
- attribute described by the given schema if necessary
+ def _relation_values(self, form, field, entity, late=False):
+ """handle edition for the (rschema, x) relation of the given entity
"""
- attr = rschema.type
- edition_needed, value = self._needs_edition(attr, formparams, entity)
- if not edition_needed:
- return
- # test if entity class defines a special handler for this attribute
- custom_edit = getattr(entity, 'custom_%s_edit' % attr, None)
- if custom_edit:
- custom_edit(formparams, value, self.relations)
- return
- attrtype = rschema.objects(entity.e_schema)[0].type
- # on checkbox or selection, the field may not be in params
- # NOTE: raising ValidationError here is not a good solution because
- # we can't gather all errors at once. Hopefully, the new 3.6.x
- # form handling will fix that
- if attrtype == 'Boolean':
- value = bool(value)
- elif attrtype == 'Decimal':
- value = Decimal(value)
- elif attrtype == 'Bytes':
- # if it is a file, transport it using a Binary (StringIO)
- # XXX later __detach is for the new widget system, the former is to
- # be removed once web/widgets.py has been dropped
- if formparams.has_key('%s__detach' % attr):
- # drop current file value
- value = None
- # no need to check value when nor explicit detach nor new file
- # submitted, since it will think the attribute is not modified
- elif isinstance(value, unicode):
- # file modified using a text widget
- encoding = entity.attr_metadata(attr, 'encoding')
- value = Binary(value.encode(encoding))
- elif value:
- # value is a 3-uple (filename, mimetype, stream)
- val = Binary(value[2].read())
- if not val.getvalue(): # usually an unexistant file
- value = None
- else:
- val.filename = value[0]
- # ignore browser submitted MIME type since it may be buggy
- # XXX add a config option to tell if we should consider it
- # or not?
- #if entity.e_schema.has_metadata(attr, 'format'):
- # key = '%s_format' % attr
- # formparams[key] = value[1]
- # self.relations.append('X %s_format %%(%s)s'
- # % (attr, key))
- # XXX suppose a File compatible schema
- if entity.e_schema.has_subject_relation('name') \
- and not formparams.get('name'):
- formparams['name'] = value[0]
- self.relations.append('X name %(name)s')
- value = val
- else:
- # no specified value, skip
- return
- elif value is not None:
- if attrtype == 'Int':
- try:
- value = int(value)
- except ValueError:
- raise ValidationError(entity.eid,
- {attr: self.req._("invalid integer value")})
- elif attrtype == 'Float':
- try:
- value = float(value)
- except ValueError:
- raise ValidationError(entity.eid,
- {attr: self.req._("invalid float value")})
- elif attrtype in ('Date', 'Datetime', 'Time'):
- try:
- value = self.parse_datetime(value, attrtype)
- except ValueError:
- raise ValidationError(entity.eid,
- {attr: self.req._("invalid date")})
- elif attrtype == 'Password':
- # check confirmation (see PasswordWidget for confirmation field name)
- confirmval = formparams.get(attr + '-confirm')
- if confirmval != value:
- raise ValidationError(entity.eid,
- {attr: self.req._("password and confirmation don't match")})
- # password should *always* be utf8 encoded
- value = value.encode('UTF8')
- else:
- # strip strings
- value = value.strip()
- elif attrtype == 'Password':
- # skip None password
- return # unset password
- formparams[attr] = value
- self.relations.append('X %s %%(%s)s' % (attr, attr))
+ values = set()
+ for eid in field.process_form_data(form):
+ if not eid: # AutoCompletionWidget
+ continue
+ typed_eid = self._get_eid(eid)
+ if typed_eid is None:
+ if late:
+ # eid is still None while it's already a late call
+ # this mean that the associated entity has not been created
+ raise Exception("eid %s is still not created" % eid)
+ self._pending_relations.append( (form, field, entity) )
+ return None
+ values.add(typed_eid)
+ return values
- def _relation_values(self, rschema, formparams, x, entity, late=False):
+ def handle_inlined_relation(self, form, field, entity, rqlquery):
"""handle edition for the (rschema, x) relation of the given entity
"""
- rtype = rschema.type
- editkey = 'edit%s-%s' % (x[0], rtype)
- if not editkey in formparams:
- return # not edited
- try:
- values = self._linked_eids(self.req.list_form_param(rtype, formparams), late)
- except ToDoLater:
- self._pending_relations.append((rschema, formparams, x, entity))
- return
- origvalues = set(typed_eid(eid) for eid in self.req.list_form_param(editkey, formparams))
- return values, origvalues
+ origvalues = set(row[0] for row in entity.related(field.name, field.role))
+ values = self._relation_values(form, field, entity)
+ if values is None or values == origvalues:
+ return # not edited / not modified / to do later
+ attr = field.name
+ if values:
+ rqlquery.kwargs[attr] = iter(values).next()
+ rqlquery.edition.append('X %s %s' % (attr, attr.upper()))
+ rqlquery.restrictions.append('%s eid %%(%s)s' % (attr.upper(), attr))
+ elif entity.has_eid():
+ self.relations_rql += self.handle_relation(form, field, entity)
- def handle_inlined_relation(self, rschema, formparams, entity, late=False):
+ def handle_relation(self, form, field, entity, late=False):
"""handle edition for the (rschema, x) relation of the given entity
"""
- try:
- values, origvalues = self._relation_values(rschema, formparams,
- 'subject', entity, late)
- except TypeError:
- return # not edited / to do later
- if values == origvalues:
- return # not modified
- attr = str(rschema)
- if values:
- formparams[attr] = iter(values).next()
- self.relations.append('X %s %s' % (attr, attr.upper()))
- self.restrictions.append('%s eid %%(%s)s' % (attr.upper(), attr))
- elif entity.has_eid():
- self.handle_relation(rschema, formparams, 'subject', entity, late)
-
- def handle_relation(self, rschema, formparams, x, entity, late=False):
- """handle edition for the (rschema, x) relation of the given entity
- """
- try:
- values, origvalues = self._relation_values(rschema, formparams, x,
- entity, late)
- except TypeError:
- return # not edited / to do later
+ origvalues = set(row[0] for row in entity.related(field.name, field.role))
+ values = self._relation_values(form, field, entity, late)
+ if values is None or values == origvalues:
+ return # not edited / not modified / to do later
etype = entity.e_schema
- if values == origvalues:
- return # not modified
- if x == 'subject':
+ rschema = self.schema.rschema(field.name)
+ if field.role == 'subject':
desttype = rschema.objects(etype)[0]
card = rschema.rproperty(etype, desttype, 'cardinality')[0]
subjvar, objvar = 'X', 'Y'
@@ -325,19 +252,19 @@
card = rschema.rproperty(desttype, etype, 'cardinality')[1]
subjvar, objvar = 'Y', 'X'
eid = entity.eid
- if x == 'object' or not rschema.inlined or not values:
+ if field.role == 'object' or not rschema.inlined or not values:
# this is not an inlined relation or no values specified,
# explicty remove relations
rql = 'DELETE %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
subjvar, rschema, objvar)
for reid in origvalues.difference(values):
- self.req.execute(rql, {'x': eid, 'y': reid}, ('x', 'y'))
+ yield (rql, {'x': eid, 'y': reid}, ('x', 'y'))
seteids = values.difference(origvalues)
if seteids:
rql = 'SET %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
subjvar, rschema, objvar)
for reid in seteids:
- self.req.execute(rql, {'x': eid, 'y': reid}, ('x', 'y'))
+ yield (rql, {'x': eid, 'y': reid}, ('x', 'y'))
def _get_eid(self, eid):
# should be either an int (existant entity) or a variable (to be
@@ -355,18 +282,5 @@
def _linked_eids(self, eids, late=False):
"""return a list of eids if they are all known, else raise ToDoLater
"""
- result = set()
- for eid in eids:
- if not eid: # AutoCompletionWidget
- continue
- eid = self._get_eid(eid)
- if eid is None:
- if not late:
- raise ToDoLater()
- # eid is still None while it's already a late call
- # this mean that the associated entity has not been created
- raise Exception('duh')
- result.add(eid)
- return result
--- a/web/views/editforms.py Wed Sep 23 09:35:36 2009 +0200
+++ b/web/views/editforms.py Wed Sep 23 09:54:25 2009 +0200
@@ -484,12 +484,6 @@
def add_hiddens(self, form, entity, peid, rtype, role):
# to ease overriding (see cubes.vcsfile.views.forms for instance)
- if self.keep_entity(form, entity, peid, rtype):
- if entity.has_eid():
- rval = entity.eid
- else:
- rval = INTERNAL_FIELD_VALUE
- form.form_add_hidden('edit%s-%s:%s' % (role[0], rtype, peid), rval)
form.form_add_hidden(name='%s:%s' % (rtype, peid), value=entity.eid,
id='rel-%s-%s-%s' % (peid, rtype, entity.eid))
--- a/web/views/formrenderers.py Wed Sep 23 09:35:36 2009 +0200
+++ b/web/views/formrenderers.py Wed Sep 23 09:54:25 2009 +0200
@@ -16,7 +16,7 @@
from cubicweb.appobject import AppObject
from cubicweb.selectors import entity_implements, yes
from cubicweb.web import eid_param, formwidgets as fwdgs
-from cubicweb.web.formfields import HiddenInitialValueField
+from cubicweb.web.widgets import checkbox
def checkbox(name, value, attrs='', checked=None):
if checked is None:
@@ -168,8 +168,6 @@
return tag + '>'
def display_field(self, form, field):
- if isinstance(field, HiddenInitialValueField):
- field = field.visible_field
return (self.display_fields is None
or field.name in form.internal_fields
or (field.name, field.role) in self.display_fields
@@ -257,8 +255,6 @@
def display_field(self, form, field):
if not super(EntityBaseFormRenderer, self).display_field(form, field):
- if isinstance(field, HiddenInitialValueField):
- field = field.visible_field
ismeta = form.edited_entity.e_schema.is_metadata(field.name)
return ismeta is not None and (
ismeta[0] in self.display_fields or
--- a/web/views/forms.py Wed Sep 23 09:35:36 2009 +0200
+++ b/web/views/forms.py Wed Sep 23 09:54:25 2009 +0200
@@ -16,7 +16,7 @@
from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
from cubicweb.web import form, formwidgets as fwdgs
from cubicweb.web.controller import NAV_FORM_PARAMETERS
-from cubicweb.web.formfields import HiddenInitialValueField, StringField
+from cubicweb.web.formfields import StringField
class FieldsForm(form.Form):
@@ -281,6 +281,9 @@
"""
raise NotImplementedError
+ def form_field_modified(self, field):
+ return field.is_visible()
+
def _field_has_error(self, field):
"""return true if the field has some error in given validation exception
"""
@@ -372,30 +375,26 @@
self.form_renderer_id, self.req, rset=self.rset, row=self.row,
col=self.col, entity=self.edited_entity)
- def form_build_context(self, values=None):
- """overriden to add edit[s|o] hidden fields and to ensure schema fields
- have eidparam set to True
-
- edit[s|o] hidden fields are used to indicate the value for the
- associated field before the (potential) modification made when
- submitting the form.
- """
- eschema = self.edited_entity.e_schema
- for field in self.fields[:]:
- for field in field.actual_fields(self):
- fieldname = field.name
- if fieldname != 'eid' and (
- (eschema.has_subject_relation(fieldname) or
- eschema.has_object_relation(fieldname))):
- field.eidparam = True
- self.fields.append(HiddenInitialValueField(field))
- return super(EntityFieldsForm, self).form_build_context(values)
+## def form_build_context(self, values=None):
+## """overriden to add edit[s|o] hidden fields and to ensure schema fields
+## have eidparam set to True
+## """
+## eschema = self.edited_entity.e_schema
+## for field in self.fields[:]:
+## for field in field.actual_fields(self):
+## fieldname = field.name
+## if fieldname != 'eid' and (
+## (eschema.has_subject_relation(fieldname) or
+## eschema.has_object_relation(fieldname))):
+## # XXX why do we need to do this here ?
+## field.eidparam = True
+## return super(EntityFieldsForm, self).form_build_context(values)
def form_field_value(self, field, load_bytes=False):
"""return field's *typed* value
overriden to deal with
- * special eid / __type / edits- / edito- fields
+ * special eid / __type
* lookup for values on edited entities
"""
attr = field.name
@@ -404,14 +403,6 @@
return entity.eid
if not field.eidparam:
return super(EntityFieldsForm, self).form_field_value(field, load_bytes)
- if attr.startswith('edits-') or attr.startswith('edito-'):
- # edit[s|o]- fieds must have the actual value stored on the entity
- assert hasattr(field, 'visible_field')
- vfield = field.visible_field
- assert vfield.eidparam
- if entity.has_eid():
- return self.form_field_value(vfield)
- return INTERNAL_FIELD_VALUE
if attr == '__type':
return entity.id
if self.schema.rschema(attr).is_final():
@@ -487,7 +478,32 @@
# cases, it doesn't make sense to sort results afterwards.
return vocabfunc(rtype, limit)
- # XXX should be on the field, no?
+ def form_field_modified(self, field):
+ if field.is_visible():
+ # fields not corresponding to an entity attribute / relations
+ # are considered modified
+ if not field.eidparam:
+ return True # XXX
+ try:
+ if field.role == 'subject':
+ previous_value = getattr(self.edited_entity, field.name)
+ else:
+ previous_value = getattr(self.edited_entity,
+ 'reverse_%s' % field.name)
+ except AttributeError:
+ # fields with eidparam=True but not corresponding to an actual
+ # attribute or relation
+ return True
+ # if it's a non final relation, we need the eids
+ if isinstance(previous_value, list):
+ # widget should return untyped eids
+ previous_value = set(unicode(e.eid) for e in previous_value)
+ new_value = field.process_form_value(self)
+ if self.edited_entity.has_eid() and (previous_value == new_value):
+ return False # not modified
+ return True
+ return False
+
def subject_relation_vocabulary(self, rtype, limit=None):
"""defaut vocabulary method for the given relation, looking for
relation's object entities (i.e. self is the subject)