flask-sqlalchemy and flask- marshmallow object.load runs update command for foreign key in put method - flask-sqlalchemy

I am working on a simple rest api using flask-sqlalchemy and flask-marshmallow.
For the PUT method I have below data:-
{ "childlist" : [
{
"childfield1" : "Value1", --> field of related table
"childfield2" : "Value2" --> field of related table
}
],
"parentprimarykey1": "VAL1", --> this is the foreign key for child table
"parentfield2" : "VAL2",
"parentfield3" : "VAL3"
}
When I pass this json to a PUT method and run the schema.load(request_data). SQLAlchemy is runs a Update SQL for the child table trying to update the foreign key with NULL.
Why an update statement is fired to update the foreign key?
What is the best way to implement the update for the REST API?
my schema for the model looks like this
Parent
class Parent(db.Model):
all fields
childlist=db.relationship('Parent', backref=db.backref('parent', cascade='delete, all', lazy='joined'))
class ParentSchema(ma.ModelScema):
childlist=ma.Nested(child, many=True)
class Meta:
model=Parent
sqla_session=db.session
the error is raised by this line in the view:-
parent_schema.load(request_data)

Related

Extending Shopware entity with foreign keys fails when merging version

I'm developing my first Shopware 6 admin plugin, for which is required to extend one of the existing Shopware plugins - Custom products.
I want to add a relation between already existing entities - TemplateExclusion and TemplateOptionDefinition. When I select from the UI my options, TemplateExclusion entity its getting saved in the database, without any problems.
When I save the Template entity (parent of TemplateExclusion), my "excluded_option_id" its getting overwritten with the 1st possible option from TemplateOptionDefinition entities.
I have notice that this is happening on "mergeVersion". Also when I try to save the Template entity with debug mode enabled and profiler, I'm getting an error during saving, that "excludedOptionId" is blank when merging, which is not true.
Error in profiler
Following the documentation I made first the migration:
class Migration1643023742TemplateExclusionRelation extends MigrationStep
{
public function getCreationTimestamp(): int
{
return 1643023742;
}
public function update(Connection $connection): void
{
$connection->executeStatement('ALTER TABLE `swag_customized_products_template_exclusion` ADD COLUMN `excluded_option_id` BINARY(16) AFTER `template_version_id`;');
$connection->executeStatement('ALTER TABLE `swag_customized_products_template_exclusion` ADD COLUMN `excluded_option_version_id` BINARY(16) AFTER `excluded_option_id`;');
$connection->executeStatement('ALTER TABLE `swag_customized_products_template_exclusion` ADD CONSTRAINT `fk.swag_cupr_template_exclusion.excluded_option_id` FOREIGN KEY (`excluded_option_id`, `excluded_option_version_id`)
REFERENCES `swag_customized_products_template_option` (`id`, `version_id`) ON DELETE CASCADE ON UPDATE CASCADE;');
}
then I made an entity extension, where to define the new fields.
class TemplateExclusionExtension extends EntityExtension
{
public function extendFields(FieldCollection $collection): void
{
$collection->add(
(new FkField('excluded_option_id', 'excludedOptionId', TemplateOptionDefinition::class))
->addFlags(new Required(), new ApiAware())
);
$collection->add(
(new ManyToOneAssociationField('excludedOption', 'excluded_option_id', TemplateOptionDefinition::class))
->addFlags(new ApiAware())
);
$collection->add(
(new ReferenceVersionField(TemplateOptionDefinition::class, 'excluded_option_version_id'))
->addFlags(new Required(), new ApiAware()),
);
}
public function getDefinitionClass(): string
{
return TemplateExclusionDefinition::class;
}
}
Solved:
It was wrong definition from my side:
public function extendFields(FieldCollection $collection): void
{
$collection->add(
(new FkField('excluded_option_id', 'excludedOptionId', TemplateOptionDefinition::class))
->addFlags(new Required(), new ApiAware())
);
$collection->add(
(new OneToOneAssociationField(
EasyExtendCustomizedProducts::TEMPLATE_EXCLUSION_EXCLUDED_OPTION_EXTENSION,
'excluded_option_id',
'id',
TemplateOptionDefinition::class,
false
))->addFlags(new CascadeDelete(), new ApiAware())
);
}
public function getDefinitionClass(): string
{
return TemplateExclusionDefinition::class;
}
If I'm not mistaken the issue was the missing CascadeDelete delete flag.
To versionize the entity it is first fetched including its associated data and is then persisted with new primary keys, so basically it gets cloned. However not all associations are taken into account when fetching the data to be cloned. You can find the responsible code here, where the affected associations get filtered by the existence of the CascadeDelete flag. If they miss the flag they will be ignored for creating the cloned version. This behavior still needs to be documented more prominently.

ON DELETE CASCADE for multiple foreign keys with Sequelize

Suppose I have three models:
Task: A thing that needs done, like "take out the recycling". Can be done many times.
TaskList: An object that represents a list of tasks, and has its own metadata.
TaskListEntry: An association between Task and TaskList, that may have data such as the priority or who is assigned to it.
I have my associations set up like this:
Task.hasMany(TaskListEntry, {onDelete: 'cascade', hooks: true}));
TaskList.hasMany(TaskListEntry, {onDelete: 'cascade', hooks: true});
TaskListEntry.belongsTo(TaskList);
TaskListEntry.belongsTo(Task);
This works fine, except for deleting. When I delete a Task, any associated TaskListEntries are deleted as expected. However, when I delete a TaskList, its associated TaskListEntries simply have their foreign key for the TaskList set to null.
It seems that Sequelize is generating the following table:
CREATE TABLE `TaskListEntries`(
`id` UUID PRIMARY KEY,
/* some other fields here */
`createdAt` DATETIME NOT NULL,
`updatedAt` DATETIME NOT NULL,
`TaskId` UUID REFERENCES `Tasks`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
`TaskListId` UUID REFERENCES `TaskLists`(`id`) ON DELETE SET NULL ON UPDATE CASCADE);
Despite the associations being configured the same, the foreign keys for Tasks and TaskLists have different DELETE behavior. If I remove one of the associations, the other works just fine.
Therefore, I think the issue is multiple foreign keys with ON DELETE CASCADE, at least as far as Sequelize seeis it.
Any thoughts on how to correct this?
I had to set the allowNull:false for the foreignKey for getting 'CASCADE' on deletions to work. So it should be something like this in your case:
TaskListEntry.belongsTo(TaskList, {
onDelete: 'cascade',
foreignKey: { allowNull: false } // <-------------
hooks: true
});
Given the case, that your models are in general similar to this structure from http://docs.sequelizejs.com/manual/associations.html#belongs-to-many-associations:
class User extends Model {}
User.init({}, { sequelize, modelName: 'user' })
class Project extends Model {}
Project.init({}, { sequelize, modelName: 'project' })
class UserProjects extends Model {}
UserProjects.init({
status: DataTypes.STRING
}, { sequelize, modelName: 'userProjects' })
User.belongsToMany(Project, { through: UserProjects })
Project.belongsToMany(User, { through: UserProjects })
can you try
TaskListEntry.belongsTo(TaskList);
TaskListEntry.belongsTo(Task);
instead of
TaskListEntry.belongsToMany(TaskList);
TaskListEntry.belongsToMany(Task);
Because, from my understanding of this problem, a single TaskListEntry record can only belong to a single Task and a single TaskList.
Or Are you trying to establish a Many-to-Many relationship here? In that case, I don't think this is the ideal way of implementation.

PredictionIO Universal Recommender

I am quite new to predictionIO/universal recommender and wondering is there any way to model events between multiple entities like I want one event courseTaken between user and courses. O there I want is workingIn which will be between user and project. So far I haven't seen that this thing is possible in predictionIO. Can anyone please guide me in this regard?
Coming from docs:
When sending usage events it is required that the entityType is "user" and targetEntityType is "item". The type of the item is inferred from the event names, which must be one of the eventNames in the engine.json
Note that a usage event always is a user and has a user id. Also the "targetEntityType" is always "item". The actual target entity is implied by the event name. So to create a "category-preference" event you would send something like this:
{
"event" : "category-preference",
"entityType" : "user",
"entityId" : "1243617",
"targetEntityType" : "item",
"targetEntityId" : "electronics",
"properties" : {},
"eventTime" : "2015-10-05T21:02:49.228Z"
}
To attach properties to items use a $set event like this:
{
"event" : "$set",
"entityType" : "item",
"entityId" : "ipad",
"properties" : {
"category": ["electronics", "mobile-phones"],
"expireDate": "2016-10-05T21:02:49.228Z",
"availableDate": "2015-10-05T21:02:49.228Z"
},
"eventTime" : "2015-10-05T21:02:49.228Z"
}
Based on above, secondary events are just like items, we need to do the following:
1. Add secondary events in configs, say ownProject
2. Add event with item id pointing to say the project, social, relation e.g.:
{
"event" : "own-Project",
"entityType" : "user",
"entityId" : "1243617",
"targetEntityType" : "item",
"targetEntityId" : "project-id",
"properties" : {},
"eventTime" : "2015-10-05T21:02:49.228Z"
}

Combining results of multiple API calls

I am invoking an API operation to fetch a list of objects and then, for each object in that list, I am invoking another API operation to fetch additional details of that object and adding those details into the object. The goal is to return a list of objects with all the properties (or details) that I need. In the example below, the /allObjects call will get a list of all objects with 2 properties - id and k1. But my application needs 3 properties, id, k1 and k4. So I invoke another API method to get detailed information for each object, which includes the k4 property. I copy that detailed property into the original result-set's object and return the "enriched" result-set. But the latency cost of this can be high. What is the best way of achieving this without slowing down the application too much? In the real world, I am dealing with 500-2000 objects.
GET /allObjects yields JSON results =>
{
"data" : [
{
"id" : "123",
"k1" : "v1"
}, {
"id" : "456",
"k1" : "v1"
}
]
}
for (obj in results.data) {
GET /object/{obj.id} yields JSON result =>
{
"data" : {
"id" : "123",
"k1" : "v1",
"k2" : "v2",
"k3" : "v3",
"k4" : "v4"
}
}
// Add k4 property to original result-set object, and assign
// it the value of the k4 property in the individual result object.
obj.k4 = result.data.k4;
}
return results.data;
Your requirement is such that, you have no other option but to go for a mash-up(unless you can convince the API developers to combine the two API).
You could, however, opt to make a mashup service with a low latency cost to stand in between your application to abstract out the mashup logic, conversely, you could opt to use a language that is custom made for this kind of work to program your application. Both of these options can be accommodated with Ballerina, I've written a post here showing how easy it is to do that using it.

RestKit: Composing Relationships with the Nil Key Path

I have two classes
Author with attributes id, papers (Paper relationship), ...
Paper with attributes id, mainAuthor (Author relationship), authors (Author relationship) ...
and want to map some JSON to it
"authors": [
{
"id": 123,
"papers": [
{
"id": 1,
"main_author_id": 123,
...
},
...
]
},
...
]
The problem is that the JSON is not structured like
"authors": [
{
"id": 123,
"papers": [
{
"id": 1,
"main_author": {
"id": 123
}
...
},
...
]
},
...
]
so that I could easily map things (note the *main_author* part of both JSON examples). I tried using mapping this value without a key path as explained here:
[authorMapping addAttributeMappingToKeyOfRepresentationFromAttribute:#"main_author_id"];
[authorMapping addAttributeMappingsFromDictionary:#{#"(main_author_id)": #"id"}];
but I'm getting an error telling me that the keyPath id already exists and I may not add a second mapping for this keyPath. I totally understand this error, but I have no idea how to map from *main_author_id* back to id. Changing the data source may be the best solution, but this is unfortunately not possible.
Any suggestion is highly welcome! Thanks!
This is exactly what foreign key mapping is for. It allows you to temporarily store the 'identity' value that you're provided with and then RestKit will find the appropriate object and complete the relationship.
Apart from the Answer #Wain (foreign key mapping) provided, it is also possible to implement a custom serialization (c.f. RKSerialization) and modify the objects before mapping takes place. However, the aforementioned method is superior to this (somehow ugly) solution.