ON_DELETE="CASCADE" does not work with flask-sqlalchemy - sqlite

I am developing a WebApp in flask and using flask-sqlalchemy to define my Models and store them in an SQLite database. In short, I have the following 3 models (A Porject can have multiple files, and each file can have multiple results):
class Project(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
files = db.relationship("File", backref="project", passive_deletes=True)
class File(db.Model):
id = db.Column(db.Integer, primary_key=True)
path = db.Column(db.String, nullable=False)
project_id = db.Column(
db.Integer,
db.ForeignKey("project.id", ondelete="CASCADE"),
nullable=False)
results = db.relationship("Result", backref="file", passive_deletes=True)
class Result(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String, nullable=False)
file_id = db.Column(
db.Integer,
db.ForeignKey("file.id", ondelete="CASCADE"),
nullable=False)
Now the problem is, that when I create a project, create and assign some files to the project, create some results and assign them to some files, and finally delete the project, the cascade deleting of the files and the results does not work. I found the following post and implemented the db.relationship attributes as suggested there, but the problem remains the same.
P.S.: here is a minimal working example, that shows the problem.
P.P.S.: Can somebody confirm, that the problem is reproducible. Not that it only happens on my computer/my environment.

Well, this answer goes in the category RTFM. As Gord Thompson "mentioned" with the link to the documentary of sqlalchemy, one needs to add the following lines somewhere reachable on flask app start:
from sqlalchemy.engine import Engine
from sqlalchemy import event
db = SQLAlchemy(app)
#event.listens_for(Engine, "connect")
def set_sqlite_pragma(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
(now I am asking myself, why not a single word is written about this in the flask-sqlalchemy docu :-/ )

Related

SQLAlchemy with Sqlite ON DELETE,UPDATE CASCADE not working

I'm using sqlalchemy as ORM in flask with sqlite as DB for my web application.
I've 4 tables in my DB and I wanna implement ON DELETE CASCADE, ON UPDATE CASCADE correctly.
My relationships are :
Table Student and Subject has 1-to-many relationship with table Major
Table Mark is an association table between Table Subject and Student
so the expected behavior is :
When I delete,update a Student or Subject the correspending line gots deleted from table Mark(association table).
When I delete,update a Major the correspending line gots deleted from tables Student and Subject.
These are my classes :
class Student(db.Model, UserMixin):
__tablename__ = 'Student'
.......
major_id = db.Column(db.Integer, db.ForeignKey('Major.id'))
class Subject(db.Model, UserMixin):
__tablename__ = 'Subject'
.......
major_id = db.Column(db.Integer, db.ForeignKey('Major.id'))
class Mark(db.Model, UserMixin):
__tablename__ = 'Mark'
.....
stdnt_id = db.Column(db.Integer,db.ForeignKey('Student.regNumber'))
subj_id = db.Column(db.String(5),db.ForeignKey('Subject.code'))
.......
class Major(db.Model, UserMixin):
__tablename__ = 'Major'
............
students = db.relationship('Student', backref='major', lazy='dynamic')
subjects = db.relationship('Subject', backref='major', lazy='dynamic')
I've tried : onupdate="cascade",ondelete="cascade",cascade="all,delete, delete-orphan",passive_deletes=True............
and tested them in my web app but to no avail.
NB: I've checked my sqlite database file sometimes and found ON DELETE,UPDATE CASCASE constraints added to my db scheme but the cascade not working from my wep app.
Please any help ?
I tried and test many links and can't delete the parent. until I get one of the link that can delete the parent and automatically delete all its children. but can't update. here is the simplified code below,
class Parent(db.Model):
__tablename__ = 'parent'
id = db.Column(db.Integer, primary_key=True)
children = db.relationship(
"Child", back_populates="parent",
cascade="all, delete",
passive_deletes=True
)
class Child(db.Model):
__tablename__ = 'child'
id = db.Column(db.Integer, primary_key=True)
parent_id = db.Column(db.Integer, db.ForeignKey('parent.id', ondelete="CASCADE"))
parent = db.relationship("Parent", back_populates="children")

Add data when running Symfony migrations

I have a Symfony project that is using the DoctrineMigrations bundle, and I have a really simple question: When I run a migration (e.g., when I'm pushing an update to production), how can I insert data to the database?
For example: I have an Entity which is the type of an add. The entity is:
private $addType; // String
private $type1; // Boolean
private $type2; // Boolean
private $type3; // Boolean
I add another field ($type4), and I want to add a new record to the database, with this values:
$addType = 'Type number 4';
$type1 = false;
$type2 = false;
$type3 = false;
$type4 = true;
How can this be done with DoctrineMigrations? Is it possible?
Using the Entity Manager as suggested in another answer is not a good idea, as it leads to troubles later.
In the first migration, I created a table with users and populated some users via $em->persist($user); which seemed fine at the beginning.
But after a month, I added a phone column to my User model. And Doctrine generates INSERT statements with this column within the first migration, which fails due to the non-existing column phone. Of course it doesn't exist yet in the first migration. So it is better to go with pure SQL INSERTs.
I just asked a related related question.
It is possible to use the migrations bundle to add data to the database. If you add a new property and use the doctrine mapping then the
php app/console doctrine:migrations:diff
command will generate a new migration file. You can just put your insert statements inside this file using the syntax:
$this->addSql('INSERT INTO your_table (name) VALUES ("foo")');
Make sure you put it after the auto-generated schema changes though. If you want to separate your schema changes and your data changes then you can use
php app/console doctrine:migrations:generate
to create an empty migrations file to put your insert statements in.
Like I said in my related question, this is one way to do it, but it requires manually creating these if you want to change this data in the database.
Edit:
Since this answer seems to get a few views I think it's worth adding that to more clearly separate the data changes from the schema changes there is a postUp method that can be overridden and that will be called after the up method.
https://www.doctrine-project.org/projects/doctrine-migrations/en/3.0/reference/migration-classes.html#postup
I've "found" the correct way to solve my problem (insert data after running migrations, using my entity classes).
Here is: https://stackoverflow.com/a/25960400
The idea is to declare the migration as ContainerAware, and then, from the postUp function, call the DI to get the EntityManager. It's really easy, and you can use all your entities and repositories.
// ...
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Version20130326212938 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
// ... migration content
}
public function postUp(Schema $schema)
{
$em = $this->container->get('doctrine.orm.entity_manager');
// ... update the entities
}
}
when you make the new field you need to enter this annotation "options={"default":1}" and it should work.
/**
* #var boolean
* #ORM\Column(name="type4", type="boolean", options={"default":1})
*/
private $type4 = true;
Took me some time to figure this out :)
It does, if you know how to format the array;
$this->connection->insert('user', ['id' => 1, 'gender' => 'Male']);
this is good solution for me. Just use bin/console make:migration and when migration is generated just edit if and add "DEFAULT TRUE":
$this->addSql('ALTER TABLE event ADD active TINYINT(1) NOT NULL DEFAULT TRUE');
It doesn't sound a good idea to fill date in migration, not its responsibility, symfony has a way of doing that. https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

Test database isolation with Behat 3, Doctrine 2 and Symfony 2

I am introducing functional behat tests on a Symfony2/Doctrine2 application and am deciding how to handle database isolation and data fixtures.
Are there any pitfalls to setting up a separate test environment with its own completely separate mysql database that gets populated by a dump import prior to execution of the behat test suite, and then emptied after suite execution? I'm trying to avoid using data fixtures unless I really need so as to save the time of manually writing foreign key relations and what not.
Any guidance is appreciated.
As far as Symfony/Doctrine/Behat is concerned and if you want to be one of those who follows the best practises then:
You should isolate your environment dev, test, prod, stag ...
Setting up isolated environments as a symfony application base
up to Build folder structure header.
You should isolate your test database and use sqlite instead of
MySQL for performance purposes. Using multiple SQLite entity
managers for multiple bundles and databases in test environment,
you can use only one.
You should use fixtures to give yourself flexibility and get rid of
burden of manual processing. Do not try to avoid using them!
Creating doctrine data fixtures in symfony
So on ..... just check the posts in this site which I often
read up on myself.
Behat 3 composer entries and the behat.yml
We currently have a separate test database and use a combination of both fixtures and prepopulated database.
The prepopulated database contains the minimum information that needs to be present in almost all tests. We used to do this with fixtures but it was too slow, so now we populate the DB like so:
/**
* #BeforeScenario
*/
function initialiseStorage(BeforeScenarioScope $scope)
{
$con = $this->getService('database_connection');
$con->executeUpdate("SET foreign_key_checks = 0;");
$filePath = $this->getMinkParameter('files_path').'/test_db.sql';
$con->exec(file_get_contents($filePath));
$con->executeUpdate("SET foreign_key_checks = 1;");
}
And then, we load the specific fixtures for every test case like Alfonso described.
We use MYSQL for our tests as in our experience the bottleneck is not the DB but doctrine's metadata caching. If you set up metadata caching in redis the speed of the tests increase dramatically.
In order to respond to #madness-method and to complete the following answer:
The prepopulated database contains the minimum information that needs
to be present in almost all tests. We used to do this with fixtures
but it was too slow, so now we populate the DB like so:
/**
* #BeforeScenario
*/
function initialiseStorage(BeforeScenarioScope $scope)
{
$con = $this->getService('database_connection');
$con->executeUpdate("SET foreign_key_checks = 0;");
$filePath = $this->getMinkParameter('files_path').'/test_db.sql';
$con->exec(file_get_contents($filePath));
$con->executeUpdate("SET foreign_key_checks = 1;");
}
And then, we load the specific fixtures for every test case like
Alfonso described.
We use MYSQL for our tests as in our experience the bottleneck is not
the DB but doctrine's metadata caching. If you set up metadata caching
in redis the speed of the tests increase dramatically.
You should use instead:
/**
* #BeforeScenario
*/
function initialiseStorage(BeforeScenarioScope $scope)
{
$con = $this->em->getConnection();
$con->executeUpdate("SET foreign_key_checks = 0;");
$filePath = $this->getMinkParameter('files_path').'/test_db.sql';
$con->exec(file_get_contents($filePath));
$con->executeUpdate("SET foreign_key_checks = 1;");
}
having the following code in your Context file:
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
Do not forget to add the following lines in your behat configuration file regarding your contexts in order to be able to use the entity manager in your constructor and then in your initialiseStorage method :
- AppBundle\Features\Context\FeatureContext:
em: '#doctrine.orm.default_entity_manager'
Basically to get the connection, we have replaced:
$this->getService('database_connection');
by:
$this->em->getConnection();
I recommend you to read something about factory girl pattern. The idea is create a
factory for every class that you have and use a instance of it in the test. I use https://github.com/carlescliment/handy-tests-bundle
Other options will be create your own steps to create a instance or the class something like this:
/**
* #Given /^there are products:$/
*/
public function thereAreRoutes(TableNode $table)
{
$em = $this->getEntityManager();
foreach ($table->getHash() as $hash) {
$entity = new Product();
$entity->setName($hash['name']);
$entity->setDescription(isset($hash['description']) ? $hash['description'] : $hash['description']);
$em->persist($entity);
}
$em->flush();
}
And you can use it like:
Given there are products:
| name | description |
| Shoes | It is blue |

Symfony2 data fixtures

very new to Symfony2 so trying to working things out. Got a couple of tables. First one is called availability_alert. This table has the usual stuff like an id, but the important thing to note is that it does not have a link to anything.
The second table is call booking_class. This one has a couple of fields, one of which is availability_alert_id. This value is linked to the id field in my first table.
I had a working application, but decided to move it to Symfony. I used my existing database to produce some entity classes. In my BookingClass Entity, I have a link to the AvailabilityAlert id.
/**
* #var \AlertBundle\Entity\AvailabilityAlert
*
* #ORM\ManyToOne(targetEntity="\AlertBundle\Entity\AvailabilityAlert")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="availability_alert_id", referencedColumnName="id")
* })
*/
private $availabilityAlert;
Now for some reason, it set the setter to this field like so
public function setAvailabilityAlert(\AlertBundle\Entity\AvailabilityAlert $availabilityAlert = null)
{
$this->availabilityAlert = $availabilityAlert;
return $this;
}
So I first want to make sure this is correct?
Next, I am doing some Data Fixtures, so I do
$alert = new AvailabilityAlert();
And then set a few other bits and pieces. I then do one for the other table
$bookingClass = new BookingClass();
However, when I try to set the id
$bookingClass->setAvailabilityAlert($this.$alert->getId());
It tells me it is expecting an AvailabilityAlert but instead it is getting a String.
So I was just wondering what I am doing wrong here?
Thanks
The problem is that you're trying to add a string instead of an AvailabilityAlert object to your BookingClass object.
Instead of this:
$bookingClass->setAvailabilityAlert($this.$alert->getId());
Simply try this:
$bookingClass->setAvailabilityAlert($alert);

Symfony2+Behat+Mink: how should I refactor?

in this tutorial is proposed this file system:
XXXBundle
|---Features
| |----FeaturesContext.php
|---ProductCategoryRelation.feature
where FeaturesContext.php is the file that stores the functions
//FeaturesContext.php
/**
* Feature context.
*/
class FeatureContext extends BehatContext
{
/**
* #Given /I have a category "([^"]*)"/
*/
public function iHaveACategory($name)
{
$category = new \Acme\DemoBundle\Entity\Category();
$category->setName($name);
...
And inside ProductCategoryRelation.feature is proposed to write the features and the scenarios:
Feature: Product Category Relationship
In order to setup a valid catalog
As a developer
I need a working relationship
This being the feature, we now need the scenarios to be defined.
Scenario: A category contains a product
Given I have a category "Underwear"
...
But if my app is growing up, how should refactor for example FeaturesContext.php?
You might use subcontexts to split step definitions into multiple context classes: http://docs.behat.org/guides/4.context.html#using-subcontexts

Resources