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

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.

Related

Python / Django: Make a lookup / database query inside a model class

What is the right way to lookup a table and use its last value as a value in a new model instance? Something like this:
class MyClass(models.Model):
id = models.AutoField(primary_key=True)
obj = MyClass.objects.latest('id')
my_field = models.IntegerField(default=(obj+1))
I need a db column, which keeps track of the primary_key, but is independent of it and can also be modified. Also I need to use its value as default when creating new instances of the Model.
you can use custom constructor as described in the docs:
https://docs.djangoproject.com/en/2.2/ref/models/instances/
you will need to define the obj field either as integer(to store the id of the previous record) or as a foreign key(if you want to reference the previous db record). In the second case you will need to pass the name of the model to the ForeignKeyField constructor as string('MyClass') and not directly(MyClass).

Django multi table inheritance: move instance between child models

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.

How to get child model related data in parent model query

I have two models:
class BusinessCard(models.Model):
name = models.CharField(_("name"),null=True,max_length=50)
class Contacts(models.Model):
businesscard_id = models.OneToOneField(BusinessCard,null=True,blank=True,related_name='contact_detail',db_column="businesscard_id")
bcard_json_data = JsonField(null=True)
I just want access contacts model data using business card model:
target_bacard=BusinessCard.objects.filter(id=target_bacard_id).select_related()
When we access the target_bacard.contact_detail it gives key errors.
How can I get the contacts data using target_bacard queryset.
use get() instead of filter() like:
target_bacard = BusinessCard.objects.get(id=target_bacard_id)
target_bacard.contact_detail
If you want to access the Contacts instance that is in the 1-to-1 relationship with a BusinessCard instance bacard, use the related name you specified in Contacts:
contact = bacard.contact_detail
Also, you have some misleading names: Contacts should rather be Contact since an instance of this model represents only one contact. And its field businesscard_id would better be named businesscard (note that the table column will be called businesscard_id at the database level automatically in that case and store the id of the related businesssscard) because in the ORM you get a BusinessCard model instance when you access it, and not just its id.
You have not passed related model (field) argument to select_related()
target_bacard=BusinessCard.objects.filter(id=target_bacard_id).select_related('contact_detail')
Assuming id of BusinessCard is unique, you may want to use ...objects.get(id=target_bacard_id) inplace of ...objects.filter(id=target_bacard_id). Anyway select_related() will work on both ways.
select_related() is used for saving database query.
here is the documentation

How to query MongoEngine documents with inheritance?

I've been using a Node MongoEngine document for a while.
I am trying to go from a simpe Node model to some more specific elements inheriting from it.
What I've done so far
At first, I was not aware of the inheritance possibility offered by MongoEngine (see here), so I was using a 'label' field to distinguish between 3 types of Nodes (respectively Keyword, Url and Domain).
Here is the original model:
class Node(Document):
project = ReferenceField(Project,
reverse_delete_rule=CASCADE,
required=True,)
name = StringField(required=True, unique_with=['project', 'label'])
label = StringField(required=True)
volume = IntField()
clusters = ListField(ReferenceField(Cluster, reverse_delete_rule=PULL))
x = FloatField(default=random.random())
y = FloatField(default=random.random())
connections = IntField(default=0)
meta = {
'indexes': ['project', 'label', 'name', 'clusters'],
}
I worked for some time with this model, so the node collection is currently populated with thousands of documents.
Then I implemented inheritance by adding 'allow_inheritance': True to the model and creating the following model:
Inherited model
class Keyword(Node):
""" A MongoEngine Document for keyword management. """
a_keywor_specific field = IntField()
def foo(self):
print('this is a keyword specific method')
Now this works fine for creating and saving new Keyword documents.
The thing I'm having trouble with is querying the old Nodes added before this change.
Question
If I try to query all the existing nodes, only the one I added after the inheritance change is returned:
In [21]: Node.objects()
Out[21]: [<Keyword: Keyword object>]
How can I access all the Nodes that were added before introducing inheritance ?
Is there any way to migrate those old Nodes to Keywords, Urls and Domains based on their original label attribute ?
Thanks !
This happened because when you created an inherited model, the old model queries use _cls attribute to query this model's records. But old records don't have this field.
Fill in this attribute to old records.
As for you second question.
I think, if you are going to make a migration script that will fill _cls field, you can fill its value depending on the value of label field.
You can find the required _cls values inserting records of each model.

How to find out whether a model's column is a foreign key?

I'm dynamically storing information in the database depending on the request:
// table, id and column are provided by the request
table_obj = getattr(models, table)
record = table_obj.objects.get(pk=id)
setattr(record, column, request.POST['value'])
The problem is that request.POST['value'] sometimes contains a foreign record's primary key (i.e. an integer) whereas Django expects the column's value to be an object of type ForeignModel:
Cannot assign "u'122'": "ModelA.b" must be a "ModelB" instance.
Now, is there an elegant way to dynamically check whether b is a column containing foreign keys and what model these keys are linked to? (So that I can load the foreign record by it's primary key and assign it to ModelA?) Or doesn't Django provide information like this to the programmer so I really have to get my hands dirty and use isinstance() on the foreign-key column?
You can use get_field_by_name on the models _meta object:
from django.db.models import ForeignKey
def get_fk_model(model, fieldname):
"""Returns None if not foreignkey, otherswise the relevant model"""
field_object, model, direct, m2m = model._meta.get_field_by_name(fieldname)
if not m2m and direct and isinstance(field_object, ForeignKey):
return field_object.rel.to
return None
Assuming you had a model class MyModel you would use this thus:
fk_model = get_fk_model(MyModel, 'fieldname')
Simple one liner to find all the relations to other models that exist in a model:
In [8]: relations = [f for f in Model._meta.get_fields() if (f.many_to_one or f.one_to_one) and f.auto_created]
Above will give a list of all the models with their relations.
Example:
In [9]: relations
Out[9]:
[<ManyToOneRel: app1.model1>,
<ManyToOneRel: app2.model1>,
<OneToOneRel: app1.model2>,
<OneToOneRel: app3.model5>,
<OneToOneRel: app5.model1>]
I encountered the same use case, and the accepted answer did not work for me directly. I am using Django 1.2 if it's relevant. Instead, I used the get_field_by_name method as follows.
def get_foreign_keys(self):
foreign_keys = []
for field in self._meta.fields:
if isinstance(self._meta.get_field_by_name(field.name)[0], models.ForeignKey):
foreign_keys.append(field.name)
if not foreign_keys:
return None
return foreign_keys
This is a method define inside a class. For my case, what I needed are the names of the ForeignKey fields. Cheers!
Explore the "ModelChoiceField" fields. Can they solve your problem putting foreign keys into forms for you; rather than doing that yourself.
http://docs.djangoproject.com/en/1.1/ref/forms/fields/#fields-which-handle-relationships
record = forms.ModelChoiceField(queryset=table_obj.objects.all())

Categories

Resources