Making a smart class factory in Django - python

I've been trying to figure this out for a while now with little success. I'm attempting to write a class factory that plays nice with Django's ORM, so that I can take a model schema like this:
Product
SubclassOfProduct0
SubclassOfProduct1
....
To work like this:
Product.objects.get(pk=7) // returns the result of SubclassOfProduct0(pk=7)
Product.objects.filter(propname="w00t") // returns a QuerySet of Product objects
So I was thinking something like this:
class ProductManager(models.Manager):
def get(self, *a, **kwa):
# Get the id from Products (somehow)
if product.type == Product.TYPE_SUBCLASS0:
return ProductSubClass0.objects.get(pk=kwa["pk"])
class Product(models.Model):
TYPE_SUBCLASS0 = 0
TYPE_SUBCLASS1 = 1
objects = ProductManager()
def __init__(self, *a, **kwa):
self.set_defaults()
def set_defaults(self):
pass
class ProductSubClass0(models.Model):
def set_defaults(self):
self.type == self.TYPE_SUBCLASS0
...but I don't know how to do it "right". Can someone shed some light here?

Django Tagging has a great example in the models.py as to how it figures out the content type of specific classes. I'm currently using the pattern in another module I developed with permissions.

You could just subclass your Product, as documented here: http://docs.djangoproject.com/en/1.2/topics/db/models/#model-inheritance
class OtherProduct(Product):
battery_life = …
Maybe also make Product an abstract base class if you don’t need to use it directly.

You could use entity framework with generic relations. For example, in models.py:
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
# Product
class Product(models.Model):
name = models.CharField(max_length=128)
pub_date = models.DateTimeField('date published', null=True)
productDescription = models.CharField(max_length=400)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
#Shirt Product type
class ShirtProduct(models.Model):
product = generic.GenericRelation(Product)
#Book Product type
class BookProduct(models.Model):
product = generic.GenericRelation(Product)
....
For search one product id, you can use this method in your ProductManager:
product = generic.GenericRelation(Product,
content_type_field='content_type_fk',
object_id_field='object_primary_key')
(reverse generic relations in the same section of djangoproject page)

Related

Django OneToOneField to multiple models and multi OneToOneField

I have the following models:
class District(models.Model):
pk_district = models.AutoField(primary=True)
class Block(models.Model):
pk_block = models.AutoField(primary=True)
class Community(models.Model):
pk_community = models.AutoField(primary=True)
class RelateOne(models.Model):
pk_object = models.OneToOneField('District or Block or Community')
name = models.CharField()
class RelateTwo(models.Model):
pk_object = models.OneToOneField('District or Block or Community')
name = models.CharField()
I want the RelateOne or RelateTwo model to associate District or Block or Community, and then I can use it like this:
district = District.objects.get(pk=1)
district.relate_one.name
district.relate_two.name
block = Block.objects.get(pk=1)
block.relate_one.name
block.relate_two.name
Block.objects.select_related('relate_one','relate_two')
How should I set up the model correctly?
You can use GenericForeignKey from django docs: https://docs.djangoproject.com/en/3.2/ref/contrib/contenttypes/
I would specify the key in Block or District to ensure there is only "one" block per generic relation
Make sure to specify related attributes to get the reverse relationship, although in your case you don't need that.
so it would look something like this:
class Community(models.Model):
pk_community = models.AutoField(primary=True)
content_type = models.ForeignKey(ContentType,
on_delete=models.CASCADE)
object_id = models.CharField(max_length=100)
content_object = GenericForeignKey("content_type", "object_id")
class RelateOne(models.Model):
name = models.CharField(primary_key=)
class RelateTwo(models.Model):
name = models.CharField(primary_key=True)
# to use it would be something like this:
rel1= RelationOne(name="john")
rel2= RelationTwo(name="dave")
com_data={content_object=rel1,**your_other_data}
com=Community(**com_data)
com.save
#then to access relation:
com.content_object.rel_one_or_two_field....
# remember to be careful as these are generic, it's best to let a
#serializer take care of this.

How can I pass my function inside my class model as a field?

I'm using the 3rd party library, Django Extensions, specifically for AutoSlugField().
I want to implement their use of RandomCharField() in my model for my slug field.
from django_extensions.db.fields import AutoSlugField, RandomCharField
class Post(models.Model):
class BucketObjects(models.Manager):
def get_queryset(self):
return super().get_queryset()
...
#unique_string = RandomCharField(length=10, unique=True)
slug = AutoSlugField(populate_from = models.random_string(), blank=True)
...
objects = models.Manager()
bucketobjects = BucketObjects()
def random_string(self):
unique_characters = RandomCharField(length=10, unique=True)
self.slug = unique_characters
self.save()
def __unicode__(self):
return self.stock_list
AutoSlugField requires the populate_from parameter. Passing RandomCharField() like so:
slug = AutoSlugField(populate_from = RandomCharField(), ...)
Does not work:
TypeError: 'populate_from' must be str or list[str] or tuple[str], found <django_extensions.db.fields.RandomCharField>
So instead I want to make a function within my class that creates and saves a RandomCharField to my slug field.
In the model above, you can see I commented out unique_string, if I pass that field to my populate_from parameter, then everything works fine. But it seems a redundant and a waste of space to have two model fields that are exactly the same.
How can I pass my random_string function to my slug populate_from parameter whenever a Post is created?
Thank you for the help.
You don't need to (or can't) use the RandomCharField(...) here. But, something like this will certainly work
from django_extensions.db.fields import AutoSlugField,RandomCharField
import string, random
class Post(models.Model):
slug = AutoSlugField(populate_from="random_string", blank=True)
def random_string(self):
length = 10
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=length))

DRF changing field name values of django models with foreign keys

I followed suggestion from this question
But i need to name one field of query_set to date filed of another object
My models are
class Choice(models.Model):
question = models.ForeignKey(Question, related_name='choice', on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
class ChoiceWithTime(models.Model):
choiceTime = models.ForeignKey(Choice,related_name='choiceTime', on_delete=models.CASCADE)
choice_date=models.DateField()
My view
class QuestionChoicesViewSet(viewsets.ModelViewSet):
queryset = Choice.objects.all()
serializer_class = ChoiceDateSerializer
def get_queryset(self):
return Choice.objects.values('choiceTime__choice_date','choice_text').annotate(
total_votes=Count('choiceTime__choice_date'),
)
I need to count number of submission in particular dates
I don't know how to name choiceTime__choice_date that serializer recognizes field in query set
class ChoiceDateSerializer(serializers.ModelSerializer):
choiceTime__choice_date = serializers.DateTimeField()
total_votes = serializers.IntegerField()
class Meta:
model = Choice
fields = ('id', 'choice_text','total_votes','choiceTime__choice_date')
i receive
{
"choice_text": "ant tower",
"total_votes": 3,
"choiceTime__choice_date": "2017-04-20"
}
But i want to recieve
{
"choice_text": "ant tower",
"total_votes": 3,
"choice_date": "2017-04-20"
}
Tried different options with no success. Definitely i am missing the point.
For my purposes it is working, but i want to have well written API.
2 option change time submission model?
class ChoiceWithTime(models.Model):
choiceTime = models.ForeignKey(Choice,related_name='choiceTime', on_delete=models.CASCADE)
choice_date=models.DateField()
coutner = models.IntegerField(default=0)
Is 2 option considers to be better approach to my particular problem? Thanks!
You are receiving a json object, which you add its key value.
for vote_detail in data:
if vote_detail.choiceTime__choice_date:
vote_detail.choice_date=vote_detail.choiceTime__choice_date
then serialize and save, a quick solution.
You could also add to your model the name that you want to call it. That's closer to backend and maybe worth delving into.
from django.db.models import Count,F
If anybody finds this problem and this is easiest answer i came up to.
As it was suggested before passing to serializer change value using model package functions
class QuestionChoicesViewSet(viewsets.ModelViewSet):
queryset = Choice.objects.all()
serializer_class = ChoiceDateSerializer
def get_queryset(self):
return Choice.objects.all().annotate(choice_date=F('choiceTime__choice_date')).values('choice_date','choice_text').annotate(
total_votes=Count('choiceTime__choice_date'),
)

Django rest framework represent flatten nested object

I have a parent and a one-to-one related child model and I would like to render the fields from the child flat in the parent representation (read only). Currently, I have achieved that with a custom to_representation implementation but that seems very involved and I wonder if there is no easier way to achieve this.
It is made more complicated by the fact that my related model is connected via a property.
So here is the concrete example:
By default a related object would be rendered like:
{
parent_name:'Bob',
child:{
name:'Alice'
}
}
This is what I want and currently get with my to_representation:
{
parent_name:'Bob',
child_name:'Alice'
}
My models look like this:
class ChildModel(models.Model):
name = models.CharField(max_length=100, null=True)
class ParentModel(models.Model):
name = models.CharField(max_length=100, null=True)
_child = models.ForeignKey('ChildModel', null=True)
#property
def child(self):
return self._most_recent_status
#name.setter
def child(self, value):
self._child = value
Here are my serializers:
class FlatChildField(serializers.RelatedField):
def to_representation(self, value):
return value.name
class FlatParentSerializer(serializers.ModelSerializer):
parent_name = serializers.CharField(source='name', read_only=True)
child_name = FlatChildField(source='_child', read_only=True)
class Meta:
model = Parent
fields = ('name', 'child_name')
For a simpler solution to get a flat representation of related models I would be grateful.
For completeness, I would be interested to hear if there is a simpler solution for "normal" related models (i.e. not property model fields as well). I was looking for the equivalent of the django model query syntax of related_model__field, but I cannot find that. Does that exist for django rest framework?
Many thanks
The simplest means would be to use source:
class FlatParentSerializer(serializers.ModelSerializer):
parent_name = serializers.CharField(source='name', read_only=True)
child_name = serializers.CharField(source='_child.name', read_only=True)
class Meta:
model = Parent
fields = ('name', 'child_name')
You can use SerializerMethodField, it saves you really a lot of work and it's so clean and trivial:
class FlatParentSerializer(serializers.ModelSerializer):
parent_name = serializers.CharField(source='name', read_only=True)
child_name = serializers.SerializerMethodField('get_child_name')
class Meta:
model = Parent
fields = ('name', 'child_name')
def get_child_name(self, obj):
return obj._child.name

django-rest-framework, multitable model inheritance, ModelSerializers and nested serializers

I can't find this info in the docs or on the interwebs.
latest django-rest-framework, django 1.6.5
How does one create a ModelSerializer that can handle a nested serializers where the nested model is implemented using multitable inheritance?
e.g.
######## MODELS
class OtherModel(models.Model):
stuff = models.CharField(max_length=255)
class MyBaseModel(models.Model):
whaddup = models.CharField(max_length=255)
other_model = models.ForeignKey(OtherModel)
class ModelA(MyBaseModel):
attr_a = models.CharField(max_length=255)
class ModelB(MyBaseModel):
attr_b = models.CharField(max_length=255)
####### SERIALIZERS
class MyBaseModelSerializer(serializers.ModelSerializer):
class Meta:
model=MyBaseModel
class OtherModelSerializer(serializer.ModelSerializer):
mybasemodel_set = MyBaseModelSerializer(many=True)
class Meta:
model = OtherModel
This obviously doesn't work but illustrates what i'm trying to do here.
In OtherModelSerializer, I'd like mybasemodel_set to serialize specific represenntations of either ModelA or ModelB depending on what we have.
If it matters, I'm also using django.model_utils and inheritencemanager so i can retrieve a queryset where each instance is already an instance of appropriate subclass.
Thanks
I've solved this issue a slightly different way.
Using:
DRF 3.5.x
django-model-utils 2.5.x
My models.py look like this:
class Person(models.Model):
first_name = models.CharField(max_length=40, blank=False, null=False)
middle_name = models.CharField(max_length=80, blank=True, null=True)
last_name = models.CharField(max_length=80, blank=False, null=False)
family = models.ForeignKey(Family, blank=True, null=True)
class Clergy(Person):
category = models.IntegerField(choices=CATEGORY, blank=True, null=True)
external = models.NullBooleanField(default=False, null=True)
clergy_status = models.ForeignKey(ClergyStatus, related_name="%(class)s_status", blank=True, null=True)
class Religious(Person):
religious_order = models.ForeignKey(ReligiousOrder, blank=True, null=True)
major_superior = models.ForeignKey(Person, blank=True, null=True, related_name="%(class)s_superior")
class ReligiousOrder(models.Model):
name = models.CharField(max_length=255, blank=False, null=False)
initials = models.CharField(max_length=20, blank=False, null=False)
class ClergyStatus(models.Model):
display_name = models.CharField(max_length=255, blank=True, null=True)
description = models.CharField(max_length=255, blank=True, null=True)
Basically - The base model is the "Person" model - and a person can either be Clergy, Religious, or neither and simply be a "Person". While the models that inherit Person have special relationships as well.
In my views.py I utilize a mixin to "inject" the subclasses into the queryset like so:
class PersonSubClassFieldsMixin(object):
def get_queryset(self):
return Person.objects.select_subclasses()
class RetrievePersonAPIView(PersonSubClassFieldsMixin, generics.RetrieveDestroyAPIView):
serializer_class = PersonListSerializer
...
And then real "unDRY" part comes in serializers.py where I declare the "base" PersonListSerializer, but override the to_representation method to return special serailzers based on the instance type like so:
class PersonListSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
if isinstance(instance, Clergy):
return ClergySerializer(instance=instance).data
elif isinstance(instance, Religious):
return ReligiousSerializer(instance=instance).data
else:
return LaySerializer(instance=instance).data
class Meta:
model = Person
fields = '__all__'
class ReligiousSerializer(serializers.ModelSerializer):
class Meta:
model = Religious
fields = '__all__'
depth = 2
class LaySerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
class ClergySerializer(serializers.ModelSerializer):
class Meta:
model = Clergy
fields = '__all__'
depth = 2
The "switch" happens in the to_representation method of the main serializer (PersonListSerializer). It looks at the instance type, and then "injects" the needed serializer. Since Clergy, Religious are all inherited from Person getting back a Person that is also a Clergy member, returns all the Person fields and all the Clergy fields. Same goes for Religious. And if the Person is neither Clergy or Religious - the base model fields are only returned.
Not sure if this is the proper approach - but it seems very flexible, and fits my usecase. Note that I save/update/create Person thru different views/serializers - so I don't have to worry about that with this type of setup.
I was able to do this by creating a custom relatedfield
class MyBaseModelField(serializers.RelatedField):
def to_native(self, value):
if isinstance(value, ModelA):
a_s = ModelASerializer(instance=value)
return a_s.data
if isinstance(value, ModelB):
b_s = ModelBSerializer(instance=value)
return b_s.data
raise NotImplementedError
class OtherModelSerializer(serializer.ModelSerializer):
mybasemodel_set = MyBaseModelField(many=True)
class Meta:
model = OtherModel
fields = # make sure we manually include the reverse relation (mybasemodel_set, )
I do have concerns that instanting a Serializer for each object is the reverse relation queryset is expensive so I'm wondering if there is a better way to do this.
Another approach i tried was dynamically changing the model field on MyBaseModelSerializer inside of __init__ but I ran into the issue described here:
django rest framework nested modelserializer
Using Django 3.1, I found that it is possible to override get_serializer instead of get_serializer_class, in which case you can access the instance as well as self.action and more.
By default get_serializer will call get_serializer_class, but this behavior can be adjusted to your needs.
This is cleaner and easier than the solutions proposed above, so I'm adding it to the thread.
Example:
class MySubclassViewSet(viewsets.ModelViewSet):
# add your normal fields and methods ...
def get_serializer(self, *args, **kwargs):
if self.action in ('list', 'destroy'):
return MyListSerializer(args[0], **kwargs)
if self.action in ('retrieve', ):
instance = args[0]
if instance.name.contains("really?"): # or check if instance of a certain Model...
return MyReallyCoolSerializer(instance)
else return MyNotCoolSerializer(instance)
# ...
return MyListSerializer(*args, **kwargs) # default
I'm attempting to use a solution that involves different serializer subclasses for the different model subclasses:
class MyBaseModelSerializer(serializers.ModelSerializer):
#staticmethod
def _get_alt_class(cls, args, kwargs):
if (cls != MyBaseModel):
# we're instantiating a subclass already, use that class
return cls
# < logic to choose an alternative class to use >
# in my case, I'm inspecting kwargs["data"] to make a decision
# alt_cls = SomeSubClass
return alt_cls
def __new__(cls, *args, **kwargs):
alt_cls = MyBaseModel.get_alt_class(cls, args, kwargs)
return super(MyBaseModel, alt_cls).__new__(alt_cls, *args, **kwargs)
class Meta:
model=MyBaseModel
class ModelASerializer(MyBaseModelSerializer):
class Meta:
model=ModelA
class ModelBSerializer(MyBaseModelSerializer):
class Meta:
model=ModelB
That is, when you try and instantiate an object of type MyBaseModelSerializer, you actually end up with an object of one of the subclasses, which serialize (and crucially for me, deserialize) correctly.
I've just started using this, so it's possible that there are problems I've not run into yet.
I found this post via Google trying to figure out how to handle multiple table inheritance without having to check the model instance type. I implemented my own solution.
I created a class factory and a mixin to generate the serializers for the child classes with the help of InheritanceManger from django-model-utils.
models.py
from django.db import models
from model_utils import InheritanceManager
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
# Use the InheritanceManager for select_subclasses()
objects = InheritanceManager()
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
serializers.py
from rest_framework import serializers
from .models import Location
def modelserializer_factory(model, class_name='ModelFactorySerializer',
meta_cls=None, **kwargs):
"""Generate a ModelSerializer based on Model"""
if meta_cls is None:
# Create a Meta class with the model passed
meta_cls = type('Meta', (object,), dict(model=model))
elif not hasattr(meta_cls, 'model'):
# If a meta_cls is provided but did not include a model,
# set it to the model passed into this function
meta_cls.model = model
# Create the ModelSerializer class with the Meta subclass
# we created above; also pass in any additional keyword
# arguments via kwargs
ModelFactorySerializer = type(class_name, (serializers.ModelSerializer,),
dict(Meta=meta_cls, **kwargs))
ModelFactorySerializer.__class__.__name__ = class_name
return ModelFactorySerializer
class InheritedModelSerializerMixin:
def to_representation(self, instance):
# Get the model of the instance
model = instance._meta.model
# Override the model with the inherited model
self.Meta.model = model
# Create the serializer via the modelserializer_factory
# This will use the name of the class this is mixed with.
serializer = modelserializer_factory(model, self.__class__.__name__,
meta_cls=self.Meta)
# Instantiate the Serializer class with the instance
# and return the data
return serializer(instance=instance).data
# Mix in the InheritedModelSerializerMixin
class LocationSerializer(InheritedModelSerializerMixin, serializers.ModelSerializer):
class Meta:
model = Location # 'model' is optional since it will use
# the instance's model
exclude = ('serves_pizza',) # everything else works as well
depth = 2 # including depth
views.py
from .models import Location
from .serializers import LocationSerializer
# Any view should work.
# This is an example using viewsets.ReadOnlyModelViewSet
# Everything else works as usual. You will need to chain
# ".select_subclasses()" to the queryset to select the
# child classes.
class LocationViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Location.objects.all().select_subclasses()
serializer_class = LocationSerializer

Categories

Resources