{ "info": { "author": "Alex Hill", "author_email": "alex@hill.net.au", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3" ], "description": "# django-relativity\n\n[![PyPI](https://img.shields.io/pypi/v/django-relativity.svg)](https://pypi.org/project/django-relativity/)\n[![Build Status](https://travis-ci.org/AlexHill/django-relativity.svg?branch=master)](https://travis-ci.org/AlexHill/django-relativity)\n\ndjango-relativity provides a `Relationship` field that lets you declare the non-foreign-key relationships between your models and use them throughout the ORM.\n\n_Non-foreign-key relationships?_\n\nLike the relationship between a node and its descendants in a tree, or between two tagged items that share a tag. Almost anything you can express with Django's filter syntax, you can use to define a relationship.\n\n_Use them throughout the ORM?_\n\nYes, across joins, in `filter()`, in methods like `prefetch_related()` or `values()` - anywhere Django expects to see a field.\n\n## What problem does this solve?\n\nSometimes the relationships between our models are more complex than equality between a primary key field on one model and a foreign key on another.\n\nFor example: when working with trees, we very often need to find a given node's descendants - its children, their children, and so on. The exact query we have to run depends on how we've chosen to implement our tree structure at the database level, and fortunately there are mature libraries available to take care of that for us. [django-mptt](http://django-mptt.readthedocs.io/en/latest/models.html#get-descendants-include-self-false) and [django-treebeard](http://django-treebeard.readthedocs.io/en/latest/api.html#treebeard.models.Node.get_descendants) both provide methods called `get_descendants()` for exactly this purpose. These return a queryset selecting the node's descendants, which we can then filter further, or use as an argument to another filter, and so on. So what's the problem?\n\nThe problem is that the node-descendants relationship is invisible to the Django ORM. We can't filter against it, like `Node.objects.filter(descendants__in=objs)`. We can't traverse it, like `Node.objects.filter(descendants__name__startswith=\"A\")`. We can't prefetch it. None of the niceties that Django provides for working with relationships are available for us to use with this relationship, because it can't be declared as a `ManyToManyField` or `ForeignKey`.\n\ndjango-relativity lets all those ORM features work with almost any kind of relationship you can dream up.\n\n### MPTT and treebeard helpers\n\nIf you use django-mptt or django-treebeard and you want to jump right in, relativity comes with fields to select a node's descendants and its subtree (which respectively exclude and include the current node). The default reverse relation names for these fields are `ascendants` and `rootpath`.\n\n```python\n# For django-mptt\nfrom relativity.mptt import MPTTDescendants, MPTTSubtree\n\n# for treebeard with materialised path\nfrom relativity.treebeard import MP_Descendants, MP_Subtree\n\n# for treebeard with nested sets\nfrom relativity.treebeard import NS_Descendants, NS_Subtree\n\n\nclass TreeNode(MPTTModel):\n ...\n\n # after defining all your other fields, including TreeForeignKey...\n descendants = MPTTDescendants()\n subtree = MPTTSubtree()\n```\n\n## What does the code look like?\n\nHere are some models for an imaginary website about chemistry, where users can filter compounds by regular expression and save their searches:\n\n```python\nfrom relativity.fields import L, Relationship\n\nclass Chemical(Model):\n common_name = TextField()\n chemical_name = TextField()\n formula = TextField()\n\nclass SavedFilter(Model):\n user = ForeignKey(User)\n search_regex = TextField()\n chemicals = Relationship(\n to=Chemical,\n predicate=Q(formula__regex=L('search_regex')),\n )\n```\n\nNow I can use that field like this:\n\n```python\nmy_filter.chemicals.all() # all the chemicals whose formulae match this filter\nmy_chemical.saved_filters.all() # all the filters whose regexps match this chemical\nmy_user.filter(saved_filters__chemicals=my_chemical) # users with filters matching this chemical\nmy_chemical.filter(saved_filters__user=my_user) # chemicals in any of this user's filters\n```\n\nIn short, I can use it just like a normal Django relation. It provides forward and reverse properties that return Managers, and I can use it in filters spanning multiple models.\n\n_How does that `Relationship` field work?_\n\nA `Relationship` behaves like a `ForeignKey` or `ManyToManyField` and defines a relationship with another model. Unlike the built-in Django relations, `Relationship` doesn't use its own database column or table to determine which instances are related. Instead, you give it an arbitrary _predicate_, expressed as a normal Django `Q`, which determines which instances of the `to` model are in the relationship.\n\n_What's that `L` doing there?_\n\nIn Django ORM expressions, `F` is a reference to a field on the model being queried. `L` is similar, but refers to a field on the model on which the `Relationship` is defined. Think of it as L for the _left-hand_ side of a join, or L for the _local_ model.\n\nGoing back to our example - the `chemicals` field provides the set of `Chemical`s whose formulae match the `SavedFilter`'s regular expression.\n\nLet's make some chemicals:\n\n```python\n>>> Chemical.objects.create(name=\"baking soda\", formula=\"NaHCO3\")\n... Chemical.objects.create(common_name=\"freon\", formula=\"CF2Cl2\")\n... Chemical.objects.create(common_name=\"grain alcohol\", formula=\"C2H5OH\")\n... Chemical.objects.create(common_name=\"quartz\", formula=\"SiO2\")\n... Chemical.objects.create(common_name=\"salt\", formula=\"NaCl\")\n```\n\nNow, say I'm a user who's interested in chemicals containing chlorine. Simple enough:\n\n```python\n>>> chloriney = SavedFilter.objects.create(user=alex, search_regex=r'Cl')\n>>> chloriney.compounds.all()\n, ]>\n```\n\nAnne is interested in oxides, so her regex is a bit more complicated:\n\n```python\n>>> oxides = SavedFilter.objects.create(user=anne, search_regex=r'([A-Z][a-z]?\\d*)O(\\d+|(?!H))')\n>>> oxides.compounds.all()\n, ]>\n```\n\nNow, this is nothing you couldn't do with a helper method on `SavedFilter` which returns the appropriate QuerySet. But now we add a new chemical to our database, and we want to identify users who are interested in that chemical so we can notify them:\n\n```python\n>>> added_chemical = Chemical.objects.create(common_name=\"chlorine dioxide\", chemical_name=\"chlorine dioxide\", formula=\"ClO2\")\n\n>>> User.objects.filter(saved_filters__chemicals=added_chemical)\n, ]>\n```\n\nThis is why I call django-relativity a _force-multiplier_ for the ORM. `Relationship`s work with the ORM just like `ForeignKey`s or `ManyToManyField`s. You can traverse them and filter on them just like you can with the built-in relationship fields. The goal of django-relativity is for `Relationship`s to be able to do anything a normal Django relationship field can do.\n\n### Reverse relations\n\n`Relationship`s work in the reverse direction as well, with the same naming behaviour as Django's fields: the default related name is `_set` or `` depending on arity, overridable with the `related_name` argument. `related_query_name` works as well.\n\nIn the example above, `my_chemical.saved_filter_set.all()` will return all of the `SavedFilter`s matching `my_chemical`. `Chemical.objects.filter(saved_filters__user=alex)` will select all of the chemicals in all of my saved filters.\n\n### Arity\n\nRelationships between models can be one-to-one, one-to-many, many-to-one, or many-to-many. `Relationship` can express all of those, using the `multiple` and `reverse_multiple` arguments. Both default to `True`.\n\nHere's a many-to-one example - many cart items can be associated with each product, but only one product is associated with each cart item.\n\n```python\nclass CartItem(models.Model):\n product_code = models.TextField()\n product = Relationship(\n to=Product,\n predicate=Q(sku=L('product_code')),\n multiple=False,\n )\n```\n\n## What state is this project in?\n\nThis project is in active development. Feel free to try it out. Things not covered by the tests have every chance of not working.\n\n\n## Note on migrations for Django 1.11 users\n\nBefore Django 2.0, Q objects and expressions were not serialisable in migrations. Relativity includes backported code to work around this problem. To make migrations work in Django 1.11, import `Q` from `relativity.compat` and use that in your predicates instead of Django's `Q`.\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/alexhill/django-relativity", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "django-relativity", "package_url": "https://pypi.org/project/django-relativity/", "platform": "", "project_url": "https://pypi.org/project/django-relativity/", "project_urls": { "Homepage": "https://github.com/alexhill/django-relativity" }, "release_url": "https://pypi.org/project/django-relativity/0.1.4/", "requires_dist": [ "django (>=1.11)" ], "requires_python": "", "summary": "A flexible relationship field for the Django ORM.", "version": "0.1.4" }, "last_serial": 4269516, "releases": { "0.1.1": [ { "comment_text": "", "digests": { "md5": "f310a57f1f578ee6b4302fb1fc7147dd", "sha256": "2cf8ea3729dc4f59f3e04408e4f7077f0b4df10275dbae150d7b5efb7d5cccb5" }, "downloads": -1, "filename": "django_relativity-0.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "f310a57f1f578ee6b4302fb1fc7147dd", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 8080, "upload_time": "2018-06-04T18:37:12", "url": "https://files.pythonhosted.org/packages/56/25/b2120b0917f514db68fd0821518369e02520c9cc902e6a846da34bdb64a3/django_relativity-0.1.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "7117a90ac9c50043a33c9c62c8717ebe", "sha256": "e6c3b61e2d287d1e70e803002861c43fa456d5aeea523a7b0811a0384ba596ef" }, "downloads": -1, "filename": "django-relativity-0.1.1.tar.gz", "has_sig": false, "md5_digest": "7117a90ac9c50043a33c9c62c8717ebe", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7402, "upload_time": "2018-06-04T18:37:13", "url": "https://files.pythonhosted.org/packages/28/9f/427726dbd038c6e9ef1074d79f546f189854fd26970ae2f0bea5bb2db933/django-relativity-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "7045f8b7fe7ae19192ae2a41fe8e98da", "sha256": "98e0d99ea1c516001ad725f088a4ffd40f402c98a33cd1b0097828177546fbe1" }, "downloads": -1, "filename": "django_relativity-0.1.2-py3-none-any.whl", "has_sig": false, "md5_digest": "7045f8b7fe7ae19192ae2a41fe8e98da", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 8893, "upload_time": "2018-06-05T16:57:41", "url": "https://files.pythonhosted.org/packages/83/2d/de831a2669fbf5ee0de6a4e6014a4ecd040195256ae7c8a9fdc1d53d269c/django_relativity-0.1.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "76c754343209fed8f95987b864177971", "sha256": "52e928df8a55114ff055252864ff99e6caf77a2862bb2aab07b9cd811736de58" }, "downloads": -1, "filename": "django-relativity-0.1.2.tar.gz", "has_sig": false, "md5_digest": "76c754343209fed8f95987b864177971", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10547, "upload_time": "2018-06-05T16:57:43", "url": "https://files.pythonhosted.org/packages/d3/0b/69c7af5d41ed8c66f81c6f1ffd4024a0fe97a3bd92ce0b5e68c9545b1ac1/django-relativity-0.1.2.tar.gz" } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "6d420629fe046a323babee4309c44aa3", "sha256": "fc4286de1276fb94af215daabb36a771b9b7058cacaf978d57f9ef0e15bf7758" }, "downloads": -1, "filename": "django_relativity-0.1.3-py3-none-any.whl", "has_sig": false, "md5_digest": "6d420629fe046a323babee4309c44aa3", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 9657, "upload_time": "2018-08-30T15:32:49", "url": "https://files.pythonhosted.org/packages/30/c4/0f9e7004f9bf94b7ab34b2aac3f2cd3bdaf2991175b1ebcc9094b05e8724/django_relativity-0.1.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "b6a61f962ce68e291ec9d7449e666cbe", "sha256": "87a546582c0e6ec826d239552354afd89794721c3861f7dec858b0558809a69d" }, "downloads": -1, "filename": "django-relativity-0.1.3.tar.gz", "has_sig": false, "md5_digest": "b6a61f962ce68e291ec9d7449e666cbe", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11365, "upload_time": "2018-08-30T15:32:52", "url": "https://files.pythonhosted.org/packages/b5/5d/5ace1ed4774b98ccd4bbd61b9389790a0f6d66d261c89fc8af6a1a16b2fd/django-relativity-0.1.3.tar.gz" } ], "0.1.4": [ { "comment_text": "", "digests": { "md5": "3663c33c31ccbee4ee8db94339b85b4f", "sha256": "159d5d90d3ddf2f18d032a7d1ea95aaccdc169faf31d55547159c7d26d39c47e" }, "downloads": -1, "filename": "django_relativity-0.1.4-py3-none-any.whl", "has_sig": false, "md5_digest": "3663c33c31ccbee4ee8db94339b85b4f", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 9987, "upload_time": "2018-09-13T17:32:34", "url": "https://files.pythonhosted.org/packages/8d/a5/2f32227007380b1c733fc1f5e96f8dc05e04542d1f85512ea9ab94584f94/django_relativity-0.1.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "0eec1f705c4493b42f01cd7fb53f9305", "sha256": "3e6fa5e45c967c062e2712a8810ef83a1f6a6de9918f8b6eedd75080c7e2c32d" }, "downloads": -1, "filename": "django-relativity-0.1.4.tar.gz", "has_sig": false, "md5_digest": "0eec1f705c4493b42f01cd7fb53f9305", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11882, "upload_time": "2018-09-13T17:32:39", "url": "https://files.pythonhosted.org/packages/84/ca/02699d955c8b873eba3bb73cccfc8ec52bf2d4f35290b2d49d9a8f7ba340/django-relativity-0.1.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "3663c33c31ccbee4ee8db94339b85b4f", "sha256": "159d5d90d3ddf2f18d032a7d1ea95aaccdc169faf31d55547159c7d26d39c47e" }, "downloads": -1, "filename": "django_relativity-0.1.4-py3-none-any.whl", "has_sig": false, "md5_digest": "3663c33c31ccbee4ee8db94339b85b4f", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 9987, "upload_time": "2018-09-13T17:32:34", "url": "https://files.pythonhosted.org/packages/8d/a5/2f32227007380b1c733fc1f5e96f8dc05e04542d1f85512ea9ab94584f94/django_relativity-0.1.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "0eec1f705c4493b42f01cd7fb53f9305", "sha256": "3e6fa5e45c967c062e2712a8810ef83a1f6a6de9918f8b6eedd75080c7e2c32d" }, "downloads": -1, "filename": "django-relativity-0.1.4.tar.gz", "has_sig": false, "md5_digest": "0eec1f705c4493b42f01cd7fb53f9305", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11882, "upload_time": "2018-09-13T17:32:39", "url": "https://files.pythonhosted.org/packages/84/ca/02699d955c8b873eba3bb73cccfc8ec52bf2d4f35290b2d49d9a8f7ba340/django-relativity-0.1.4.tar.gz" } ] }