Django JSONField complex query ... practical example of querying complex nested data structure - django

I have inherited the following JSONField data structure:
[
{
"name": "Firstname",
"show": {
"value": true
},
"type": "text",
"uuid": "55668e45-07d1-404e-bf65-f6a3cacfaa97",
"label": {
"for": "Firstname",
"display": "First name"
},
"value": "Michael",
"options": [],
"required": true,
"component": "Input",
"placeholder": "Input text here",
"validationErrors": []
},
{
"name": "Surname",
"show": {
"value": true
},
"type": "text",
"uuid": "ce91fefa-66e3-4b08-8f1a-64d95771aa49",
"label": {
"for": "Surname",
"display": "Surname"
},
"value": "Roberts",
"options": [],
"required": true,
"component": "Input",
"placeholder": "Input text here",
"validationErrors": []
},
{
"name": "EmailAddress",
"show": {
"value": true
},
"type": "email",
"uuid": "6012a805-da62-4cee-8656-b7565b5f8756",
"label": {
"for": "Email",
"display": "Email"
},
"value": "michael#hiyield.co.uk",
"options": [],
"required": true,
"component": "Input",
"placeholder": "Input text here",
"validationErrors": []
},
{
"name": "University",
"show": {
"value": true
},
"type": "text",
"uuid": "434e3781-ab8a-4f09-9c68-5ec35188f3c7",
"label": {
"for": "University",
"display": "University/College"
},
"value": "University College London",
"options": [],
"required": true,
"component": "Input",
"placeholder": "Input text here",
"validationErrors": []
},
{
"name": "Subscribe",
"show": {
"value": true
},
"type": "checkbox",
"uuid": "79bdc29e-6357-4175-bf65-07be60776a29",
"label": {
"for": "Subscribe",
"display": "Subscribe to the KEVRI mailing list"
},
"value": true,
"options": [],
"required": true,
"component": "Checkbox",
"description": "KEVRI is committed to respecting and protecting your privacy. The data collected here will create your personalised report which we can email to you after this review if you wish. We will not share personal data with anyone else or send you any further emails.",
"placeholder": "",
"validationErrors": []
}
]
which exists on the models.JSONField called "about" for "MyModel", as follows:
class MyModel(
AbstractTimestampedModel
):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
about = models.JSONField()
I was wondering, how do I filter MyModel where the field within about is called by the "name": "EmailAddress" ... then query for that particular fields "value"?
Essentially, for the queryset MyModel.objects.all().filter() ... I want to filter out all the values where the EmailAddress is equal to some value ...
I'm not sure this is achievable within the Django ORM. However, there might be someone who could advise ...

If i am correct you should use jsonb_to_recordset PostgreSQL function.
Firstly you should create a custom database function since there is no function for that in Django Core.
class JSONRecordSet(Func):
template = "(SELECT id from %(function)s(%(expressions)s) as items(%(key)s %(output_type)s) where %(key)s='%(search)s')"
function = "jsonb_to_recordset"
def __init__(self, expression, key, output_type, search):
super().__init__(expression, key=key, output_type=output_type, search=search)
Please be aware of SQL injection.
After that, you can use this function with annotate.
MyModel.objects.annotate(_id=JSONRecordSet(expression="about", key="EmailAddress", output_type="text", search="foo#bar.com")).filter(id=F("_id"))
Return all MyModel instance which has "foo#bar.com" value in EmailAddress Key.

Try this approach:
MyModel.objects.filter(about__name='EmailAddress')
It might return the result you want.
Also, have a look at this link. It also describes how to query into nested dictionary using JSONField:
https://docs.djangoproject.com/en/3.2/topics/db/queries/#key-index-and-path-transforms

Related

LoopBackJs REST API Create response not returning full model, only form data

when I POST to api/testmodel using an object with only the required fields, the object is being created correctly in the DB. However, I only get the object I sent in the request body. I'm trying to get the full object with null fields in the response.
Thanks for the help!
{
"name": "test",
"plural": "test",
"base": "PersistedModel",
"idInjection": true,
"replaceOnPUT": false,
"properties": {
"city": {
"type": "string",
"length": 100
},
"name": {
"type": "string",
"required": true,
"length": 100
},
"id": {
"type": "string",
"id": true,
"required": true,
},
"officePhone": {
"type": "string",
"length": 100
},
"status": {
"type": "string",
"required": false,
"length": 200
},
"street": {
"type": "string",
"length": 100
}
},
"methods": {}`
Then you need to create default values for your model, for example city:
"properties": {
"city": {
"type": "string",
"length": 100,
"default": ""
},
...
In your controller, after you have created your new record and have the record ID, perform a findById query and return that object instead of the object returned from create. This should give you a response similar to a GET route.

correct way to define Loopback.io belongsTo relationship

I have a simple has_many-belongst_to relationship between 2 Loopback models:
{
"name": "ApiUser",
"base": "PersistedModel",
"idInjection": true,
"options": {
"postgresql": {
"table": "users"
},
"validateUpsert": true
},
"properties": {
"id": {
"type": "string",
"id": true,
"required": true,
"defaultFn": "uuid",
"postgresql": {
"dataType": "uuid"
}
},
"email": {
...
and
{
"name": "ShopifyAccount",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"api_key": {
"type": "string",
"required": true
},
"password": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {
"orders": {
"type": "hasMany",
"model": "ShopifyOrder",
"foreignKey": ""
},
"user": {
"type": "belongsTo",
"model": "ApiUser",
"foreignKey": "userid"
}
},
"acls": [],
"methods": {}
}
When I run automigration, the shopifyaccount table is created, but it looks weird:
Column | Type | Modifiers
-------------------+---------+-------------------------------------------------------------
api_key | text | not null
password | text | not null
id | integer | not null default nextval('shopifyaccount_id_seq'::regclass)
userid | uuid |
apiuserid | uuid |
Indexes:
"shopifyaccount_pkey" PRIMARY KEY, btree (id)
Why did it create 2 columns named like this? Even if I try to specify that foreignkey is "userid", it will still create the apiuserid column. The insert will never update the apiuserid column but it will update the userid column. And then at join time, it will try to join on apiuserid. What am I doing wrong?
So... it seems that the "foreignkey" needs to be specified in both places, in ApiUser and in ShopifyAccount:
{
"name": "ApiUser",
"base": "PersistedModel",
"idInjection": true,
"options": {
"postgresql": {
"table": "users"
},
"validateUpsert": true
},
"properties": {
"id": {
"type": "string",
"id": true,
"required": true,
"defaultFn": "uuid",
"postgresql": {
"dataType": "uuid"
}
},
"email": {
"type": "string",
...
"relations": {
"shopifyAccounts": {
"type": "hasOne",
"model": "ShopifyAccount",
"foreignKey": "userid"
}
},
"acls": [],
"methods": {}
}

Loopback: How to define a property with an array of strings in Loopback?

I have the following model in a loopback application, that will be persisted in a MongoDB:
Model
Name Coffeshop:
Id
Name (string)
City (String)
Question:
Now i want to be able to store a list of strings in a new property called "tags":
Tags (Array of string)
There is no relation to other models necessary. I need just a plain flat list of strings.
How can i achieve this?
Code:
{
"name": "CoffeeShop",
"plural": "CoffeeShops",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": true
},
"city": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}
Thats easy:
{
"name": "CoffeeShop",
"plural": "CoffeeShops",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": true
},
"city": {
"type": "string",
"required": true
},
"tags": {
"type": [
"string"
],
"required": false
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}

scope in inherited model

I have a contact db table and model. Employee model inherits from contact.
If i do GET employees/ it returns all the contacts.
How should I set up my employee.json if I want to return only the contacts with partnerId = 1?
{
"name": "employee",
"base": "contact",
"strict": false,
"idInjection": false,
"options": {
"validateUpsert": true,
"postgresql": {
"schema": "public",
"table": "contact"
}
},
"scope": {
"where": {
"partnerId": 1
}
},
//...
}
Debug says calling GET employees/ makes the following query:
SELECT "name", "position", "email", "password", "id" FROM "public"."contact" ORDER BY "id"
It does not seem that scope is added.
models/partner.json
{
"name": "partner",
// ...
"properties": {
"name": {
"type": "string",
"required": true
},
// ...
},
"validations": [],
"relations": {
"contacts": {
"type": "hasMany",
"model": "contact"
}
//...
},
"acls": [],
"methods": {}
}
Try using the where filter, either in the REST API
/employees?filter[where][partnerId]=1
or in your Employee.js
Employee.find({ where: {partnerId:1} });
https://docs.strongloop.com/display/APIC/Where+filter

Createmany in Strongloop Loopback

I have an Order model which hasMany OrderItem models. But once a client wants to create an Order, it has to create an Order object first then for each product he added to his basket, he needs to create responding OrderItems separately. As you may notice it causes many reduntant requests. May be I can make a custom method for OrderItems which consumes a product list. But i was wondering if there is a built in mechanism for this like createMany since it is a very useful operation.
ORDER MODEL
{
"name": "Order",
"plural": "Orders",
"base": "PersistedModel",
"idInjection": true,
"properties": {
"customerId": {
"type": "number",
"required": true
},
"branchId": {
"type": "number",
"required": true
}
},
"validations": [],
"relations": {
"orderItems": {
"type": "hasMany",
"model": "OrderItem",
"foreignKey": "orderId"
}
},
"acls": [],
"methods": []
}
ORDERITEM MODEL
{
"name": "OrderItem",
"plural": "OrderItems",
"base": "PersistedModel",
"idInjection": true,
"properties": {
"UnitPrice": {
"type": "number"
},
"productId": {
"type": "number",
"required": true
},
"purchaseOrderId": {
"type": "number",
"required": true
},
"quantity": {
"type": "number"
}
},
"validations": [],
"relations": {
"product": {
"type": "belongsTo",
"model": "Product",
"foreignKey": "productId"
},
"purchaseOrder": {
"type": "belongsTo",
"model": "PurchaseOrder",
"foreignKey": ""
}
},
"acls": [],
"methods": []
}
Loopback "create" method accepts also an array of objects (see PersistedModel.create docs) so you should try creating one "create" call and send an array of OrderItems.