Django abstract models with M2M fields - django

Let's suppose I have the following:
class Base(Model):
m2m_1 = ManyToManyField("SomeModel1")
m2m_2 = ManyToManyField("SomeModel2")
class Meta:
abstract = True
class A(Base):
def __init__(self):
super(A, self).__init__()
pass
class B(Base):
def __init__(self):
super(B, self).__init__()
pass
However, I cannot do that because it requires related name for M2M field. However, that does not help as the model is abstract and django tries to create the same related name for both A and B models.
Any ideas how to specify related names for each model separately or even do not use them at all?

The answer is right in the docs for abstract classes (under section entitled "Be careful with related_name"):
m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")

Related

How to chain model managers?

I have two abstract models:
class SoftDeleteModel(models.Model):
objects = SoftDeletableManager()
class Meta:
abstract = True
class BookAwareModel(models.Model):
book = models.ForeignKey(Book)
class Meta:
abstract = True
I use often use these models together for DRY purposes, e.g.:
class MyNewModel(SoftDeleteModel, BookAwareModel):
The SoftDeleteModel has a custom manager SoftDeletableManager():
class SoftDeletableManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_removed=False)
If I want to extend the BookAware abstract model to add a queryset filter on Books whilst still preserving the SoftDeletableManager() how would I go about this?
E.g. I can't add objects = BookManager() to BookAwareModel because it will overwrite the SoftDeletableManager.
Having played with your code a bit I came up with three possible solutions which seem to work (according to my tests):
Option 1:
Create a combined manager which is used when defining your concrete MyNewModel and use it for that model:
class CombiManager(SoftDeletableManager, BookAwareManager):
def get_queryset(self):
qs1 = SoftDeletableManager.get_queryset(self)
qs2 = BookAwareManager.get_queryset(self)
return qs1.intersection(qs2)
and then
class MyNewModel(SoftDeleteModel, BookAwareModel):
objects = CombiManager()
Option 2:
Create a Manager for the BookAware model as a subclass of the SoftDeleteableManager
class BookAwareManager(SoftDeletableManager):
def get_queryset(self):
return super().get_queryset().filter(your_filter)
and then add it to your BookAware model with a different name than 'objects':
class BookAwareModel(models.Model):
book = models.ForeignKey(Book)
book_objects = BookAwareManager()
class Meta:
abstract = True
allowing you to get the filtered queryset like
MyNewModel.book_objects.all()
Option 3
Put the BookAwareManager as in Option two as manager into your concrete MyNewModel. Then you can leave the managers name as the default 'objects'

Django Rest Framework: serializer for two models that inherit from a common base abstract model

There is an abstract model that defines an interface for two child models.
I've been asked to create an API endpoint that will return instances from those child models (including only the common fields defined thanks to the interface father class).
The problem raises when defining the Serializer.Meta.model attribute.
Anyway, code is always clearer:
models.py
class Children(Model):
class Meta:
abstract = True
def get_foo(self):
raise NotImplementedError
class Daughter(Children):
def get_foo(self):
return self.xx
class Son(Children):
def get_foo(self):
return self.yy
api/views.py
class ChildrenApiView(ListAPIView):
serializer_class = ChildrenSerializer
def get_queryset(self):
daughters = Daughter.objects.all()
sons = Son.objects.all()
return list(daughters) + list(sons)
serializers.py
class ChildrenSerializer(ModelSerializer):
foo = CharField(source="get_foo", read_only=True)
class Meta:
model = Children # <========= HERE IS THE PROBLEM
fields = ('foo',)
Some thoughts;
I know I'm not able to point out to the abstract model Children (wrote it for showing the inntention)
I tried to leave ChildrenSerializer.Meta.model empty
Seems that I can choose whichever Daughter or Son but not sure if that solution has any side-effect or is the way to go.
Tried to create DaughterSerializer & SonSerializer and use the method get_serializer_class(self) at the view, but wasn't able to make it run
I would probabaly not have a model serializer, and instead have a standard Serializer, with all the fields that you want to return in the view.
This will make it applicable for both Son and Daughter.
So the serializer would be something like:
class ChildrenSerializer(serializers.Serializer):
foo = CharField(source="get_foo", read_only=True)

How to implement two abstract models with ForeignKey relation of one to other in Django?

I.e. we have SomeSeries with several SomeDecors, where ForeignKey of SomeDecor points to SomeSeries. I want both to be abstract and later instantiate several pairs of it (with it's own tables in db). Is it possible?
I.e.
class SomeSeries(models.Model):
class Meta:
abstract = True
vendor = models.ForeignKey(Vendor)
name = models.CharField(max_length=255, default='')
def __unicode__(self):
return "{} {}".format(self.vendor, self.name)
class SomeDecor(WithFileFields):
class Meta:
abstract = True
series = models.ForeignKey(SomeSeries) # some magic here to make ForeignKey to abstract model
texture = models.ImageField()
# -------------------------------------------
class PlinthSeries(SomeSeries): pass
class PlinthDecor(SomeDecor): pass
# Some magic to make PlinthDecor.series points to PlinthSeries
EDIT
Actually I don't want complicity of polymorphic relations, I want pure abstract models just to save typing (what abstract models are initially for). Suppose in my case the simplest way is to exclude ForeignKey from base model and type it only in all inherited models:
class SomeSeries(models.Model):
class Meta:
abstract = True
#...
class SomeDecor(WithFileFields):
class Meta:
abstract = True
series = None #?
#..
texture = models.ImageField()
def do_anything_with_series(self): pass
class PlinthSeries(SomeSeries): pass
class PlinthDecor(SomeDecor): pass
series = models.ForeignKey(PlinthSeries)
You can't create ForeignKey referencing abstract model. It's, even, doesn't make any sense, because ForeignKey translates into Foreign Key Constraint which have to reference existing table.
As a workaround, you can create GenericForeignKey field.
You can not do it because if you create two class inherit from your abstract class to what class your foreignkey should do? for first or for second?
So you need to create GenericForeignKey or not do any field and only after create model inherits from your abstract model add your foreign key.

Django - Can I alter construction of field defined in abstract base model for specific child model?

I am adding a slug to all my models for serialization purposes, so I have defined an abstract base class which uses the AutoSlugField from django_autoslug.
class SluggerModel(models.Model):
slug = AutoSlugField(unique=True, db_index=False)
class Meta:
abstract=True
I also have a custom manager and a natural_key method defined, and at this point I have about 20 child classes, so there are several things that make using an abstract base model worthwhile besides just the single line that defines the field.
However, I want to be able to switch a few of the default arguments for initializing the AutoSlugField for some of the child models, while still being able to utilize the abstract base class. For example, I'd like some of them to utilize the populate_from option, specifiying fields from their specific model, and others to have db_index=True instead of my default (False).
I started trying to do this with a custom Metaclass, utilizing custom options defined in each child Model's inner Meta class, but thats become a rat's nest. I'm open to guidance on that approach, or any other suggestions.
One solution would be to dynamically construct your abstract base class. For example:
def get_slugger_model(**slug_kwargs):
defaults = {
'unique': True,
'db_index': False
}
defaults.update(slug_kwargs)
class MySluggerModel(models.Model):
slug = AutoSlugField(**defaults)
class Meta:
abstract = True
return MySluggerModel
class MyModel(get_slugger_model()):
pass
class MyModel2(get_slugger_model(populate_from='name')):
name = models.CharField(max_length=20)
Update: I started out with the following solution, which was ugly, and switched to Daniel's solution, which is not. I'm leaving mine here for reference.
Here's my Metaclass rat trap that seems to be working (without extensive testing yet).
class SluggerMetaclass(ModelBase):
"""
Metaclass hack that provides for being able to define 'slug_from' and
'slug_db_index' in the Meta inner class of children of SluggerModel in order to set
those properties on the AutoSlugField
"""
def __new__(cls, name, bases, attrs):
# We don't want to add this to the SluggerModel class itself, only its children
if name != 'SluggerModel' and SluggerModel in bases:
_Meta = attrs.get('Meta', None)
if _Meta and hasattr(_Meta, 'slug_from') or hasattr(_Meta, 'slug_db_index'):
attrs['slug'] = AutoSlugField(
populate_from=getattr(_Meta, 'slug_from', None),
db_index=getattr(_Meta, 'slug_db_index', False),
unique=True
)
try:
# ModelBase will reject unknown stuff in Meta, so clear it out before calling super
delattr(_Meta, 'slug_from')
except AttributeError:
pass
try:
delattr(_Meta, 'slug_db_index')
except AttributeError:
pass
else:
attrs['slug'] = AutoSlugField(unique=True, db_index = False) # default
return super(SlugSerializableMetaclass, cls).__new__(cls, name, bases, attrs)
The SlugModel looks basically like this now:
class SluggerModel(models.Model):
__metaclass__ = SluggerMetaclass
objects = SluggerManager()
# I don't define the AutoSlugField here because the metaclass will add it to the child class.
class Meta:
abstract = True
And I can acheive the desired effect with:
class SomeModel(SluggerModel, BaseModel):
name = CharField(...)
class Meta:
slug_from = 'name'
slug_db_index = True
I have to put SluggerModel first in the inheritance list for models having more than one abstract parent model, or else the fields aren't picked up by the other parent models and validation fails; however, I couldn't decipher why.
I guess I could put this an answer to my own question, since it works, but I'm hoping for a better way since its a bit on the ugly side. Then again, hax is hax so what can you do, so maybe this is the answer.

Interface like behavior with django models - Overriding models.Field

I am trying to design an abstract model that contains a field. Subclassed models will have this field, but they will be of various field types.
Example
class AbsModel(models.Model):
data = models.??? #I want subclasses to choose this
def __unicode__(self):
return data.__str__()
class Meta:
abstract = True
class TimeModel(AbsModel):
data = models.TimeField()
...
class CharModel(AbsModel):
data = models.CharField(...)
...
I am looking for a way to enforce the existence of the data field so I can write unicode once for all objects.
If this isn't possible, how can I refer to the "data" field of the subclass when calling the super class's unicode
I have a feeling this second question has an obvious answer I am missing.
It's not possible to override a superclass field where the field is of type models.Field.
https://docs.djangoproject.com/en/1.4/topics/db/models/#field-name-hiding-is-not-permitted
You can get round this by defining a field of another type in the superclass, and then overriding it in the child (perhaps include a __str__() method just in case the data field isn't overriden).
from django.db import models
class AbsDataField:
def __str__(self):
return "undefined"
class AbsModel(models.Model):
data = AbsDataField
def __unicode__(self):
return self.data.__str__()
class Meta:
abstract = True
class TimeModel(AbsModel):
data = models.TimeField()
#...
class CharModel(AbsModel):
data = models.CharField(max_length=32)
#...
You can write something like that:
class AbsModel(models.Model):
def __unicode__(self):
if hasattr(self, "data") and isinstance(self.data, models.Field):
return data.__str__()
return u"Unknown"
You cannot do that in Django:
In normal Python class inheritance, it is permissible for a child
class to override any attribute from the parent class. In Django, this
is not permitted for attributes that are Field instances (at least,
not at the moment). If a base class has a field called author, you
cannot create another model field called author in any class that
inherits from that base class.
Overriding fields in a parent model leads to difficulties in areas
such as initializing new instances (specifying which field is being
initialized in Model.__init__) and serialization. These are features
which normal Python class inheritance doesn't have to deal with in
quite the same way, so the difference between Django model inheritance
and Python class inheritance isn't arbitrary.
[...]
Django will raise a FieldError if you override any model field in any
ancestor model.