- New interface methods required by types and schema nodes:
``pserialize`` and ``pdeserialize``. These partially serialize or partially deserialize a value (the definition of "partial" is up to the type). - Mapping type: missing -> partial.
This commit is contained in:
		
							
								
								
									
										12
									
								
								CHANGES.txt
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								CHANGES.txt
									
									
									
									
									
								
							| @@ -14,12 +14,20 @@ Next release | ||||
|  | ||||
| - Add a ``__setitem__`` method to the ``colander.Invalid`` class. | ||||
|  | ||||
| - The ``colander.Mapping`` keyword argument ``unknown_keys`` has been | ||||
|   renamed to ``unknown``. | ||||
|  | ||||
| - Allow ``colander.Mapping`` type to accept a new constructor | ||||
|   argument: ``missing``. | ||||
|   argument: ``partial``. | ||||
|  | ||||
| - Allow ``colander.Mapping.serialize`` and | ||||
|   ``colander.Mapping.deserialize`` methods to accept ``unknown`` and | ||||
|   ``serialize`` keyword arguments. | ||||
|   ``partial`` keyword arguments. | ||||
|  | ||||
| - New interface methods required by types and schema nodes: | ||||
|   ``pserialize`` and ``pdeserialize``.  These partially serialize or | ||||
|   partially deserialize a value (the definition of "partial" is up to | ||||
|   the type). | ||||
|  | ||||
| 0.5 (2010-03-31) | ||||
| ---------------- | ||||
|   | ||||
| @@ -189,20 +189,28 @@ class OneOf(object): | ||||
|             raise Invalid(node, '"%s" is not one of %s' % ( | ||||
|                 value, ', '.join(['%s' % x for x in self.values]))) | ||||
|  | ||||
| class Mapping(object): | ||||
| class Type(object): | ||||
|     """ Abstract base class for types (only for convenience) """ | ||||
|     def pserialize(self, node, value): | ||||
|         return self.serialize(node, value) | ||||
|  | ||||
|     def pdeserialize(self, node, value): | ||||
|         return self.deserialize(node, value) | ||||
|  | ||||
| class Mapping(Type): | ||||
|     """ A type which represents a mapping of names to nodes. | ||||
|  | ||||
|     The subnodes of the :class:`colander.SchemaNode` that wraps | ||||
|     this type imply the named keys and values in the mapping. | ||||
|  | ||||
|     The constructor of this type accepts two extra optional keyword | ||||
|     arguments that other types do not: ``unknown`` and ``missing``. | ||||
|     arguments that other types do not: ``unknown`` and ``partial``. | ||||
|  | ||||
|     unknown | ||||
|         ``unknown`` controls the behavior of this type when an unknown | ||||
|         key is encountered in the value passed to the ``serialize`` or | ||||
|       ``deserialize`` methods of this instance.  The potential values | ||||
|       of ``unknown`` are: | ||||
|         ``deserialize`` methods of this instance.  The potential | ||||
|         values of ``unknown`` are: | ||||
|  | ||||
|         - ``ignore`` means that keys that are not present in the schema | ||||
|           associated with this type will be ignored during | ||||
| @@ -216,22 +224,22 @@ class Mapping(object): | ||||
|  | ||||
|         Default: ``ignore``. | ||||
|  | ||||
|     missing | ||||
|         ``missing`` controls the behavior of this type when a | ||||
|     partial | ||||
|         ``partial`` controls the behavior of this type when a | ||||
|         schema-expected key is missing from the value passed to the | ||||
|         ``serialize`` and ``deserialize`` methods of this instance. | ||||
|         During serialization and deserialization, when ``missing`` is | ||||
|         ``raise``, a :exc:`colander.Invalid` exception will be raised | ||||
|         During serialization and deserialization, when ``partial`` is | ||||
|         ``False``, a :exc:`colander.Invalid` exception will be raised | ||||
|         if the mapping value does not contain a key specified by the | ||||
|         schema node related to this mapping type.  When ``missing`` is | ||||
|         ``ignore``, no exception is raised and a partial mapping will | ||||
|         schema node related to this mapping type.  When ``partial`` is | ||||
|         ``True``, no exception is raised and a partial mapping will | ||||
|         be serialized/deserialized. | ||||
|  | ||||
|         Default: ``raise``. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, unknown='ignore', missing='raise'): | ||||
|         self.missing = self._check_missing(missing) | ||||
|     def __init__(self, unknown='ignore', partial=False): | ||||
|         self.partial = partial | ||||
|         self.unknown = self._check_unknown(unknown) | ||||
|  | ||||
|     def _check_unknown(self, unknown): | ||||
| @@ -241,23 +249,15 @@ class Mapping(object): | ||||
|                 'or "preserve"') | ||||
|         return unknown | ||||
|  | ||||
|     def _check_missing(self, missing): | ||||
|         if not missing in ['ignore', 'raise']: | ||||
|             raise ValueError( | ||||
|                 'missing argument must be one of "ignore" or "raise"') | ||||
|         return missing | ||||
|  | ||||
|     def _validate(self, node, value): | ||||
|         try: | ||||
|             return dict(value) | ||||
|         except Exception, e: | ||||
|             raise Invalid(node, '%r is not a mapping type: %s' % (value, e)) | ||||
|  | ||||
|     def _impl(self, node, value, callback, default_callback, unknown, missing): | ||||
|         if missing is None: | ||||
|             missing = self.missing | ||||
|         else: | ||||
|             missing = self._check_missing(missing) | ||||
|     def _impl(self, node, value, callback, default_callback, unknown, partial): | ||||
|         if partial is None: | ||||
|             partial = self.partial | ||||
|  | ||||
|         if unknown is None: | ||||
|             unknown = self.unknown | ||||
| @@ -276,7 +276,7 @@ class Mapping(object): | ||||
|             try: | ||||
|                 if subval is _missing: | ||||
|                     if subnode.required: | ||||
|                         if missing == 'raise': | ||||
|                         if not partial: | ||||
|                             raise Invalid( | ||||
|                                 subnode, | ||||
|                                 '"%s" is required but missing' % subnode.name) | ||||
| @@ -303,12 +303,12 @@ class Mapping(object): | ||||
|                  | ||||
|         return result | ||||
|  | ||||
|     def deserialize(self, node, value, unknown=None, missing=None): | ||||
|     def deserialize(self, node, value, unknown=None, partial=None): | ||||
|         """ | ||||
|         Along with the normal ``node`` and ``value`` arguments, this | ||||
|         method implementation accepts two additional optional | ||||
|         arguments that other type implementations do not: ``missing`` | ||||
|         and ``raise``.  These arguments can be used to override the | ||||
|         arguments that other type implementations do not: ``unknown`` | ||||
|         and ``partial``.  These arguments can be used to override the | ||||
|         instance defaults of the same name for the duration of a | ||||
|         particular serialization or deserialization. | ||||
|          | ||||
| @@ -319,12 +319,12 @@ class Mapping(object): | ||||
|           this instance.  It defaults to ``None``, which signifies | ||||
|           that the instance default should be used. | ||||
|  | ||||
|         missing | ||||
|           If this value is provided, it must be one of ``raise`` or | ||||
|           ``ignore``, overriding the behavior implied by the value set | ||||
|           by the ``missing`` argument to constructor of this instance. | ||||
|           It defaults to ``None``, which signifies that the instance | ||||
|           default should be used. | ||||
|         partial | ||||
|           If this value is provided, it must be a boolean, overriding | ||||
|           the behavior implied by the value set by the ``partial`` | ||||
|           argument to constructor of this instance.  It defaults to | ||||
|           ``None``, which signifies that the instance default should | ||||
|           be used. | ||||
|  | ||||
|         """ | ||||
|         def callback(subnode, subval): | ||||
| @@ -332,14 +332,14 @@ class Mapping(object): | ||||
|         def default_callback(subnode): | ||||
|             return subnode.default | ||||
|         return self._impl( | ||||
|             node, value, callback, default_callback, unknown, missing) | ||||
|             node, value, callback, default_callback, unknown, partial) | ||||
|  | ||||
|     def serialize(self, node, value, unknown=None, missing=None): | ||||
|     def serialize(self, node, value, unknown=None, partial=None): | ||||
|         """ | ||||
|         Along with the normal ``node`` and ``value`` arguments, this | ||||
|         method implementation accepts two additional optional | ||||
|         arguments that other type implementations do not: ``missing`` | ||||
|         and ``raise``.  These arguments can be used to override the | ||||
|         arguments that other type implementations do not: ``unknown`` | ||||
|         and ``partial``.  These arguments can be used to override the | ||||
|         instance defaults of the same name for the duration of a | ||||
|         particular serialization or deserialization. | ||||
|          | ||||
| @@ -350,12 +350,12 @@ class Mapping(object): | ||||
|           this instance.  It defaults to ``None``, which signifies | ||||
|           that the instance default should be used. | ||||
|  | ||||
|         missing | ||||
|           If this value is provided, it must be one of ``raise`` or | ||||
|           ``ignore``, overriding the behavior implied by the value set | ||||
|           by the ``missing`` argument to constructor of this instance. | ||||
|           It defaults to ``None``, which signifies that the instance | ||||
|           default should be used. | ||||
|         partial | ||||
|           If this value is provided, it must be a boolean, overriding | ||||
|           the behavior implied by the value set by the ``partial`` | ||||
|           argument to constructor of this instance.  It defaults to | ||||
|           ``None``, which signifies that the instance default should | ||||
|           be used. | ||||
|         """ | ||||
|         def callback(subnode, subval): | ||||
|             return subnode.serialize(subval) | ||||
| @@ -363,7 +363,13 @@ class Mapping(object): | ||||
|             return subnode.serialize(subnode.default) | ||||
|  | ||||
|         return self._impl( | ||||
|             node, value, callback, default_callback, unknown, missing) | ||||
|             node, value, callback, default_callback, unknown, partial) | ||||
|  | ||||
|     def pserialize(self, node, value): | ||||
|         return self.serialize(node, value, partial=True) | ||||
|  | ||||
|     def pdeserialize(self, node, value): | ||||
|         return self.serialize(node, value, partial=True) | ||||
|  | ||||
| class Positional(object): | ||||
|     """ | ||||
| @@ -373,7 +379,7 @@ class Positional(object): | ||||
|     creating a dictionary representation of an error tree. | ||||
|     """ | ||||
|  | ||||
| class Tuple(Positional): | ||||
| class Tuple(Type, Positional): | ||||
|     """ A type which represents a fixed-length sequence of nodes. | ||||
|  | ||||
|     The subnodes of the :class:`colander.SchemaNode` that wraps | ||||
| @@ -427,7 +433,7 @@ class Tuple(Positional): | ||||
|             return subnode.serialize(subval) | ||||
|         return self._impl(node, value, callback) | ||||
|  | ||||
| class Sequence(Positional): | ||||
| class Sequence(Type, Positional): | ||||
|     """ | ||||
|     A type which represents a variable-length sequence of nodes, | ||||
|     all of which must be of the same type. | ||||
| @@ -535,7 +541,7 @@ Seq = Sequence | ||||
|  | ||||
| default_encoding = 'utf-8' | ||||
|  | ||||
| class String(object): | ||||
| class String(Type): | ||||
|     """ A type representing a Unicode string.  This type constructor | ||||
|     accepts a single argument ``encoding``, representing the encoding | ||||
|     which should be applied to object serialization.  It defaults to | ||||
| @@ -568,7 +574,7 @@ class String(object): | ||||
|  | ||||
| Str = String | ||||
|  | ||||
| class Integer(object): | ||||
| class Integer(Type): | ||||
|     """ A type representing an integer. | ||||
|  | ||||
|     The subnodes of the :class:`colander.SchemaNode` that wraps | ||||
| @@ -592,7 +598,7 @@ class Integer(object): | ||||
|  | ||||
| Int = Integer | ||||
|  | ||||
| class Float(object): | ||||
| class Float(Type): | ||||
|     """ A type representing a float. | ||||
|  | ||||
|     The subnodes of the :class:`colander.SchemaNode` that wraps | ||||
| @@ -616,7 +622,7 @@ class Float(object): | ||||
|  | ||||
| Int = Integer | ||||
|  | ||||
| class Boolean(object): | ||||
| class Boolean(Type): | ||||
|     """ A type representing a boolean object. | ||||
|  | ||||
|     During deserialization, a value in the set (``false``, ``0``) will | ||||
| @@ -649,7 +655,7 @@ class Boolean(object): | ||||
|  | ||||
| Bool = Boolean | ||||
|  | ||||
| class GlobalObject(object): | ||||
| class GlobalObject(Type): | ||||
|     """ A type representing an importable Python object.  This type | ||||
|     serializes 'global' Python objects (objects which can be imported) | ||||
|     to dotted Python names. | ||||
| @@ -756,7 +762,7 @@ class GlobalObject(object): | ||||
|         except AttributeError: | ||||
|             raise Invalid(node, '%r has no __name__' % value) | ||||
|  | ||||
| class DateTime(object): | ||||
| class DateTime(Type): | ||||
|     """ A type representing a Python ``datetime.datetime`` object. | ||||
|  | ||||
|     This type serializes python ``datetime.datetime`` objects to a | ||||
| @@ -822,7 +828,7 @@ class DateTime(object): | ||||
|                                                          'exc':e}) | ||||
|         return result | ||||
|  | ||||
| class Date(object): | ||||
| class Date(Type): | ||||
|     """ A type representing a Python ``datetime.date`` object. | ||||
|  | ||||
|     This type serializes python ``datetime.date`` objects to a | ||||
| @@ -833,7 +839,7 @@ class Date(object): | ||||
|  | ||||
|     You can adjust the error message reported by this class by | ||||
|     changing its ``err_template`` attribute in a subclass on an | ||||
|     instance of this class.  By default, the ``err_tempalte`` | ||||
|     instance of this class.  By default, the ``err_template`` | ||||
|     attribute is the string ``%(value)s cannot be parsed as an iso8601 | ||||
|     date: %(exc)s``.  This string is used as the interpolation subject | ||||
|     of a dictionary composed of ``value`` and ``exc``.  ``value`` and | ||||
| @@ -946,20 +952,35 @@ class SchemaNode(object): | ||||
|         return self.typ.serialize(self, self.default) | ||||
|  | ||||
|     def deserialize(self, value): | ||||
|         """ Derialize the value based on the schema represented by this | ||||
|         node """ | ||||
|         """ Deserialize the value based on the schema represented by | ||||
|         this node.  The values passed as ``kw`` will be passed along | ||||
|         to the ``deserialize`` method of this node's type.""" | ||||
|         value = self.typ.deserialize(self, value) | ||||
|         if self.validator is not None: | ||||
|             self.validator(self, value) | ||||
|         return value | ||||
|  | ||||
|     def serialize(self, value): | ||||
|         """ Serialize the value based on the schema represented by this | ||||
|         node """ | ||||
|         """ Serialize the value based on the schema represented by | ||||
|         this node.  The values passed as ``kw`` will be passed along | ||||
|         to the ``serialize`` method of this node's type.""" | ||||
|         return self.typ.serialize(self, value) | ||||
|  | ||||
|     def pserialize(self, value): | ||||
|         """ Partially serialize the value based on the schema | ||||
|         represented by this node.  The values passed as ``kw`` will be | ||||
|         passed along to the ``pserialize`` method of this node's type.""" | ||||
|         return self.typ.pserialize(self, value) | ||||
|  | ||||
|     def pdeserialize(self, value): | ||||
|         """ Partially deserialize the value based on the schema | ||||
|         represented by this node.  The values passed as ``kw`` will be | ||||
|         passed along to the ``pdeserialize`` method of this node's | ||||
|         type.""" | ||||
|         return self.typ.pdeserialize(self, value) | ||||
|  | ||||
|     def add(self, node): | ||||
|         """ Add a subnode to this node """ | ||||
|         """ Add a subnode to this node. """ | ||||
|         self.children.append(node) | ||||
|  | ||||
|     def __getitem__(self, name): | ||||
|   | ||||
| @@ -34,7 +34,6 @@ class Type(object): | ||||
|  | ||||
|     def deserialize(self, struct, value): | ||||
|         """ | ||||
|  | ||||
|         Deserialze the serialization represented by ``value`` to a | ||||
|         data structure.  The deserialization should be composed of one | ||||
|         or more objects which can be serialized by the | ||||
| @@ -53,3 +52,28 @@ class Type(object): | ||||
|         raised. | ||||
|         """ | ||||
|          | ||||
|     def pserialize(self, node, value): | ||||
|         """ Partially serialize a value, ignoring any missing | ||||
|         components. | ||||
|  | ||||
|         The description of the ``node`` and ``value`` arguments are | ||||
|         the same as those provided to ``serialize``. | ||||
|  | ||||
|         The return value and behavior of any partial serialization is | ||||
|         completely type-dependent.  If partial serialization is not | ||||
|         applicable for a type, this method will usually be an alias | ||||
|         for that type's 'serialize' method. | ||||
|         """ | ||||
|  | ||||
|     def pdeserialize(self, node, value): | ||||
|         """ Partially deserialize a value, ignoring any missing | ||||
|         components. | ||||
|  | ||||
|         The description of the ``node`` and ``value`` arguments are | ||||
|         the same as those provided to ``deserialize``. | ||||
|  | ||||
|         The return value and behavior of any partial deserialization | ||||
|         is completely type-dependent.  If partial deserialization is | ||||
|         not applicable for a type, this method will usually be an | ||||
|         alias for that type's 'deserialize' method. | ||||
|         """ | ||||
|   | ||||
| @@ -211,6 +211,23 @@ class TestOneOf(unittest.TestCase): | ||||
|         e = invalid_exc(validator, None, None) | ||||
|         self.assertEqual(e.msg, '"None" is not one of 1, 2') | ||||
|  | ||||
| class TestType(unittest.TestCase): | ||||
|     def _makeOne(self): | ||||
|         from colander import Type | ||||
|         return Type() | ||||
|  | ||||
|     def test_pserialize(self): | ||||
|         typ = self._makeOne() | ||||
|         typ.serialize = lambda *arg: arg | ||||
|         result = typ.pserialize(None, None) | ||||
|         self.assertEqual(result, (None, None)) | ||||
|  | ||||
|     def test_pdeserialize(self): | ||||
|         typ = self._makeOne() | ||||
|         typ.deserialize = lambda *arg: arg | ||||
|         result = typ.pdeserialize(None, None) | ||||
|         self.assertEqual(result, (None, None)) | ||||
|  | ||||
| class TestMapping(unittest.TestCase): | ||||
|     def _makeOne(self, *arg, **kw): | ||||
|         from colander import Mapping | ||||
| @@ -227,16 +244,6 @@ class TestMapping(unittest.TestCase): | ||||
|         except ValueError, e: # pragma: no cover | ||||
|             raise AssertionError(e) | ||||
|  | ||||
|     def test_ctor_bad_missing(self): | ||||
|         self.assertRaises(ValueError, self._makeOne, 'ignore', 'badarg') | ||||
|  | ||||
|     def test_ctor_good_missing(self): | ||||
|         try: | ||||
|             self._makeOne(missing='ignore') | ||||
|             self._makeOne(missing='raise') | ||||
|         except ValueError, e: # pragma: no cover | ||||
|             raise AssertionError(e) | ||||
|  | ||||
|     def test_deserialize_not_a_mapping(self): | ||||
|         node = DummySchemaNode(None) | ||||
|         typ = self._makeOne() | ||||
| @@ -316,24 +323,24 @@ class TestMapping(unittest.TestCase): | ||||
|         e = invalid_exc(typ.deserialize, node, {'a':1}) | ||||
|         self.assertEqual(e.children[0].msg, '"b" is required but missing') | ||||
|  | ||||
|     def test_deserialize_subnode_missing_ignore(self): | ||||
|     def test_deserialize_subnode_partial(self): | ||||
|         node = DummySchemaNode(None) | ||||
|         node.children = [ | ||||
|             DummySchemaNode(None, name='a'), | ||||
|             DummySchemaNode(None, name='b'), | ||||
|             ] | ||||
|         typ = self._makeOne(missing='ignore') | ||||
|         typ = self._makeOne(partial=True) | ||||
|         result = typ.deserialize(node, {'a':1}) | ||||
|         self.assertEqual(result, {'a':1}) | ||||
|  | ||||
|     def test_deserialize_subnode_missing_ignore_override(self): | ||||
|     def test_deserialize_subnode_partial_ignore_override(self): | ||||
|         node = DummySchemaNode(None) | ||||
|         node.children = [ | ||||
|             DummySchemaNode(None, name='a'), | ||||
|             DummySchemaNode(None, name='b'), | ||||
|             ] | ||||
|         typ = self._makeOne() | ||||
|         result = typ.deserialize(node, {'a':1}, 'ignore', 'ignore') | ||||
|         result = typ.deserialize(node, {'a':1}, 'ignore', True) | ||||
|         self.assertEqual(result, {'a':1}) | ||||
|  | ||||
|     def test_serialize_not_a_mapping(self): | ||||
| @@ -415,24 +422,44 @@ class TestMapping(unittest.TestCase): | ||||
|         e = invalid_exc(typ.serialize, node, {'a':1}) | ||||
|         self.assertEqual(e.children[0].msg, '"b" is required but missing') | ||||
|  | ||||
|     def test_serialize_subnode_missing_ignore(self): | ||||
|     def test_serialize_subnode_partial(self): | ||||
|         node = DummySchemaNode(None) | ||||
|         node.children = [ | ||||
|             DummySchemaNode(None, name='a'), | ||||
|             DummySchemaNode(None, name='b'), | ||||
|             ] | ||||
|         typ = self._makeOne(missing='ignore') | ||||
|         typ = self._makeOne(partial=True) | ||||
|         result = typ.serialize(node, {'a':1}) | ||||
|         self.assertEqual(result, {'a':1}) | ||||
|  | ||||
|     def test_serialize_subnode_missing_ignore_override(self): | ||||
|     def test_serialize_subnode_partial_ignore_override(self): | ||||
|         node = DummySchemaNode(None) | ||||
|         node.children = [ | ||||
|             DummySchemaNode(None, name='a'), | ||||
|             DummySchemaNode(None, name='b'), | ||||
|             ] | ||||
|         typ = self._makeOne() | ||||
|         result = typ.serialize(node, {'a':1}, 'ignore', 'ignore') | ||||
|         result = typ.serialize(node, {'a':1}, 'ignore', True) | ||||
|         self.assertEqual(result, {'a':1}) | ||||
|  | ||||
|     def test_pserialize(self): | ||||
|         node = DummySchemaNode(None) | ||||
|         node.children = [ | ||||
|             DummySchemaNode(None, name='a'), | ||||
|             DummySchemaNode(None, name='b'), | ||||
|             ] | ||||
|         typ = self._makeOne() | ||||
|         result = typ.pserialize(node, {'a':1}) | ||||
|         self.assertEqual(result, {'a':1}) | ||||
|  | ||||
|     def test_pdeserialize(self): | ||||
|         node = DummySchemaNode(None) | ||||
|         node.children = [ | ||||
|             DummySchemaNode(None, name='a'), | ||||
|             DummySchemaNode(None, name='b'), | ||||
|             ] | ||||
|         typ = self._makeOne() | ||||
|         result = typ.pdeserialize(node, {'a':1}) | ||||
|         self.assertEqual(result, {'a':1}) | ||||
|  | ||||
| class TestTuple(unittest.TestCase): | ||||
| @@ -1200,12 +1227,24 @@ class TestSchemaNode(unittest.TestCase): | ||||
|         e = invalid_exc(node.deserialize, 1) | ||||
|         self.assertEqual(e.msg, 'Wrong') | ||||
|  | ||||
|     def test_pdeserialize(self): | ||||
|         typ = DummyType() | ||||
|         node = self._makeOne(typ) | ||||
|         result = node.pdeserialize(1) | ||||
|         self.assertEqual(result, 1) | ||||
|  | ||||
|     def test_serialize(self): | ||||
|         typ = DummyType() | ||||
|         node = self._makeOne(typ) | ||||
|         result = node.serialize(1) | ||||
|         self.assertEqual(result, 1) | ||||
|  | ||||
|     def test_pserialize(self): | ||||
|         typ = DummyType() | ||||
|         node = self._makeOne(typ) | ||||
|         result = node.pserialize(1) | ||||
|         self.assertEqual(result, 1) | ||||
|  | ||||
|     def test_add(self): | ||||
|         node = self._makeOne(None) | ||||
|         node.add(1) | ||||
| @@ -1474,3 +1513,9 @@ class DummyType(object): | ||||
|     def deserialize(self, node, value): | ||||
|         return value | ||||
|  | ||||
|     def pserialize(self, node, value): | ||||
|         return value | ||||
|      | ||||
|     def pdeserialize(self, node, value): | ||||
|         return value | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Chris McDonough
					Chris McDonough