Django multi table inheritance: move instance between child models - python

Assume I have in models.pysomething like:
Class ModelA(models.Model):
# many fields, including
relatives = models.ManyToManyField(Person)
)
# also, A is foreign key to other models:
Class SomeOtherModel(models.Model):
mya = models.ForeignKey(A)
# now we produce two classes with multi-table inheritance
# WITHOUT any additional fileds
Class InhertA1(ModelA):
pass
Class InhertA2(ModelA):
pass
So as as I understand, this will create Tables for ModelA, InheritA1 and InheritA1; each instance of ModelA will get a row in the ModelA-table only, each instance of InheritA1 will have a row both in the ModelA-table (basically containing all the data) and another in the InheritA1-table (only containing a PK and a OneToOne key pointing to the ModelA-table row), etc. Django queries for ModelA-objects will give all objects, queries for InheritA1-objects only the InheritA1 ones etc.
So now I have an InheritA1-object a1, and want to make it into a InheritA2-object, without changing the according ModelA-object. So previously the parent-IbeToOne Key of a1 points to the ModelA-row with key 3, say (and the ForeignKey of some SomeOtherModel-object is set to 3). In the end, I want a InheritA1-object a2 pointing to the same, unchanged ModelA-row (and the object a1removed).
It seems that django doesn't offer any such move-Class-functionality?
Can I safely implement the according SQL operations myself?
Or will things go horribly wrong? I.e., can I just execute the SQL commands that
Create a new row in the InheritA2-table, setting the parent-OneToOne key to the one of a1,
Remove the a1 row in the InheritA2-table?
It seems I cannot do this from non-SQL-django without automatically creating a ModelA-row. Well, for 1., maybe I can create a temporary object x that way, then let p be the parent of x, then change the parent-OneToOne key of x to point to the one of a1, then delete the obect p? But for 2, I do not think that it is possible in non-SQL-django to remove an instance of a child while keeping the parent object?
Alternatively, is there a good django way to copy instances in django and change references to them?
I.e., I could create a new InheritA2 object y, and copy all the properties of a1 into the new object, and then go through the database and find all ManyToMany and ForeignKey entries that point to the parent of a1
and change it to point to the parent of y instead, and then delete a1.
That could be done with non-SQL-django, the downside is that it seems wasteful performance-wise (which would be of no great concern for my project), and that it might also not be so "stable" I.e., if I change ModelA or other models in relation to it, the code that copies everything might break? (Or is there a good way to do something like
Forall models M in my project:
Forall keys k used in M:
If k is a descendant of a ManyToMany or Foreign or ... key:
If k points to ModelA:
Forall instances x of M:
If x.k=a1:
x.k=y
The first four lines seem rather dubious.
Remarks:
Copying without changing the instance can be done in a stable, simple, standard way, see e.g. here, but we are still stuck in the same child class (and still have to modify ForeignKeys etc)?
Changing the class by just declaring it in the standard python way, see here, is not an option for me, as nobody seems to know whether it will horribly break django.

If you plan on changing the children but not the parent, then maybe you could use OneToOneField instead of direct inheritance.
class ModelA(models.Model):
pass
class InhertA1(models.Model):
a = models.OneToOneField(ModelA, primary_key=True)
class InhertA2(models.Model):
a = models.OneToOneField(ModelA, primary_key=True)
It gives you the same 3 tables in the database. (One difference is that the pk fields of InheritA1 and InheritA2 will be the same id from the parent.)
For changing from InheritA1 to InheritA2 you would delete one child instance (this would not affect the parent instance) and then create the other new instance, pointing it to the parent instance.
Well, you can even have a parent instance which has children from both other models, but that would be checked in your view to prevent that.
Let me know if this helps you, even if the answer is a bit late.

Related

Prevent related object persistence in sqlalchemy

Environment
Python 3.8.10
SQLAlchemy 1.3.22
Problem
I am having a problem related to the stated here, but couldn't find a solution yet: How to prevent related object persistence in sqlalchemy?
I have two models with a one to many relationship:
class A:
b = db.relationship("B", back_populates="a", cascade="all,delete")
class B:
a_id = db.Column('A_ID', db.Integer, db.ForeignKey('A.ID'), nullable=False)
a = db.relationship('A', back_populates="b")
The thing is that at a certain point, I need to modify a certain field of B. To do so, I need to access the object A related to the current B object to do some checks. But, as A is a class that uses translations and we need to have that in mind.
What translate does is overwrite the translatable fields of A in the current instance, without storing them in the database (we have the translations on a diferent table due to backwards compatibility). I need to do that translation to do the checks and get the correct value I should set to B. The problem is as follows:
# some_service.py
def get_by_id(a_id):
a = A.get_by_id(a_id)
if a:
return translate(a)
# file_y.py
def get_value_to_update_b(a_id)
a = some_service.get_by_id(a_id)
# do some stuff without saving anything to A
# file_x.py
b = B.get_by_id(id) # At this point, b.a stores the original A object
value = file_y.get_value_to_update_b(b.a_id) # After this executes, b.a points to the translated one, instead of the original, so when B is saved, the translated A is saved too.
b.value = value
session.add(b)
session.commit()
As you can see, the problem is that when I translate A to do the checks, the reference from B gets updated to A_translated, so when I save B, A is saved too with the translated values, which is incorrect.
I have already tried modifying the cascade attribute of the relationships (to None and merge), making a copy of the object A before translating and some other choices. And changing the whole translation process, although a possibility, it's something I would rather have as the last option as this is something that urges us.
Is there anything else I could do to prevent A being saved when B is saved? If you have any question about the process, as I think it can be a little bit messy, I would gladly answer you. Thank you very much
In the end I ended up with another approach. I couldn't find a solution for the simultaneous storing of A and B, but I just made the checks different so the translation didn't overwrite the original instance.

Django multi-table inheritance - make sure only one child exists (CheckConstraint)

How can I make sure that a parent object has only one child/type?
class Property(...):
class Meta:
abstract = False
class Flat(Property):
pass
class House(Property):
pass
class Land(Property):
pass
I want every property object to have none or at most one child. It can be either flat, house or land (or null).
Is it possible to create a DB constraint for this?
My idea was to create a constraint that checks:
class Meta:
constraints = [
models.CheckConstraint(check=Q(Q(flat__isnull=True) & Q(house__isnull=True))
|
Q(Q(flat__isnull=True) & Q(land__isnull=True))
|
Q(Q(house__isnull=True) & Q(land__isnull=True)),
name="constraint")]
But apparently, there are no such fields on a DB level (you can get flat by property.flat getter in Django but not in DB)
Edit:
properties.Property: (models.E012) 'constraints' refers to the nonexistent field 'flat'.
But apparently, there are no such fields on a DB level (you can get flat by property.flat getter in Django but not in DB)
That is correct: Django adds a property to the Property model to lazily load the related Flat object and will make a query for that, but there is no database field named flat: this is just a query in reverse where Django basically queries with:
SELECT * FROM app_name_flat WHERE property_ptr=pk
with pk the primary key of the property object. It this makes a query.
A CHECK constraint [w3-schools] spans only over a row: it can not look on other rows nor can it look at other tables. It thus can not restrict other tables, and therefore is limited. It can for example prevent one column to have a certain value based on a value for another column in the same row (record), but that is how far a CHECK constraint normally looks.

SQLAlchemy multiple backrefs causing problems

I'm using SQLAlchemy with Python (linking to an MySQL database) and am having a little design issue, which has presented itself as a problem with a backref.
So the situation is this. I have a SearchGroup which contains TargetObjects and SearchObjects. These are both many to many relationships, and so the SearchGroup table comes with two association tables, one for each. The SearchObject is the same time for any SearchGroup, but the TargetObject varies. So far so good. The whole idea here is that a SearchObject is simply a string with a few other variables, and a SearchGroup compares them all to a given string and then, if there's a match, supplies the target objects.
Now for some code: the declaration of these three classes, although with the parent logic hidden for brevity:
class AssocTable_GroupCMClassesGrades_Grades(AssociationTable_Group_TargetObjectsParent, med.DeclarativeBase):
__tablename__ = 'AssocTable_GroupCMClassesGrades_Grades'
_groupsTableName = 'SearchGroup_CMClasses_Grades'
_targetObjectsTableName = 'Grades'
class AssocTable_GroupCMClassesGrades_SearchObjects(AssociationTable_Group_SearchObjectsParent, med.DeclarativeBase):
__tablename__ = 'AssocTable_GroupCMClassesGrades_SearchObjects'
_groupsTableName = 'SearchGroup_CMClasses_Grades'
_searchObjectsTableName = 'SearchObjects'
class SearchGroup_CMClasses_Grades(SearchObjectGroupParent, med.DeclarativeBase):
__tablename__ = 'SearchGroup_CMClasses_Grades'
targetAssociatedTargetObjectTableName = 'AssocTable_GroupCMClassesGrades_Grades'
targetAssociatedSearchObjectTableName = 'AssocTable_GroupCMClassesGrades_SearchObjects'
targetClassName = 'Grade'
myClassName = 'SearchGroup_CMClasses_Grades'
searchObjectClassName = 'SearchObject'
searchObjectChildrenBackRefName = 'Groups'
The top two are the association tables and the bottom is the main class. The strings are used to set up various foreign keys and relationships and such.
Let's look at a specific example, which is crucial to the question:
#declared_attr
def searchObject_childen(cls):
return relationship(f'{cls.searchObjectClassName}', secondary=f'{cls.targetAssociatedSearchObjectTableName}', backref=f'{cls.searchObjectChildrenBackRefName}')
This is inside the SearchObjectGroupParent class and, as you can see, is for the 'children' of the SearchGroup, which are SearchObjects.
So now to the problem.
That all works rather well, except for one thing. If I could direct your attention back to the large bit of code above, and to this line:
searchObjectChildrenBackRefName = 'Groups'
This, as seen in the second posted piece of code (the declared_attr one), sets up a backref; a property in the target - it creates that property and then populates it. I'm not an expert at this by any means so I won't pretend to be. The point is this: if I create another SearchObjectGroupParent derived class, like the one above, with its association tables, I can't put another 'Groups' property into SearchObject - in fact it will throw an error telling me as much:
sqlalchemy.exc.ArgumentError: Error creating backref 'Groups' on relationship 'SearchGroup_CMClasses_Grades.searchObject_childen': property of that name exists on mapper 'mapped class SearchObject->SearchObjects'
There is a rather unsatisfying way to solve this, which is to simple change that name each time, but then the SearchObject won't have a common list of SearchGroups. In fact it will contain the 'Groups' property for every SearchGroup. This will work, but will be messy and I'd rather not do it. What I would like is to say 'okay, if this backref already exists, just use that one'. I don't know if that's possible, but I think such a thing would solve my problem.
Edit: I thought an image might help explain better:
Figure 1: what I have now:
The more of these objects derived from SearchObjectsGroupParent I have, the messier it will be (SearchObject will contain Groups03, Groups04, Groups05, etc.).
Figure 2: what I want:

Odoo delegation inheritance delete corresponding records of inherited class

I've extended a default class using _inherits. I am using Odoo v9.
class new_product_uom(models.Model):
_inherits = {'product.uom':'uomid', }
_name = "newproduct.uom"
uomid = fields.Many2one('product.uom',ondelete='cascade', required=True).
#declare variables and functions specific to new_product_uom
sellable = fields.Boolean('Sell products using this UoM?', default=True)
[...]
If I delete the corresponding record in product.uom, the new_product_uom is deleted.
If I were to delete a new_product_uom record, nothing happens to the corresponding product_uom record.
I'd like for BOTH records to be automatically deleted when either is deleted. Is there a way I can do this? Thanks in advance for the help.
Clarification:
product.uom is a default odoo class. It holds UoM records (inches, centimeters, etc). I use delegation inheritance to extend this class. See:
https://www.odoo.com/documentation/9.0/howtos/backend.html#model-inheritance
So, when I add a record for newproduct.uom, a record is automatically created under the model product.uom. I can assign the values of the corresponding record in product.uom by addressing them in newproduct.uom.
For my uses, it will be intended as a Parent->child relation, with newproduct.uom being the parent, and the default product.uom being the child. I chose this method of inheritance to allow quicker creation and modification of related values, as well as a separation of functions (rather than overriding the default methods for default operations).
In your parent class override unlink. Not sure if I have the correct class name. Delete the child record and then delete the current record.
#api.multi
def unlink(self):
self.uom_id.unlink()
return super(new_product_uom, self).unlink()

Relational database design - Two Relations 1:1 or one 1:2?

This question is about how to design a SQL relationship. I am pretty newbie in this matter and I'd like to know the answers of (way) more experts guys...
I am currently migrating a ZopeDB (Object oriented) database to MySQL (relational) using MeGrok and SqlAlchemy (although I don't think that's really too relevant, since my question is more about designing a relationship in a relational database).
I have two classes related like this:
class Child(object):
def __init__(self):
self.field1 = "hello world"
class Parent(object):
def __init__(self):
self.child1 = Child()
self.child2 = Child()
The "Parent" class has two different instances of a Child() class. I am not even sure about how to treat this (two different 1:1 relationships or a 1:2 relationship).
Currently, I have this:
class Child(rdb.Model):
rdb.metadata(metadata)
rdb.tablename("children_table")
id = Column("id", Integer, primary_key=True)
field1 = Column("field1", String(64)) #Irrelevant
def __init__(self):
self.field1 = "hello world"
class Parent(rdb.Model):
rdb.metadata(metadata)
rdb.tablename("parent_table")
id = Column("id", Integer, primary_key=True)
child1_id = Column("child_1_id", Integer, ForeignKey("children_table.id"))
child2_id = Column("child_2_id", Integer, ForeignKey("children_table.id"))
child1 = relationship(Child,
primaryjoin = ("parent_table.child1_id == children_table.id")
)
child2 = relationship(Child,
primaryjoin = ("parent_table.child2_id == children_table.id")
)
Meaning... Ok, I store the two "children" ids as foreign keys in the Parent and retrieve the children itself using that information.
This is working fine, but I don't know if it's the most proper solution.
Or I could do something like:
class Child(rdb.Model):
rdb.metadata(metadata)
rdb.tablename("children_table")
id = Column("id", Integer, primary_key=True)
parent_id = Column("id", Integer, ForeignKey("parent_table.id")) # New!
type = Column("type", ShortInteger) # New!
field1 = Column("field1", String(64)) #Irrelevant
def __init__(self):
self.field1 = "hello world"
class Parent(rdb.Model):
rdb.metadata(metadata)
rdb.tablename("parent_table")
id = Column("id", Integer, primary_key=True)
child1 = relationship(
# Well... this I still don't know how to write it down,
# but it would be something like:
# Give me all the children whose "parent_id" is my own "id"
# AND their type == 1
# I'll deal with the joins and the actual implementation depending
# on your answer, guys
)
child2 = relationship(
# Would be same as above
# but selecting children whose type == 2
)
This may be good for adding new children to the parent class... If I add a "Parent.child3", I just need to create a new relationship very similar to the already existing ones.
The way I have it now would imply creating a new relationship AND adding a new foreign key to the parent.
Also, having a "parent" table with a bunch of foreign keys may not make it the best "parent" table in the world, right?
I'd like to know what people that know much more about databases think :)
Thank you.
PS: Related post? Question 3998545
Expanded in Response to Comments
The issue is, you are thinking in the terms that you know (understandable), and you have the limitations of an OO database ... which would not be good to carry over into the Relational db. So for many reasons, it is best to simply identify the Entities and Relations, and to Normalise them. The method you use to call is easy to change and you will not be limited to only what you have now.
There are some good answers here, but even those are limited and incomplete. If you Normalise Parent and Child (being people, they will have many common columns), you get Person, with no duplicated columns.
People have "upward" relations to other people, their Parents, but that is context, not the fact that the Parent exists as a Person first (and you can have more than two if you like). People also have "downward" relations to their Children, also contextual. The limitation of two children per Parent is absurd (you may have to inspect your methods/classes: I suspect one is an "upward" navigation and the other is "downward"). And you do not want to have to store the relations as duplicates (once that Fred is a father of Sally; twice that Sally is a child of Fred), that single fact exists in a single row, which can be interpreted Parent⇢Child or Parent⇠Child.
This requirement has come up in many questions, therefore I am using a single generic, but detailed, illustration. The model defines any tree structure that needs to be walked up or down, handled by simple recursion. It is called a Bill of Materials structure, originally created for inventory control systems, and can be applied to any tree structure requirement. It is Fifth Normal Form; no duplicate columns; no Update Anomalies.
Bill of Materials
For Assemblies and Components, which would have many common columns, they are Normalised into Part; whether they are Assemblies or Components is contextual, and these contextual columns are located in the Associative (many-to-many) table.
Two Relations 1:1 or one 1:2 ?
Actually, it is two times 1::n.
Ordinals, or Ranking, is explicit in the Primary Key (chronological order). If some other ordinal is required, simply add a column to the Associative table. better yet, it is truly a derived column, so compute it at runtime from current values.
I'll admit that I'm not too familiar with object databases, but in relational terms this is a straightforward one-to-many (optional) relationship.
create table parent (
id int PK,
otherField whatever
)
create table child (
id int PK,
parent_id int Fk,
otherField whatever
)
Obviously, that's not usable code as it stands....
I think this is similar to your second example. If you need to track the ordinal postion of the children in their relationships to the parent, you'd add a column to the child table such as:
create table child (
id int PK,
parent_id int Fk,
birth_order int,
otherField whatever
)
You'd have to be responsible for managing that field at teh application level, it's not something you can expect the DBMS to do for you.
I called it an optional relationship on the assumption that childless parents can exist--if that's not true, it becomes a required relationship logically, though you'd still have to let the DBMS create a new parent record childlessly, then grab its id to create the child--and once again manage the requirement at the application level.
This is probably a little out of context, since I use none of the things you've mentioned - but as far as the general design goes, here are a couple ideas:
Keep relationships based on common types: has_one, has_many, belongs_to, has_and_belongs_to_many.
With children, it's better to not specify N number of children explicitly; either there are none, one, or there could potentially be many. Thus your model declarations of child1 and child2 would be replaced by a single property - an array containing children.
To be totally honest, I don't know how well that fits in with what you're using. However, that's generally how relationships work in an ORM sense. So, based on this:
If a model belongs to another (it has a foreign key for another table), it would have a parent [sic] property with a reference to the parent object
If a model has one model that belongs to it (the other model has a foreign key to the first model's table), it would have a child [sic] property with a reference to the child object
If a model has many models that belong to it (many other models have foreign keys to the first model's table), it would have a children [sic] property that is an array of references to child objects
If a model has and belongs to many other models... you might want to consider using both parents and children properties, or something similar; nomenclature is less important than you having access to a group of models that it belongs to, and another group of models that belong to it.
Sorry if that's totally unhelpful, but it might shed some light. HTH.

Categories

Resources