{ "info": { "author": "Guillaume Cisco", "author_email": "guillaumecisco@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 2" ], "description": "Django ES\n=========\n\n\n.. contents:: Table of contents\n :depth: 2\n\nPurpose\n=======\n\nDjango ES is a Django wrapper for\n`elasticsearch-dsl-py `__.\n\nOriginally it's a fork from `bungiesearch `__ so\nyou'll find a lot of things in common.\nThe big change is it uses register admin as a philosophy instead of django manager.\nSo a lot of code has been removed and there is a lot of changes.\nThere are no alias, no management commands.\n\nThis contribution use elasticsearch 5.x and its restrictions (unique field name related to one unique mapping definition).\nCRUD operations are mostly done by elasticsearch-dsl library for more control and maintainability.\n\nFeatures\n========\n\n- Django Model Mapping\n\n - Very easy mapping (no lies).\n - Automatic model mapping (and supports undefined models by\n returning a ``Result`` instance of ``elasticsearch-dsl-py``).\n\n- Django Admin register like\n - Register your model as you do with Django Admin contribution\n in a separated file.\n\n- Django signals\n\n - Connect to pre_save, post save and pre delete signals for the elasticsearch\n index to correctly reflect the database (almost) at all times.\n\n- Requirements\n\n - Django >= 1.8\n - Python 2.7 (**no Python 3 support yet**)\n\n\nInstallation\n============\n\nInstall the package\n-------------------\n\nThe easiest way is to install the package from github:\n\n``pip install git+ssh://git@github.com/GuillaumeCisco/django-es.git``\n\n**Note:** Check your version of Django after installing django-es. It\nwas reported to me directly that installing django-es may upgrade\nyour version of Django, although I haven't been able to confirm that\nmyself. django-es depends on Django 1.8 and above.\n\nIn Django\n---------\n\nUpdating your Django models\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nCreate a ``djangoes.py`` python file (or package) and register your models.\nMore description, in examples following.\n\nCreating Django ES search indexes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe search indexes define how Django ES should serialize each of the\nmodel's objects. It effectively defines how your object is serialized\nand how the Elasticsearch index should be structured.\nNow you should run a first time your server for allowing autodiscover\nmodule to generate mapping and communicate with your ElasticSearch\nserver.\n\n\nIn Elasticsearch\n----------------\n\nYou can now open your elasticsearch dashboard, such as Elastic HQ, and\nsee that your index is created with the appropriate mapping and has\nitems that are indexed.\n\nQuick start example\n===================\n\n\nProcedure\n---------\n\nAdd 'django_es' to `INSTALLED_APPS`.\nYou can define in your own code an `ES_CLIENT` parameter for connecting to your Elasticsearch instance,\nBy default `ES_CLIENT` is `Elasticsearch()`\n\nExample\n-------\n\n### Django Model\n\n.. code:: python\n\n from django.db import models\n from django.core.urlresolvers import reverse\n from autoslug import AutoSlugField\n from wall.models import Wall\n from category.models import Category\n\n\n class MyModel(models.Model):\n\n name = models.CharField(max_length=128, null=True, blank=True)\n created = models.DateTimeField(auto_now_add=True)\n wall = models.ForeignKey(Wall, related_name='mymodels', null=True, blank=True)\n slug = AutoSlugField(populate_from='populate_slug', unique=True)\n last_modified = models.DateTimeField(auto_now_add=True)\n is_finalized = models.BooleanField(default=False)\n is_recorded = models.BooleanField(default=False)\n desc = models.CharField(max_length=4096, null=True, blank=True)\n diff_date = models.DateTimeField()\n duration = models.DurationField(null=True, blank=True)\n category = models.ForeignKey(Category)\n\n def __str__(self):\n return self.name\n\n def get_absolute_url(self):\n return reverse('video', kwargs={'slug': self.slug})\n\n # use this technique because name if from parent class\n def populate_slug(self):\n return self.name or 'mymodel'\n\n class Meta(Media.Meta):\n app_label = 'media'\n\n\nModelIndex\n~~~~~~~~~~\n\n\nThe following ModelIndex will generate a mapping containing all fields\nfrom ``MyModel``, minus those defined in ``MyModelModelIndex.Meta.exclude``.\nWhen the mapping is generated, each field will the most appropriate\n`elasticsearch core\ntype `__,\nwith default attributes (as defined in django_es.fields).\n\nThese default attributes can be overwritten with\n``MyModelModelIndex.Meta.hotfixes``: each dictionary key must be field\ndefined either in the model or in the ModelIndex subclass\n(``MyModelModelIndex`` in this case).\n\n.. code:: python\n\n from django_es import mapping\n from django_es.fields import String, Date, Integer\n from django_es.indices import ModelIndex\n from media.models import MyModel\n from elasticsearch_dsl.analysis import Analyzer\n from utils.fields import Completion\n\n\n class MyModelModelIndex(ModelIndex):\n description = String(analyzer='snowball', _model_attr='desc')\n created_date = Date(_model_attr='created')\n category = Integer(_eval_as='obj.category.id')\n img = String()\n author = String()\n suggest = Completion(\n analyzer=Analyzer('simple'),\n search_analyzer=Analyzer('simple'),\n preserve_position_increments=False,\n preserve_separators=False,\n payloads=True,\n context={\n 'type': {\n 'type': 'category',\n 'path': '_type'\n }\n }\n )\n\n class Meta:\n index = 'django_es' # optional but recommended, default is `django_es`, ever use `populate_index` method\n exclude = ('last_modified', 'is_finalized', 'is_recorded', 'diff_date', 'duration',)\n\n def prepare_img(self, obj):\n # How we want to store this field in elasticsearch\n from media.serializers.liveVideo import MyModelSerializer\n return MyModelSerializer._img(obj, '48x48') # getting related image passing res\n\n def prepare_author(self, obj):\n return obj.wall.profile.get_full_name()\n\n def prepare_suggest(self, obj):\n # How we want to store this field in elasticsearch\n return {\n 'input': [obj.name, obj.desc, obj.wall.profile.get_full_name()],\n 'output': obj.name + ' - ' + obj.wall.profile.get_full_name(),\n 'payload': {\n 'slug': obj.slug,\n 'img': self.prepare_img(obj),\n 'category': obj.category.id\n }\n }\n\n mapping.register(MyModel, MyModelModelIndex)\n\nThe last line is important, it allows Django ES to create the mapping related to this model\nand to put in on the elasticsearch server.\n\nThis `djangoes.py` file use a Completion Field not related to the model field\nderived from elasticsearch-dsl.fields.\nYou can create your own fields if there are not already provided by elasticsearch-dsl\nor this contribution.\n\n.. code:: python\n\n from elasticsearch_dsl import Field\n\n class Completion(Field):\n _param_defs = {\n 'fields': {'type': 'field', 'hash': True},\n 'analyzer': {'type': 'analyzer'},\n 'search_analyzer': {'type': 'analyzer'},\n 'max_input_length': {'type': 'integer'}\n }\n name = 'completion'\n\n def _empty(self):\n return ''\n\n\nNow, for your mapping and index to be generated, you need to launch your server a first time.\nYour mappings can be updated following these\n`elasticsearch mappings rules `__,\n\nCreating/Updating, Deleting documents\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nBy default, your documents are created on ``post_save`` signal of the model.\nBut with an API oriented website or with django forms, you can directly use\nelasticsearch-dsl methods or simply use functions defined in ``utils``:\n``update_index`` and ``delete_index``\n\nExample:\n\n.. code:: python\n\n # for updating/deleting one or more instances simultaneously\n\n update_index([instance, ...], sender, bulk_size=1) # chose your action : index or delete, default is index\n\n\n # for deleting\n delete_index_item(instance, sender)\n\n\nThe ``update_index`` functions use the ``bulk``/``bulk_index`` method of elasticsearch for performing\nseveral actions in a row.\n\nYou can create your own utils methods.\n\n\nQuerying your elasticsearch documents\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can query your documents using elasticsearch-dsl methods. It's the easier way.\nExample:\n\n.. code:: python\n\n from elasticsearch import Elasticsearch\n from elasticsearch_dsl import Q as _Q\n from elasticsearch_dsl import Search\n from elasticsearch_dsl.query import MultiMatch\n\n searchstr = 'Some terms to research'\n\n client = Elasticsearch()\n s = Search().using(client)\n fields = [\"name^2.0\", \"description^1.5\", \"author^1.0\"]\n s.query(MultiMatch(query=searchstr, type='best_fields', fields=fields, tie_breaker=0.3))\n #or\n #s.query(_Q('query_string', query=' AND '.join([x + '~2' for x in searchstr.split(' ')]), use_dis_max=True, fields=fields, tie_breaker=0.3))\n s.aggs.bucket('list', 'filter', term={'_type': 'mymodel'}) \\\n .metric('obj',\n 'top_hits',\n **{'_source': ['name', 'slug', 'img'],\n 'from': (int(page) - 1) * 20,\n 'size': 20\n }\n )\n s = s[:0] # getting only aggregations results\n response = s.execute()\n\n count = response.aggregations.list.obj.hits.total\n res = [x._source.to_dict() for x in response.aggregations.list.obj.hits.hits]\n\n\nYou also can use your ``suggest`` field defined previously:\n\n.. code:: python\n\n from elasticsearch import Elasticsearch\n from elasticsearch_dsl import Search\n\n searchstr = 'Some terms to research'\n client = Elasticsearch()\n\n s = Search().using(client)\\\n .suggest('lives', searchstr, completion={'field': 'suggest', 'fuzzy': True, 'size': 5, 'context': {'type': 'mymodel'}})\n s = s[:0] # getting only suggestions results\n response = s.execute()\n\n def format_result(options):\n results = []\n for x in options:\n d = x['payload'].to_dict()\n d.update(name=x.text)\n results.append(d)\n return results\n\n lives = format_result(response.suggest.lives[0]['options'])\n\n\nDjango settings\n~~~~~~~~~~~~~~~\n\nYou can define a ``DJANGO_ES`` dict in your settings for overriding the way signals\nare dealt with models associated with Django_ES instances.\nYou can inspect the code and find in the signals packages inspiration for your business logic,\nor use the classic ``BaseDjangoESSignalProcessor`` which will use a buffer of 100 objects before\ncreating/updating/deleting deleting elasticsearch doctype objects.\n\n.. code:: python\n\n DJANGO_ES = {\n 'SIGNAL_CLASS': 'BaseDjangoESSignalProcessor' # default\n }\n\nDocumentation\n=============\n\nModelIndex\n----------\n\nA ``ModelIndex`` defines mapping and object extraction for indexing of a\ngiven Django model. It is possible to create directly a mapping without\na model too, just pass a doctype.\n\nAny Django model to be managed by Django ES must have a defined\nModelIndex subclass. This subclass must contain a subclass called\n``Meta``.\n\nClass attributes\n~~~~~~~~~~~~~~~~\n\nAs detailed below, the doc type mapping will contain fields from the\nmodel it related to. However, one may often need to index fields which\ncorrespond to either a concatenation of fields of the model or some\nlogical operation.\n\nDjango ES makes this very easy: simply define a class attribute as\nwhichever core type, and set to the ``eval_as`` constructor parameter to\na one line Python statement. The object is referenced as ``obj`` (not\n``self`` nor ``object``, just ``obj``).\n\nYou can also use ``prepare_%s`` functions with name of the field for more complex\nserialization.\n\nExample\n^^^^^^^\n\nThis is a partial example as the Meta subclass is not defined, yet\nmandatory (cf. below).\n\n.. code:: python\n\n from django_es.fields import Date, String, Integer\n from django_es.indices import ModelIndex\n\n class MyModelModelIndex(ModelIndex):\n description = String(analyzer='snowball', _model_attr='desc')\n created_date = Date(_model_attr='created')\n category = Integer(_eval_as='obj.category.id')\n img = String()\n\n def prepare_img(self, obj):\n # How we want to store this field in elasticsearch\n from media.serializers.liveVideo import MyModelSerializer\n return MyModelSerializer._img(obj, '48x48')\n\nHere, ``img`` will be part of the doc\ntype mapping, but won't be reversed mapped since those fields do not\nexist in the model.\n``description`` and ``created_date`` use the ``_model_attr`` link for redefining fields name.\n``category`` will be evaluated as an integer related to the Category foreignkey.\n\nThis can also be used to index foreign keys:\n\n.. code:: python\n\n some_field_name = String(_eval_as='\",\".join([item for item in obj.some_foreign_relation.values_list(\"some_field\", flat=True)]) if obj.some_foreign_relation else \"\"')\n\n # or\n\n def prepare_some_field_name(self, obj):\n if obj.some_foreign_relation:\n return ','.join([item for item in obj.some_foreign_relation.values_list(\"some_field\", flat=True)])\n return ''\n\nClass methods\n~~~~~~~~~~~~~\n\npopulate\\_index\n^^^^^^^^^^^^^^^\n\nOverride this method if you want to deal with dynamic index generation.\nExample with dynamic date:\nIt will create a new index every day.\n\n.. code:: python\n\n def populate_index(self):\n return 'my_index_name-%(now)s' % {'now': now().strftime(\"%Y.%m.%d\")}\n\nmatches\\_indexing\\_condition\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nOverride this function to specify whether an item should be indexed or\nnot. This is useful when defining multiple indices (and ModelIndex\nclasses) for a given model. This method's signature and super class code\nis as follows, and allows indexing of all items.\n\n.. code:: python\n\n def matches_indexing_condition(self, item):\n return True\n\nFor example, if a given elasticsearch index should contain only item\nwhose title starts with ``\"Awesome\"``, then this method can be\noverridden as follows.\n\n.. code:: python\n\n def matches_indexing_condition(self, item):\n return item.title.startswith(\"Awesome\")\n\nMeta subclass attributes\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Note**: in the following, any variable defined as being a ``list``\ncould also be a ``tuple``.\n\nfields\n^^^^^^\n\n*Optional:* list of fields (or columns) which must be fetched when\nserializing the object for elasticsearch, or when reverse mapping the\nobject from elasticsearch back to a Django Model instance. By default,\nall fields will be fetched. Setting this *will* restrict which fields\ncan be fetched and may lead to errors when serializing the object. It is\nrecommended to use the ``exclude`` attribute instead (cf. below).\n\nexclude\n^^^^^^^\n\n*Optional:* list of fields (or columns) which must not be fetched when\nserializing or deserializing the object.\n\nhotfixes\n^^^^^^^^\n\n*Optional:* a dictionary whose keys are index fields and whose values\nare dictionaries which define `core type\nattributes `__.\nBy default, there aren't any special settings, apart for String fields,\nwhere the\n`analyzer `__\nis set to\n```snowball`` `__\n(``{'analyzer': 'snowball'}``).\n\nadditional\\_fields\n^^^^^^^^^^^^^^^^^^\n\n*Optional:* additional fields to fetch for mapping, may it be for\n``eval_as``/``prepare_%s`` fields or when returning the object from the database.\n\n\nid\\_field\n^^^^^^^^^\n\n*Optional:* the model field to use as a unique ID for elasticsearch's\nmetadata ``_id``. Defaults to ``id`` (also called\n```pk`` `__).\n\nSettings\n--------\nAdd 'django_es' to INSTALLED_APPS.\n\n\nSIGNAL_CLASS\n~~~~~~~~~~~~\n\n*Optional:* if it exists, it must be a dictionary (even empty), and will\nconnect to the ``pre_save``, ``post save``, ``pre delete`` model functions of *all*\nmodels registered.\nOne may also define a signal processor class for more custom\nfunctionality by placing the string value of the module path under a key\ncalled ``SIGNAL_CLASS`` defining ``setup`` and ``teardown`` methods,\nwhich take ``model`` as the only parameter. These methods connect and disconnect the signal\nprocessing class to django signals (signals are connected to each model\nregistered).\n\nExample with a customized ``SIGNAL_CLASS``\n\nIn the settings:\n\n.. code:: python\n\n DJANGO_ES = {\n 'SIGNAL_CLASS': '.signals.DjangoESSignalProcessor'\n }\n\nIn a separated file:\n\n.. code:: python\n\n from django.db.models import signals\n\n class DjangoESSignalProcessor(object):\n\n @staticmethod\n def post_save_connector(sender, instance, created, **kwargs):\n from django_es.utils import update_index\n # Only create index if created\n if created:\n update_index([instance], sender, bulk_size=1)\n\n @staticmethod\n def pre_delete_connector(sender, instance, **kwargs):\n from django_es.utils import delete_index_item\n delete_index_item(instance, sender)\n\n def setup(self, model):\n signals.post_save.connect(self.post_save_connector, sender=model)\n signals.pre_delete.connect(self.pre_delete_connector, sender=model)\n\n def teardown(self, model):\n signals.pre_delete.disconnect(self.pre_delete_connector, sender=model)\n signals.post_save.disconnect(self.post_save_connector, sender=model)\n\n\nBUFFER\\_SIZE\n^^^^^^^^^^^^\n\n*Optional:* an integer representing the number of items to buffer before\nmaking a bulk index update, defaults to ``100``.\n\n**WARNING**: if your application is shut down before the buffer is\nemptied, then any buffered instance *will not* be indexed on\nelasticsearch. Hence, a possibly better implementation is wrapping\n``post_save_connector`` and ``pre_delete_connector`` from\n``django_es.signals`` in a celery task. It is not implemented as such\nhere in order to not require ``celery``.\n\n", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/GuillaumeCisco/django-es", "keywords": "elasticsearch haystack django bungiesearch django_es", "license": "BSD-3", "maintainer": "", "maintainer_email": "", "name": "django-es", "package_url": "https://pypi.org/project/django-es/", "platform": "", "project_url": "https://pypi.org/project/django-es/", "project_urls": { "Homepage": "https://github.com/GuillaumeCisco/django-es" }, "release_url": "https://pypi.org/project/django-es/0.0.5/", "requires_dist": [ "django (>=1.8)", "elasticsearch-dsl (>=5.3.0)", "elasticsearch (>=5.4.0)", "python-dateutil", "six" ], "requires_python": "", "summary": "A Django elasticsearch wrapper for model and helper using elasticsearch-dsl-py high level library.", "version": "0.0.5" }, "last_serial": 3016776, "releases": { "0.0.2": [ { "comment_text": "", "digests": { "md5": "60c4e3ca59de2491fbd85bc19c77cf44", "sha256": "a2f8ea30f7cfb2474bd3f97439e810ce92f020071e8696bcb065bb1e3107a27d" }, "downloads": -1, "filename": "django_es-0.0.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "60c4e3ca59de2491fbd85bc19c77cf44", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 27883, "upload_time": "2017-07-10T09:00:27", "url": "https://files.pythonhosted.org/packages/51/1d/c263058f0660eb859b79bb7d55bd81fd1815bb6360edb6d715065c871366/django_es-0.0.2-py2.py3-none-any.whl" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "ca2e0b92959ad4af4fe6f805cae9f0bf", "sha256": "e16ddbc3d80daaa030bff26bd690b960df086fa8f128d3941f72ce6eabcc80ed" }, "downloads": -1, "filename": "django_es-0.0.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "ca2e0b92959ad4af4fe6f805cae9f0bf", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 27849, "upload_time": "2017-07-10T09:09:00", "url": "https://files.pythonhosted.org/packages/27/85/2510d4faf0b7560968d1d538b8b4625ed317fecf3c45f0efd2931a882f98/django_es-0.0.3-py2.py3-none-any.whl" } ], "0.0.4": [ { "comment_text": "", "digests": { "md5": "582ccfae9bef8606feac2bb21b669881", "sha256": "6d30a25c2cf14bcc358d75152470e426026515658b3d6f14f3eceac814cd252b" }, "downloads": -1, "filename": "django_es-0.0.4-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "582ccfae9bef8606feac2bb21b669881", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 27873, "upload_time": "2017-07-11T09:19:59", "url": "https://files.pythonhosted.org/packages/0b/b3/81e0087ca2bc2306c3c517431097c19d77aa31defc66be45442b387275d5/django_es-0.0.4-py2.py3-none-any.whl" } ], "0.0.5": [ { "comment_text": "", "digests": { "md5": "0e10c5e84d283b763ced14a444aeb8b2", "sha256": "89ab0be880fc3c842ddbe9a34999179ed60a0e7f64861a16d515e4445301c019" }, "downloads": -1, "filename": "django_es-0.0.5-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "0e10c5e84d283b763ced14a444aeb8b2", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 27882, "upload_time": "2017-07-12T07:55:52", "url": "https://files.pythonhosted.org/packages/da/ac/329e819adbca260ff84958e70c93e6d32b5ddbf1a21dfdac9020e983da60/django_es-0.0.5-py2.py3-none-any.whl" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "0e10c5e84d283b763ced14a444aeb8b2", "sha256": "89ab0be880fc3c842ddbe9a34999179ed60a0e7f64861a16d515e4445301c019" }, "downloads": -1, "filename": "django_es-0.0.5-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "0e10c5e84d283b763ced14a444aeb8b2", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 27882, "upload_time": "2017-07-12T07:55:52", "url": "https://files.pythonhosted.org/packages/da/ac/329e819adbca260ff84958e70c93e6d32b5ddbf1a21dfdac9020e983da60/django_es-0.0.5-py2.py3-none-any.whl" } ] }