{ "info": { "author": "John Montgomery", "author_email": "john@littlespikeyland.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "===================\nDjango Batch Select\n===================\n\nThe idea of Django Batch Select is to provide an equivalent to\nDjango's select_related_ functionality. As of such it's another handy\ntool for avoiding the \"n+1 query problem\".\n\nselect_related_ is handy for minimizing the number of queries that need\nto be made in certain situations. However it is only usual for\npre-selecting ForeignKey_ relations.\n\nbatch_select is handy for pre-selecting ManyToManyField_ relations and\nreverse ForeignKey_ relations.\n\nIt works by performing a single extra SQL query after a QuerySet_ has\nbeen evaluated to stitch in the the extra fields asked for. This requires\nthe addition of a custom Manager_, which in turn returns a custom QuerySet_\nwith extra methods attached.\n\nExample Usage\n=============\n\nAssuming we have models defined as the following::\n\n from batch_select.models import BatchManager\n\n class Tag(models.Model):\n name = models.CharField(max_length=32)\n\n class Section(models.Model):\n name = models.CharField(max_length=32)\n\n objects = BatchManager()\n\n class Entry(models.Model):\n title = models.CharField(max_length=255)\n section = models.ForeignKey(Section, blank=True, null=True)\n tags = models.ManyToManyField(Tag)\n\n objects = BatchManager()\n\nI'll also define a helper function to show the SQL queries generated::\n\n from django import db\n\n def show_queries():\n for query in db.connection.queries:\n print query[\"sql\"]\n db.reset_queries()\n\nHere are a few example (with generated sql queries)::\n\n >>> Entry.objects.batch_select('tags').all()\n []\n >>> show_queries() # no results, so no 2nd query\n SELECT \"batch_select_entry\".\"id\", \"batch_select_entry\".\"title\", \"batch_select_entry\".\"section_id\" FROM \"batch_select_entry\"\n >>> Entry.objects.create()\n >>> Entry.objects.create()\n >>> tag1 = Tag.objects.create(name='tag1')\n >>> tag2 = Tag.objects.create(name='tag2')\n >>> db.reset_queries()\n >>> entries = Entry.objects.batch_select('tags').all()\n >>> entry = entries[0]\n >>> print entry.tags_all\n []\n >>> show_queries()\n SELECT \"batch_select_entry\".\"id\", \"batch_select_entry\".\"title\", \"batch_select_entry\".\"section_id\" FROM \"batch_select_entry\" LIMIT 1\n SELECT (`batch_select_entry_tags`.`entry_id`) AS \"entry_id\", \"batch_select_tag\".\"id\", \"batch_select_tag\".\"name\" FROM \"batch_select_tag\" INNER JOIN \"batch_select_entry_tags\" ON (\"batch_select_tag\".\"id\" = \"batch_select_entry_tags\".\"tag_id\") WHERE \"batch_select_entry_tags\".entry_id IN (1)\n >>> entry.tags.add(tag1)\n >>> db.reset_queries()\n >>> entries = Entry.objects.batch_select('tags').all()\n >>> entry = entries[0]\n >>> print entry.tags_all\n []\n >>> show_queries()\n SELECT \"batch_select_entry\".\"id\", \"batch_select_entry\".\"title\", \"batch_select_entry\".\"section_id\" FROM \"batch_select_entry\" LIMIT 1\n SELECT (`batch_select_entry_tags`.`entry_id`) AS \"entry_id\", \"batch_select_tag\".\"id\", \"batch_select_tag\".\"name\" FROM \"batch_select_tag\" INNER JOIN \"batch_select_entry_tags\" ON (\"batch_select_tag\".\"id\" = \"batch_select_entry_tags\".\"tag_id\") WHERE \"batch_select_entry_tags\".entry_id IN (1)\n >>> entries = Entry.objects.batch_select('tags').all()\n >>> for entry in entries:\n .... print entry.tags_all\n ....\n []\n []\n >>> show_queries()\n SELECT \"batch_select_entry\".\"id\", \"batch_select_entry\".\"title\", \"batch_select_entry\".\"section_id\" FROM \"batch_select_entry\"\n SELECT (`batch_select_entry_tags`.`entry_id`) AS \"entry_id\", \"batch_select_tag\".\"id\", \"batch_select_tag\".\"name\" FROM \"batch_select_tag\" INNER JOIN \"batch_select_entry_tags\" ON (\"batch_select_tag\".\"id\" = \"batch_select_entry_tags\".\"tag_id\") WHERE \"batch_select_entry_tags\".entry_id IN (1, 2)\n\nRe-running that same last for loop without using batch_select\ngenerate three queries instead of two (n+1 queries)::\n\n >>> entries = Entry.objects.all()\n >>> for entry in entries:\n .... print entry.tags.all()\n ....\n []\n []\n\n >>> show_queries()\n SELECT \"batch_select_entry\".\"id\", \"batch_select_entry\".\"title\", \"batch_select_entry\".\"section_id\" FROM \"batch_select_entry\"\n SELECT \"batch_select_tag\".\"id\", \"batch_select_tag\".\"name\" FROM \"batch_select_tag\" INNER JOIN \"batch_select_entry_tags\" ON (\"batch_select_tag\".\"id\" = \"batch_select_entry_tags\".\"tag_id\") WHERE \"batch_select_entry_tags\".\"entry_id\" = 1\n SELECT \"batch_select_tag\".\"id\", \"batch_select_tag\".\"name\" FROM \"batch_select_tag\" INNER JOIN \"batch_select_entry_tags\" ON (\"batch_select_tag\".\"id\" = \"batch_select_entry_tags\".\"tag_id\") WHERE \"batch_select_entry_tags\".\"entry_id\" = 2\n\nThis also works with reverse foreign keys. So for example we can get\nthis entries that belong to each section::\n\n >>> section1 = Section.objects.create(name='section1')\n >>> section2 = Section.objects.create(name='section2')\n >>> Entry.objects.create(section=section1)\n >>> Entry.objects.create(section=section1)\n >>> Entry.objects.create(section=section2)\n >>> db.reset_queries()\n >>> Section.objects.batch_select('entry_set')\n [, ]\n >>> show_queries()\n SELECT \"batch_select_section\".\"id\", \"batch_select_section\".\"name\" FROM \"batch_select_section\" LIMIT 21\n SELECT (\"batch_select_entry\".\"section_id\") AS \"__section_id\", \"batch_select_entry\".\"id\", \"batch_select_entry\".\"title\", \"batch_select_entry\".\"section_id\", \"batch_select_entry\".\"location_id\" FROM \"batch_select_entry\" WHERE \"batch_select_entry\".\"section_id\" IN (1, 2)\n\nEach section object in that query will have an entry_set_all field\ncontaining the relevant entries.\n\nYou need to pass batch_select the \"related name\" of the foreign key,\nin this case \"entry_set\". NB by default the related name for a foreign\nkey does not actually include the _set suffix, so you can use just \"entry\"\nin this case. I have made sure that the _set suffix version also works to\ntry and keep the API simpler.\n\n\nMore Advanced Usage\n=========================\n\nBy default the batch fields are inserted into fields named ``_all``,\non each object. So::\n\n Entry.objects.batch_select('tags').all()\n\nresults in the Entry instances having fields called ``'tags_all'``\ncontaining the Tag objects associated with that Entry.\n\nIf you want to give the field a different name just use a keyword\nargument - in the same way as using the Aggregation_ API::\n\n Entry.objects.batch_select(selected_tags='tags').all()\n\nWould means the Tag objects would be assigned to fields called\n``'selected_tags'``.\n\nIf you want to perform filtering of the related objects you will need to\nuse a Batch object. By doing this you can pass extra keyword arguments\nin the same way as when using the filter method of a QuerySet::\n\n from batch_select.models import Batch\n\n Entry.objects.batch_select(tags_containing_blue=Batch('tags', name__contains='blue'))\n\nWould return Entry objects with fields called 'tags_containing_name' with\nonly those Tags whose name contains 'blue'.\n\nIn addition to filtering using keyword arguments, you can also call the\nfollowing methods on a Batch object, with their effects being passed on\nto the underlying QuerySet_ object:\n\n* filter_\n* exclude_\n* annotate_\n* order_by_\n* reverse_\n* select_related_\n* extra_\n* defer_\n* only_\n* batch_select\n\n(Note that distinct(), values() etc are not included as they would have\nside-effects on how the extra query is associated with the original query)\nSo for example to achieve the same effect as the filter above you could\ndo the following::\n\n from batch_select.models import Batch\n\n Entry.objects.batch_select(tags_containing_blue=Batch('tags').filter(name__contains='blue'))\n\nWhereas the following would exclude tags containing \"blue\" and order by name::\n\n from batch_select.models import Batch\n\n batch = Batch('tags').exclude(name__contains='blue').order_by('name')\n Entry.objects.batch_select(tags_not_containing_blue=batch)\n\n\nCompatibility\n=============\n\nDjango batch select should work with Django 1.1-1.3 at least.\n\n\nTODOs and BUGS\n==============\n\nSee: http://github.com/lilspikey/django-batch-select/issues\n\n.. _select_related: http://docs.djangoproject.com/en/dev/ref/models/querysets/#id4\n.. _ForeignKey: http://docs.djangoproject.com/en/dev/ref/models/fields/#foreignkey\n.. _ManyToManyField: http://docs.djangoproject.com/en/dev/ref/models/fields/#manytomanyfield\n.. _QuerySet: http://docs.djangoproject.com/en/dev/ref/models/querysets/\n.. _Manager: http://docs.djangoproject.com/en/dev/topics/db/managers/\n.. _Aggregation: http://docs.djangoproject.com/en/dev/topics/db/aggregation/\n.. _filter: http://docs.djangoproject.com/en/dev/ref/models/querysets/#filter-kwargs\n.. _exclude: http://docs.djangoproject.com/en/dev/ref/models/querysets/#exclude-kwargs\n.. _annotate: http://docs.djangoproject.com/en/dev/ref/models/querysets/#annotate-args-kwargs\n.. _order_by: http://docs.djangoproject.com/en/dev/ref/models/querysets/#order-by-fields\n.. _reverse: http://docs.djangoproject.com/en/dev/ref/models/querysets/#reverse\n.. _extra: http://docs.djangoproject.com/en/dev/ref/models/querysets/#extra-select-none-where-none-params-none-tables-none-order-by-none-select-params-none\n.. _defer: http://docs.djangoproject.com/en/dev/ref/models/querysets/#defer-fields\n.. _only: http://docs.djangoproject.com/en/dev/ref/models/querysets/#only-fields", "description_content_type": null, "docs_url": null, "download_url": "http://github.com/lilspikey/django-batch-select/downloads", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/lilspikey/django-batch-select/", "keywords": null, "license": "BSD", "maintainer": null, "maintainer_email": null, "name": "django-batch-select", "package_url": "https://pypi.org/project/django-batch-select/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/django-batch-select/", "project_urls": { "Download": "http://github.com/lilspikey/django-batch-select/downloads", "Homepage": "http://github.com/lilspikey/django-batch-select/" }, "release_url": "https://pypi.org/project/django-batch-select/0.2.4/", "requires_dist": null, "requires_python": null, "summary": "batch select many-to-many and one-to-many fields (to help avoid n+1 query problem)", "version": "0.2.4" }, "last_serial": 1143096, "releases": { "0.2.1": [], "0.2.2": [], "0.2.3": [], "0.2.4": [ { "comment_text": "", "digests": { "md5": "fe4214f01d9dd334742430b74b5f9593", "sha256": "0acfb2c6b9e228cc20c77aac2f3feed084c94c32223ea0684fda5cb7248a27a1" }, "downloads": -1, "filename": "django-batch-select-0.2.4.tar.gz", "has_sig": false, "md5_digest": "fe4214f01d9dd334742430b74b5f9593", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13273, "upload_time": "2014-07-01T12:28:58", "url": "https://files.pythonhosted.org/packages/bb/4c/cb1af358a4d366ae5e69f5ffc6178b5d903395fc09730df6f28620f13755/django-batch-select-0.2.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "fe4214f01d9dd334742430b74b5f9593", "sha256": "0acfb2c6b9e228cc20c77aac2f3feed084c94c32223ea0684fda5cb7248a27a1" }, "downloads": -1, "filename": "django-batch-select-0.2.4.tar.gz", "has_sig": false, "md5_digest": "fe4214f01d9dd334742430b74b5f9593", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 13273, "upload_time": "2014-07-01T12:28:58", "url": "https://files.pythonhosted.org/packages/bb/4c/cb1af358a4d366ae5e69f5ffc6178b5d903395fc09730df6f28620f13755/django-batch-select-0.2.4.tar.gz" } ] }