Django elasticsearch dsl completion field issue - django

I am trying to implement search suggestions using django-elasticsearch-dsl-drf for streets.
This is documents.py:
class StreetDocument(Document):
id = fields.IntegerField()
name_ru = StringField(
fields={
'raw': KeywordField(),
'suggest': fields.CompletionField(),
}
)
... # same for name_uz and name_oz
tags_ru = fields.CompletionField()
... # same for tags_uz and tags_oz
class Django:
model = Street
fields = (
'code',
'street_type'
)
in views.py I have this:
from django_elasticsearch_dsl_drf.constants import SUGGESTER_COMPLETION
from django_elasticsearch_dsl_drf.filter_backends import SuggesterFilterBackend, CompoundSearchFilterBackend
from django_elasticsearch_dsl_drf.viewsets import DocumentViewSet
class SuggestionListAPI(DocumentViewSet):
document = StreetDocument
serializer_class = StreetDocumentSerializer
filter_backends = [
CompoundSearchFilterBackend,
SuggesterFilterBackend,
]
search_fields = (
'code',
'name_ru',
'name_uz',
'name_oz',
'tags_ru',
'tags_uz',
'tags_oz'
)
suggester_fields = {
'name_ru_suggest': {
'field': 'name_ru.suggest',
'suggesters': [
SUGGESTER_COMPLETION,
],
},
... # same for name_uz and name_oz
'tags_ru': {
'field': 'tags_ru',
'suggesters': [
SUGGESTER_COMPLETION,
],
},
... # same for tags_uz and tags_oz
}
Request to /suggestions?name_ru_suggest__completion=tolstoy does nothing, just receiving all streets unfiltered.
Request to /suggestions?search=tolstoy works great, but I need autocompletion.
Where did I go wrong? And that will be great if it's possible to use two fields for suggestion at once.
Thanks for your time and help.

It looks like you're using wrong endpoint for suggestions. Correct name is suggest.
Example: http://127.0.0.1:8000/search/publishers/suggest/?country_suggest__completion=Ar
Corresponding viewsets
As of your question about using two fields for suggestion at once, it's possible to get suggestions for multiple fields at once. For instance, if you have title and author, both properly indexed and configured, you could query it as follows:
http://127.0.0.1:8000/search/books/suggest/?title_suggest=wa&author_suggest=le
This will, however, return you the following result:
{
"title_suggest": [...],
"author_suggest": [...]
}
However, if you need to refine your suggestions based on some criteria, you could do that using context suggesters (search for it in the official documentation of django-elasticsearch-dsl-drf).

Related

Django ElasticSearch dsl drf returning duplicate search result

I have search based api views, when I search something, it return value, but return the duplicate value with that term. in my database, there is no duplicate data, even in elasticsearch build.
This is my API views:
from django_elasticsearch_dsl_drf.viewsets import DocumentViewSet
class ExampleAPIView(DocumentViewSet):
document = ExampleDocument
serializer_class = ExampleSerializer
filter_backends = [
MultiMatchSearchFilterBackend
]
multi_match_options = {
'type': 'phrase_prefix',
}
multi_match_search_fields = {
'title': 'title',
}
I am not getting why the search result returns duplicate data? Can anyone help me to fix this?

Django rest elasticsearch filter range in url query params

I am using elasticsearch with django rest framework. I am using this lib:
https://github.com/barseghyanartur/django-elasticsearch-dsl-drf/
I am trying to filter by price range according to these docs: https://github.com/barseghyanartur/django-elasticsearch-dsl-drf/
This is my views:
class TestAPIView(DocumentViewSet):
document = TestDocument
serializer_class = TestSerializer
queryset = TestModel.objects.all()
filter_backends = [
FilteringFilterBackend
]
filter_fields = {
'price': {
'field': 'price',
'lookups': [
LOOKUP_FILTER_RANGE,
LOOKUP_QUERY_IN,
],
},
}
and this is my document.py file
#registry.register_document
class TestDocument(Document):
price = fields.IntegerField(attr='price')
class Index:
name = 'TestModel'
settings = {
'number_of_shards': 1,
'number_of_replicas': 0,
}
class Django:
model = TestModel
fields = [
'id',
]
When I hit on browser this url: http://127.0.0.1:8000/search/api/v1/test-model/?price=12 It works very well, even when I try with this url : http://127.0.0.1:8000/search/api/v1/test-model/?price=55 it works,
I am facing a problem to filter by range, like I want to filter price 12 to price 90, in this case how can I pass query parameter for the range? can anyone help me in this case?
The source code [GitHub] provides an example for this:
# Example: {"query": {"range": {"age": {"gte": "16", "lte": "67"}}}}
# Example: http://localhost:8000/api/users/?age__range=16__67
You thus can filter with:
http://127.0.0.1:8000/search/api/v1/test-model/?price__range=12__90
to retrieve items with a price between 12 and 90.

What is the best way for connecting Django models choice fields with React js select options

I find myself stuck in dealing with Django models choice fields and react select options. May someone kindly help. This is my models code:
class AccountType(models.Model):
category = models.SmallIntegerField(
choices=(
(AccountCategories.ASSET, "Asset"),
(AccountCategories.LIABILITY, "Liability"),
(AccountCategories.EQUITY, "Equity"),
(AccountCategories.REVENUE, "Revenue"),
(AccountCategories.EXPENSE, "Operating Expense"),
)
)
classification = models.SmallIntegerField(
choices=(
(AccountClassifications.NONE, ""),
(AccountClassifications.CURRENT, "Current"),
(AccountClassifications.NONCURRENT, "Long-Term"),
)
)
I cant seem to figure out on how to make these choices to be my select options in React form. Was thinking maybe the solution may be in validating or cleaning these choices in my serializers but I am stuck on the how especially on linking with a React Form. Thanks in advance
So I remembered Beazley's Tutorial on Python builtin SuperHeros or something like that and came up with this solution. Might not be the best as far as the DRY principle is concerned, but it works like a charm and for anyone who has struggled with the same issue and has no other way around, here is how I did it:
ACCOUNT_TYPES_CATEGORY_CHOICES = [
(100, 'Do Not Choose Me'),
(0, 'Asset'),
(1, 'Liability'),
(2, 'Equity'),
(3, 'Revenue'),
(4, 'Operating Expense')
]
I put the choices in a seperate file.
class AccountType(models.Model):
class Meta:
ordering = ['order']
objects = AccountTypeManager()
category = models.IntegerField(choices=ACCOUNT_TYPES_CATEGORY_CHOICES)
classification = models.IntegerField(choices=ACCOUNT_TYPES_CLASSIFICATION_CHOICES)
I imported the file and put it in my model and called python manage.py makemigrations
class AccountingPeriodsChoicesAPIView(views.APIView):
def get(self, request, format=None):
my_choices = []
choice_dict = dict(ACCOUNTING_PERIODS_CHOICES)
for key, value in choice_dict.items():
itered_dict = {"key": key, "value": value}
my_choices.append(itered_dict)
return Response(my_choices, status=status.HTTP_200_OK)
I created an api endpoint for it. I know that might be too much of work but it does work. Converting it into a dictionary and then unpacking it through .items(), and assigning the value and key and then returning it in Response did the trick. Calling it as an endpoint allows me to manage it under redux state and its doing what its suppose to do. Violla!!!!!
You can use django-js-choices
From the docs, the usage is simple:
Overview Django JS Choices is a small Django app that makes handling
of model field choices in javascript easy.
For example, given the model…
# models.py:
class Student(models.Model):
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
YEAR_IN_SCHOOL_CHOICES = (
(FRESHMAN, 'Freshman'),
(SOPHOMORE, 'Sophomore'),
(JUNIOR, 'Junior'),
(SENIOR, 'Senior'),
)
year_in_school = models.CharField(
max_length=2,
choices=YEAR_IN_SCHOOL_CHOICES,
default=FRESHMAN,
)
…the choices are accesible in javascript.
Choices.pairs("year_in_school");
Result:
[
{value: "FR", label: "Freshman"},
{value: "SO", label: "Sophomore"},
{value: "JR", label: "Junior"},
{value: "SR", label: "Senior"}
]
Display values are also accesible.
Choices.display("year_in_school", "FR")
Choices.display("year_in_school", {"year_in_school": "FR"})
In both cases the result is
"Freshman"
The preferred way for me was to save the choices data in json format and convert the file in models.py.
So the choices.json file would look like that:
// choices.json
{
"ACCOUNT_TYPES_CATEGORY_CHOICES": [
{"value": 100, "display": 'Do Not Choose Me'),
{"value": 0, "display": 'Asset'),
{"value": 1, "display": 'Liability'),
{"value": 2, "display": 'Equity'),
{"value": 3, "display": 'Revenue'),
{"value": 4, "display": 'Operating Expense')
],
...
}
In the models.py file convert the json file to a list of dict with tuples:
# models.py
def json2Tuples(jsonData):
"""
Django only takes tuples (actual value, human readable name) so we need to repack the json in a dictionay of tuples
"""
dicOfTupple = dict()
for key, valueList in jsonData.items():
dicOfTupple[str(key)] = [(dic["value"], dic["display"])
for dic in valueList]
return dicOfTupple
with open('choices.json') as f:
choices_json = json.load(f)
choices = json2Tuples(choices_json)
class AccountType(models.Model):
category = models.SmallIntegerField(
choices=choices[“ACCOUNT_TYPES_CATEGORY_CHOICES“]
)
...
For React you would need to install json-loader:
npm install json-loader
And adjust your react form:
import React from "react";
import { Form } from "react-bootstrap";
import jsonData from import jsonData from "/choices.json";
const MyForm = ({ handleChange }) => {
const choices = jsonData.ACCOUNT_TYPES_CATEGORY_CHOICES;
return (
<div className="d-flex flex-column align-items-center">
<h2>Account Type</h2>
<Form.Group className="w-75 mt-4">
<Form.Control
placeholder="Account type"
as="select"
onChange={handleChange("account_type")}
name="account_type"
>
{choices.map((c) => (
<option key={c.value} value={c.value}>
{c.display}
</option>
))}
</Form.Control>
</Form.Group>
</div>
);
};
export default MyForm;

To add GET parameters in Swagger

Use django rest framework and django-rest-swagger in documentation of the methods it is not showing available GET parameters and the question is how can I set?
code:
# views.py
#api_view(['GET'])
def tests_api(request):
"""
:param request:
:return:
"""
id = request.query_params.get('id')
name = request.query_params.get('name')
return Response({'user': name, 'text': 'Hello world'})
# urls.py
urlpatterns = [
url(r"^api/v1/tests_api/$", tests_api),
]
http api:
GET https://127.0.0.1/api/v1/tests_api/?name=Denis&id=3
HTTP/1.1 200 OK
...
{
"user": "Denis",
"text": "Hello world"
}
now:
example:
Russian version.
Declare schema manually and specify fields there with location query
schema = ManualSchema(fields=[
coreapi.Field(
"name",
required=True,
location="query",
schema=coreschema.String()
),
]
)
See doc for details
urlpatterns = [
url(r'^api/v1/tests_api/(?P<id>\d+)/(?P<name>\w+)/$', tests_api),
]

Enumerating model choices in a Django Rest Framework serializer

I have a model that uses a Django choices field, like this:
class Question(models.Model):
QUESTION_TYPES = (
(10,'Blurb'),
(20,'Group Header'),
(21,'Group Footer'),
(30,'Sub-Group Header'),
(31,'Sub-Group Footer'),
(50,'Save Button'),
(100,'Standard Question'),
(105,'Text-Area Question'),
(110,'Multiple-Choice Question'),
(120,'Standard Sub-Question'),
(130,'Multiple-Choice Sub-Question')
)
type = models.IntegerField(default=100,choices=QUESTION_TYPES)
I'm using Django Rest Framework to present this model as an API to an Angular web app. In my Angular web app, I want a combo box widget that drops down with all those choices. Not the integers, but the text choices, like "blurb", "standard question" and so on.
Now, I could hand code the combo box into the Angular app, but in the spirit of DRY, is it possible to write a DRF serializer that just returns those choices (ie the QUESTION_TYPES object), so I can populate the combo box with a ReST query?
And by "possible", I guess I mean "simple and elegant". And maybe I also mean "ReSTful". (Is it ReSTful to do it that way?)
Just wondering . . .
Thanks
John
I would probably try something like the following:
# models.py
class Question(models.Model):
QUESTION_NAMES = (
'Blurb',
'Group Header',
'Group Footer',
'Sub-Group Header',
'Sub-Group Footer',
'Save Button',
'Standard Question',
'Text-Area Question',
'Multiple-Choice Question',
'Standard Sub-Question',
'Multiple-Choice Sub-Question')
QUESTION_VALS = (10, 20, 21, 30,
31, 50, 100, 105, 110,
120, 130)
QUESTION_TYPES = tuple(zip(QUESTION_VALS, QUESTION_NAMES))
# Personal choice here: I never name attribs after Python built-ins:
qtype = models.IntegerField(default=100,choices=QUESTION_TYPES)
The following doesn't work as I thought it should
(Following was my original intuition on serializing a list of objects, but it did not work. I'm leaving it in here anyway, because it seems like it should work.)
Okay, so we have a way to access the strings on their own, now we just need to serialize them, and for that, I'd probably try to use the ListField in DRF3, which should support the source kwarg, I would think?
# serializers.py
from .models import Question
class YourSerializer(ModelSerializer):
names = serializers.ListField(
child=serializers.CharField(max_length=40),
source=Question.QUESTION_NAMES
)
class Meta:
model = Question
fields = ('names', etc.)
The following does return a list of results
Fallback: use a SerializerMethodField:
from .models import Question
class YourSerializer(serializers.ModelSerializer):
...
names = serializers.SerializerMethodField()
def get_names(self, obj):
return Question.QUESTION_NAMES
class Meta:
model = Question
Demo:
In [1]: q = Question.objects.create()
Out[1]: <Question: Question object>
In [2]: ser = YourSerializer(q)
In [3]: ser.data
Out[3]: {'id': 1, 'names': ['Blurb', 'Group Header', 'Group Footer', 'Sub-Group Header', 'Sub-Group Footer', 'Save Button', 'Standard Question', 'Text-Area Question', 'Multiple-Choice Question', 'Standard Sub-Question', 'Multiple-Choice Sub-Question'], 'qtype': 100}
if you use a ModelViewSet in combination with a ModelSerializer, the OPTIONS request will return metadata that you can use to get the choice options.
from models import Question
from rest_framework import serializers
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
from rest_framework.viewsets import ModelViewSet
class QuestionChoicesViewSet(ModelViewSet):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
This will give you a response that includes the actions attribute, that might look something like this:
"actions": {
"POST": {
"id": {
"type": "integer",
"required": false,
"read_only": true,
"label": "ID"
},
"qtype": {
"type": "choice",
"required": false,
"read_only": false,
"label": "Qtype",
"choices": [
{
"display_name": "Blurb",
"value": 10
},
{
"display_name": "Group Header",
"value": 20
},
{
"display_name": "Group Footer",
"value": 21
},
{
"display_name": "Sub-Group Header",
"value": 30
},
//...
}
}
}
You can iterate over the choices attribute on qtype to get all of the available choices.
To get more familiar with this topic you can read: Metadata
I accomplished this by making an API endpoint for the choices which only use the GET verb.
models.py
QUESTION_TYPES = (
(10,'Blurb'),
(20,'Group Header'),
(21,'Group Footer'),
(30,'Sub-Group Header'),
(31,'Sub-Group Footer'),
(50,'Save Button'),
(100,'Standard Question'),
(105,'Text-Area Question'),
(110,'Multiple-Choice Question'),
(120,'Standard Sub-Question'),
(130,'Multiple-Choice Sub-Question')
)
class Question(models.Model):
type = models.IntegerField(default=100,choices=QUESTION_TYPES)
viewsets.py
from models import QUESTION_NAMES, Question
from rest_framework import serializers
class QuestionSerializer(serializers.ModelSerializer):
type = serializers.ChoiceField(choices=QUESTION_NAMES, default=100)
class Meta:
model = Question
from rest_framework.response import Response
from rest_framework.views import APIView
class QuestionChoicesViewSet(APIView):
def get(self, request):
return Response(QUESTION_NAMES)
from rest_framework import viewsets
class QuestionViewSet(viewsets.ModelViewSet):
queryset = Question.objects.all()
serializer_class = QuestionSerializer