Change model to inherit from abstract base class without changing DB - python

I have a simple model for a product that looks like this:
class Product(models.Model):
name = models.CharField(max_length=80)
# other attributes
We already have this rolled out, and have a DB with these fields filled out. I want to change this model to inherit from a base class, that looks like this:
class BaseProduct(models.Model):
name = models.CharField(max_length=80)
class Meta(object):
abstract = True
And modify the Product class like so:
class Product(BaseProduct):
# other attributes
Based on my understanding of Abstract Base Classes, these two setups will create the same tables (right?). So technically, after changing this model, I shouldn't have to do any modifications in the database. However, when I try to apply it using South, it wants to drop the 'name' column of the Product table.
Since we already have these tables rolled out, I would ideally like to keep the 'name' column, as opposed to using other solutions (like a OneToOneField).
Thanks!

You cannot override model fields of the same name in Django, which is why South is asking you to remove the 'name' field from the child class. See https://docs.djangoproject.com/en/dev/topics/db/models/#field-name-hiding-is-not-permitted for further details.
You may need to export the existing name from each row and map them back into the updated table (perhaps by using row id as the key).

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.

Django, treat OneToOne related field as my own field

I'm essentially trying to come up with my own inheritance scheme because Django's inheritance doesn't fit my needs.
I'd like parent table(class) hold common data fields.
sub classess would have its own additional data in a separate table.
class ProductBase(models.Model):
common = models.IntegerField()
def get_price(self):
return some_price
class FooProduct(ProductBase):
# no field because I'm proxy
class Meta:
proxy = True
def get_price(self):
return price_using_different_logic
class FooExtra(models.Model):
base = models.OneToOneField(ProductBase, primary_key=True)
phone = models.CharField(max_length=10)
My question is, would it be able to treat as if Foo has FooExtra's fields?
I'd like to do things like following..
foo = FooProduct.objects.create()
foo.phone = "3333" # as django does with its multiple inheritance
foo.save()
FooProduct.objects.filter(phone="3333")
I'd like to list Products of different kind(data)
I need to list them together, so abstract Base inheritance is out
from the list, I'd like to treat each model as polymorphic model, when iterating over ProductBase.objects.all(), product.get_price() will use appropriate classe's method. (without incurring join if don't have to)
When and only when I want, I retrieve the addtional table data (by something like .select_related('fooextra')
Django-polymorphic is close to what I want, but it is rather obscure what it does so I'm afraid to use it, and I think it fails #3.
If I understand well, you want inheritance and you want the fields that are specific to the child class to be on a separate table.
As far as I know, you don't need a proxy class to achieve that, you could just implement multi-table inheritance as specified in the manual at https://docs.djangoproject.com/en/1.9/topics/db/models/#multi-table-inheritance e.g.:
class Base(models.Model):
common = models.IntegerField()
class Foo(Base):
phone = models.CharField(max_length=10)
This, as explained at the link above, will automatically create a one-to-one relationship. And of course you can do foo.phone = "3333" (where foo is of type Foo) as in your example above. And the neat thing is that you can also access foo.common whereas in your example it would have been foo.base.common.
It doesn't seem like you want anything different to Django's standard inheritance.
class ProductBase(models.Model):
common1 = models.IntegerField()
common2 = models.IntegerField()
class FooProduct(ProductBase):
fooextra = models.IntegerField()
class BarProduct(ProductBase):
barextra = models.IntegerField()
If you create instances of each:
foo1 = FooProduct(common1=1, common2=1, fooextra=1)
foo2 = FooProduct(common1=1, common2=1, fooextra=2)
bar1 = BarProduct(common1=1, common2=1, barextra=1)
bar2 = BarProduct(common1=1, common2=1, barextra=2)
You can loop over all products:
for product in ProductBase.objects.all():
print product.common1, product.common2
From a ProductBase object that is actually a FooProduct, you can get the custom field with:
product.foo.fooextra
From a ProductBase object that is actually a BarProduct, you can get the custom field with:
product.bar.barextra
You can still do querying:
foo = FooProduct.objects.get(fooextra=1)
bar = BarProduct.objects.get(barextra=2)
And you can access the common fields directly on those objects:
foo.common1
bar.common2
You can use the InheritanceManager from django-model-utils if you need more control over querying etc - and this should address point 3, too: ProductBase.objects.filter(...).select_subclasses() would give you the FooProduct and BarProduct objects instead of ProductBase objects.

I want to write a generic function to pair two database fields

Let's say that I have two teams, "red" and "black". And let's say that I have a Story class, which presents similar information in two very different ways, depending on your team:
class Story(models.Model):
red_title = models.CharField()
black_title = models.CharField()
red_prologue = models.TextField()
black_prologue = models.TextField()
# ... and so on ...
def get_field(self, genericName, team):
"""Return the field with suffix genericName belonging to the given team.
>>>self.get_field("prologue", "red") is self.red_prologue
True
>>>self.get_field("title", "black") is self.black_title
True
"""
assert(team in ["red", "black"])
specificName = "{}_{}".format(team, genericName)
return self.__dict__[specificName]
I'm happy with the getter function, but I feel like I should be able to refactor the code which created the fields in the first place. I'd like a function that looks something like this:
def make_fields(self, genericName, fieldType, **kwargs):
"""Create two fields with suffix genericName.
One will be 'red_{genericName}' and one will be 'black_{genericName}'.
"""
for team in ["red", "black"]:
specificName = "{}_{}".format(team, genericName)
self.__dict__[specificName] = fieldType(**kwargs)
But self and __dict__ are meaningless while the class is first defined, and I think Django requires that database fields be class variables rather than instance variables.
So... is there some way to create this make_fields function within Django, or am I out of luck?
Not sure why you're even doing this. A much more sane model would be:
TEAMS = (
("r","red"),
("b","black"),
)
class Story(models.Model):
team = models.CharField(max_length=1, choices=TEAMS)
title = models.CharField()
prologue = models.TextField()
Your current model is creating lots of duplicate columns (for red and black) that should just be defined by a column itself. Using the model above, you queries would be like Story.objects.filter(team="r").
You then wouldn't need your get_field function at all.
No. A Django model shouldn't be treated as something that can be dyamically constructed; it's a Python representation of a database table. For instance, what would be the semantics of changing the format of specificName after you had already run syncdb? There's no definitive, obvious answer - so Django doesn't try to answer it. You columns are defined at the class level, and that's that.
(At some level, you can always drill into the internal ORM data structures and set up these fields - but all you're doing is opening yourself up to a world of ambiguity and not-well-defined problems. Don't do it.)

How do I filter by a ForeignKey's db_column field without going through the foreign object in Django

I have a legacy database with non-django naming conventions. If I have the following (cut down) models:
class Registration(models.Model):
projectId=models.IntegerField(primary_key=True)
class Application(models.Model):
applicationId=models.IntegerField(primary_key=True)
registration=models.ForeignKey(Registration,db_column='projectId')
The ForeignKey instance causes a property to be created on Application called registration_id, but this is neither the correct name for the field (I have a hack to fix this), nor is it able to be used in a QuerySet.
Is there some way of using the id field provided by the ForeignKey on the Application model, rather than having to reference it via Registration?
Ie. I write lots of code like:
Application.objects.get(projectId=1234)
And don't want to have to write it out as:
Application.objects.get(registration__projectId=1234)
or even
Application.objects.get(registration__pk=1234)
I'm slightly surprised that:
Application.objects.get(registration_id=1234)
doesn't work...
Also note, I tried defining the id column as a field as well as the foreignkey which worked for queryset, but inserts complain of trying to insert into the same column twice:
class Application(models.Model):
...
projectId=models.IntegerField()
...
Have you tried this?
Application.objects.get(registration=1234)
I think just doing Application.objects.registration.get(projectId=1234) should do what you want.

Categories

Resources