I have a GraphQL schema that returns orders and the products that are part of that order. I have added a filter to return only orders that contains products searched for by the user. This filter is done on a icontains.
However I have found that if a user searches for angel for example, and an order contains two or more products that contain the string angel it returns the order that many times duplicating the records returned to the frontend.
Query:
{
dateFilterList
Purchases(first: 15, after: "", username_Icontains: "", PurchaseOrdersProductsOrderId_ProductName: "angel") {
pageCursors {
previous {
cursor
}
first {
cursor
page
}
around {
cursor
isCurrent
page
}
last {
cursor
page
}
next {
cursor
}
}
edges {
node {
id
cmOrderId
username
date
articles
merchandiseValue
shippingValue
trusteeValue
totalValue
products {
edges {
node {
id
productId
productName
productNumber
}
}
}
}
}
}
}
Json Response:
{
"data": {
...
"purchases": {
...
"edges": [
{
"node": {
"id": "",
"cmOrderId": 12345,
"username": "UserA",
"date": "2022-06-29T16:38:51",
"articles": 40,
"merchandiseValue": "",
"shippingValue": "",
"trusteeValue": "",
"totalValue": "",
"products": {
"edges": [
{
"node": {
"id": "",
"productId": "",
"productName": "Angel of Grace",
"productNumber": "1"
}
},
{
"node": {
"id": "",
"productId": "",
"productName": "Angel of Sanctions",
"productNumber": "1"
}
},
...
]
}
},
"node": {
"id": "",
"cmOrderId": 12345,
"username": "UserA",
"date": "2022-06-29T16:38:51",
"articles": 40,
"merchandiseValue": "",
"shippingValue": "",
"trusteeValue": "",
"totalValue": "",
"products": {
"edges": [
{
"node": {
"id": "",
"productId": "",
"productName": "Angel of Grace",
"productNumber": "1"
}
},
{
"node": {
"id": "",
"productId": "",
"productName": "Angel of Sanctions",
"productNumber": "1"
}
},
...
]
}
},
...
Schema:
# region Integration Purchase Orders
class PurchaseOrderFilter(FilterSet):
class Meta:
model = purchase_orders
fields = {'date': ['gt', 'lt', 'isnull'], 'username': ['icontains'],}
purchase_orders_products_order_id__product_name = CharFilter(lookup_expr="icontains")
class PurchaseOrderProductFilter(FilterSet):
class Meta:
model = purchase_orders_products
fields = {"product_name": ["icontains"]}
class PurchasesProducts(DjangoObjectType):
id = graphene.ID(source='pk', required=True)
class Meta:
model = purchase_orders_products
interfaces = (graphene.relay.Node,)
filterset_class = PurchaseOrderProductFilter
class Purchases(DjangoObjectType):
id = graphene.ID(source='pk', required=True)
products = DjangoFilterConnectionField(PurchasesProducts)
class Meta:
model = purchase_orders
interfaces = (graphene.relay.Node,)
filterset_class = PurchaseOrderFilter
connection_class = ArtsyConnection
#staticmethod
def resolve_products(self, info, **kwargs):
return purchase_orders_products.objects.filter(order_id=self.id).order_by('product_name').all()
class PurchasesQuery(ObjectType):
date_filter_list = graphene.List(graphene.List(graphene.String))
purchases = ArtsyConnectionField(Purchases)
#staticmethod
def resolve_date_filter_list(self, info, **kwargs):
years = purchase_orders.objects.filter(user_id=info.context.user.id).annotate(year=ExtractYear('date'), month=ExtractMonth('date'), ).order_by().values_list('year', 'month').order_by('-year', '-month').distinct()
return years
#staticmethod
def resolve_purchases(self, info, **kwargs):
return purchase_orders.objects.filter(user_id=info.context.user.id).all().order_by("-date")
PurchasesSchema = graphene.Schema(query=PurchasesQuery)
# endregion
Problem
Your query is likely returning duplicate results. It's sometimes not enough to just call .distinct().
Solution
You must specify DISTINCT ON <field> as it seems your id field is returning an empty string. Consider a adding .distinct('cmOrderId') to your query.
I also notice that most of your purchase_order_product queries do not include distinct—consider adding the .distinct('cmOrderId') to all of these queries.
Notes
Furthermore, I notice that you call all() on your filter() queries—it has no effect on the resulting object.
References
https://docs.djangoproject.com/en/4.0/ref/models/querysets/#filter
https://docs.djangoproject.com/en/4.0/ref/models/querysets/#django.db.models.query.QuerySet.distinct
Related
{
"127.0.0.1":{
"addresses":{
"ipv4":"127.0.0.1"
},
"hostnames":[
{
"name":"localhost",
"type":"PTR"
}
],
"status":{
"reason":"conn-refused",
"state":"up"
},
"tcp":{
"5000":{
"conf":"10",
"cpe":"cpe:/a:python:python:3.9.2",
"extrainfo":"Python 3.9.2",
"name":"http",
"product":"Werkzeug httpd",
"reason":"syn-ack",
"script":{
"vulners":"\n cpe:/a:python:python:3.9.2: \n \tCVE-2021-29921\t7.5\thttps://vulners.com/cve/CVE-2021-29921\n \tCVE-2021-23336\t4.0\thttps://vulners.com/cve/CVE-2021-23336\n \tMSF:ILITIES/DEBIAN-CVE-2021-3426/\t2.7\thttps://vulners.com/metasploit/MSF:ILITIES/DEBIAN-CVE-2021-3426/\t*EXPLOIT*\n \tCVE-2021-3426\t2.7\thttps://vulners.com/cve/CVE-2021-3426"
},
"state":"open",
"version":"1.0.1"
},
"6000":{
"conf":"10",
"cpe":"cpe:/a:python:python:3.9.2",
"extrainfo":"Python 3.9.2",
"name":"http",
"product":"Werkzeug httpd",
"reason":"syn-ack",
"script":{
"vulners":"\n cpe:/a:python:python:3.9.2: \n \tCVE-2021-29921\t7.5\thttps://vulners.com/cve/CVE-2021-29921\n \tCVE-2021-23336\t4.0\thttps://vulners.com/cve/CVE-2021-23336\n \tMSF:ILITIES/DEBIAN-CVE-2021-3426/\t2.7\thttps://vulners.com/metasploit/MSF:ILITIES/DEBIAN-CVE-2021-3426/\t*EXPLOIT*\n \tCVE-2021-3426\t2.7\thttps://vulners.com/cve/CVE-2021-3426"
},
"state":"open",
"version":"1.0.1"
}
},
"vendor":{
}
}
}
I want to extract "vulners" value here i tried this -
results = []
for x in collection.find({},{"scan": 1, "_id": 0 }):
results.append(json.loads(json_util.dumps(x)))
portnumber = []
datay = []
datapro = []
for result in results:
ips = result['scan']
for ip in ips:
ports = result['scan'][ip]['tcp']
ipdomain = result['scan'][ip]['hostnames']
for ip2 in ipdomain:
ip3 = ip2['name']
for port in ports:
portnumber.append(port)
datax = ports[port]['script']
datay.append(datax)
datapro2 = ports[port]['product']
datapro.append(datapro2)
date = datetime.datetime.now()
date_now = date.strftime("%x, %X")
pass_json_var = {'domain': ip3, 'ports': portnumber, 'product': datapro, 'vulnerabilities': datay, "date": date_now}
if isinstance(pass_json_var, list):
domaindata.insert_many(pass_json_var)
else:
domaindata.insert_one(pass_json_var)
Ok so here if the "results" output gives me one "vulners" value then it works fine but when it's multiple ports with vulners values it doesn't work!
How can i access the 'vulners' value? Hoping for someone to guide me also a bit, Please try to give a solution which is dynamic
Thanks a lot!
Model based approach
this approach is based on a model of your data you want to parse. From my point of view this is more work in the beginning. With the advantage, that you will have clean error messages and you can control the behaviour by adapting your data model.
make a model of the data you want to parse
from typing import Any, Optional
from pydantic import BaseModel, Field
class ExScript(BaseModel):
vulners:str = ""
class Ex30000(BaseModel):
script:ExScript = Field(default=Any)
class ExTcp(BaseModel):
root:Ex30000= Field(default=Any, alias="30000")
class ExRoot(BaseModel):
tcp:ExTcp = Field() # Required
class Base(BaseModel):
root:ExRoot = Field(default=Any, alias="35.0.0.0.0")
change your input data to a raw string outherwise you will have to escape \n and \t
input_will_work = r"""{
"35.0.0.0.0": {
"hostnames": [
{
"name": "domain.com",
"type": "PTR"
}
],
"addresses": {
"ipv4": "35.0.0.0"
},
"vendor": {},
"status": {
"state": "up",
"reason": "syn-ack"
},
"tcp": {
"30000": {
"state": "open",
"reason": "syn-ack",
"name": "http",
"product": "nginx",
"version": "1.20.0",
"extrainfo": "",
"conf": "10",
"cpe": "cpe:/a:igor_sysoev:nginx:1.20.0",
"script": {
"http-server-header": "nginx/1.20.0",
"vulners": "\n cpe:/a:igor_sysoev:nginx:1.20.0: \n \tNGINX:CVE-2021-23017\t6.8\thttps://vulners.com/nginx/NGINX:CVE-2021-23017\n \t9A14990B-D52A-56B6-966C-6F35C8B8EB9D\t6.8\thttps://vulners.com/githubexploit/9A14990B-D52A-56B6-966C-6F35C8B8EB9D\t*EXPLOIT*\n \t1337DAY-ID-36300\t6.8\thttps://vulners.com/zdt/1337DAY-ID-36300\t*EXPLOIT*\n \tPACKETSTORM:162830\t0.0\thttps://vulners.com/packetstorm/PACKETSTORM:162830\t*EXPLOIT*"
}
}
}
}
}
"""
input_will_fail = r"""{
"35.0.0.0.0": {}
}
"""
3.1 this should give you the expected result
obj1 = Base.parse_raw(input_will_work)
print(obj1.root.tcp.root.script.vulners)
3.2 this should throw an exception
obj2 = Base.parse_raw(input_will_fail)
Search data with jsonpath
should return all objects with the name vulners
from jsonpath_ng import jsonpath, parse
import json
obj = json.loads(input_will_work)
p = parse('$..vulners')
for match in p.find(obj):
print(match.value)
Update:
def extract_data(ip_address_data):
domains = ip_address_data["hostnames"]
ports_data = []
# Each port can have different products and vulners
# So that data is grouped together in a dictionary
for port in ip_address_data["tcp"].keys():
port_data = ip_address_data["tcp"][port]
product = port_data["product"]
vulners = port_data['script']['vulners']
ports_data.append({
"port": port,
"product": product,
"vulners": vulners
})
return {
"domains": domains,
"ports_data": ports_data
}
# Result is the data from mongo db
# result = collection.find({})["scan"]
result = {
"127.0.0.1": {
"addresses": {
"ipv4": "127.0.0.1"
},
"hostnames": [
{
"name": "localhost",
"type": "PTR"
}
],
"status": {
"reason": "conn-refused",
"state": "up"
},
"tcp": {
"5000": {
"conf": "10",
"cpe": "cpe:/a:python:python:3.9.2",
"extrainfo": "Python 3.9.2",
"name": "http",
"product": "Werkzeug httpd",
"reason": "syn-ack",
"script": {
"vulners": "\n cpe:/a:python:python:3.9.2: \n \tCVE-2021-29921\t7.5\thttps://vulners.com/cve/CVE-2021-29921\n \tCVE-2021-23336\t4.0\thttps://vulners.com/cve/CVE-2021-23336\n \tMSF:ILITIES/DEBIAN-CVE-2021-3426/\t2.7\thttps://vulners.com/metasploit/MSF:ILITIES/DEBIAN-CVE-2021-3426/\t*EXPLOIT*\n \tCVE-2021-3426\t2.7\thttps://vulners.com/cve/CVE-2021-3426"
},
"state": "open",
"version": "1.0.1"
},
"6000": {
"conf": "10",
"cpe": "cpe:/a:python:python:3.9.2",
"extrainfo": "Python 3.9.2",
"name": "http",
"product": "Werkzeug httpd",
"reason": "syn-ack",
"script": {
"vulners": "\n cpe:/a:python:python:3.9.2: \n \tCVE-2021-29921\t7.5\thttps://vulners.com/cve/CVE-2021-29921\n \tCVE-2021-23336\t4.0\thttps://vulners.com/cve/CVE-2021-23336\n \tMSF:ILITIES/DEBIAN-CVE-2021-3426/\t2.7\thttps://vulners.com/metasploit/MSF:ILITIES/DEBIAN-CVE-2021-3426/\t*EXPLOIT*\n \tCVE-2021-3426\t2.7\thttps://vulners.com/cve/CVE-2021-3426"
},
"state": "open",
"version": "1.0.1"
}
},
"vendor": {
}
}
}
def scandata():
for ip_address in result:
ip_address_data = extract_data(
result[ip_address]
)
print(ip_address, ip_address_data)
scandata()
def extract_data(ip_address_data):
domains = ip_address_data["hostnames"]
ports_data = []
# Each port can have different products and vulners
# So that data is grouped together in a dictionary
for port in ip_address_data["tcp"].keys():
port_data = ip_address_data["tcp"][port]
product = port_data["product"]
vulners = port_data['script']['vulners']
ports_data.append({
"port": port,
"product": product,
"vulners": vulners
})
return {
"domains": domains,
"ports_data": ports_data
}
#app.route('/api/vulnerableports', methods=['GET'])
def show_vulnerableports():
data = []
resultz = []
for x in collection.find({}, {"scan": 1, "_id": 0}):
resultz.append(json.loads(json_util.dumps(x)))
for resultx in resultz:
result = resultx['scan']
for ip_address in result:
ip_address_data = extract_data(
result[ip_address]
)
data.append({ip_address: ip_address_data})
return jsonify(data)
So this is the solution! i had to iterate over script and then access vulner!
I was applying elastic search in one my django app, Below is my code snippets
documents.py
ads_index = Index("ads_index")
ads_index.settings(
number_of_shards=1,
number_of_replicas=0
)
html_strip = analyzer(
'html_strip',
tokenizer="standard",
filter=["standard", "lowercase", "stop", "snowball"],
char_filter=["html_strip"]
)
#ads_index.doc_type
class AdDocument(Document):
id = fields.IntegerField(attr='id')
title = fields.TextField(
analyzer=html_strip,
fields={
'title': fields.TextField(analyzer='keyword'),
}
)
description = fields.TextField(
analyzer=html_strip,
fields={
'description': fields.TextField(analyzer='keyword'),
}
)
category = fields.ObjectField(
properties={
'title': fields.TextField(),
}
)
class Django:
model = Ad # The model associated with this Document
# The fields of the model you want to be indexed in Elasticsearch
fields = [
'price',
'created_at',
]
related_models = [Category]
def get_queryset(self):
return super().get_queryset().select_related('category')
def get_instances_from_related(self, related_instance):
if isinstance(related_instance, Category):
return related_instance.ad_set.all()
serializer
class AdDocumentSerializer(DocumentSerializer):
class Meta:
document = AdDocument
fields = (
"id",
"title",
"description",
"price",
"created_at",
)
viewset
class AdViewSet(DocumentViewSet):
document = AdDocument
serializer_class = AdDocumentSerializer
ordering = ('id',)
lookup_field = 'id'
filter_backends = [
DefaultOrderingFilterBackend,
FilteringFilterBackend,
CompoundSearchFilterBackend,
SuggesterFilterBackend,
]
search_fields = (
'title',
'description',
)
filter_fields = {
'id': {
'field': 'id',
'lookups': [
LOOKUP_FILTER_RANGE,
LOOKUP_QUERY_IN,
LOOKUP_QUERY_GT,
LOOKUP_QUERY_GTE,
LOOKUP_QUERY_LT,
LOOKUP_QUERY_LTE,
],
},
'title': 'title.raw',
'description': 'description.raw',
}
ordering_fields = {
'id': 'id',
}
Below is my data I have
When I hit http://127.0.0.1:8000/ads/search/?search=Tit it's not returning anything but when I hit http://127.0.0.1:8000/ads/search/?search=a it's giving me one result.
What's wrong here with my code? Any help would be appreciated :-)
With keyword analyzer the entire input string is indicized into inverted index as it is, so you could search only by exact match. I use elasticsearch library in python and I don't know very well elasticsearch-dsl. I will try to answer to you using pure elastic configuration, and then you should search how to implement that conf with elasticsearch-dsl library in python. If you would search also partial string inside some words, you should indicize them with the edge-ngram token filter - doc here. With this method you could perform also an autocompletion on search bar, because partial string from the beginning of the word are searchable. You should implement a specific search_analyzer because you want that your input query string don't be tokenized with edge-ngram token filter - for an explanation have a look here and here
{
"settings": {
"number_of_shards": 1,
"analysis": {
"filter": {
"autocomplete_filter": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 20
}
},
"analyzer": {
"autocomplete": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"autocomplete_filter"
]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer" : "autocomplete",
"search_analyzer" : "standard"
},
"description": {
"type": "text",
"analyzer" : "autocomplete",
"search_analyzer" : "standard"
}
}
}
}
If you don't like this solution because you would also search for partial string in the middle of a word - such as querying for itl to retrieve title string, you should implement a new ngram-tokenizer from scratch - doc here:
{
"settings": {
"analysis": {
"analyzer": {
"autocomplete": {
"tokenizer": "my_tokenizer",
"filter": ["lowercase"]
}
},
"tokenizer": {
"my_tokenizer": {
"type": "ngram",
"min_gram": 1,
"max_gram": 20,
"token_chars": [
"letter",
"digit"
]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer" : "autocomplete",
"search_analyzer" : "standard"
},
"description": {
"type": "text",
"analyzer" : "autocomplete",
"search_analyzer" : "standard"
}
}
}
}
I'm trying to compare 2 json that show a different order in the ids value, even thouh they are concettually the same, as you can see from the example.
For instance, each person object points to an Address object based on its id. The ids could be different but, concettually, the 2 json are the same. Here an example: The order of the address_id is different (as the address object position), but the 2 json are the same, (John lives in NYC and Pippo in BCN).
{
"Persons": [
{
"Name": "John",
"Address_Id": "0"
},
{
"Name": "Pippo",
"Address_Id": "1"
}
],
"Addresses": [
{
"City": "NYC"
},
{
"City": "BCN"
}
]
}
{
"Persons": [
{
"Name": "John",
"Address_Id": "1"
},
{
"Name": "Pippo",
"Address_Id": "0"
}
],
"Addresses": [
{
"City": "BCN"
},
{
"City": "NYC"
}
]
}
Is there a way to compare the 2 json considering this "special case" ??
Thanks.
You can deserialise the json yourself by creating the equivalent Python classes and implementing the comparison yourself
. For example:
import json
class Person_book:
def __init__(self, person_list):
self.person_list = person_list
def __eq__(self, other):
if len(self.person_list) != len(other.person_list):
return False
for person in self.person_list:
if person not in other.person_list:
return False
return True
class Person:
def __init__(self, person_id, address):
self.person_id = person_id
self.address = address
def __str__(self):
return str(self.person_id) + ' ' + str(self.address )
def __eq__(self, other):
if self.person_id == other.person_id and self.address == other.address:
return True
return False
def deserialize_persons(persons_array):
persons = persons_array['Persons']
addresses = persons_array['Addresses']
person_list = []
for person in persons:
person_entry = Person(person['Name'], addresses[int(person['Address_Id'])])
person_list.append(person_entry)
return Person_book(person_list)
json_resp_1 = """{
"Persons": [
{
"Name": "John",
"Address_Id": "0"
},
{
"Name": "Pippo",
"Address_Id": "1"
}
],
"Addresses": [
{
"City": "NYC"
},
{
"City": "BCN"
}
]
}"""
json_resp_2 = """
{
"Persons": [
{
"Name": "John",
"Address_Id": "1"
},
{
"Name": "Pippo",
"Address_Id": "0"
}
],
"Addresses": [
{
"City": "BCN"
},
{
"City": "NYC"
}
]
}"""
resp_1 = json.loads(json_resp_1)
resp_2 = json.loads(json_resp_2)
person_book_1 = deserialize_persons(resp_1)
person_book_2 = deserialize_persons(resp_2)
print(person_book_1 == person_book_2)
I have two models, Appointment and EmployeeEvent. I need to get data from these models and combine the result in to a single get api request.
urls.py
url(r'^calenderevents', calender_events)
views.py
#api_view(['GET'])
def calender_events(request):
queryset1 = Appointment.objects.all()
queryset2 = EmployeeEvent.objects.all()
return Response({'Appointments':json.loads(serializers.serialize('json', queryset1)), 'EmployeeEvents': json.loads(serializers.serialize('json', queryset2))})
When I call the API, I am getting the result, but it includes some unwanted keys like "pk", "model", "fields" etc. Also in the appoinments result, I need the full customer object instead of the customer id. Is there any way to specify the CustomerSerializer along with the query set?
Results am getting
{
"Appointments": [
{
"pk": "33a0fffb-326e-4566-bfb4-b146a87a4f3f",
"model": "appointment.appointment",
"fields": {
"customer": "25503315-8bac-4070-87c1-86bf0630c846",
"status": "Requested",
"description": "Assigned appointment",
}
},
{
"pk": "9da806f5-77f1-41e6-a745-7be3f79d6f7a",
"model": "appointment.appointment",
"fields": {
"customer": "25503315-8bac-4070-87c1-86bf0630c846",
"status": "Requested",
"description": "Assigned appointment",
}
}
],
"EmployeeEvents": [
{
"pk": "f76b5de0-1ab8-4ac3-947d-15ba8941d97d",
"model": "employee_event.employeeevent",
"fields": {
"event_name": "New Event",
"start_date": "2017-02-17",
"end_date": "2017-02-22"
}
},
{
"pk": "56f02290-370e-426c-951e-a93c57fde681",
"model": "employee_event.employeeevent",
"fields": {
"event_name": "New Event",
"start_date": "2017-02-02",
"end_date": "2017-03-22"
}
}
]
}
Expected Result
{
"Appointments": [
{
"id": "33a0fffb-326e-4566-bfb4-b146a87a4f3f",
"customer": {
"id": "25503315-8bac-4070-87c1-86bf0630c846",
"firstname": "Customre 1",
"photo_url": "imagepath",
},
"status": "Requested",
"description": "Assigned appointment"
},
{
"id": "9da806f5-77f1-41e6-a745-7be3f79d6f7a",
"customer": {
"id": "15ba8941d97d-8bac-4070-87c1-86bf0630c846",
"firstname": "Customre 2",
"photo_url": "imagepath",
},
"status": "Requested",
"description": "Assigned appointment"
},
}
],
"EmployeeEvents": [
{
"id": "f76b5de0-1ab8-4ac3-947d-15ba8941d97d",
"event_name": "New Event 1",
"start_date": "2017-02-17",
"end_date": "2017-02-22"
},
{
"id": "56f02290-370e-426c-951e-a93c57fde681",
"event_name": "New Event 2”,
"start_date": "2017-02-17",
"end_date": "2017-02-22"
}
]
}
You need to write a serializer to display the data in the desired format. Read the excellent tutorial to guide you though it properly. But if you want a quick hacky answer, then do something like this:
serializer = AppointmentSerializer(Appointment.objects.all(), many=True)
return Response(serializer.data)
Where the serializer looks something like this:
class AppointmentSerializer(serializers.ModelSerializer):
customer = CustomerSerializer(required=False, allow_null=True)
class Meta:
model = Appointment
fields = ('id', 'customer', 'status', 'etc...')
related_object = 'customer'
class CustomerSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = ('id', 'first_name', 'etc...')
Edit: updated to include example of a related object
from django.db import models
# django user
from django.contrib.auth.models import User
class Entity(models.Model):
"""
Entity of EAV
"""
entity = models.CharField(max_length=216,
null=False, default='entity_name',
name='entity', verbose_name='Entity of EAV',
db_index=True,
unique=True
)
class Asset(models.Model):
"""
Asset of EAV
"""
asset = models.CharField(max_length=216, null=False,
default='asset', name='asset',
verbose_name='Asset of EAV'
)
entity = models.ForeignKey(to=Entity)
class Meta:
unique_together = ("asset", "entity")
class Value(models.Model):
"""
Value of EAV
"""
value = models.CharField(max_length=216,
null=False, default='value',
name='value', verbose_name='Value of EAV'
)
asset = models.ForeignKey(to=Asset)
owner = models.ForeignKey(User, verbose_name='EAV Owner', related_name='eav')
class Meta:
unique_together = ('value', 'asset', 'owner')
Serializers
class EntitySerializer(serializers.Serializer):
id = serializers.IntegerField(label='ID', read_only=True)
entity = serializers.CharField(label='Entity of EAV', max_length=216, required=False)
class AssetSerializer(serializers.Serializer):
id = serializers.IntegerField(label='ID', read_only=True)
asset = serializers.CharField(default='asset', label='Asset of EAV', max_length=216)
entity = EntitySerializer(read_only=True)
class ValueSerializer(serializers.Serializer):
id = serializers.IntegerField(label='ID', read_only=True)
value = serializers.CharField(default='value', label='Value of EAV', max_length=216)
asset = AssetSerializer(read_only=True)
owner = UserModelSerializer(read_only=True)
class EntityAssetValueSerializer(serializers.Serializer):
entity = EntitySerializer(many=True)
asset = AssetSerializer(many=True)
value = ValueSerializer(many=True)
Expected Serialization
{
"entities": [
{
"entity": "Hero",
"id": 1,
"owner": {
"name": "BatMan",
"id": "1"
},
"groups": [
{
"id": "1",
"name": "SuperHeroes Group"
}
],
"asset": [
{
"asset": "Name",
"value": "BatMan",
"asset_id": 1,
"value_id": 1
},
{
"asset": "Age",
"value": "30",
"asset_id": 1,
"value_id": 2
}
]
},
{
"entity": "Hero",
"id": 1,
"owner": {
"name": "SuperMan",
"id": "2"
},
"groups": [
{
"id": "1",
"name": "SuperHeroes Group"
}
],
"asset": [
{
"asset": "Name",
"value": "SuperMan",
"asset_id": 1,
"value_id": 3
},
{
"asset": "Age",
"value": "30",
"asset_id": 1,
"value_id": 4
}
]
},
{
"entity": "Villian",
"id": 1,
"owner": {
"name": "Joker",
"id": "3"
},
"groups": [
{
"id": "2",
"name": "SuperVillians Group"
}
],
"asset": [
{
"asset": "Name",
"value": "Joker",
"asset_id": 3,
"value_id": 4
},
{
"asset": "Age",
"value": "30",
"asset_id": 4,
"value_id": 5
}
]
},
{
"entity": "Person",
"id": 1,
"owner": {
"name": "Puny Human",
"id": "3"
},
"groups": [
{
"id": "2",
"name": "Humans Group"
}
],
"asset": [
{
"asset": "Name",
"value": "Human Being",
"asset_id": 5,
"value_id": 6
},
{
"asset": "Age",
"value": "30",
"asset_id": 6,
"value_id": 7
}
]
}
]
}
Achieved Serialization
{
"eav": [
{
"id": 1,
"value": "Human",
"asset": {
"id": 1,
"asset": "Name",
"entity": {
"id": 1,
"entity": "Human"
}
},
"owner": {
"id": 1,
"username": "PunyHuman"
}
},
{
"id": 2,
"value": "26",
"asset": {
"id": 2,
"asset": "Age",
"entity": {
"id": 1,
"entity": "Human"
}
},
"owner": {
"id": 1,
"username": "PunyHuman"
}
},
{
"id": 3,
"value": "26",
"asset": {
"id": 3,
"asset": "Age",
"entity": {
"id": 2,
"entity": "Hero"
}
},
"owner": {
"id": 2,
"username": "BatMan"
}
},
{
"id": 4,
"value": "BatMan",
"asset": {
"id": 3,
"asset": "Name",
"entity": {
"id": 2,
"entity": "Hero"
}
},
"owner": {
"id": 2,
"username": "BatMan"
}
},
{
"id": 5,
"value": "26",
"asset": {
"id": 3,
"asset": "Age",
"entity": {
"id": 2,
"entity": "Hero"
}
},
"owner": {
"id": 3,
"username": "SuperMan"
}
},
{
"id": 6,
"value": "SuperMan",
"asset": {
"id": 4,
"asset": "Name",
"entity": {
"id": 2,
"entity": "Hero"
}
},
"owner": {
"id": 3,
"username": "SuperMan"
}
}
]
}
API View
class EntityAssetValueAPIView(APIView):
queryset = Value.objects.select_related('asset', 'asset__entity', 'owner')
serializer_class = ValueSerializer
# If you want to use object lookups other than pk, set 'lookup_field'.
# For more complex lookup requirements override `get_object()`.
lookup_field = 'pk'
# lookup_url_kwarg = None
# The filter backend classes to use for queryset filtering
# filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
# The style to use for queryset pagination.
# pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
# def allowed_methods(self):
# http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
# return http_method_names
def get(self, request, *args, **kwargs):
eav = self.queryset.all()
serializer = self.serializer_class(eav, many=True)
return Response(serializer.data)
What I want to do is, Get all the ENTITIES Assigned to a USER ( along with the ASSET & VALUES ).
The same data is posted for a NEW ENTITY ASSIGNMENT to USER .
From What I understood from DRF, I would need an API view, API view would call serializer, so, I would have to create a custom Serializer, then to save the data I have to override create method, where in I will have these separate serializers that would validate the data and save it.
I am not able to send the desired RESPONSE or ingest the REQUEST coming in.
What should be a way forward ?
I have faced the similar problem So I'll explain a little scenario here. So you can take reference from that.
Added some related names in models :
class Asset(models.Model):
"""
Asset of EAV
"""
asset = models.CharField(max_length=216, null=False,
default='asset', name='asset',
verbose_name='Asset of EAV'
)
entity = models.ForeignKey(to=Entity, related_name='asset_entity')
class Value(models.Model):
"""
Value of EAV
"""
value = models.CharField(max_length=216,
null=False, default='value',
name='value', verbose_name='Value of EAV'
)
asset = models.ForeignKey(to=Asset, related_name='asset_value')
owner = models.ForeignKey(User, verbose_name='EAV Owner', related_name='eav')
initial queryset looks like this, the idea is to fetch all information that is required initially:
queryset = Entity.objects.filter('**condition comes here**')
.values('id', 'entity', 'asset_entity', 'asset_entity__asset', 'asset_entity__asset_value', 'asset_entity__asset_value__value',
'asset_entity__asset_value__owner_id',)
Pass this queryset when trying to make response :
serializer = serializer(queryset, many=True, context={'request': request})
Serailizers :
class Owner_Serializer(serializers.ModelSerializer)
class Meta:
model = User
exclude = ('**exclude fields you want to exclude**', )
class EntitySerializer(serializers.Serializer):
id = serializers.IntegerField(source='id')
entity = serializers.CharField(source='entity')
owner = serializers.SerializerMethodField()
groups = serializers.SerializerMethodField()
asset = serializers.SerializerMethodField()
def get_owner(self, obj):
return Owner_Serializer(obj.get('asset_entity__asset_value__owner_id'), context=self.context).data
Same process goes for groups and asset fields.
in get_owner() we have entity object and from that object we can get owner_id , as we have already fetched related data initially.
So the main idea here to fetch all data initially and then serilaize that data according to your requirement.
Existing Nested Serailization do not support the response format you required.
Note : Initial querset is vry important , you may need to use prefetch related there because we are fetching data using reverse relationship. Also I didn't test the queryset so have to make sure that correct related_names are used to fetch related data.