Fields of Gold
==============
A Django library providing useful DB model fields.
Installation & setup
--------------------
1. `pip install fields-of-gold`
2. Add `"fields_of_gold"` to your `INSTALLED_APPS`.
3. Use the fields in your models.
Fields
------
### TypedJSONField
A JSONField which can have an enforced schema using [Pydantic](https://docs.pydantic.dev/) models.
The underlying storage uses the JSONField, but you can interact with the data via the declared Pydantic type.
Because the underlying storage is using JSON, you can swap out existing JSONFields for TypedJSONField without
having to perform any manipulation to the stored data, so long as the existing data conforms to your Pydantic schema.
Example usage:
```python
from django.db import models
from fields_of_gold import TypedJSONField
from pydantic import BaseModel
class MyType(BaseModel):
my_int: int
my_str: str
class MyModel(models.Model):
typed_field = TypedJSONField(type=MyType)
...
instance = MyModel(typed_field=MyType(my_int=1, my_str=2))
instance.full_clean()
instance.save()
```
#### Handling existing data
If you've already got data in your database which doesn't conform to the Pydantic model schema that
you've defined then when you fetch a Django model instance from the database the field value will
simply be set to the raw underlying JSON value rather than the Pydantic model instance.
This allows you to still fetch the objects from your DB, and to fix the data. Here an example
using the `MyType` and `MyModel` from above:
```python
obj = MyModel.objects.get()
if not isinstnce(obj.typed_field, MyType):
# Invalid data, need to fix it:
if isinstance(obj.typed_field, dict):
if "my_int" not in obj.typed_field:
obj.typed_field["my_int"] = 0
if "my_str" not in obj.typed_field:
obj.typed_field["my_str"] = ""
# Note that leaving the data as a dict is fine, so long as it matches the schema of MyType
elif isinstance(obj.typed_field, list):
# Totally the wrong type of data
obj.typed_field = MyType()
obj.save()
```
You might want to handle existing invalid data in your model's `__init__` method.
### NullableOneToOneField
A modified OneToOneField which is unique but nullable.
In Django, using `ForeignKey(unique=True, null=True)` will raise a warning recommending that you should use
`OneToOneField` instead. But using `OneToOneField(null=True)` won't respect the null-able-ness, so trying to
access `obj.my_one_to_one_field` will raise `DoesNotExist`, and the same with the reverse lookup.
This field solves that problem by allowing the field to be nullable while still enforcing its uniqueness and
not giving you unnecessary warnings.
Raw data
{
"_id": null,
"home_page": "https://github.com/adamalton/fields-of-gold",
"name": "fields-of-gold",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "Django, Pydantic, JSON, JSONField, Schema",
"author": "Adam Alton",
"author_email": "270255+adamalton@users.noreply.github.com",
"download_url": "https://files.pythonhosted.org/packages/c9/55/e8e1a915c982851e7868e9b32d804c0e111545539cb85ef6926148a5582d/fields_of_gold-0.1.2.tar.gz",
"platform": null,
"description": "Fields of Gold\n==============\n\nA Django library providing useful DB model fields.\n\n\nInstallation & setup\n--------------------\n\n1. `pip install fields-of-gold`\n2. Add `\"fields_of_gold\"` to your `INSTALLED_APPS`.\n3. Use the fields in your models.\n\n\nFields\n------\n\n### TypedJSONField\n\nA JSONField which can have an enforced schema using [Pydantic](https://docs.pydantic.dev/) models.\nThe underlying storage uses the JSONField, but you can interact with the data via the declared Pydantic type.\n\nBecause the underlying storage is using JSON, you can swap out existing JSONFields for TypedJSONField without\nhaving to perform any manipulation to the stored data, so long as the existing data conforms to your Pydantic schema.\n\nExample usage:\n\n```python\nfrom django.db import models\nfrom fields_of_gold import TypedJSONField\nfrom pydantic import BaseModel\n\nclass MyType(BaseModel):\n my_int: int\n my_str: str\n\n\nclass MyModel(models.Model):\n typed_field = TypedJSONField(type=MyType)\n\n\n...\n\ninstance = MyModel(typed_field=MyType(my_int=1, my_str=2))\ninstance.full_clean()\ninstance.save()\n```\n\n#### Handling existing data\n\nIf you've already got data in your database which doesn't conform to the Pydantic model schema that\nyou've defined then when you fetch a Django model instance from the database the field value will\nsimply be set to the raw underlying JSON value rather than the Pydantic model instance.\nThis allows you to still fetch the objects from your DB, and to fix the data. Here an example\nusing the `MyType` and `MyModel` from above:\n\n```python\n\nobj = MyModel.objects.get()\nif not isinstnce(obj.typed_field, MyType):\n # Invalid data, need to fix it:\n if isinstance(obj.typed_field, dict):\n if \"my_int\" not in obj.typed_field:\n obj.typed_field[\"my_int\"] = 0\n if \"my_str\" not in obj.typed_field:\n obj.typed_field[\"my_str\"] = \"\"\n # Note that leaving the data as a dict is fine, so long as it matches the schema of MyType\n elif isinstance(obj.typed_field, list):\n # Totally the wrong type of data\n obj.typed_field = MyType()\n obj.save()\n```\n\nYou might want to handle existing invalid data in your model's `__init__` method.\n\n\n### NullableOneToOneField\n\nA modified OneToOneField which is unique but nullable.\n\nIn Django, using `ForeignKey(unique=True, null=True)` will raise a warning recommending that you should use\n`OneToOneField` instead. But using `OneToOneField(null=True)` won't respect the null-able-ness, so trying to\naccess `obj.my_one_to_one_field` will raise `DoesNotExist`, and the same with the reverse lookup.\n\nThis field solves that problem by allowing the field to be nullable while still enforcing its uniqueness and\nnot giving you unnecessary warnings.\n",
"bugtrack_url": null,
"license": null,
"summary": "A Django library for providing useful DB model fields.",
"version": "0.1.2",
"project_urls": {
"Homepage": "https://github.com/adamalton/fields-of-gold"
},
"split_keywords": [
"django",
" pydantic",
" json",
" jsonfield",
" schema"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "2d46ff476de0b7b1aa0b77a7fb9ffe0a56db34cb7f776748986b228352be2662",
"md5": "cd008055ca70ce2d89fef994a38c9614",
"sha256": "8a2cea30eae8694a6b95939124ebaf330c2bc65e9c26cf130876a203c99f9979"
},
"downloads": -1,
"filename": "fields_of_gold-0.1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "cd008055ca70ce2d89fef994a38c9614",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 7862,
"upload_time": "2024-11-27T11:47:38",
"upload_time_iso_8601": "2024-11-27T11:47:38.652235Z",
"url": "https://files.pythonhosted.org/packages/2d/46/ff476de0b7b1aa0b77a7fb9ffe0a56db34cb7f776748986b228352be2662/fields_of_gold-0.1.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "c955e8e1a915c982851e7868e9b32d804c0e111545539cb85ef6926148a5582d",
"md5": "43664f2f33917c89af707c442f57ad81",
"sha256": "fa047189f917ffff496975dc4566a0836f31d612227b323becec15d7f4e9f9f6"
},
"downloads": -1,
"filename": "fields_of_gold-0.1.2.tar.gz",
"has_sig": false,
"md5_digest": "43664f2f33917c89af707c442f57ad81",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 7613,
"upload_time": "2024-11-27T11:47:40",
"upload_time_iso_8601": "2024-11-27T11:47:40.163847Z",
"url": "https://files.pythonhosted.org/packages/c9/55/e8e1a915c982851e7868e9b32d804c0e111545539cb85ef6926148a5582d/fields_of_gold-0.1.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-27 11:47:40",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "adamalton",
"github_project": "fields-of-gold",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "fields-of-gold"
}