I'm upgrading a project from django 1.8 to 1.10 and it looks like django has improved the check of eventual name collision between foreign keys and model inheritance.
This is obviously a good thing, but the projet I need to upgrade is a big one and it would be a hell to rename a model.
Let me explain the problem : I have a base class called Parent and many children which are linked together, like so :
class Parent(models.Model):
title = models.CharField(max_length=10)
class ChildA(Parent):
description = models.TextField()
class ChildB(Parent):
description = models.TextField()
childa = models.ForeignKey(ChildA)
The clash here is that a childb object has 2 "childa" attributes :
The "childa" ForeignKey
The instance inherited by the ChildA model (because childb has also the parent attributes).
The 2 obvious solutions here are :
Rename the ForeignKey ChildB.childa to ChildB.somethingelse
Rename the ChildA model to something else.
Both solutions costs a lot and will probably introduce new bugs.
So I wondered : Is it possible to rename the reverse related name of the inherited object ?
For example :
p = Parent.objects.get(pk=1)
print p.childa_child # Hit the ChildA instance
I have no idea if I'm clear enough but I'll keep this question up to date.
==== EDIT ====
To be more concise, if I have 2 models class Parent(models.Model) and class Child(Parent), a dynamic attribute parent.child is created.
Is it possible to edit this attribute name without touching the class name ?
Multi-table inheritance creates an implicit OneToOneField field between the base model and the subclass.
Django allows you to modify this relationship by explicitly setting the one to one field.
class Parent(models.Model):
title = models.CharField(max_length=10)
class ChildA(Parent):
parent = models.OneToOneField(to=Parent, parent_link=True)
description = models.TextField()
class ChildB(Parent):
parent = models.OneToOneField(to=Parent, parent_link=True)
description = models.TextField()
childa = models.ForeignKey(ChildA)
The important bit here is the parent_link=True argument which tells Django to use this field declaration for managing the multi-table inheritance with these two models.
So you can now set related_name='+' to prevent Django from creating a reverse relationship or you can set related_name to a more unique name:
class ChildA(Parent):
parent = models.OneToOneField(to=Parent, parent_link=True, related_name='child_a_here')
description = models.TextField()
class ChildB(Parent):
parent = models.OneToOneField(to=Parent, parent_link=True, related_name='child_b_here')
description = models.TextField()
childa = models.ForeignKey(ChildA)
I'm a little confused as to how ChildB has to two ChildA links, it looks like you left out some relationship fields from the models perhaps? Regardless, what I think you are looking for is the related_name parameter.
So with:
class ChildB(Parent):
description = models.TextField()
childa = models.ForeignKey(ChildA, related_name='b_children')
You could do a query like so:
a = ChildA.objects.get(id=1)
print(a.b_children)
You may also be interested in abstract models.
Related
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.
I have this abstract model:
class HasSystemMessage(models.Model):
class Meta:
abstract = True
messages = models.ManyToManyField(SystemMessage, related_name=?)
I am going to use this abstract model in at least three other models, lets say, A, B, and C. How can I set the related_name dynamically for these classes? for example, for class B, I want the related_name to be Bs. Is it possible to do so?
To further clarify the question, The classes will look like this:
class B(HasSystemMessage):
# Some model fields
class A(HasSystemMessage):
# Some model fields
HasSystemMessage.objects.filter(a__contains=[some elements])
You can use %(class)s or %(app_label)s
class HasSystemMessage(models.Model):
class Meta:
abstract = True
messages = models.ManyToManyField(SystemMessage, related_name=%(app_label)s_%(class)s_related)
From Django docs
Be careful with related_name and related_query_nameĀ¶ If you are using
related_name or related_query_name on a ForeignKey or ManyToManyField,
you must always specify a unique reverse name and query name for the
field. This would normally cause a problem in abstract base classes,
since the fields on this class are included into each of the child
classes, with exactly the same values for the attributes (including
related_name and related_query_name) each time.
To work around this problem, when you are using related_name or
related_query_name in an abstract base class (only), part of the value
should contain '%(app_label)s' and '%(class)s'.
'%(class)s' is replaced by the lower-cased name of the child class
that the field is used in. '%(app_label)s' is replaced by the
lower-cased name of the app the child class is contained within. Each
installed application name must be unique and the model class names
within each app must also be unique, therefore the resulting name will
end up being different.
Ref: https://docs.djangoproject.com/en/2.0/topics/db/models/#be-careful-with-related-name-and-related-query-name
You Just need to put a string in this attribute which specifies the name of the reverse relation from the SystemMessage.Also read in Django Docs
Try this:
class HasSystemMessage(models.Model):
class Meta:
abstract = True
messages = models.ManyToManyField(SystemMessage, related_name='system_message')
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.
class ItemForm(djangoforms.ModelForm):
class Meta:
model = Item
exclude = ['added_by']
i can not understand what this piece of code is doing .i understood that ItemForm is inheriting Modelform but then a class definition inside a class ??
The Item class is :
class Item(db.Model):
name = db.StringProperty()
quantity = db.IntegerProperty(default=1)
target_price = db.FloatProperty()
priority = db.StringProperty(default='Medium',choices=[
'High', 'Medium', 'Low'])
entry_time = db.DateTimeProperty(auto_now_add=True)
added_by = db.UserProperty()
It's part of Django's magic. The metaclass for ModelForm (among other classes) looks for an inner Meta class and uses it to make various changes to the outer class. It's one of the deeper parts of Python that most people will never have to deal with first-hand.
In Python you can define classes within other classes as a way of encapsulating the inner class. The way Django is using this is actually quite excellent.
See this link for more info: http://www.geekinterview.com/question_details/64739
Meta is a special class definition.
In this example, it is a simple inheritance model. ModelForm creates a form based on a Model class, so via giving a class definiton to ModelForm class, it creates the form elements according to related Model class definiton.
I have the following model in django:
class Node(models.Model):
name = models.CharField(max_length=255)
And this subclass of the above model:
class Thingy(Node):
name = models.CharField(max_length=100)
otherstuff = models.CharField(max_length=255)
The problem with this setup is that while everything Just Works, a look into the database shows that syncdb has created two tables. One called appname_node with a column called name and another one called appname_thingy with two columns: name and otherstuff. When a new object is created, the name value is copied into both tables... not really cool if you dig the whole concept of normalisation :-)
Can someone explain to me how I might modify the max_length value of the "name" property in "Thingy" without re-defining it?
Your implementation is totally wrong, that is not how you suppose to write parent and child class. either define name in parent class or child class, if you define it in a parent class then you can't define again in the child because new field will be created instead. so if you want to change max_length in the child, then I would recommend that you declare name in the child so that any class that inherits from node will set its own name with its own max_length.
the correct implementation is
class Node(models.Model):
#other class attributes
child class should inherit parent attributes and add its own stuff.
class Thingy(Node):
name = models.CharField(max_length=100)
otherstuff = models.CharField(max_length=255)
now when you query, you only get one name instead of the two.