Converting a model to its base model - python

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.

Related

Django many to many relation, include all IDs in queryset in both directions

I have 2 models connected via M2M relation
class Paper(models.Model):
title = models.CharField(max_length=70)
authors = models.ManyToManyField(B, related_name='papers')
class Author():
name = models.CharField(max_length=70)
Is there a way to include authors as all related authors' IDs (and maybe name somehow)?
Is there a way to include papers IDs as reverse relation (and maybe title as well)?
Author.objects.all().annotate(related_papers=F('papers'))
this only adds id of one paper, first one it finds I think.
Furthermore, changing related_papers to papers gives an error:
ValueError: The annotation ‘papers’ conflicts with a field on the
model.
From what I understand in your comments, you're using DRF. I will give you 2 answers.
1) If you're talking about model serializer, you can use PrimaryKeyRelatedField :
class AuthorSerializer(serializers.ModelSerializer):
papers=serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Author
fields = ['name', 'papers']
class PaperSerializer(serializers.ModelSerializer):
class Meta:
model = Paper
fields = '__all__'
This will return the IDs for the other side of the relationship whether you're on Paper or Author side. That will return the primary keys, not a representation of the object itself.
2) Now you're also talking about performance (e.g. database hit at each iteration).
Django (not DRF-specific) has a queryset method to handle preloading related objects. It's called prefetch_related.
For example, if you know you're going to need the relation object attributes and want to avoid re-querying the database, do as follow:
Author.objects.all().prefetch_related('papers')
# papers will be already loaded, thus won't need another database hit if you iterate over them.
Actually, it has already been implemented for you. You should include a Many-to-Many relationship to author in your Paper model like this:
class Paper(models.Model):
title = models.CharField(max_length=70)
authors = models.ManyToManyField(Author, related_name='papers')
That gives you the opportunity to add Author objects to a related set using
p.authors.add(u), assuming that p is the object of Paper model, and a is an object of Author model.
You can access all related authors of a Paper instance using p.authors.all().
You can access all related papers of an Author instance using u.papers.all().
This will return an instance of QuerySet that you can operate on.
See this documentation page to learn more.

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.

Any way to fetch the through fields for an object linked via Many2Many field without knowing the column name in advance?

I am trying to write a generic method that can take any Django Model and returns it in a dictionary form.
So for example, if my models are defined thus (very generic):
class A(models.Model):
somefieldA = models.TextField()
m2mfield = models.ManyToManyField(B, through='AandB')
def __unicode__(self):
return self.somefieldA
class B(models.Model):
somefieldB = models.TextField()
def __unicode__(self):
return self.somefieldB
class AandB(models.Model):
a = models.ForeignKey(A)
b = models.ForeignKey(B)
field1 = models.DecimalField()
field2 = models.TextField()
field3 = models.DateField()
Now, assume we have an instance of the object A a_obj.
I can get all the related B objects using:
# This loop is there because I am working with other fields as well.
def instance_to_dict(instance):
for field in instance._meta.get_fields():
if field.many_to_many:
m2m_mgr = getattr(instance, field.name)
for idx, assoc_obj in enumerate(m2m_mgr.all()):
assoc_obj_str = str(assoc_obj)
# How to obtain the related through field values?
# m2m_mgr.through.objects.get() would need prior knowlegde
# of field name so get(a=instance, b=assoc_obj) is not possible
# m2m_mgr.through.objects.all() fetches all the objects
# in the Many to Many manager.
And then call instance_to_dict(a_obj). This method could be called by passing other models' instances.
Ideally, I would like to create a dict of the obj and related "through" fields for any object. Is this possible to do?
In addition to the explicitly defined ManyToMany manager, there is also an implicit reverse relationship for the ForeignKey from AandB to A. So you can do something like this:
for field in instance._meta.get_fields(include_hidden=True):
if field.one_to_many: # reverse ForeignKey
m2m_through_mgr = getattr(instance, field.get_accessor_name()) # e.g. aandb_set
m2m_through_mgr.all() # all related instances from the through table
Another approach is to go through the through table fields looking at field.related_model to see which one points back to your original table.
This all gets quite messy, but there should be enough meta information to do what you want. One obstacle is that the API isn't fully documented. Specifically, relation fields are represented by instances of the ManyToOneRel class, which as of Django 2.1 remains undocumented for reasons hinted at in the source code. Hence my use of the undocumented get_accessor_name() method.

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

How do Django model fields work?

First of all,I'm not into web programming. I bumped into django and read a bit about models. I was intrigued by the following code ( from djangoproject.com ) :
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
def __str__(self):
# Note use of django.utils.encoding.smart_str() here because
# first_name and last_name will be unicode strings.
return smart_str('%s %s' % (self.first_name, self.last_name))
By my understanding of python , first_name and last_name are class variables , right ? How is that used in code ( because I guess that setting Person.first_name or Person.last_name will affect all Person instances ) ? Why is it used that way ?
The essence of your question is "how come these class variables (which I assign Field objects to) suddenly become instance variables (which I assign data to) in Django's ORM"? The answer to that is the magic of Python metaclasses.
A metaclass allows you to hook into and modify the process of creating a Python class (not the creation of an instance of that class, the creation of the class itself).
Django's Model object (and thus also your models, which are subclasses) has a ModelBase metaclass. It looks through all the class attributes of your model, and any that are instances of a Field subclass it moves into a fields list. That list is assigned as an attribute of the _meta object, which is a class attribute of the model. Thus you can always get to the actual Field objects via MyModel._meta.fields, or MyModel._meta.get_field('field_name').
The Model.__init__ method is then able to use the _meta.fields list to determine what instance attributes should be initialized when a model instance is created.
Don't be afraid to dive into the Django source code; it's a great source of education!
Yes, first_name and last_name are class variables. They define fields that will be created in a database table. There is a Person table that has first_name and last_name columns, so it makes sense for them to be at Class level at this point.
For more on models, see:
http://docs.djangoproject.com/en/dev/topics/db/models/
When it comes to accessing instances of a Person in code, you are typically doing this via Django's ORM, and at this point they essentially behave as instance variables.
For more on model instances, see:
http://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs
Not a real answer, but for enrichment:
Person.first_name
won't work
p = Person.objects.get(pk=x)
p.first_name
will work. so an object instance of person has a first and last name, but static context Person does not.
Also note: Django has Model Managers which are allow "Person" to do static queryset operations. (https://docs.djangoproject.com/en/dev/topics/db/managers/#managers).
so for example
peoples = Person.objects.all()

Categories

Resources