# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['django_es_drf', 'django_es_drf.drf', 'django_es_drf.drf.backends']

package_data = \
{'': ['*']}

install_requires = \
['django>=4,<5',
 'djangorestframework>=3.13.1,<4.0.0',
 'elasticsearch-dsl>=7,<8',
 'lazy-object-proxy>=1.7.1,<2.0.0',
 'luqum>=0.11.0,<0.12.0']

setup_kwargs = {
    'name': 'django-es-drf',
    'version': '1.0.0a5',
    'description': 'Simple django - elasticsearch - drf integration',
    'long_description': '# Django ES DRF\n\n**Work in progress**\n\nA simple integration layer between Django, Elasticsearch and Django rest framework\n\n- [Django ES DRF](#django-es-drf)\n    - [Model and ES Document example](#model-and-es-document-example)\n    - [DRF example](#drf-example)\n        - [Returned fields from listing](#returned-fields-from-listing)\n        - [Search, facets](#search-facets)\n            - [Search via "q" GET parameters](#search-via-q-get-parameters)\n                - [Simple search](#simple-search)\n                - [Luqum search](#luqum-search)\n            - [Facets and faceted filtering](#facets-and-faceted-filtering)\n    - [Django Document and mapping](#django-document-and-mapping)\n        - [Custom declaration for fields](#custom-declaration-for-fields)\n        - [Excluding fields](#excluding-fields)\n        - [Custom mapping between serializer fields and ES fields](#custom-mapping-between-serializer-fields-and-es-fields)\n        - [Disabling the mapping](#disabling-the-mapping)\n        - [Relations](#relations)\n    - [Serializer](#serializer)\n    - [Objects and nested](#objects-and-nested)\n\n## Model and ES Document example\n\nTo declare a document, create a model and register it with a subclass of DjangoDocument. You do not need to declare any\nfields on the document - they are generated automatically.\n\n```python\nfrom django.db import models\nfrom django_es_drf import registry, DjangoDocument\n\n\nclass School(models.Model):\n    name = models.CharField(max_length=100)\n    address = models.TextField()\n\n\n@registry.register(School)\nclass SchoolDocument(DjangoDocument):\n    class Index:\n        name = "schools"\n```\n\nIf you want to split the file into models and documents, be free to do so but ensure that the file including documents\nis loaded at the startup - in app\'s ready function, imported at the bottom of the model etc.\n\nLater on, use `SchoolDocument` (which is a subclass of elasticsearch-dsl document):\n\n```python\ns = SchoolDocument(name=\'Blah\', address=\'somewhere\')\ns.save()\n\nfor s in SchoolDocument.search().filter(\'term\', name=\'Blah\'):\n    print(s)\n    s.name = s.name + \'-test\'\n    s.save()  # will save django and index to ES\n```\n\nYou can work with django objects if you prefer - they will get persisted into Elastic whenever they are saved:\n\n```python\ns = School.objects.create(name=\'blah\', address=\'somewhere\')\n# it was persisted, see:\ndoc = SchoolDocument.get(s.id)\nprint(doc.name)\n```\n\nYou can switch between django and document representations:\n\n```python\ndjango_school = School.objects.get(pk=1)\n\ndocument_school = SchoolDocument.from_django(django_school)\n\ndjango_again = document_school.to_django()\n\n# they are not the same instances though\nassert django_school is not django_again\n```\n\n## DRF example\n\nESViewSet is inherited from `ModelViewSet`, so feel free to use any of its features. You have to remember that\n`get_object` will give you DSL Document, not an instance of model.\n\nIn the viewset, specify a document, not a model/queryset.\n\n```python\nfrom django_es_drf import ESViewSet\n\n\nclass SchoolAPI(ESViewSet):\n    document = SchoolDocument\n```\n\nIf you need to pre-filter the initial queryset, override the `get_queryset`, call the parent and add your own initial\nfilter:\n\n```python\nfrom django_es_drf import ESViewSet\n\n\nclass SchoolAPI(ESViewSet):\n    document = SchoolDocument\n\n    def get_queryset(self):\n        qs = super().get_queryset()\n        return qs.filter(\'term\', cooperating=True)\n```\n\n### Returned fields from listing\n\nA `DynamicSourceFilter` is added automatically to filters and can be used to filter the properties returned in listings.\n\nYou can specify the props returned on the viewset:\n\n```python\nclass SchoolAPI(ESViewSet):\n    document = SchoolDocument\n    source = [\'id\', \'name\']\n```\n\nor alternatively, use `_include` or `_exclude` GET parameters containing a list of fields separated by commas\n(if `source` is set, they are active only on the fields from the source, otherwise from all fields)\n\nThe filter is only used for listing and listing-like operations (that is those where object id is not present in the\nURL). If you want to restrict the filter only on some calls, add a `is_dynamic_source_filtering_enabled(self, request)`\nmethod on the viewset class.\n\nExample:\n\n```\nGET /schools/?_exclude=address\n```\n\n### Search, facets\n\nDRF filter backends (that is `filter_backends` property on viewset) are used for both aggregations and search.\n\n#### Search via "q" GET parameters\n\nImplicitly `filter_backends` contain `QueryFilterBackend` that parses and interprets `q` and `parser` GET parameters:\n\n* the `parser` parameter contains name of the query interpreter that will be used. The interpreters are registered on \'\n  ESViewSet.query_interpreters\' dictionary which defaults\n  to ``{"simple": simple_query_interpreter, "luqum": luqum_query_interpreter}``. If the parameter is not passed,\n  ESViewSet.default_query_interpreter is used. If invalid value is passed, empty result list is returned.\n* `q` parameter is passed to the interpreter which applies it to a queryset\n\n##### Simple search\n\nThe simple interpreter performs `multi_match` operation on all fields.\n\n##### Luqum search\n\nThis search interpreter takes the content of the query and interprets it as a luqum query string. This library enables\nuse of `nested` mapping in query and supports boolean operators.\nSee [https://luqum.readthedocs.io/en/latest/about.html](https://luqum.readthedocs.io/en/latest/about.html)\nfor details on the language/capabilities.\n\n#### Facets and faceted filtering\n\nTo enable aggregations and filtering by bucket values, add `aggs` to the viewset class. Example:\n\n```python\nfrom django_es_drf import ESViewSet, BucketAgg\n\n\nclass SchoolAPI(ESViewSet):\n    document = SchoolDocument\n    aggs = [\n        BucketAgg("name"),\n        BucketAgg("address")\n    ]\n```\n\nYou can use the following aggregations:\n\n* `BucketAgg(name, field=None, type=\'terms\')` - a terms bucket filtering. If field is not set, it is the same as `name`.\n  Dot notation in the `field` is supported.\n* `NestedAgg(name, path, filter)` - generates a nested aggregation\n* `TranslatedBucketAgg(name, type="terms", field=None, map=None)` - translates the bucket keys through a map to get\n  human labels\n\nThe aggregations are chainable. Chaining `BucketAgg` does not mean nesting - the aggregations are on the same level.\n\nBigger Example:\n\n```python\naggs = [\n    TranslatedBucketAgg(\'available\', label=\'Available\', map={\n        True: \'yes\',\n        False: \'no\'\n    }),\n    NestedAgg("tagsWithYear")\n        .bucket("tagsWithYear.tag", label="Tag")\n        .bucket("tagsWithYear.year", label="Year"),\n]\n```\n\nAny options not interpreted by ES (in this case \'label\') are copied into the API output (and might be used, for example,\nin UI renderer).\n\nSample returned value (aggs = `aggs = [BucketAgg("name"), BucketAgg("address")]`):\n\n```json\n{\n  "aggs": [\n    {\n      "code": "name",\n      "__missing__": 0,\n      "__count__": 2,\n      "doc_count_error_upper_bound": 0,\n      "sum_other_doc_count": 0,\n      "buckets": [\n        {\n          "key": "test 1",\n          "doc_count": 1\n        },\n        {\n          "key": "test 2",\n          "doc_count": 1\n        }\n      ]\n    },\n    {\n      "code": "address",\n      "__missing__": 0,\n      "__count__": 2,\n      "doc_count_error_upper_bound": 0,\n      "sum_other_doc_count": 0,\n      "buckets": [\n        {\n          "key": "blah",\n          "doc_count": 2\n        }\n      ]\n    }\n  ],\n  ...\n}\n```\n\n##### Filtering by aggregation buckets\n\nTo filter by aggregation buckets, add `?f=<bucket_code>:<value>:<bucket_code>:<value>...` to the url. URLEncode \'%\'\nand \':\' if there is \':\' in the unlikely case these chars are in the value. If you need a different syntax, define\na `parse_facet_query(self, request)` method on you viewset that returns either None or dictionary with facet code as a\nkey and a list of values to match as the value.\n\nExample:\n\n```GET /schools/?f=name:test 2```\n\nResponse:\n\n```json5\n            {\n  "count": 1,\n  // ...\n  "hits": [\n    {\n      "id": 2,\n      "name": "test 2",\n      "address": "blah"\n    }\n  ],\n  "aggs": [\n    // ...\n  ]\n}\n```\n\n##### Filtering without aggregations\n\nA bucket aggregation can be used just to filter the queryset, not to generate any aggregations. To do so, specify\n`display=False` option to the aggregation. The `?f` will still be available, but no aggregations will be generated.\n\n```python\naggs = [\n    BucketAgg(\'available\', display=False),\n    NestedAgg("tagsWithYear")\n        .bucket("tagsWithYear.tag", label="Tag")\n        .bucket("tagsWithYear.year", label="Year"),\n]\n```\n\n## Django Document and mapping\n\n### Custom declaration for fields\n\nAny fields that are already present on the document will be keep as they are. In the example above, to set that\n`name` is a `text` and not `keyword` (the default), just declare:\n\n```python\nimport elasticsearch_dsl as e\n\n\n@registry.register(School)\nclass SchoolDocument(DjangoDocument):\n    name = e.Text()\n\n    class Index:\n        name = "schools"\n```\n\n### Excluding fields\n\nTo exclude a field from the automatic generation, add it to `excluded`. If "nested/object" mapping will be generated,\nyou can use a dot-separated path.\n\n```python\n@registry.register(School, excluded=[\'address\'])\nclass SchoolDocument(DjangoDocument):\n    class Index:\n        name = "schools"\n```\n\n### Custom mapping between serializer fields and ES fields\n\nAdd your own mapping - the key is the DRF field type, value is a function that takes the field and context and returns\nES field:\nThe context is an instance of `RegistrationContext`.\n\n```python\nimport elasticsearch_dsl as e\nimport rest_framework.serializers as s\n\n\n@registry.register(School,\n                   mapping={\n                       s.CharField: lambda fld, ctx, **kwargs: e.Keyword()\n                   })\nclass SchoolDocument(DjangoDocument):\n    class Index:\n        name = "schools"\n```\n\n### Disabling the mapping\n\nAdd `generate=False` to decorator\'s parameters:\n\n```python\nimport elasticsearch_dsl as e\n\n\n@registry.register(School,\n                   generate=False)\nclass SchoolDocument(DjangoDocument):\n    # you need to provide your own mapping here\n\n    class Index:\n        name = "schools"\n```\n\n### Relations\n\nThe framework has a rudimentary support for read-only relations. If there is an M2M or ForeignKey on model and\nserializer uses target\'s serializer to embed the representation of the relation target, the document will contain an\nobject mapping for the target\'s data. It can be changed to Nested via mapping parameter in Document\'s registration\ndecorator.\n\nThe framework does not provide any support for propagating changes on related models. If you need this, write your own\nchange listeners or use a more complete library, such as django-elasticsearch-dsl-drf.\n\nA simple example with a foreign key:\n\n```python\nclass City(models.Model):\n    name = models.CharField(max_length=100)\n\n\nclass School(models.Model):\n    name = models.CharField(max_length=100)\n    city = models.ForeignKey(City, related_name="+", on_delete=models.CASCADE)\n\n\n@registry.register(School, serializer_meta={"depth": 1})\nclass SchoolDocument(DjangoDocument):\n    class Index:\n        name = "schools"\n```\n\n*Note:* the `depth` in `serializer_meta` argument - it instructs the generated model serializer to create a serializer\nfor city as well. This is roughly equivalent to the following code:\n\n```python\nfrom rest_framework import serializers\n\n\nclass CitySerializer(serializers.ModelSerializer):\n    class Meta:\n        model = City\n        exclude = ()\n\n\nclass SchoolSerializer(serializers.ModelSerializer):\n    city = CitySerializer()\n\n    class Meta:\n        model = School\n        exclude = ()\n\n\n@registry.register(School, serializer=SchoolSerializer)\nclass SchoolDocument(DjangoDocument):\n    class Index:\n        name = "schools"\n```\n\nIf you want to change the type to nested:\n\n```python\nimport elasticsearch_dsl as e\n\n\n@registry.register(School, serializer_meta={"depth": 1}, mapping={\n    \'city\': e.Nested\n})\nclass SchoolDocument(DjangoDocument):\n    class Index:\n        name = "schools"\n\n```\n\n*Note:* There is no support for creating nested objects. If you need this support, you\'ll have to write your\nown `create`/`update` method on the serializer.\n\n## Serializer\n\nOptionally you might want to provide your own serializer, to transform/generate additional fields into the document.\n\nThe serializer is just a plain DRF serializer that converts django fields to document\'s fields, there is nothing fancy\nabout it.\n\n*Note:* If you use `SerializerMethodField`, be sure to add the correct field into the mapping:\n\n```python\nfrom rest_framework import serializers\n\n\nclass SchoolSerializer(serializers.ModelSerializer):\n    name_with_address = serializers.SerializerMethodField()\n\n    def get_name_with_address(self, instance):\n        return f"{instance.name}, {instance.address}"\n\n    class Meta:\n        model = School\n        exclude = ()\n\n\n@registry.register(School, serializer=SchoolSerializer, mapping={\n    \'name_with_address\': e.Text\n})\nclass SchoolDocument(DjangoDocument):\n    class Index:\n        name = "schools"\n```\n',
    'author': 'Mirek Simek',
    'author_email': 'miroslav.simek@vscht.cz',
    'maintainer': None,
    'maintainer_email': None,
    'url': None,
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.9,<4.0',
}


setup(**setup_kwargs)
