2006-07-05 18:35:22 +02:00
|
|
|
# this will go to src/common/xmpp later, for now it is in src/common
|
2008-08-15 19:31:51 +02:00
|
|
|
# -*- coding:utf-8 -*-
|
2008-08-15 05:20:23 +02:00
|
|
|
## src/common/dataforms.py
|
|
|
|
##
|
|
|
|
## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
|
2012-01-11 22:20:34 +01:00
|
|
|
## Copyright (C) 2006-2012 Yann Leboulanger <asterix AT lagaule.org>
|
2008-08-15 05:20:23 +02:00
|
|
|
## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
|
|
|
|
##
|
|
|
|
## This file is part of Gajim.
|
|
|
|
##
|
|
|
|
## Gajim is free software; you can redistribute it and/or modify
|
|
|
|
## it under the terms of the GNU General Public License as published
|
|
|
|
## by the Free Software Foundation; version 3 only.
|
|
|
|
##
|
|
|
|
## Gajim is distributed in the hope that it will be useful,
|
|
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
## GNU General Public License for more details.
|
|
|
|
##
|
|
|
|
## You should have received a copy of the GNU General Public License
|
|
|
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
##
|
|
|
|
|
2009-11-26 12:58:12 +01:00
|
|
|
"""
|
|
|
|
This module contains wrappers for different parts of data forms (JEP 0004). For
|
|
|
|
information how to use them, read documentation
|
|
|
|
"""
|
2006-07-05 18:35:22 +02:00
|
|
|
|
2012-12-09 21:37:51 +01:00
|
|
|
import nbxmpp
|
2010-08-10 16:08:06 +02:00
|
|
|
import helpers
|
2006-07-05 18:35:22 +02:00
|
|
|
|
2006-11-18 19:39:02 +01:00
|
|
|
# exceptions used in this module
|
2009-01-28 08:47:38 +01:00
|
|
|
# base class
|
|
|
|
class Error(Exception): pass
|
2012-12-09 21:37:51 +01:00
|
|
|
# when we get nbxmpp.Node which we do not understand
|
2009-01-28 08:47:38 +01:00
|
|
|
class UnknownDataForm(Error): pass
|
2012-12-09 21:37:51 +01:00
|
|
|
# when we get nbxmpp.Node which contains bad fields
|
2009-01-28 08:47:38 +01:00
|
|
|
class WrongFieldValue(Error): pass
|
2006-11-18 19:39:02 +01:00
|
|
|
|
|
|
|
# helper class to change class of already existing object
|
2012-12-09 21:37:51 +01:00
|
|
|
class ExtendedNode(nbxmpp.Node, object):
|
2010-02-08 15:08:40 +01:00
|
|
|
@classmethod
|
|
|
|
def __new__(cls, *a, **b):
|
|
|
|
if 'extend' not in b.keys() or not b['extend']:
|
|
|
|
return object.__new__(cls)
|
2006-11-18 19:39:02 +01:00
|
|
|
|
2010-02-08 15:08:40 +01:00
|
|
|
extend = b['extend']
|
|
|
|
assert issubclass(cls, extend.__class__)
|
|
|
|
extend.__class__ = cls
|
|
|
|
return extend
|
2006-11-18 19:39:02 +01:00
|
|
|
|
|
|
|
# helper decorator to create properties in cleaner way
|
|
|
|
def nested_property(f):
|
2010-02-08 15:08:40 +01:00
|
|
|
ret = f()
|
|
|
|
p = {'doc': f.__doc__}
|
|
|
|
for v in ('fget', 'fset', 'fdel', 'doc'):
|
|
|
|
if v in ret.keys(): p[v]=ret[v]
|
|
|
|
return property(**p)
|
2006-11-18 19:39:02 +01:00
|
|
|
|
|
|
|
# helper to create fields from scratch
|
|
|
|
def Field(typ, **attrs):
|
2010-02-08 15:08:40 +01:00
|
|
|
''' Helper function to create a field of given type. '''
|
|
|
|
f = {
|
|
|
|
'boolean': BooleanField,
|
|
|
|
'fixed': StringField,
|
|
|
|
'hidden': StringField,
|
|
|
|
'text-private': StringField,
|
|
|
|
'text-single': StringField,
|
2010-08-10 16:08:06 +02:00
|
|
|
'jid-multi': JidMultiField,
|
|
|
|
'jid-single': JidSingleField,
|
2010-02-08 15:08:40 +01:00
|
|
|
'list-multi': ListMultiField,
|
|
|
|
'list-single': ListSingleField,
|
|
|
|
'text-multi': TextMultiField,
|
|
|
|
}[typ](typ=typ, **attrs)
|
|
|
|
return f
|
2006-11-18 19:39:02 +01:00
|
|
|
|
|
|
|
def ExtendField(node):
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
|
|
|
Helper function to extend a node to field of appropriate type
|
|
|
|
"""
|
|
|
|
# when validation (XEP-122) will go in, we could have another classes
|
|
|
|
# like DateTimeField - so that dicts in Field() and ExtendField() will
|
|
|
|
# be different...
|
|
|
|
typ=node.getAttr('type')
|
|
|
|
f = {
|
|
|
|
'boolean': BooleanField,
|
|
|
|
'fixed': StringField,
|
|
|
|
'hidden': StringField,
|
|
|
|
'text-private': StringField,
|
|
|
|
'text-single': StringField,
|
2010-08-10 16:08:06 +02:00
|
|
|
'jid-multi': JidMultiField,
|
|
|
|
'jid-single': JidSingleField,
|
2010-02-08 15:08:40 +01:00
|
|
|
'list-multi': ListMultiField,
|
|
|
|
'list-single': ListSingleField,
|
|
|
|
'text-multi': TextMultiField,
|
|
|
|
}
|
|
|
|
if typ not in f:
|
|
|
|
typ = 'text-single'
|
|
|
|
return f[typ](extend=node)
|
2006-11-18 19:39:02 +01:00
|
|
|
|
2006-11-18 21:10:37 +01:00
|
|
|
def ExtendForm(node):
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
|
|
|
Helper function to extend a node to form of appropriate type
|
|
|
|
"""
|
|
|
|
if node.getTag('reported') is not None:
|
|
|
|
return MultipleDataForm(extend=node)
|
|
|
|
else:
|
|
|
|
return SimpleDataForm(extend=node)
|
2006-11-18 21:10:37 +01:00
|
|
|
|
2006-11-18 19:39:02 +01:00
|
|
|
class DataField(ExtendedNode):
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
|
|
|
Keeps data about one field - var, field type, labels, instructions... Base
|
|
|
|
class for different kinds of fields. Use Field() function to construct one
|
|
|
|
of these
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, typ=None, var=None, value=None, label=None, desc=None,
|
|
|
|
required=False, options=None, extend=None):
|
|
|
|
|
|
|
|
if extend is None:
|
|
|
|
ExtendedNode.__init__(self, 'field')
|
|
|
|
|
2012-08-22 12:55:57 +02:00
|
|
|
self.type_ = typ
|
2010-02-08 15:08:40 +01:00
|
|
|
self.var = var
|
|
|
|
if value is not None:
|
|
|
|
self.value = value
|
|
|
|
if label is not None:
|
|
|
|
self.label = label
|
|
|
|
if desc is not None:
|
|
|
|
self.desc = desc
|
|
|
|
self.required = required
|
|
|
|
self.options = options
|
|
|
|
|
|
|
|
@nested_property
|
2012-08-24 10:37:24 +02:00
|
|
|
def type_():
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
|
|
|
Type of field. Recognized values are: 'boolean', 'fixed', 'hidden',
|
|
|
|
'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi',
|
|
|
|
'text-private', 'text-single'. If you set this to something different,
|
|
|
|
DataField will store given name, but treat all data as text-single
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
t = self.getAttr('type')
|
|
|
|
if t is None:
|
|
|
|
return 'text-single'
|
|
|
|
return t
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
assert isinstance(value, basestring)
|
|
|
|
self.setAttr('type', value)
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def var():
|
|
|
|
"""
|
|
|
|
Field identifier
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
return self.getAttr('var')
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
assert isinstance(value, basestring)
|
|
|
|
self.setAttr('var', value)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
self.delAttr('var')
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def label():
|
|
|
|
"""
|
|
|
|
Human-readable field name
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
l = self.getAttr('label')
|
|
|
|
if not l:
|
|
|
|
l = self.var
|
|
|
|
return l
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
assert isinstance(value, basestring)
|
|
|
|
self.setAttr('label', value)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
if self.getAttr('label'):
|
|
|
|
self.delAttr('label')
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def description():
|
|
|
|
"""
|
|
|
|
Human-readable description of field meaning
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
return self.getTagData('desc') or u''
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
assert isinstance(value, basestring)
|
|
|
|
if value == '':
|
|
|
|
fdel(self)
|
|
|
|
else:
|
|
|
|
self.setTagData('desc', value)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
t = self.getTag('desc')
|
|
|
|
if t is not None:
|
|
|
|
self.delChild(t)
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def required():
|
|
|
|
"""
|
|
|
|
Controls whether this field required to fill. Boolean
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
return bool(self.getTag('required'))
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
t = self.getTag('required')
|
|
|
|
if t and not value:
|
|
|
|
self.delChild(t)
|
|
|
|
elif not t and value:
|
|
|
|
self.addChild('required')
|
|
|
|
|
|
|
|
return locals()
|
2006-11-18 19:39:02 +01:00
|
|
|
|
2010-06-27 23:09:07 +02:00
|
|
|
@nested_property
|
|
|
|
def media():
|
|
|
|
"""
|
|
|
|
Media data
|
|
|
|
"""
|
|
|
|
def fget(self):
|
2012-12-09 21:37:51 +01:00
|
|
|
media = self.getTag('media', namespace=nbxmpp.NS_DATA_MEDIA)
|
2010-06-27 23:09:07 +02:00
|
|
|
if media:
|
|
|
|
return Media(media)
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
fdel(self)
|
|
|
|
self.addChild(node=value)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
t = self.getTag('media')
|
|
|
|
if t is not None:
|
|
|
|
self.delChild(t)
|
|
|
|
|
|
|
|
return locals()
|
2012-08-22 12:55:57 +02:00
|
|
|
|
2010-08-10 16:08:06 +02:00
|
|
|
def is_valid(self):
|
|
|
|
return True
|
2010-06-27 23:09:07 +02:00
|
|
|
|
2012-12-09 21:37:51 +01:00
|
|
|
class Uri(nbxmpp.Node):
|
2010-06-27 23:09:07 +02:00
|
|
|
def __init__(self, uri_tag):
|
2012-12-09 21:37:51 +01:00
|
|
|
nbxmpp.Node.__init__(self, node=uri_tag)
|
2010-06-27 23:09:07 +02:00
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def type_():
|
|
|
|
"""
|
|
|
|
uri type
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
return self.getAttr('type')
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
self.setAttr('type', value)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
self.delAttr('type')
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def uri_data():
|
|
|
|
"""
|
|
|
|
uri data
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
return self.getData()
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
self.setData(value)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
self.setData(None)
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
2012-12-09 21:37:51 +01:00
|
|
|
class Media(nbxmpp.Node):
|
2010-06-27 23:09:07 +02:00
|
|
|
def __init__(self, media_tag):
|
2012-12-09 21:37:51 +01:00
|
|
|
nbxmpp.Node.__init__(self, node=media_tag)
|
2010-06-27 23:09:07 +02:00
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def uris():
|
|
|
|
"""
|
|
|
|
URIs of the media element.
|
|
|
|
"""
|
|
|
|
def fget(self):
|
2010-06-30 04:38:21 +02:00
|
|
|
return map(Uri, self.getTags('uri'))
|
2010-06-27 23:09:07 +02:00
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
fdel(self)
|
|
|
|
for uri in values:
|
|
|
|
self.addChild(node=uri)
|
|
|
|
|
|
|
|
def fdel(self, value):
|
|
|
|
for element in self.getTags('uri'):
|
|
|
|
self.delChild(element)
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
2006-11-18 19:39:02 +01:00
|
|
|
class BooleanField(DataField):
|
2010-02-08 15:08:40 +01:00
|
|
|
@nested_property
|
|
|
|
def value():
|
|
|
|
"""
|
|
|
|
Value of field. May contain True, False or None
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
v = self.getTagData('value')
|
|
|
|
if v in ('0', 'false'):
|
|
|
|
return False
|
|
|
|
if v in ('1', 'true'):
|
|
|
|
return True
|
|
|
|
if v is None:
|
|
|
|
return False # default value is False
|
|
|
|
raise WrongFieldValue
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
self.setTagData('value', value and '1' or '0')
|
|
|
|
|
|
|
|
def fdel(self, value):
|
|
|
|
t = self.getTag('value')
|
|
|
|
if t is not None:
|
|
|
|
self.delChild(t)
|
|
|
|
|
|
|
|
return locals()
|
2006-11-18 19:39:02 +01:00
|
|
|
|
|
|
|
class StringField(DataField):
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
|
|
|
Covers fields of types: fixed, hidden, text-private, text-single
|
|
|
|
"""
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def value():
|
|
|
|
"""
|
|
|
|
Value of field. May be any unicode string
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
return self.getTagData('value') or u''
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
assert isinstance(value, basestring)
|
2010-08-10 18:25:45 +02:00
|
|
|
if value == '' and not self.required:
|
2010-02-08 15:08:40 +01:00
|
|
|
return fdel(self)
|
|
|
|
self.setTagData('value', value)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
try:
|
|
|
|
self.delChild(self.getTag('value'))
|
|
|
|
except ValueError: # if there already were no value tag
|
|
|
|
pass
|
|
|
|
|
|
|
|
return locals()
|
2006-11-18 19:39:02 +01:00
|
|
|
|
|
|
|
class ListField(DataField):
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
|
|
|
Covers fields of types: jid-multi, jid-single, list-multi, list-single
|
|
|
|
"""
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def options():
|
|
|
|
"""
|
|
|
|
Options
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
options = []
|
|
|
|
for element in self.getTags('option'):
|
|
|
|
v = element.getTagData('value')
|
|
|
|
if v is None:
|
|
|
|
raise WrongFieldValue
|
|
|
|
l = element.getAttr('label')
|
|
|
|
if not l:
|
|
|
|
l = v
|
|
|
|
options.append((l, v))
|
|
|
|
return options
|
|
|
|
|
|
|
|
def fset(self, values):
|
|
|
|
fdel(self)
|
|
|
|
for value, label in values:
|
|
|
|
self.addChild('option', {'label': label}).setTagData('value', value)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
for element in self.getTags('option'):
|
|
|
|
self.delChild(element)
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
|
|
|
def iter_options(self):
|
|
|
|
for element in self.iterTags('option'):
|
|
|
|
v = element.getTagData('value')
|
|
|
|
if v is None:
|
|
|
|
raise WrongFieldValue
|
|
|
|
l = element.getAttr('label')
|
|
|
|
if not l:
|
|
|
|
l = v
|
|
|
|
yield (v, l)
|
2006-11-18 19:39:02 +01:00
|
|
|
|
|
|
|
class ListSingleField(ListField, StringField):
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
2010-08-10 16:08:06 +02:00
|
|
|
Covers list-single field
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
2010-08-11 09:31:41 +02:00
|
|
|
def is_valid(self):
|
|
|
|
if not self.required:
|
|
|
|
return True
|
|
|
|
if not self.value:
|
|
|
|
return False
|
2010-08-12 21:37:26 +02:00
|
|
|
return True
|
2006-11-18 19:39:02 +01:00
|
|
|
|
2010-08-10 16:08:06 +02:00
|
|
|
class JidSingleField(ListSingleField):
|
|
|
|
"""
|
|
|
|
Covers jid-single fields
|
|
|
|
"""
|
|
|
|
def is_valid(self):
|
|
|
|
if self.value:
|
|
|
|
try:
|
|
|
|
helpers.parse_jid(self.value)
|
|
|
|
return True
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
if self.required:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2006-11-18 19:39:02 +01:00
|
|
|
class ListMultiField(ListField):
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
2010-08-10 16:08:06 +02:00
|
|
|
Covers list-multi fields
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def values():
|
|
|
|
"""
|
|
|
|
Values held in field
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
values = []
|
|
|
|
for element in self.getTags('value'):
|
|
|
|
values.append(element.getData())
|
|
|
|
return values
|
|
|
|
|
|
|
|
def fset(self, values):
|
|
|
|
fdel(self)
|
|
|
|
for value in values:
|
|
|
|
self.addChild('value').setData(value)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
for element in self.getTags('value'):
|
|
|
|
self.delChild(element)
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
|
|
|
def iter_values(self):
|
|
|
|
for element in self.getTags('value'):
|
|
|
|
yield element.getData()
|
2012-08-22 12:55:57 +02:00
|
|
|
|
2010-08-11 09:31:41 +02:00
|
|
|
def is_valid(self):
|
|
|
|
if not self.required:
|
|
|
|
return True
|
|
|
|
if not self.values:
|
|
|
|
return False
|
2010-08-12 21:42:15 +02:00
|
|
|
return True
|
2006-11-18 19:39:02 +01:00
|
|
|
|
2010-08-10 16:08:06 +02:00
|
|
|
class JidMultiField(ListMultiField):
|
|
|
|
"""
|
|
|
|
Covers jid-multi fields
|
|
|
|
"""
|
|
|
|
def is_valid(self):
|
|
|
|
if len(self.values):
|
|
|
|
for value in self.values:
|
|
|
|
try:
|
2010-08-16 16:13:55 +02:00
|
|
|
helpers.parse_jid(value)
|
2010-08-10 16:08:06 +02:00
|
|
|
except:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
if self.required:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2006-11-18 19:39:02 +01:00
|
|
|
class TextMultiField(DataField):
|
2010-02-08 15:08:40 +01:00
|
|
|
@nested_property
|
|
|
|
def value():
|
|
|
|
"""
|
|
|
|
Value held in field
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
value = u''
|
|
|
|
for element in self.iterTags('value'):
|
|
|
|
value += '\n' + element.getData()
|
|
|
|
return value[1:]
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
fdel(self)
|
|
|
|
if value == '':
|
|
|
|
return
|
|
|
|
for line in value.split('\n'):
|
|
|
|
self.addChild('value').setData(line)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
for element in self.getTags('value'):
|
|
|
|
self.delChild(element)
|
|
|
|
|
|
|
|
return locals()
|
2006-11-18 19:39:02 +01:00
|
|
|
|
|
|
|
class DataRecord(ExtendedNode):
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
|
|
|
The container for data fields - an xml element which has DataField elements
|
|
|
|
as children
|
|
|
|
"""
|
|
|
|
def __init__(self, fields=None, associated=None, extend=None):
|
|
|
|
self.associated = associated
|
|
|
|
self.vars = {}
|
|
|
|
if extend is None:
|
|
|
|
# we have to build this object from scratch
|
2012-12-09 21:37:51 +01:00
|
|
|
nbxmpp.Node.__init__(self)
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
if fields is not None:
|
|
|
|
self.fields = fields
|
|
|
|
else:
|
2012-12-09 21:37:51 +01:00
|
|
|
# we already have nbxmpp.Node inside - try to convert all
|
2010-02-08 15:08:40 +01:00
|
|
|
# fields into DataField objects
|
|
|
|
if fields is None:
|
|
|
|
for field in self.iterTags('field'):
|
|
|
|
if not isinstance(field, DataField):
|
|
|
|
ExtendField(field)
|
|
|
|
self.vars[field.var] = field
|
|
|
|
else:
|
|
|
|
for field in self.getTags('field'):
|
|
|
|
self.delChild(field)
|
|
|
|
self.fields = fields
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def fields():
|
|
|
|
"""
|
|
|
|
List of fields in this record
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
return self.getTags('field')
|
|
|
|
|
|
|
|
def fset(self, fields):
|
|
|
|
fdel(self)
|
|
|
|
for field in fields:
|
|
|
|
if not isinstance(field, DataField):
|
|
|
|
ExtendField(extend=field)
|
|
|
|
self.addChild(node=field)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
for element in self.getTags('field'):
|
|
|
|
self.delChild(element)
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
|
|
|
def iter_fields(self):
|
|
|
|
"""
|
|
|
|
Iterate over fields in this record. Do not take associated into account
|
|
|
|
"""
|
|
|
|
for field in self.iterTags('field'):
|
|
|
|
yield field
|
|
|
|
|
|
|
|
def iter_with_associated(self):
|
|
|
|
"""
|
|
|
|
Iterate over associated, yielding both our field and associated one
|
|
|
|
together
|
|
|
|
"""
|
|
|
|
for field in self.associated.iter_fields():
|
|
|
|
yield self[field.var], field
|
|
|
|
|
|
|
|
def __getitem__(self, item):
|
|
|
|
return self.vars[item]
|
2012-08-22 12:55:57 +02:00
|
|
|
|
2010-08-10 16:08:06 +02:00
|
|
|
def is_valid(self):
|
|
|
|
for f in self.iter_fields():
|
|
|
|
if not f.is_valid():
|
|
|
|
return False
|
|
|
|
return True
|
2006-07-05 18:35:22 +02:00
|
|
|
|
2006-11-18 19:39:02 +01:00
|
|
|
class DataForm(ExtendedNode):
|
2010-02-08 15:08:40 +01:00
|
|
|
def __init__(self, type_=None, title=None, instructions=None, extend=None):
|
|
|
|
if extend is None:
|
|
|
|
# we have to build form from scratch
|
2012-12-09 21:37:51 +01:00
|
|
|
nbxmpp.Node.__init__(self, 'x', attrs={'xmlns': nbxmpp.NS_DATA})
|
2010-02-08 15:08:40 +01:00
|
|
|
|
|
|
|
if type_ is not None:
|
|
|
|
self.type_=type_
|
|
|
|
if title is not None:
|
|
|
|
self.title=title
|
|
|
|
if instructions is not None:
|
|
|
|
self.instructions=instructions
|
|
|
|
|
|
|
|
@nested_property
|
2012-08-24 10:37:24 +02:00
|
|
|
def type_():
|
2010-02-08 15:08:40 +01:00
|
|
|
"""
|
|
|
|
Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'.
|
|
|
|
'form' - this form is to be filled in; you will be able soon to do:
|
|
|
|
filledform = DataForm(replyto=thisform)
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
return self.getAttr('type')
|
|
|
|
|
|
|
|
def fset(self, type_):
|
|
|
|
assert type_ in ('form', 'submit', 'cancel', 'result')
|
|
|
|
self.setAttr('type', type_)
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def title():
|
|
|
|
"""
|
|
|
|
Title of the form
|
|
|
|
|
|
|
|
Human-readable, should not contain any \\r\\n.
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
return self.getTagData('title')
|
|
|
|
|
|
|
|
def fset(self, title):
|
|
|
|
self.setTagData('title', title)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
try:
|
|
|
|
self.delChild('title')
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def instructions():
|
|
|
|
"""
|
|
|
|
Instructions for this form
|
|
|
|
|
|
|
|
Human-readable, may contain \\r\\n.
|
|
|
|
"""
|
|
|
|
# TODO: the same code is in TextMultiField. join them
|
|
|
|
def fget(self):
|
|
|
|
value = u''
|
|
|
|
for valuenode in self.getTags('instructions'):
|
|
|
|
value += '\n' + valuenode.getData()
|
|
|
|
return value[1:]
|
|
|
|
|
|
|
|
def fset(self, value):
|
|
|
|
fdel(self)
|
|
|
|
if value == '': return
|
|
|
|
for line in value.split('\n'):
|
|
|
|
self.addChild('instructions').setData(line)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
for value in self.getTags('instructions'):
|
|
|
|
self.delChild(value)
|
|
|
|
|
|
|
|
return locals()
|
2006-11-18 19:39:02 +01:00
|
|
|
|
|
|
|
class SimpleDataForm(DataForm, DataRecord):
|
2010-02-08 15:08:40 +01:00
|
|
|
def __init__(self, type_=None, title=None, instructions=None, fields=None, \
|
|
|
|
extend=None):
|
|
|
|
DataForm.__init__(self, type_=type_, title=title,
|
|
|
|
instructions=instructions, extend=extend)
|
|
|
|
DataRecord.__init__(self, fields=fields, extend=self, associated=self)
|
|
|
|
|
|
|
|
def get_purged(self):
|
|
|
|
c = SimpleDataForm(extend=self)
|
|
|
|
del c.title
|
|
|
|
c.instructions = ''
|
|
|
|
to_be_removed = []
|
|
|
|
for f in c.iter_fields():
|
|
|
|
if f.required:
|
2010-08-10 18:25:45 +02:00
|
|
|
# add <value> if there is not
|
|
|
|
if hasattr(f, 'value') and not f.value:
|
|
|
|
f.value = ''
|
2010-02-08 15:08:40 +01:00
|
|
|
# Keep all required fields
|
|
|
|
continue
|
2011-03-05 09:40:01 +01:00
|
|
|
if (hasattr(f, 'value') and not f.value and f.value != 0) or (
|
|
|
|
hasattr(f, 'values') and len(f.values) == 0):
|
2010-02-08 15:08:40 +01:00
|
|
|
to_be_removed.append(f)
|
|
|
|
else:
|
|
|
|
del f.label
|
|
|
|
del f.description
|
2010-06-27 23:09:07 +02:00
|
|
|
del f.media
|
2010-02-08 15:08:40 +01:00
|
|
|
for f in to_be_removed:
|
|
|
|
c.delChild(f)
|
|
|
|
return c
|
2006-11-18 19:39:02 +01:00
|
|
|
|
|
|
|
class MultipleDataForm(DataForm):
|
2010-02-08 15:08:40 +01:00
|
|
|
def __init__(self, type_=None, title=None, instructions=None, items=None,
|
|
|
|
extend=None):
|
|
|
|
DataForm.__init__(self, type_=type_, title=title,
|
|
|
|
instructions=instructions, extend=extend)
|
|
|
|
# all records, recorded into DataRecords
|
|
|
|
if extend is None:
|
|
|
|
if items is not None:
|
|
|
|
self.items = items
|
|
|
|
else:
|
2012-12-09 21:37:51 +01:00
|
|
|
# we already have nbxmpp.Node inside - try to convert all
|
2010-02-08 15:08:40 +01:00
|
|
|
# fields into DataField objects
|
|
|
|
if items is None:
|
|
|
|
self.items = list(self.iterTags('item'))
|
|
|
|
else:
|
|
|
|
for item in self.getTags('item'):
|
|
|
|
self.delChild(item)
|
|
|
|
self.items = items
|
|
|
|
reported_tag = self.getTag('reported')
|
|
|
|
self.reported = DataRecord(extend=reported_tag)
|
|
|
|
|
|
|
|
@nested_property
|
|
|
|
def items():
|
|
|
|
"""
|
|
|
|
A list of all records
|
|
|
|
"""
|
|
|
|
def fget(self):
|
|
|
|
return list(self.iter_records())
|
|
|
|
|
|
|
|
def fset(self, records):
|
|
|
|
fdel(self)
|
|
|
|
for record in records:
|
|
|
|
if not isinstance(record, DataRecord):
|
|
|
|
DataRecord(extend=record)
|
|
|
|
self.addChild(node=record)
|
|
|
|
|
|
|
|
def fdel(self):
|
|
|
|
for record in self.getTags('item'):
|
|
|
|
self.delChild(record)
|
|
|
|
|
|
|
|
return locals()
|
|
|
|
|
|
|
|
def iter_records(self):
|
|
|
|
for record in self.getTags('item'):
|
|
|
|
yield record
|
|
|
|
|
|
|
|
# @nested_property
|
|
|
|
# def reported():
|
|
|
|
# """
|
|
|
|
# DataRecord that contains descriptions of fields in records
|
|
|
|
# """
|
|
|
|
# def fget(self):
|
|
|
|
# return self.getTag('reported')
|
|
|
|
# def fset(self, record):
|
|
|
|
# try:
|
|
|
|
# self.delChild('reported')
|
|
|
|
# except:
|
|
|
|
# pass
|
2007-04-19 15:27:14 +02:00
|
|
|
#
|
2010-02-08 15:08:40 +01:00
|
|
|
# record.setName('reported')
|
|
|
|
# self.addChild(node=record)
|
|
|
|
# return locals()
|