How to reference two ForeignKeys in one model - python

I want to accomplish the following:
I have three classes derived from an abstract class:
class Person(models.Model):
name = models.CharField()
...
class Meta:
abstract = True
class TypA(Person):
...
class TypB(Person):
...
class TypC(Person):
...
In another class I would like to reference TypA and TypB as a Foreign Key, something like this:
class Project(models.Model):
worker = models.ForeignKey(TypA or TypB)
Since it is not possible to declare two different models as a Foreign Key I am on the look for solutions.
I read about Generic Foreign Keys; but I am unsure how to apply that to my model.
Another idea is to use the limit_choices_to declaration for ForeignKeys.
worker = models.ForeignKey(Person, limit_choices_to={??})
But this is not possible as it seems:
Field defines a relation with model 'Person', which is either not installed, or is abstract.
Thank you in advance for the help.

A Django ForeignKey field translates to a database foreign key. Your Person model is abstract, so that one doesn't exist in the database, so there can be no foreign keys to that one.
Likewise a database foreign key can only reference one table, not two.
If you really want a flexible relation to more than one kind of table, the only possibility I see is Django's contenttypes framework.
You also want to limit the kinds of models you can point at. For that you'd best look at How can I restrict Django's GenericForeignKey to a list of models? for an example.

you just need to reference your abstract class(like JAVA):
class Project(models.Model):
worker = models.ForeignKey(Person)
#in your code:
worker = TypeA()
worker.save()
proj = Project()
proj.worker = worker
proj.save()

Related

Converting a model to its base model

Consider this file :
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
Now, let's say that I have a Restaurant, named restaurant. But this place is no longer a restaurant, so I want to transform it to a Place. For that, I do :
p = Place.objects.get(pk=place_id)
p.restaurant.delete()
p.save()
It works well, p is no longer a restaurant, but something strange happens : The primary key (ID) of p in the Place table change, like if the Place was deleted and then recreated.
Why is this happening ?
And how can I transform my restaurant to a place without changing the place ID ?
You inherited restaurant from place and Django do some stuff about this kind of relation between two table in DB.Django calls that Multi-table inheritance.
Some of the features of this type of design include the following:
PK of two objects are same.
Two object in Django point of view seems as a one object so every change in PK of one object cause automatic change in other object.
There is an automatically-created OneToOneField relation between two model.
There is atomic transaction for query in these objects.
So this is normal behavior of Django ORM and Django manage change in those two object's PK. You can read more about this concept with concrete model and multi-table inheritance in this link.

Inheritance model update to its parent model

I need extend a model from another model.
Case:
core/models.py
class Master(models.Model):
code = models.CharField(max_length=30, unique=True)
name = models.CharField(max_length=100, blank=False, null=False)
class Meta:
abstract = True
class City(Master):
zipcode = models.IntegerField()
custom/models.py
from core.models import City
class City(City)
newfield = models.CharField(max_length=20)
custom is an app.
I have tried with proxy model but it is not what I need, since proxy model adds a new table. https://docs.djangoproject.com/en/2.2/topics/db/models/#proxy-models
I need is that when I migrate add the new field to City.
More info.
In core the table is created and in custom you can add new fields that the client needs. The idea is that core is only maintained as standard.
Proxy models don't add new tables. From the docs link you mentioned:
The MyPerson class operates on the same database table as its parent Person class.
If you want one table called core_city, and another called custom_city, the second one having an extra field, you simply subclass it. Perhaps it would be easier to use an alias:
from core.models import City as CoreCity
class City(CoreCity):
newfield = models.CharField(max_length=20)
custom_city will have all fields from core_city, plus a newfield. The description of how this works (and an example) is covered in the docs section Multi-table inheritance.
If what you want is to have one single database table, then you should use a proxy Model, however they really don't allow you to create new fields. The field should be created in the parent model, or otherwise exist in the database and not be handled by Django migrations at all.
You are looking for Abstract base classes models:
Abstract base classes are useful when you want to put some common information into a number of other models. You write your base class and put abstract=True in the Meta class.
This is the base class:
#core/models.py
class City(Master):
zipcode = models.IntegerField()
class Meta:
abstract = True # <--- here the trick
Here your model:
#custom/models.py
from core.models import City as CoreCity
class City(CoreCity):
newfield = models.CharField(max_length=20)
For many uses, this type of model inheritance will be exactly what you want. It provides a way to factor out common information at the Python level, while still only creating one database table per child model at the database level.
You can update or create your class constants after its defined like this
from core.models import City
City.newfield = models.CharField(max_length=20)
You may need to use swappable models, using them you can define a City class and change it with whichever model you need later,
but that way you can't import and use the base City model directly, you may need to provide a method like get_city_model for that, as your public API.
class City(Master):
zipcode = models.IntegerField()
class Meta:
swappable = 'CORE_CITY_MODEL'
and maybe replace it later with some other model, then just set CORE_CITY_MODEL to that model in the form of 'app_name.model_name'.
The django.contrib.auth is a good example of this, you may consider checking User model and get_user_model method. Although I think you may face problems if you change your city model after you did run migrate, it may not move your data to the new table, but I'm not sure about this.

How to find all Django foreign key references to an instance

How do you find all direct foreign key references to a specific Django model instance?
I want to delete a record, but I want to maintain all child records that refer to it, so I'm trying to "swap out" the reference to the old record with a different one before I delete it.
This similar question references the Collector class. I tried:
obj_to_delete = MyModel.objects.get(id=blah)
new_obj = MyModel.objects.get(id=blah2)
collector = Collector(using='default')
collector.collect([obj_to_delete])
for other_model, other_data in collector.field_updates.iteritems():
for (other_field, _value), other_instances in other_data.iteritems():
# Why is this necessary?
if other_field.rel.to is not type(first_obj):
continue
for other_instance in other_instances:
setattr(other_instance, other_field.name, new_obj)
other_instance.save()
# All FK references should be gone, so this should be safe to delete.
obj_to_delete.delete()
However, this seems to have two problems:
Sometimes collector.field_updates contains references to models and fields that have nothing to do with my target obj_to_delete.
My final obj_to_delete.delete() call fails with IntegrityErrors complaining about remaining records that still refer to it, records that weren't caught by the collector.
What am I doing wrong?
I just need a way to lookup all FK references to a single model instance. I don't need any kind of fancy dependency lookup like what's used in Django's standard deletion view.
You can use Django's reverse foreign key support.
Say you have two models, like so:
class Foo(models.Model):
name = models.CharField(max_length=10)
class Bar(models.Model):
descr = models.CharField(max_length=100)
foo = models.ForeignKey(Foo)
Then you know you can do bar_instance.foo to access the Foo object it keys to. But you can use the reverse foreign key on a Foo instance to get all the Bar objects that point to it using, e.g, foo.bar_set.
Personally, I think the best option is to avoid the cascaded deletion.
Declaring the foreign keys in the related models with the proper Django option, e.g.
on_delete=models.SET_NULL
should suffice.
Borrowing the sample models from #Joseph's answer:
class Foo(models.Model):
name = models.CharField(max_length=10)
class Bar(models.Model):
descr = models.CharField(max_length=100)
foo = models.ForeignKey(Foo, blank=True, null=True, on_delete=models.SET_NULL))
As described in the official Django docs, here are the predefined behaviours you can use and experiment with:
SET_NULL: Set the ForeignKey null; this is only possible if null is
True.
SET_DEFAULT: Set the ForeignKey to its default value; a default for
the ForeignKey must be set.
SET(): Set the ForeignKey to the value passed to SET(), or if a
callable is passed in, the result of calling it. In most cases, passing a callable will be necessary to avoid executing queries at the time your models.py is imported:
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
def get_sentinel_user():
return get_user_model().objects.get_or_create(username='deleted')[0]
class MyModel(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.SET(get_sentinel_user))
DO_NOTHING: Take no action. If your database backend enforces
referential integrity, this will cause an IntegrityError unless you
manually add an SQL ON DELETE constraint to the database field

Python Classes Foreign Keys Inheritance

I wonder - is it possible to inherit classes like this?
For exmple, i have 2 abstract classes:
class Book(models.Model):
name = models.TextField()
class Meta:
abstract = True
class Page(models.Model)
num = models.IntegerField()
book = models.ForeignKey('Book')
class Meta:
abstract = True
And so - I want to make inherited classes for these two, let them be BigBook and BigBookPage
But if i do so, python says to me, that my FK field can't have a relation with an abstract model. And i can't find a way to redefine FK in inherited models. So do i have to ONLY create foreign keys in the inherited models - not the parents?
And if i have same model methods, that use foreign keys, defined in the parent models... i have to move them to every child - so they could use their foreign keys?
It sounds like you want to mark Book as proxy=True. See proxy models. It will create a book model, but also let you have other models that inherit from it letting you customize the functionality of the subclasses.
class Book(models.Model):
class Meta:
proxy = True
class BigBook(Book):
# BigBook properties go here.

Generic many2many relationships in Django

So, I have the 4 entities represented below which are strong and independent entities on my application, now the problem is that each Article or Picture could be "tagged" with a Presenter or an Event, being as they are the 4 of them independent entities that could become more complex It doesn't look right to add Event and Presenter field to both Article and Picture or the contrary, specially because they could be tagged with none.
In the long run as well other entities might need to be tagged and other taggable entities might appear.
class Article(models.Model):
#Fields
class Picture(models.Model):
#Fields
class Presenter(models.Model):
# Fields
class Event(models.Model):
# Fields
The closer I am getting is to some kind of double-headed Generic contenttype based intermediate model like this(haven't tested yet as it is a bit more complex than that), but I am looking for ideas:
class GenericTag(models.Model):
# Event,Presenter instance..
tagcontent_type = models.ForeignKey(ContentType)
tagobject_id = models.PositiveIntegerField()
tagcontent_object = generic.GenericForeignKey('tagcontent_type', 'tagobject_id')
# Picture,Article instance
objcontent_type = models.ForeignKey(ContentType)
objobject_id = models.PositiveIntegerField()
objcontent_object = generic.GenericForeignKey('objcontent_type', 'objobject_id')
And with that just do queries based on the information I have, I think there have to be more elegant ways to do this without stuffing all tagmodels as fields into taggablemodels.
A different approach without generic foreign keys would be to use model inheritance:
class Tag(models.Model):
pass
class Taggable(models.Model):
pass
class Article(Taggable):
# Fields
class Picture(Taggable):
# Fields
class Presenter(Tag):
# Fields
class Event(Tag):
# Fields
class TagInstance(models.Model):
tagged = models.ForeignKey(Taggable)
tag = models.ForeignKey(Tag)
This introduces additional tables for the two base classes and shared primary-key spaces for their respective instances, so they can be referenced with ordinary foreign keys. This might be reasonable depending on what further you want to do with your tags.

Categories

Resources