Setting a foreign key as a nullable key in model file is not working in loopback 4 - loopbackjs

Describe the bug
I want to set up a field that is defined as a foreign key, but also supports the ability to be null. In the model file, I have this already
#belongsTo(() => Bill, { keyTo: 'BillID', name: 'Bill' })
BillID: Number;
But what I want to do is something like this
#property({
type: Number,
required: false,
jsonSchema: { nullable: true },
mssql: { "columnName": "BillID", "dataType": "int", "dataPrecision": null, "dataScale": null, "nullable": "YES" },
})
#belongsTo(() => Bill, { keyTo: 'BillID', name: 'Bill' })
BillID: Number;
but that does not work. How can I set this up?

Related

How to filter nested list of list of objects with JSON field in django

I have a Model named Ad and it has a JSONfield named location_details.
while fetching the list of objects from Ad Model, I am applying filter for the location_details field
http://127.0.0.1:8000/ad-list/?limit=10&offset=0&ordering=-start_date&location_category=bus_station%2Chospital
While creating an ad, I compute the nearby places and store it as a JSON field as shown below
[ { name: "transit", label: "Transit", sub_categories: [ { name: "bus_station", label: "Bus Stop", values: [{name: 'A'}, {name: 'B'}], }, { name: "airport", label: "Airport", values: [], }, { name: "train_station", label: "Train Station", values: [], }, ], }, { name: "essentials", label: "Essentials", sub_categories: [ { name: "hospital", label: "Hospital", values: [{name: 'te'}], }, { name: "school", label: "Schools", values: [{name: 'B'}], }, { name: "atm", label: "ATMs", values: [], }, { name: "gas_station", label: "Gas Stations", values: [{name: 'C'}], }, { name: "university", label: "Universities", values: [], }, ], }, { name: "utility", label: "Utility", sub_categories: [ { name: "movie_theater", label: "Movie Theater", values: [], }, { name: "shopping_mall", label: "Shopping Mall", values: [], }, ], }, ];
I want to filter the ad list with multiple location_categories with the above JSON object, only if the location category has some data in it.
I don't know how to start with it, this is how my code looks like now
location_category = django_filters.CharFilter(method="filter_location_category")
def filter_location_category(self, queryset, name, value):
return queryset.filter(location_details__sub_categories__name__in=value.split(","))
For example, with the below request
http://127.0.0.1:8000/ad-list/?limit=10&offset=0&ordering=-start_date&location_category=bus_station%2Chospital
I want to get all the ads where bus_station and hospital have some data in it (inside the values key) and want to ignore it if the values list is empty in the JSON.
Any help would be highly appreciated. Thank you so much for your attention and participation.

Loopback setting a property that somehow doesn't persist to the database sql?

We're porting our api from C# to Loopback ^v3.19.0 and have run into a blocker.
Many of our models have shared properties, so we've created a base model "Base" which they inherit from.
{
"name": "Base",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"mixins": {
"Timestamp": {}
},
"properties": {
"created-by": {
"type": "number",
"postgresql": {
"columnName": "created_by"
}
},
"created-date": {
"type": "date",
"postgresql": {
"columnName": "created_on_utc"
}
},
"updated-by": {
"type": "number",
"postgresql": {
"columnName": "updated_by"
}
},
"updated-date": {
"type": "date",
"postgresql": {
"columnName": "updated_on_utc"
}
},
"soft-deleted": {
"type": "boolean",
"postgresql": {
"columnName": "is_deleted"
}
},
"deleted-by": {
"type": "number",
"postgresql": {
"columnName": "deleted_by"
}
},
"deleted-date": {
"type": "date",
"postgresql": {
"columnName": "deleted_on_utc"
}
},
"tenant-id": {
"type": "number",
"postgresql": {
"columnName": "tenant_id"
}
}
},
...
}
Inside the Timestamp mixin (our own), those properties get set accordingly
module.exports = function(Model, options) {
Model.observe('before save', function event(ctx, next) {
const token = ctx.options && ctx.options.accessToken;
const userId = token && token.userId;
const now = new Date().toISOString();
if (ctx.instance) {
ctx.instance['created-by'] = userId;
ctx.instance['created-date'] = now;
ctx.instance['updated-by'] = userId;
ctx.instance['updated-date'] = now;
} else {
if (ctx.data['soft-deleted'] &&
ctx.data['soft-deleted'] === true) {
ctx.data['deleted-by'] = userId;
ctx.data['deleted-date'] = now;
ctx.data['is-active'] = false;
}
ctx.data['updated-by'] = userId;
ctx.data['updated-date'] = now;
}
next();
});
};
This works great when creating a new model. It was working great for updates (PATCH /modelname/:id), but unexpectedly broke and we can't figure out why. (This is consistent across all the models that inherit from this Base model.)
The mixin correctly sees the model and adds the updated properties like so
LoopbackJS | ************* 'before save' ctx.data **************
LoopbackJS | { 'is-active': false,
LoopbackJS | 'updated-by': 1,
LoopbackJS | 'updated-date': '2018-08-16T17:57:23.660Z' }
LoopbackJS | ************* END 'before save' ctx.data **************
But when loopback executes the update SQL, it somehow omits/removes the value for updated-by? (2nd param should be 1, not null)
LoopbackJS | 2018-08-16T17:57:23.666Z loopback:connector:postgresql SQL: UPDATE "public"."asset_types" SET "is_active"=$1,"updated_by"=$2,"updated_on_utc"=$3::TIMESTAMP WITH TIME ZONE,"tenant_id"=$4 WHERE "id"=$5
LoopbackJS | Parameters: [false,null,"2018-08-16T17:57:23.660Z",1,5]
updated_by in Postgres is nullable, so that shouldn't generate an error... but Loopback is sending a stringified function?
LoopbackJS | 2018-08-16T18:04:12.522Z loopback:connector:postgresql error: invalid input syntax for integer: "function () { [native code] }"
LoopbackJS | at Connection.parseE (/home/src/back-end/node_modules/pg/lib/connection.js:553:11)
LoopbackJS | at Connection.parseMessage (/home/src/back-end/node_modules/pg/lib/connection.js:378:19)
LoopbackJS | at TLSSocket.<anonymous> (/home/src/back-end/node_modules/pg/lib/connection.js:119:22)
LoopbackJS | at emitOne (events.js:115:13)
LoopbackJS | at TLSSocket.emit (events.js:210:7)
LoopbackJS | at addChunk (_stream_readable.js:264:12)
LoopbackJS | at readableAddChunk (_stream_readable.js:251:11)
LoopbackJS | at TLSSocket.Readable.push (_stream_readable.js:209:10)
LoopbackJS | at TLSWrap.onread (net.js:587:20)
If we don't touch the updated_by column, the SQL is correct and updates.
Incidentally, if we soft-delete and the deleted_by column is in play, the same thing happens there.
Feels like I'm spinning in circles here and probably overlooking something basic. Any suggestions?
EDIT
So it appears that it's not limited to a mixin... when we remove it completely and manually set the k:v pair in the payload (ie 'created-by': 1) we still get the same error back from Postgres.
The root cause of this was due to incorrect relationships.
I created this as a gist, but pasting it here too in case it helps someone else.
It's a PostgreSQL best-practice to use lowercase names, using snakecase if you need to. ie, my_column_name.
Also, since I'm using a JSON API client, I've installed the excellent loopback-component-jsonapi to handle the de/serialization stuff... but that just added additional complexities.
JSON API calls for dasherized property names. When you start with something like my-property-name, Loopback or the PostgreSQL driver (doesn't really matter) collapses the dasherized property down to mypropertyname by default.
This is bad... especially when you have an existing schema you're working with.
It's worse when you're working with relationships, because Loopback also appends the id suffix by default, so now you have issues unless you happen to have a mypropertynameid column.
An example
Let's say we have a Customer model. I needed endpoints that are lowercase (and dasherized, where applicable), so just change the plural to match here.
{
"name": "Customer",
"plural": "customers",
"base": "PersistedModel",
...
}
Inside of options.postgresql, you can set a tableName. Loopback will use the name value by default, but remember PostgreSQL doesn't like CamelCase. You need to override this unless you use lowercase model names.
(It's a religious preference, but I like my tables to be plurals. Fight me.)
{
...
"options": {
"validateUpsert": true,
"postgresql": {
"tableName": "customers"
}
}
...
}
Back to the properties, use the postgresql.columnName property to map to the correct column name in the db. If it's not a dasherized property name (ie status) then you can ignore the postgresql.columnName bit.
{
...
"properties": {
"is-active": {
"type": "boolean",
"default": false,
"postgresql": {
"columnName": "is_active"
}
}
}
}
Relationships can be a headache.
Let's say our Customer has people who work there. To do a basic one-many relationship between the models...
{
...
"relations": {
"people": {
"type": "hasMany",
"model": "Person",
"foreignKey": "customer_id"
}
},
...
}
people is the name of the relationship element of the JSON API payload.
A "gotcha" here for me was the foreignKey property.
The Loopback docs say it's optional - and it is - but if you leave it out then it adds the id suffix to the name (people) and then looks for that column in your customers table. That wasn't highlighted very well, but it was clear enough.
This part wasn't clear => I originally thought the foreignKey value pointed to the property of the Person model, so I had the dasherized customer-id property here. That's incorrect. It's literally asking you for the database column name, which feels like a bit of an antipattern... In the properties you had to define a columnName if you wanted to refer to the db columns under the ORM.
Also, note that the foreignKey property is reused in relationships but it means different things to different type contexts. In a hasMany, it's asking "Which column there maps to the primary key here?"
Final Customer model:
{
"name": "Customer",
"plural": "customers",
"base": "PersistedModel",
"options": {
"validateUpsert": true,
"postgresql": {
"tableName": "customers"
}
},
"properties": {
"name": {
"type": "string"
},
"is-active": {
"type": "boolean",
"default": false,
"postgresql": {
"columnName": "is_active"
}
}
},
"validations": [],
"relations": {
"people": {
"type": "hasMany",
"model": "Person",
"foreignKey": "customer_id"
}
},
"acls": [],
"methods": {}
}
The Person model on the other end of the relationship.
The foreignKey for a belongsTo relationship is asking the opposite question... "Which property here maps to the primary key there?"
Also, if you have properties you don't want exposed (especially if you've inherited a model and don't want/need all those properties for whatever reason) then you can hide them with the hidden element. See below.
{
"name": "Person",
"plural": "people",
"base": "User",
"idInjection": false,
"options": {
"validateUpsert": true,
"postgresql": {
"tableName": "people"
}
},
"hidden": [
"emailVerified",
"realm",
"username",
],
"properties": {
"first-name": {
"type": "string",
"postgresql": {
"columnName": "first_name"
}
},
"last-name": {
"type": "string",
"postgresql": {
"columnName": "last_name"
}
},
"email": {
"type": "string"
},
...
},
"validations": [],
"relations": {
"customer": {
"type": "belongsTo",
"model": "Customer",
"foreignKey": "customer_id"
}
},
"acls": [],
"methods": {}
}

Strongloop UUID

heres a snippet of the model I've created. I would like the id property to be ALWAYS automatically generated via the uuid default function. However, when even added to hidden the id property could be decide by the user if they pass that into the post create method.
Other than using the method hooks OR operation hooks, to always overwrite the value is there another Loopback approach or flag (I have tried the foreId property as well) to ensure that the id is always a uuid even when the user provides a value? If not then for my case, "defaultFn": "uuid" completely pointless, if I'm always overwriting it.
{
"name": "Account",
"base": "PersistedModel",
"strict": true,
"idInjection": false,
"options": {
"validateUpsert": true
},
"properties": {
"id": {
"id": true,
"required": true,
"type": "string",
"defaultFn": "uuid"
},
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}
For some reason, the id can be redefined by the user through the REST api and is not protected when using idInjection: false and defining a custom id property.
I'll open a ticket on github if it's not already done, in the meantime you can fix it easily using a before save operation hook
In Account.js, using the uuid package
const uuidV4 = require('uuid/v4');
module.exports = function(Account) {
Account.observe('before save', function(ctx, cb){
if (ctx.instance) {
ctx.instance.id = uuidV4();
} else {
ctx.data.id = uuidV4();
}
cb();
});
};

How to embed model in model (nest records) using ember-data?

I am trying to setup embedded records in my project using Ember-cli.
I can't get it working, I tried different configurations with {embedded: "always", etc...} but all I get is: Error: Assertion Failed: You must include an id for user in an object passed to push Please help.
I'm using
DEBUG: -------------------------------
DEBUG: Ember : 1.12.1
DEBUG: Ember Data : 1.13.4
DEBUG: jQuery : 2.1.4
DEBUG: Ember Simple Auth : 0.8.0
DEBUG: -------------------------------
My JSON with QuestionDefinitions is:
{
"data": [
{
"created": 1439824135440,
"updated": 1439824135440,
"userID": 20,
"user": {
"password": null,
"created": null,
"updated": null,
"photoID": null,
"photo": null,
"email": "super#duper.com",
"emailConfirmed": false,
"phoneNumber": null,
"phoneNumberConfirmed": false,
"accessFailedCount": 0,
"id": 20,
"userName": "qwerty"
},
"addCategoriesIDs": [],
"removeCategoriesIDs": [],
"recommendations": [],
"removeRecommendstionIDs": [],
"patternAnswers": [],
"removePatternAnswerIDs": [],
"hint": null,
"version": 1,
"commonVersion": 2,
"id": 1,
"questionText": "Test?",
"weight": 0,
"answerType": 0,
"status": 0,
"estimatedTime": null,
"private": false
},
{
"created": 1439824143340,
"updated": 1439824143340,
"userID": 20,
"user": {
"password": null,
"created": null,
"updated": null,
"photoID": null,
"photo": null,
"email": "super#duper.com",
"emailConfirmed": false,
"phoneNumber": null,
"phoneNumberConfirmed": false,
"accessFailedCount": 0,
"id": 20,
"userName": "qwerty"
},
"addCategoriesIDs": [],
"removeCategoriesIDs": [],
"recommendations": [],
"removeRecommendstionIDs": [],
"patternAnswers": [],
"removePatternAnswerIDs": [],
"hint": null,
"version": 1,
"commonVersion": 3,
"id": 2,
"questionText": "Test?",
"weight": 0,
"answerType": 0,
"status": 0,
"estimatedTime": null,
"private": false
}
]
}
QuestionDefinition model is:
//app/models/questiondefinition.js
import Ember from 'ember';
import DS from "ember-data";
export default DS.Model.extend({
//id : DS.attr('string'), //sie nie uzywa
created : DS.attr('pl-date'),
updated : DS.attr('pl-date'),
userID : DS.attr('number'),
user : DS.belongsTo('user',{async: false, embedded: 'always'}),
//hint : DS.attr('string'),
hint : null,
version : DS.attr('number'),
commonVersion : DS.attr('number'),
questionText : DS.attr('string'),
weight : DS.attr('number'),
answerType : 0,
status : 0,
estimatedTime : DS.attr('number'),
"private" : DS.attr('boolean'),
questionDefLegalBasis: function () {
return this.get('questionText').length % 2 > 0;
}.property('questionText'),
/**
* One-to-many
*/
patternAnswers : DS.hasMany('patternanswer'),
recommendations: DS.hasMany('recommendation'),
categories : DS.hasMany('questiondefinitioncategory', {async: true}),
comments : DS.hasMany('questiondefinitioncomment', {async: true})
});
User model is:
//app/models/user.js
import Ember from 'ember';
import DS from "ember-data";
export default DS.Model.extend({
"password": DS.attr('string'),
"created": DS.attr('pl-date'),
"updated": DS.attr('pl-date'),
"photoID": DS.attr('number'),
"photo": DS.attr('string'),
"email": DS.attr('string'),
"emailConfirmed": DS.attr('boolean'),
"phoneNumber": DS.attr('string'),
"phoneNumberConfirmed": DS.attr('boolean'),
"accessFailedCount": DS.attr('number'),
"userName": DS.attr('string'),
/**
* One-to-many
*/
//questionDefinitions : DS.hasMany('questiondefinition'),
questionDefinitionComments : DS.hasMany('questiondefinitioncomment'),
patternAnswers : DS.hasMany('patternanswer'),
});
And last but not least, serializer:
//app/serializers/questiondefinition.js:4
import DS from "ember-data";
function removeErrorsIfEmtpy(payload) {
if (typeof payload.errors !== 'undefined' && payload.errors.length === 0) {
delete payload.errors;
}
}
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs : {
user: {embedded: 'always',
serialize : 'record',
deserialize: 'record'
}
//comments: { serialize: 'ids' }
},
extractArray : function (store, type, payload) {
payload.questiondefinitions = payload.data;
delete payload.data;
removeErrorsIfEmtpy(payload);
//console.log(arguments);
//return this._super(store, type, payload);
return this._super.apply(this, arguments);
},
extractSingle: function (store, primaryTypeClass, payload, recordId) {
payload.questiondefinition = payload.data;
delete payload.data;
removeErrorsIfEmtpy(payload);
//return this._super(store, primaryTypeClass, payload, recordId);
return this._super.apply(this, arguments);
}
});
This enigmatic question could be without answer to the next 20m of Stack, but here is the answer.
Looking at the questiondefinition serializer one sees that there is tampering with payload because server responses with objects kept in 'data' property.
The enigmatic error Error: Assertion Failed: You must include an id for user in an object passed to push led me to the source of ember-data where I find out that I didn't prepared serializer for user model. So ember just wanted the user properties but all it could get it was 'data' property. So remember, always keep your serializers up to date.
That's IT!

List paging from json

I am finaly able to retrieve my datas from the json file BUT the paging system still sucks. The pageSize property seems not to respond therefore when i press on the load more plugin text that appears at the bottom of my list, it appends all my json elements to my list everytime. I am confident about the fact that i am almost there but i can't see how to make it happen.
Here is the code:
Ext.setup({
tabletStartupScreen: 'tablet_startup.png',
phoneStartupScreen: 'phone_startup.png',
icon: 'icon.png',
glossOnIcon: false,
onReady : function() {
var dataLink;
if (Ext.is.Desktop) {
dataLink = "http://127.0.0.1/Appsfan-v2";
} else {
dataLink = "http://appsfan.kreactive.eu";
}
Ext.regModel('Profile', {
fields: [
{name: 'firstname', type: 'string'},
{name: 'lastname', type: 'string'},
{name: 'age', type: 'number'}
]
});
var store = new Ext.data.JsonStore({
model: 'Profile',
autoLoad: false,
remoteFilter: true,
sortOnFilter: true,
//sorters: [{property : 'lastname', direction: 'ASC'}],
pageSize: 1,
clearOnPageLoad: false,
proxy: {
type: 'ajax',
url: dataLink+'/data.json',
reader: {
root: 'profile',
type: 'tree'
}
}
});
console.log(store)
//console.log(store.loadPage(0))
var groupingBase = new Ext.List({
fullscreen: true,
itemTpl: '<div class="contact2"><strong>{firstname}</strong> {lastname} -> {age}</div>',
indexBar: false,
store: store,
plugins: [{
ptype: 'listpaging',
autoPaging: false
}]
});
var panel = new Ext.Panel({
layout: 'card',
fullscreen: true,
items: [groupingBase],
dockedItems: [{
xtype: 'toolbar',
title: 'paging example',
}]
})
//console.log(datas)
}
});
The json
{
"profile": [{
"firstname": "firstname1",
"lastname": "lastname1",
"age": "1"
},{
"firstname": "firstname2",
"lastname": "lastname2",
"age": "2"
},{
"firstname": "firstname3",
"lastname": "lastname3",
"age": "3"
}]
}
Thank you
You can set the clearOnPageLoad config option in your store. This will remove previous records when the next page of the paginated data is loaded.
Here is an example:
Ext.regStore('BarginsStore', {
model: 'BarginModel',
autoLoad: false,
pageSize: 6,
clearOnPageLoad: false,
});