From e049cef00afb2fc1ae0e315674282a2d39464b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20Bevan=E2=80=93McGregor?= Date: Tue, 13 Oct 2015 21:54:58 -0400 Subject: [PATCH 1/5] Add arbitrary metadata capture to `BaseField`. Includes ability to detect and report conflicts. --- mongoengine/base/fields.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/mongoengine/base/fields.py b/mongoengine/base/fields.py index 304c084d..d92e9b3a 100644 --- a/mongoengine/base/fields.py +++ b/mongoengine/base/fields.py @@ -42,7 +42,8 @@ class BaseField(object): def __init__(self, db_field=None, name=None, required=False, default=None, unique=False, unique_with=None, primary_key=False, validation=None, choices=None, verbose_name=None, - help_text=None, null=False, sparse=False, custom_data=None): + help_text=None, null=False, sparse=False, custom_data=None + **kwargs): """ :param db_field: The database field to store this field in (defaults to the name of the field) @@ -70,6 +71,7 @@ class BaseField(object): :param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False` means that uniqueness won't be enforced for `None` values :param custom_data: (optional) Custom metadata for this field. + :param **kwargs: (optional) Arbitrary indirection-free metadata for this field. """ self.db_field = (db_field or name) if not primary_key else '_id' @@ -89,6 +91,15 @@ class BaseField(object): self.sparse = sparse self._owner_document = None self.custom_data = custom_data + + conflicts = set(dir(self)).intersect(kwargs) + if conflicts: + raise TypeError("%s already has attribute(s): %s" % ( + self.__class__.__name__, + ', '.join(conflicts) + )) + + self.__dict__.update(kwargs) # Adjust the appropriate creation counter, and save our local copy. if self.db_field == '_id': From d133913c3dd561e3b0a0002489cad4be9973637f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20Bevan=E2=80=93McGregor?= Date: Tue, 13 Oct 2015 21:59:29 -0400 Subject: [PATCH 2/5] Remove now superfluous special cases. Removes `verbose_name`, `help_text`, and `custom_data`. All three are covered by the one metadata assignment and will continue working as expected. --- mongoengine/base/fields.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/mongoengine/base/fields.py b/mongoengine/base/fields.py index d92e9b3a..3427a094 100644 --- a/mongoengine/base/fields.py +++ b/mongoengine/base/fields.py @@ -41,8 +41,7 @@ class BaseField(object): def __init__(self, db_field=None, name=None, required=False, default=None, unique=False, unique_with=None, primary_key=False, - validation=None, choices=None, verbose_name=None, - help_text=None, null=False, sparse=False, custom_data=None + validation=None, choices=None, null=False, sparse=False, **kwargs): """ :param db_field: The database field to store this field in @@ -61,16 +60,10 @@ class BaseField(object): field. Generally this is deprecated in favour of the `FIELD.validate` method :param choices: (optional) The valid choices - :param verbose_name: (optional) The verbose name for the field. - Designed to be human readable and is often used when generating - model forms from the document model. - :param help_text: (optional) The help text for this field and is often - used when generating model forms from the document model. :param null: (optional) Is the field value can be null. If no and there is a default value then the default value is set :param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False` means that uniqueness won't be enforced for `None` values - :param custom_data: (optional) Custom metadata for this field. :param **kwargs: (optional) Arbitrary indirection-free metadata for this field. """ self.db_field = (db_field or name) if not primary_key else '_id' @@ -85,12 +78,9 @@ class BaseField(object): self.primary_key = primary_key self.validation = validation self.choices = choices - self.verbose_name = verbose_name - self.help_text = help_text self.null = null self.sparse = sparse self._owner_document = None - self.custom_data = custom_data conflicts = set(dir(self)).intersect(kwargs) if conflicts: From 3f3747a2fe660d08bb8af4f079fb2f4b0c3540eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20Bevan=E2=80=93McGregor?= Date: Tue, 13 Oct 2015 21:59:46 -0400 Subject: [PATCH 3/5] Minor formatting tweaks and additional comments. --- mongoengine/base/fields.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mongoengine/base/fields.py b/mongoengine/base/fields.py index 3427a094..9f054a2b 100644 --- a/mongoengine/base/fields.py +++ b/mongoengine/base/fields.py @@ -82,13 +82,14 @@ class BaseField(object): self.sparse = sparse self._owner_document = None + # Detect and report conflicts between metadata and base properties. conflicts = set(dir(self)).intersect(kwargs) if conflicts: raise TypeError("%s already has attribute(s): %s" % ( - self.__class__.__name__, - ', '.join(conflicts) - )) - + self.__class__.__name__, ', '.join(conflicts) )) + + # Assign metadata to the instance + # This efficient method is available because no __slots__ are defined. self.__dict__.update(kwargs) # Adjust the appropriate creation counter, and save our local copy. From a57f28ac832041ccb5c21cb7713c9723a3fd672f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20Bevan=E2=80=93McGregor?= Date: Tue, 13 Oct 2015 22:41:58 -0400 Subject: [PATCH 4/5] Correction for local monkeypatch. --- mongoengine/base/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongoengine/base/fields.py b/mongoengine/base/fields.py index 9f054a2b..d14ecce0 100644 --- a/mongoengine/base/fields.py +++ b/mongoengine/base/fields.py @@ -83,7 +83,7 @@ class BaseField(object): self._owner_document = None # Detect and report conflicts between metadata and base properties. - conflicts = set(dir(self)).intersect(kwargs) + conflicts = set(dir(self)) & set(kwargs) if conflicts: raise TypeError("%s already has attribute(s): %s" % ( self.__class__.__name__, ', '.join(conflicts) )) From 50b271c868c6b1fac95eb650d1b4b2592540e236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alice=20Bevan=E2=80=93McGregor?= Date: Tue, 13 Oct 2015 22:51:03 -0400 Subject: [PATCH 5/5] Arbitrary metadata documentation. --- docs/guide/defining-documents.rst | 10 +++++----- mongoengine/base/fields.py | 6 +++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/guide/defining-documents.rst b/docs/guide/defining-documents.rst index 8f7382ee..c3ad208e 100644 --- a/docs/guide/defining-documents.rst +++ b/docs/guide/defining-documents.rst @@ -172,11 +172,11 @@ arguments can be set on all fields: class Shirt(Document): size = StringField(max_length=3, choices=SIZE) -:attr:`help_text` (Default: None) - Optional help text to output with the field -- used by form libraries - -:attr:`verbose_name` (Default: None) - Optional human-readable name for the field -- used by form libraries +:attr:`**kwargs` (Optional) + You can supply additional metadata as arbitrary additional keyword + arguments. You can not override existing attributes, however. Common + choices include `help_text` and `verbose_name`, commonly used by form and + widget libraries. List fields diff --git a/mongoengine/base/fields.py b/mongoengine/base/fields.py index d14ecce0..b1024526 100644 --- a/mongoengine/base/fields.py +++ b/mongoengine/base/fields.py @@ -64,7 +64,11 @@ class BaseField(object): then the default value is set :param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False` means that uniqueness won't be enforced for `None` values - :param **kwargs: (optional) Arbitrary indirection-free metadata for this field. + :param **kwargs: (optional) Arbitrary indirection-free metadata for + this field can be supplied as additional keyword arguments and + accessed as attributes of the field. Must not conflict with any + existing attributes. Common metadata includes `verbose_name` and + `help_text`. """ self.db_field = (db_field or name) if not primary_key else '_id'