Django: Model inheritance from abstract models based on condition - python

In my model there is an option field recursion_period:
class RecurringMeeting(models.Model):
RECURSION_OPTIONS = (
('daily', 'Daily'),
('weekly', 'Weekly'),
)
recursion_period = models.CharField(max_length=50, choices=RECURSION_OPTIONS)
Based on the value of the recursion_period value I would like to inherit fields from either the DailyMeeting or the WeeklyMeeting abstract models:
class DailyMeeting(models.Model):
# some fields here
class Meta:
abstract = True
and
class WeeklyMeeting(models.Model):
# some other fields here
class Meta:
abstract = True
Would it be possible to choose which model to inherit based on the value of the recursion_period field?

Related

How can I display all fields from a Many-to-Many Model in Django Admin

I have the following Models:
class ModelA(models.Model):
some_field_A = models.CharField()
some_other_field_A = models.CharField()
class ModelB(models.Model):
some_field_B = models.CharField()
many_to_many_relation = models.ManyToManyField(ModelA)
In admin.py I am using filter_horizontal to edit the ManyToManyField:
class ModelB(admin.ModelAdmin):
model = ModelB
filter_horizontal = ('many_to_many_relation',)
but it shows only some_field_A and I want it to show both fields from ModelA, because the entries in ModelA are unique depending on both fields and as you can see from the picture there are multiple entries with the same value (i.e. some_field_A = EUV) but they have different values for some_other_field_A:
It displays the result of the __str__(…) method you defined in your ModelA, so if you return the value of some_field in the __str__(…) method, then it will return only the data of some_field.
You thus can alter this method and return both fields:
class ModelA(models.Model):
some_field_A = models.CharField()
some_other_field_A = models.CharField()
def __str__(self):
return f'{self.some_field_A} {self.some_other_field_A}'
I'm not sure if this exactly the solution you are looking for but you could override the __str__ method of ModelA to return the information in a single line.
So for example:
class ModelA(models.Model):
first_field = models.CharField(max_length=16)
second_field = models.CharField(max_length=16)
def __str__(self):
return f"{self.first_field} ({self.second_field'})"
Your admin view should then show each object as "foo (bar)"

Can Django contribute_to_class method be used with abstract models?

Is there a way to use contribute_to_class method with abstract models, such that it would be executed separately for each child?
I have the following models:
class BaseHistoryModel(Model):
change = JSONField(blank=True, verbose_name=_('Change'))
comment = models.TextField(blank=True)
class Meta:
abstract = True
class HistoryHelper:
def contribute_to_class(self, cls, *_args):
self.create_history_model(cls)
def create_history_model(self, cls):
attrs = {'__module__': cls.__module__}
history_model = type(
'{}{}'.format(sender.__name__, 'History'),
(BaseHistoryModel, ), attrs
)
history_model.target = models.ForeignKey(cls)
return history_model
class BaseModel(models.Model):
name = models.CharField()
history = HistoryHelper()
class Meta:
abstract = True
I need it to create a separate history model for every child of BaseModel, but when I run makemigrations all I get is one change which creates history model for the abstract BaseModel. Is there a way to somehow get it to create one for every child?

Writable nested serializer in django-rest-framework?

My design is as following about Django ModelSerializer.
There are model A and model B. Model B has a foreign key field of Model A. For some reasons, I can not use the primary key directly to serialize Model B. As my thought, what I need is to serialize two other fields(unique together in Model A).
And I see the SlugRelatedField must be used for one slug field.
I searched there is a NaturalKeyField can support NaturalKeyField. But it looks like it is superseeded by django-rest-framework. But I checked the django-rest-framework, there is no such field at all.
Can anyone help?? What should I do?
The code is as following.
Model A
class AssetModel(models.Model):
org = models.ForeignKey(Org, related_name='models')
name = models.CharField(max_length=128)
model_type = models.SmallIntegerField(default = 3,choices = MODEL_TYPE )
directory = models.CharField(max_length = 128)
...
class Meta:
unique_together = ('org', 'name',)
Model B
class Dataitem(models.Model):
mod = models.ForeignKey(AssetModel, related_name='dataitems')
name = models.CharField(max_length=128)
data_type = models.SmallIntegerField(default =0,choices = DATAITEM_DATATYPE)
...
Serializer of model A
class AssetModelSerializer(serializers.ModelSerializer):
org = serializers.SlugRelatedField(queryset=Org.objects.all(), slug_field='name')
class Meta:
model = AssetModel
fields = ('org', 'name', 'model_type',..
Serializer of model B
class DataitemSerializer(serializers.ModelSerializer):
class Meta:
model = Dataitem
fields = ('mod', 'name','data_type'...)
The primary key of Model A is just a id Django auto added. When serialize the model B, I need to get the org and name of model A. Both read and write are needed.
Nested Serializer
You can do something like this, define a serializer for Dataitem that can reuse a serializer of the AssetModel model
class AssetModelSerializer(serializers.ModelSerializer):
class Meta:
model = AssetModel
# Fields org and name of AssetModel will be inlcuded by default
class DataitemSerializer(serializers.ModelSerializer):
class Meta:
model = Dataitem
mod = AssetModelSerializer()
# This is the Dataitem.mod field
# which is a FK to AssetModel,
# Now it'll be serilized using the AssetModelSerializer
# and include the org and name fields of AssetModelSerializer
I prefer this approach because of the control I get.
If you serialize using the above you get a structure like this:
data_item = {'name': ..., 'mod': {'org': ..., 'name': ...}}
^
|___ AssetModel fields
Alternatively you can also use depth = n
You can also use depth = 1 in Dataitem
class DataitemSerializer(serializers.ModelSerializer):
class Meta:
model = Dataitem
depth = 1 # Will include fields from related models
# e.g. the mod FK to AssetModel
Writable Nested Serializer
Because the behavior of nested creates and updates can be ambiguous,
and may require complex dependencies between related models, REST
framework 3 requires you to always write these methods explicitly.
We have to implement create/update to make this writable as per DRF's documentation
class DataitemSerializer(serializers.ModelSerializer):
class Meta:
model = Dataitem
# Nested serializer
mod = AssetModelSerializer()
# Custom create()
def create(self, validated_data):
# First we create 'mod' data for the AssetModel
mod_data = validated_data.pop('mod')
asset_model = AssetModel.objects.create(**mod_data)
# Now we create the Dataitem and set the Dataitem.mod FK
dataitem = Dataitem.objects.create(mod=asset_model, **validated_data)
# Return a Dataitem instance
return dataitem
There seems to be a library that does this
drf-writable-nested
it handles the creation and serialisation of these types
OneToOne (direct/reverse)
ForeignKey (direct/reverse)
ManyToMany (direct/reverse excluding m2m relations with through model)
GenericRelation (this is always only reverse)

django rest framework abstract class serializer

I have some models like these:
class TypeBase(models.Model):
name = models.CharField(max_length=20)
class Meta:
abstract=True
class PersonType(TypeBase):
pass
class CompanyType(TypeBase):
pass
Having this, I want to create just one serializer that holds all these field types (serialization, deserialization, update and save).
To be more specific, I want only one serializer (TypeBaseSerializer) that print the Dropdown on the UI, serialize the json response, deserialize it on post and save it for all my based types.
Something like this:
class TypeBaseSerializer(serializers.Serializer):
class Meta:
model = TypeBase
fields = ('id', 'name')
Is it possible?
I think the following approach is more cleaner. You can set "abstract" field to true for the base serializer and add your common logic for all child serializers.
class TypeBaseSerializer(serializers.ModelSerializer):
class Meta:
model = TypeBase
fields = ('id', 'name')
abstract = True
def func(...):
# ... some logic
And then create child serializers and use them for data manipulation.
class PersonTypeSerializer(TypeBaseSerializer):
class Meta:
model = PersonType
fields = ('id', 'name')
class CompanyTypeSerializer(TypeBaseSerializer):
class Meta:
model = CompanyType
fields = ('id', 'name')
Now you can use the both of these serializers normally for every model.
But if you really want to have one serializers for both the models, then create a container model and a serializer for him too. That is much cleaner :)
You can't use a ModelSerializer with an abstract base model.
From restframework.serializers:
if model_meta.is_abstract_model(self.Meta.model):
raise ValueError(
'Cannot use ModelSerializer with Abstract Models.'
)
I wrote a serializer_factory function for a similar problem:
from collections import OrderedDict
from restframework.serializers import ModelSerializer
def serializer_factory(mdl, fields=None, **kwargss):
""" Generalized serializer factory to increase DRYness of code.
:param mdl: The model class that should be instanciated
:param fields: the fields that should be exclusively present on the serializer
:param kwargss: optional additional field specifications
:return: An awesome serializer
"""
def _get_declared_fields(attrs):
fields = [(field_name, attrs.pop(field_name))
for field_name, obj in list(attrs.items())
if isinstance(obj, Field)]
fields.sort(key=lambda x: x[1]._creation_counter)
return OrderedDict(fields)
# Create an object that will look like a base serializer
class Base(object):
pass
Base._declared_fields = _get_declared_fields(kwargss)
class MySerializer(Base, ModelSerializer):
class Meta:
model = mdl
if fields:
setattr(Meta, "fields", fields)
return MySerializer
You can then use the factory to produce serializers as needed:
def typebase_serializer_factory(mdl):
myserializer = serializer_factory(
mdl,fields=["id","name"],
#owner=HiddenField(default=CurrentUserDefault()),#Optional additional configuration for subclasses
)
return myserializer
Now instanciate different subclass serializers:
persontypeserializer = typebase_serializer_factory(PersonType)
companytypeserializer = typebase_serializer_factory(CompanyType)
As already mentioned in Sebastian Wozny's answer, you can't use a ModelSerializer with an abstract base model.
Also, there is nothing such as an abstract Serializer, as some other answers have suggested. So setting abstract = True on the Meta class of a serializer will not work.
However you need not use use a ModelSerializer as your base/parent serializer. You can use a Serializer and then take advantage of Django's multiple inheritance. Here is how it works:
class TypeBaseSerializer(serializers.Serializer):
# Need to re-declare fields since this is not a ModelSerializer
name = serializers.CharField()
id = serializers.CharField()
class Meta:
fields = ['id', 'name']
def someFunction(self):
#... will be available on child classes ...
pass
class PersonTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):
class Meta:
model = PersonType
fields = TypeBaseSerializer.Meta.fields + ['another_field']
class CompanyTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):
class Meta:
model = CompanyType
fields = TypeBaseSerializer.Meta.fields + ['some_other_field']
So now since the fields name and id are declared on the parent class (TypeBaseSerializer), they will be available on PersonTypeSerializer and since this is a child class of ModelSerializer those fields will be populated from the model instance.
You can also use SerializerMethodField on the TypeBaseSerializer, even though it is not a ModelSerializer.
class TypeBaseSerializer(serializers.Serializer):
# you will have to re-declare fields here since this is not a ModelSerializer
name = serializers.CharField()
id = serializers.CharField()
other_field = serializers.SerializerMethodField()
class Meta:
fields = ['id', 'name', 'other_field']
def get_other_field(self, instance):
# will be available on child classes, which are children of ModelSerializers
return instance.other_field
Just iterating a bit over #adki's answer:
it is possible to skip model for TypeBaseSerializer;
derived serializers can refer to TypeBaseSerializer.Meta, so you would change them in a single place.
class TypeBaseSerializer(serializers.Serializer):
class Meta:
fields = ('id', 'name', 'created')
abstract = True
def func(...):
# ... some logic
class PersonTypeSerializer(TypeBaseSerializer):
class Meta:
model = PersonType
fields = TypeBaseSerializer.Meta.fields + ('age', 'date_of_birth')
class CompanyTypeSerializer(TypeBaseSerializer):
class Meta:
model = CompanyType
fields = TypeBaseSerializer.Meta.fields

Using filters with model inheritance

I have used Django Filters successfully before to filter models like so:
class ProductFilter(django_filters.FilterSet):
minCost = django_filters.NumberFilter(name="cost", lookup_type='gte')
maxCost = django_filters.NumberFilter(name="cost", lookup_type='lte')
class Meta:
model = Product
fields = ['name', 'minPrice', 'maxPrice', 'manufacturer',]
Now I want to use a Django Filter to filter between many different models which all inherit from a base model, for example (my models are not this simple but to illustrate the point):
class BaseProduct(models.Model):
name = models.CharField(max_length=256)
cost = models.DecimalField(max_digits=10,decimal_places=2)
class FoodProduct(BaseProduct):
farmer = models.CharField(max_length=256)
class ClothingProduct(BaseProduct):
size = models.CharField(max_length=256)
Is there a way to use a Django filter that would work with all models that inherit from BaseProduct? In my case there would be a large number of models some with a large number of variables.
Add to your BaseProduct
class Meta:
abstract = True
https://docs.djangoproject.com/en/dev/topics/db/models/#abstract-base-classes
Base model will not be used to create any database table. Instead, when it is used as a base class for other models, its fields will be added to those of the child class.
https://django-filter.readthedocs.org/en/latest/usage.html#the-filter
Just like with a ModelForm we can also override filters, or add new ones using a declarative syntax
class BaseProductFilter(django_filters.FilterSet):
name = django_filters.CharFilter(lookup_type='icontains')
cost = django_filters.NumberFilter(lookup_type='lt')
class FoodProductFilter(BaseProductFilter):
farmer = django_filters.CharFilter(lookup_type='icontains')
class Meta:
model = FoodProduct
fields = ['name', 'cost', 'farmer']
class ClothingProductFilter(BaseProductFilter):
# size lookup_type will be 'exact'
class Meta:
model = ClothingProduct
fields = ['name', 'cost', 'size']

Categories