DRF: how to join many-to-many field into a string in json - react-native

models.py
class SalesPerson(models.Model):
name = models.CharField(max_length=50)
office = models.ForeignKey(Location, on_delete=models.SET_NULL, null=True)
class Project(models.Model):
title = models.CharField(max_length=50)
leader = models.ForeignKey(SalesPerson, on_delete=models.SET_NULL, null=True,related_name='leader')
location = models.ForeignKey(Location, on_delete=models.SET_NULL, null=True)
product = models.ManyToManyField(Product)
sales_person = models.ManyToManyField(SalesPerson)
serializers.py
class ProjectSerializer(serializers.ModelSerializer):
leader_name= serializers.ReadOnlyField(source='leader.name')
location_name= serializers.ReadOnlyField(source='location.name')
product = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
sales_person = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
class Meta:
model = Project
fields = ('id', 'title', 'leader_name', 'location_name', 'product', 'sales_person')
class SPSerializer(serializers.ModelSerializer):
projects = ProjectSerializer(many=True, read_only=True, source='project_set')
office_name= serializers.ReadOnlyField(source='office.city')
class Meta:
model = SalesPerson
fields = ('id', 'name', 'office_name', 'email', 'projects')
result:
{
"id": 2,
"name": "Angela",
"office_name": "NSW Sydney",
"email": "angela#angela.com",
"projects": [
{
"id": 1,
"title": "Mall Orchid",
"leader_name": "Boris",
"product": [
"Split wall mounted"
],
"sales_person": [
"Angela",
"Boris",
"David"
],
},
{
"id": 6,
"title": "Mall Petunia",
"leader_name": "Boris",
"product": [
"Split duct"
],
"sales_person": [
"Angela",
"Boris",
"David"
],
},
]
},
I am going to consume the json using react native
I know how to iterate over "projects"
However, I want to avoid iterating over "sales_person" to make rendering the array simpler
So I am sure I have to make the sales_person into a string but after googling for many hours today, I can't find an answer
I am hoping to do this in Django Rest Framework and not in Expo React Native, if possible
So in short, I want to make the result like this:
{
"id": 2,
"name": "Angela",
"office_name": "NSW Sydney",
"email": "angela#angela.com",
"projects": [
{
"id": 1,
"title": "Mall Orchid",
"leader_name": "Boris",
"product": [
"Split wall mounted"
],
"sales_person": "Angela", "Boris", "David",
},
{
"id": 6,
"title": "Mall Petunia",
"leader_name": "Boris",
"product": [
"Split duct"
],
"sales_person": "Angela", "Boris", "David",
},
]
},
Thank you so much for your help.

I've found the answer
I am writing it for my own future reference
class ProjectSerializer(serializers.ModelSerializer):
leader_name= serializers.ReadOnlyField(source='leader.name')
location_name= serializers.ReadOnlyField(source='location.city')
product = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
sales_person = serializers.SerializerMethodField('get_sales_person')
class Meta:
model = Project
fields = ('id', 'title', 'leader_name', 'location_name', 'product', 'sales_person')
def get_sales_person(self, obj):
return ', '.join([sales_person.name for sales_person in obj.sales_person.all()])

Related

Couchbase query filtering by tag in an array

I have a author bucket. And in this bucket I keep the author infos and author's articles. I want to select the articles that have the tags I want from the author bucket.
I have tried this but I could not find how to do the filtering.
SELECT art.* FROM author AS a
UNNEST a.articles AS art
WHERE art.tags = 'History'
This is author bucket:
{
"about": {
"name": "sassa",
"userName": "sassatur"
},
"articles": [
{
"authorId": [
"8c7ba33e-0674-4d99-bfad-29d144028bc9"
],
"claps": [],
"comments": [],
"content": {
"articleType": "HTML",
"data": "My First Article"
},
"id": "71d6fa22-61be-4a93-8e86-8d569080da97",
"publishStatus": "UNLISTED",
"statistic": {
"articleId": "71d6fa22-61be-4a93-8e86-8d569080da97",
"views": [
1602683127039,
1602683148270
]
},
"tags": [
"Art, History"
],
"title": "Culture"
},
{
"authorId": [
"8c7ba33e-0674-4d99-bfad-29d144028bc9"
],
"claps": [],
"comments": [],
"content": {
"articleType": "HTML",
"data": "My First Article"
},
"id": "81d6fa22-63be-4a93-8e86-8d569080da97",
"publishStatus": "UNLISTED",
"statistic": {
"views": [
1602683127039,
1602683148270
]
},
"tags": [
"Art"
],
"title": "Culture"
}
],
"id": "8c7ba33e-0674-4d99-bfad-29d144028bc9",
}
Try using ANY/IN/SATISFIES, like so:
SELECT art.* FROM author AS a
UNNEST a.articles AS art
WHERE ANY x IN art.tags SATISFIES x == 'Art' END;
This works for 'Art' in your example, but not 'History' because of the way you are storing tags. It's an array, but it appears to have a single(?) item with comma-separated values. So, instead of "tags": ["Art,History"], I would recommend: "tags": ["Art","History"] instead, and then it will work.
However, if you are stuck with the comma-separate string, you can use SPLIT and ARRAY_CONTAINS as well:
SELECT art.* FROM author AS a
UNNEST a.articles AS art
WHERE ANY x IN art.tags SATISFIES ARRAY_CONTAINS(SPLIT(x,", "), 'History') END;

Serializing MPTT Tree with Django REST Framework

I searched for a similar question without success.. So, i am working on a website in which i am using django-mptt to organize categories. My Model looks like:
class Category(MPTTModel):
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, related_name='children')
name = models.CharField(max_length=90)
slug = models.SlugField(unique=True)
_full_slug_separator = '/'
#property
def url(self):
names = [category.name for category in self.get_ancestors(include_self=True)]
return self._full_slug_separator.join(names)
I defined the CategorySerializer as bellow:
class CategorySerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ('name', 'url', 'children')
def get_children(self, obj):
return CategorySerializer(obj.get_children(), many=True).data
# views.py
class CategoryList(generics.ListAPIView):
queryset = Category.objects.root_nodes()
serializer_class = CategorySerializer
The question is how can i:
1. have the 'url' data included in leaf nodes only.
2. have the 'children' data included in non leaf nodes only.
Here is an example of the output I am looking for
[
{
"title":"root node",
"children":[
{
"title":"leaf node",
"url":"link"
},
{
"title":"non leaf node",
"children":[
{
"title":"leaf node",
"url":"link"
}
]
},
{
"title":"non leaf node",
"children":[
{
"title":"non leaf node",
"children":[
{
"title":"leaf node",
"url":"link"
}
]
}
}
]
},
{
"title":"root node",
"url":"link"
}
]
Also i want to know if there is a good way for generating the 'url' to reduce queries
And thanks for any help in advance.
I ran into the same problem and found this question. But there were no answers. So, I am posting how I managed to do it for anyone who may need it in the future. This is most likely not the best solution but it works.
My models.py:
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel, TreeForeignKey
class Category(MPTTModel):
name = models.CharField(max_length=100, null=False, blank=False, verbose_name=_("category name"), help_text=_("format: required, max_length=100"))
slug = models.SlugField(max_length=150, null=False, blank=False, editable=False, verbose_name=_("category url"), help_text=_("format: required, letters, numbers, underscore or hyphen"))
parent = TreeForeignKey("self", on_delete=models.SET_NULL, related_name="children", null=True, blank=True, verbose_name=_("parent category"), help_text=_("format: not required"))
class MPTTMeta:
order_insertion_by = ['name']
class Meta:
verbose_name = _('product category')
verbose_name_plural = _('product categories')
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
def __str__(self):
return self.name
My serializers.py:
from rest_framework import serializers
class SubCategorySerializer(serializers.ModelSerializer):
"""Serializer for lowest level category that has no children."""
parent = serializers.SerializerMethodField(source='get_parent')
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'parent', ]
def get_parent(self, obj):
if obj.parent:
return obj.parent.name
class CategorySerializer(serializers.ModelSerializer):
"""Serializer for category."""
parent = serializers.SerializerMethodField(source='get_parent')
children = serializers.SerializerMethodField(source='get_children')
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'parent', 'children', ]
def get_parent(self, obj):
if obj.parent:
return obj.parent.name
def get_children(self, obj):
if obj.children.exists():
children = [child for child in obj.children.all()]
children_with_children = [child for child in children if child.children.exists()]
children_without_children = [child for child in children if not child.children.exists()]
if children_with_children:
return CategorySerializer(children_with_children, many=True).data
if children_without_children:
return SubCategorySerializer(children_without_children, many=True).data
This way children is included in non-leaf nodes only.
Here is an example of how the data looks:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": 4,
"name": "Clothes",
"slug": "clothes",
"parent": null,
"children": [
{
"id": 5,
"name": "Men's Clothes",
"slug": "mens-clothes",
"parent": "Clothes",
"children": [
{
"id": 7,
"name": "Men's Jeans",
"slug": "mens-jeans",
"parent": "Men's Clothes"
}
]
},
{
"id": 6,
"name": "Women's Clothes",
"slug": "womens-clothes",
"parent": "Clothes",
"children": [
{
"id": 8,
"name": "Women's Jeans",
"slug": "womens-jeans",
"parent": "Women's Clothes"
}
]
}
]
},
{
"id": 1,
"name": "Technology",
"slug": "technology",
"parent": null,
"children": [
{
"id": 3,
"name": "Laptop",
"slug": "laptop",
"parent": "Technology"
},
{
"id": 2,
"name": "Mobile Phone",
"slug": "mobile-phone",
"parent": "Technology"
}
]
}
]
}

find object in nested array with lodash

I have json data similar to this:
{
"Sections": [
{
"Categories": [
{
"Name": "Book",
"Id": 1,
"Options": [
{
"Name": "AAAA",
"OptionId": 111
},
"Selected": 0
},
{
"Name": "Car",
"Id": 2,
"Options": [
{
"Name": "BBB",
"OptionId": 222
},
"Selected": 0
},
],
"SectionName": "Main"
},
... more sections like the one above
]
}
Given this data, I want to find a category inside a section based on its (Category) Id, and set its selected option, I tried this, but couldn't get it to work....Note Category Id will be unique in the whole data set.
_.find(model.Sections, { Categories: [ { Id: catId } ]});
According to your data model, it looks like you're trying to find an element that is inside a matrix: Sections can have multiple Categories and a Category can have multiple types (car, book...).
I'm afraid there isn't a function in lodash that allows a deep find, you'll have to implement it the 'traditional' way (a couple of fors).
I provide this solution that is a bit more 'functional flavoured' than the traditional nested fors. It also takes advantage of the fact that when you explicitly return false inside a forEach, the loop finishes. Thus, once an element with the provided id is found, the loop is ended and the element returned (if it's not found, undefined is returned instead).
Hope it helps.
const findCategoryById = (sections, id) => {
var category;
_.forEach(sections, (section) => {
category = _.find(section.Categories, ['Id', id]);
return _.isUndefined(category);
});
return category;
};
const ex = {
"Sections": [{
"Categories": [{
"Name": "Book",
"Id": 1,
"Options": [{
"Name": "AAAA",
"OptionId": 111
}],
"Selected": 0
},
{
"Name": "Car",
"Id": 2,
"Options": [{
"Name": "BBB",
"OptionId": 222
}],
"Selected": 0
}
],
"SectionName": "Main"
}]
};
console.log(findCategoryById(ex.Sections, 2));
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>

Return SQL Server database query with nested Json

I am trying to get this kind of answer when I consume my endpoint :
[
{
"McqID":"7EED5396-9151-4E3D-BCBF-FDB72CDD22B7",
"Questions":[
{
"QuestionId":"C8440686-531D-4099-89E9-014CAF9ED054",
"Question":"human text",
"Difficulty":3,
"Answers":[
{
"AnswerId":"7530DCF4-B2D9-48B0-9978-0E4690EA0C34",
"Answer":"human text2",
"IsTrue":false
},
{
"AnswerId":"5D16F17F-E205-42A5-873A-1A367924C182",
"Answer":"human text3",
"IsTrue":false
},
{
"AnswerId":"64E78326-77C3-4628-B9E3-2E8614D63632",
"Answer":"human text4",
"IsTrue":false
},
{
"AnswerId":"199241A9-0EF6-4F96-894A-9256B129CB1F",
"Answer":"human text5",
"IsTrue":true
},
{
"AnswerId":"EDCCAC18-5209-4457-95F2-C91666F8A916",
"Answer":"human text6",
"IsTrue":false
}
]
}
]
}
]
Here's my query (example) :
SELECT
Questions.QcmID AS QcmID,
(SELECT
Questions.id AS QuestionId,
Questions.Intitule AS Question,
Questions.Difficulte AS Difficulty,
(SELECT
Reponses.id AS AnswerId,
Reponses.Libelle AS Answer,
Reponses.IsTrue AS IsTrue
FROM
Reponses
WHERE
Reponses.QuestionID = Questions.id
FOR JSON PATH) AS Answers
FROM
Questions
WHERE
Questions.QcmID = '7EED5396-9151-4E3D-BCBF-FDB72CDD22B7'
FOR JSON PATH) AS Questions
FROM
Questions
WHERE
Questions.QcmID = '7EED5396-9151-4E3D-BCBF-FDB72CDD22B7'
FOR JSON PATH
I want a nested JSON representing my data, but it ends up being formatted like (smaller example) :
[
{
"JSON_F52E2B61-18A1-11d1-B105-00805F49916B":"[{\"QcmID\":\"7EED5396-9151-4E3D-BCBF-FDB72CDD22B7\"}]"
}
]
I've tried everything, FOR JSON PATH, FOR JSON AUTO, JSON_QUERY, etc...
Nothing works. FOR JSON PATH doesn't seem to work with multiple nested collections.
How do I get this result ?
You need to use JOINs as you would normally.
Using FOR JSON AUTO will pick the JOIN alias and if you want more control use the FOR JSON PATH.
I'm going to give you a generic example that will be easy to map to your scenario:
Option 1 - FOR JSON AUTO:
The JOIN alias will be used as the nested collection property name.
SELECT
ent.Id AS 'Id',
ent.Name AS 'Name',
ent.Age AS 'Age',
Emails.Id AS 'Id',
Emails.Email AS 'Email'
FROM Entities ent
LEFT JOIN EntitiesEmails Emails ON Emails.EntityId = ent.Id
FOR JSON AUTO
Option 2 - FOR JSON PATH:
You handle everything and note that the inner select must return a string, here also using FOR JSON PATH.
SELECT
ent.Id AS 'Id',
ent.Name AS 'Name',
ent.Age AS 'Age',
EMails = (
SELECT
Emails.Id AS 'Id',
Emails.Email AS 'Email'
FROM EntitiesEmails Emails WHERE Emails.EntityId = ent.Id
FOR JSON PATH
)
FROM Entities ent
FOR JSON PATH
Both generate the same result:
[{
"Id": 1,
"Name": "Alex",
"Age": 35,
"Emails": [{
"Id": 1,
"Email": "abc#domain.com"
}, {
"Id": 2,
"Email": "def#domain.com"
}, {
"Id": 3,
"Email": "ghi#domain.net"
}]
}, {
"Id": 2,
"Name": "Another Ale",
"Age": 40,
"Emails": [{
"Id": 4,
"Email": "user#skdfh.com"
}, {
"Id": 5,
"Email": "asldkj#als09q834.net"
}]
}, {
"Id": 3,
"Name": "John Doe",
"Age": 33,
"Emails": [{
"Id": 6,
"Email": "ooaoasdjj#ksjsk0913.org"
}]
}, {
"Id": 4,
"Name": "Mario",
"Age": 54,
"Emails": [{}]
}]
Cheers!

Transform JSON response with lodash

I'm new in lodash (v3.10.1), and having a hard time understanding.
Hope someone can help.
I have an input something like this:
{
{"id":1,"name":"Matthew","company":{"id":1,"name":"abc","industry":{"id":5,"name":"Medical"}}},
{"id":2,"name":"Mark","company":{"id":1,"name":"abc","industry":{"id":5,"name":"Medical"}}},
{"id":3,"name":"Luke","company":{"id":1,"name":"abc","industry":{"id":5,"name":"Medical"}}},
{"id":4,"name":"John","company":{"id":1,"name":"abc","industry":{"id":5,"name":"Medical"}}},
{"id":5,"name":"Paul","company":{"id":1,"name":"abc","industry":{"id":5,"name":"Medical"}}}
];
I would like to output this or close to this:
{
"industries": [
{
"industry":{
"id":5,
"name":"Medical",
"companies": [
{
"company":{
"id":1,
"name":"abc",
"employees": [
{"id":1,"name":"Matthew"},
{"id":2,"name":"Mark"},
{"id":3,"name":"Luke"},
{"id":4,"name":"John"},
{"id":5,"name":"Paul"}
]
}
}
]
}
}
]
}
Here's something that gets you close to what you want. I structured the output to be an object instead of an array. You don't need the industries or industry properties in your example output. The output structure looks like this:
{
"industry name": {
"id": "id of industry",
"companies": [
{
"company name": "name of company",
"id": "id of company",
"employees": [
{
"id": "id of company",
"name": "name of employee"
}
]
}
]
}
}
I use the _.chain function to wrap the collection with a lodash wrapper object. This enables me to explicitly chain lodash functions.
From there, I use the _.groupBy function to group elements of the collection by their industry name. Since I'm chaining, I don't have to pass in the array again to the function. It's implicitly passed via the lodash wrapper. The second argument of the _.groupBy is the path to the value I want to group elements by. In this case, it's the path to the industry name: company.industry.name. _.groupBy returns an object with each employee grouped by their industry (industries are keys for this object).
I then do use _.transform to transform each industry object. _.transform is essentially _.reduce except that the results returned from the _.transform function is always an object.
The function passed to the _.transform function gets executed against each key/value pair in the object. In the function, I use _.groupBy again to group employees by company. Based off the results of _.groupBy, I map the values to the final structure I want for each employee object.
I then call the _.value function because I want to unwrap the output collection from the lodash wrapper object.
I hope this made sense. If it doesn't, I highly recommend reading Lo-Dash Essentials. After reading the book, I finally got why lodash is so useful.
"use strict";
var _ = require('lodash');
var emps = [
{ "id": 1, "name": "Matthew", "company": { "id": 1, "name": "abc", "industry": { "id": 5, "name": "Medical" } } },
{ "id": 2, "name": "Mark", "company": { "id": 1, "name": "abc", "industry": { "id": 5, "name": "Medical" } } },
{ "id": 3, "name": "Luke", "company": { "id": 1, "name": "abc", "industry": { "id": 5, "name": "Medical" } } },
{ "id": 4, "name": "John", "company": { "id": 1, "name": "abc", "industry": { "id": 5, "name": "Medical" } } },
{ "id": 5, "name": "Paul", "company": { "id": 1, "name": "abc", "industry": { "id": 5, "name": "Medical" } } }
];
var result = _.chain(emps)
.groupBy("company.industry.name")
.transform(function(result, employees, industry) {
result[industry] = {};
result[industry].id = _.get(employees[0], "company.industry.id");
result[ industry ][ 'companies' ] = _.map(_.groupBy(employees, "company.name"), function( employees, company ) {
return {
company: company,
id: _.get(employees[ 0 ], 'company.id'),
employees: _.map(employees, _.partialRight(_.pick, [ 'id', 'name' ]))
};
});
return result;
})
.value();
Results from your example are as follows:
{
"Medical": {
"id": 5,
"companies": [
{
"company": "abc",
"id": 1,
"employees": [
{
"id": 1,
"name": "Matthew"
},
{
"id": 2,
"name": "Mark"
},
{
"id": 3,
"name": "Luke"
},
{
"id": 4,
"name": "John"
},
{
"id": 5,
"name": "Paul"
}
]
}
]
}
}
If you ever wanted the exact same structure as in the questions, I solved it using the jsonata library:
(
/* lets flatten it out for ease of accessing the properties*/
$step1 := $ ~> | $ |
{
"employee_id": id,
"employee_name": name,
"company_id": company.id,
"company_name": company.name,
"industry_id": company.industry.id,
"industry_name": company.industry.name
},
["company", "id", "name"] |;
/* now the magic begins*/
$step2 := {
"industries":
[($step1{
"industry" & $string(industry_id): ${
"id": $distinct(industry_id)#$I,
"name": $distinct(industry_name),
"companies": [({
"company" & $string(company_id): {
"id": $distinct(company_id),
"name": $distinct(company_name),
"employees": [$.{
"id": $distinct(employee_id),
"name": $distinct(employee_name)
}]
}
} ~> $each(function($v){ {"company": $v} }))]
}
} ~> $each(function($v){ {"industry": $v} }))]
};
)
You can see it in action on the live demo site: https://try.jsonata.org/VvW4uTRz_