Can I make a model know about its ModelForm? - django

I have a standard Model and ModelForm set-up. I want to be able to return the ModelForm object from my Model. This involves an impossible circular reference. I thought that since Django allows foreign key models to be expressed as strings, perhaps it's possible to do something similar. At the moment I'm doing this:
class Thing(models.Model):
stuff = models.TextField()
def get_form(self):
return getattr(sys.modules[__name__], "ThingForm")(self)
class ThingForm(ModelForm):
class Meta:
model = Thing
It works. But I feel that in doing this I bring shame upon myself and my family. There must be a more honourable way.
By the way, I want do to this because I'm using ContentTypes to create generic foreign keys, so my view code doesn't know what class the model is in the static context.

That's... not an impossible circular reference. Names are only looked up when the code that references them is run.
class Thing(models.Model):
stuff = models.TextField()
def get_form(self):
return ThingForm(self)
class ThingForm(ModelForm):
class Meta:
model = Thing

Related

How to merge two models inherited from Django into one serializer

In Django, is there a way to combine two models that inherit the same class into one serializer?
from django.db.models import Model
class A(Model):
a = IntegerField(...)
# other fields...
class Meta:
abstract = True
class B(A):
# There may or may not be such a thing as class C.
class C(A):
# There may or may not be such a thing as class B.
I have code like above.
Could it be possible to create a serializer based on the class A model?
I tried to create a view table in SQL, but gave up due to performance issues.
Any good ideas please. It's so painful...😥
In general it is not a good idea to reuse serializes because doing so may expose you to unexpected behavior, when something changes in the base serializer or when you add/remove attributes to one of your models.
If model B and C have some attributes in common, then perhaps, you should consider changing your DB design.
However, in this case I would define 2 serializes that have the attribute of the abstract model:
class BSerializer(serializers.Serializer):
a = serialzier.IntegerField()
...
class CSerializer(serialziers.Serializer):
a = serializer.IntegerField()
...

What is the simplest way to fake a Django model field?

Currently my Django models looks like this:
class Car(Model):
horse_power = IntegerField()
I have lots of models similar to this, they are used all over the application, there are serializers of DRF endpoints using these model fields as well. Of course the field names are different in other models.
However, the horse_power value as well as other fields are no longer IntegerField, but a foreign key to another table containing more complex information like below:
class Car(Model):
horse_power_complex = OneToOneField(ComplexDataModel, on_delete=CASCADE)
To avoid lots of code change in every place the Car model is manipulated, I would like to keep the same getter and setter interfaces as before, in other words, I would like car.horse_power to return a simple integer calculated from the ComplexDataModel, and I also would like car.horse_power = 23 to access the ComplexDataModel and save the processed data so that I dont have to go over the entire application to update all references.
It is important to keep in mind that the model constructor should still work, for example, car = Car(horse_power=50).
I have tried python descriptors like below:
class HorsePower:
def __get__(self, obj, objtype=None):
return obj.horse_power_complex.get_value()
def __set__(self, obj, value):
obj.horse_power_complex.set_value(value)
class Car(Model):
horse_power = HorsePower()
horse_power_complex = OneToOneField(ComplexDataModel, on_delete=CASCADE)
This approach works well in most cases, but it fails to instantiate a new Car because the constructor does not accept non-django-field arguments.
Please do not question the reason it has to be made complex, because it really is complex, the car and horse power was just a way to make the example look clean and not confuse you with weird scientific terms
Use a property
class Car( Model)
horse_power_complex = ...
#property
def horse_power(self): # getter
return self.horse_power_complex.get_value()
#horse_power.setter
def horse_power( self, value):
self.horse_power_complex.set_value(value)

Django override model get() method won't work on foreign keys

I have a custom model defined as following, with get() method override
class CustomQuerySetManager(models.QuerySet):
def get(self, *args, **kwargs):
print('Using custom manager')
# some other logics here...
return super(CustomQuerySetManager, self).get(*args, **kwargs)
class CustomModel(models.Model):
objects = CustomQuerySetManager.as_manager()
class Meta:
abstract = True
Then I have two models defined as
class Company(CustomModel):
name = models.CharField(max_length=40)
class People(CustomModel):
company = models.ForeignKey(Company, on_delete=models.CASCADE)
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
If I use get() directly like People.objects.get(pk=1) then it works, "Using custom manager" gets printed, but if I try to get the foreign key info, django still uses the get() method from the default manager, nothing gets printed and the rest of logic defined won't get executed, for example
someone = People.objects.get(id=1) # prints Using custom manager, custom logic executed
company_name = someone.company.name # nothing gets printed, custom logic does not execute
Is the foreign key field in a model using a different manager even though the foreign key model is also using my custom model class? Is there a way to make my custom get() method work for all fields?
As django doc says
By default, Django uses an instance of the Model._base_manager manager
class when accessing related objects (i.e. choice.question), not the
_default_manager on the related object
See more here.
So you have to tell django model which manager to use as base manager, like this:
class CustomModel(models.Model):
objects = CustomQuerySetManager.as_manager()
class Meta:
#django will use your custom "objects" manager as base_manager
#or you may have different managers for base and default managers
#if you define two managers with different names
base_manager_name = 'objects'
abstract = True
But, please, pay attention that you do not filter away any results from base manager. Django doc says:
This manager is used to access objects that are related to from some
other model. In those situations, Django has to be able to see all the
objects for the model it is fetching, so that anything which is
referred to can be retrieved.
Therefore, do not override get_queryset() for this kind of managers.
See more here.

django: model inheritance and validators in admin

I have an abstract model that is inherited by 2 children. In the children, one is setting a validator. When I run the code, I see both children having the validator.
Here is the pseudo code:
class myAbstract(models.Model):
Intro = models.TextField(blank=True, null=True, )
class Meta:
abstract = True
class child1(myAbstract):
class Meta:
verbose_name = 'child 1'
class child2(myAbstract):
def __init__(self, *args, **kwargs):
super(child2, self).__init__(*args, **kwargs)
intro = self._meta.get_field('Intro')
intro.validators.append(MaxLengthValidator(60))
class Meta:
verbose_name = 'child 2'
In the admin if I add a child1 and then add a child2 then the validator kicks in for child2 and limits the number of characters. If I start with child2 then child2 doesn't get the validator.
Is this the expected behavior? If there, what is the suggested way of coding this? I thought about moving Intro to the child classes.
Solved:
As Alasdair pointed out the validators is a class variable therefore this is the expected behavior.
I tried moving the Intro field to the child but that didn't work. I used this solution:
https://stackoverflow.com/a/3209550/757955 that sets forms.CharField in the modelform.
The validators are not set per model instance. When you append the MaxLengthValidator, you are altering the intro field of the parent class.
I don't think there's an easy way around this. You could write a clean() method for each child model, and perform the validation there. However, I think that moving the intro field into the child classes is probably the best option here.
I did not really expect this behaviour, but there is a lot of magic happenign in the background with models. I see 2 solutions:
Change the instance, not the class. _meta is an class variable, thus _meta.get_field will return class attributes. I would rather try to manipulate the instance fields like so
def init(...):
self.intro.validators.append(MaxLengthValidator(60))
If 1 does not work, or you do not like it, leave the models alone, i.e. do not add the validator, but add the validators to the form that you use for the models. There you have more flexibility and can do what you want.

Loose coupling of apps & model inheritance

I have a design question concerning Django. I am not quite sure how to apply the principle of loose coupling of apps to this specific problem:
I have an order-app that manages orders (in an online shop). Within this order-app I have two classes:
class Order(models.Model):
# some fields
def order_payment_complete(self):
# do something when payment complete, ie. ship products
pass
class Payment(models.Model):
order = models.ForeignKey(Order)
# some more fields
def save(self):
# determine if payment has been updated to status 'PAID'
if is_paid:
self.order.order_payment_complete()
super(Payment, self).save()
Now the actual problem: I have a more specialized app that kind of extends this order. So it adds some more fields to it, etc. Example:
class SpecializedOrder(Order):
# some more fields
def order_payment_complete(self):
# here we do some specific stuff
pass
Now of course the intended behaviour would be as follows: I create a SpecializedOrder, the payment for this order is placed and the order_payment_complete() method of the SpecializedOrder is called. However, since Payment is linked to Order, not SpecializedOrder, the order_payment_complete() method of the base Order is called.
I don't really know the best way to implement such a design. Maybe I am completely off - but I wanted to build this order-app so that I can use it for multiple purposes and wanted to keep it as generic as possible.
It would be great if someone could help me out here!
Thanks,
Nino
I think what you're looking for is the GenericForeignKey from the ContentTypes framework, which is shipped with Django in the contrib package. It handles recording the type and id of the subclass instance, and provides a seamless way to access the subclasses as a foreign key property on the model.
In your case, it would look something like this:
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class Payment(models.Model):
order_content_type = models.ForeignKey(ContentType)
order_object_id = models.PositiveIntegerField()
order = generic.GenericForeignKey('order_content_type', 'order_object_id')
You don't need to do anything special in order to use this foreign key... the generics handle setting and saving the order_content_type and order_object_id fields transparently:
s = SpecializedOrder()
p = Payment()
p.order = s
p.save()
Now, when your Payment save method runs:
if is_paid:
self.order.order_payment_complete() # self.order will be SpecializedOrder
The thing you want is called dynamic polymorphism and Django is really bad at it. (I can feel your pain)
The simplest solution I've seen so far is something like this:
1) Create a base class for all your models that need this kind of feature. Something like this: (code blatantly stolen from here)
class RelatedBase(models.Model):
childclassname = models.CharField(max_length=20, editable=False)
def save(self, *args, **kwargs):
if not self.childclassname:
self.childclassname = self.__class__.__name__.lower()
super(RelatedBase, self).save(*args, **kwargs)
#property
def rel_obj(self):
return getattr(self, self.childclassname)
class Meta:
abstract = True
2) Inherit your order from this class.
3) Whenever you need an Order object, use its rel_obj attribute, which will return you the underlying object.
This solution is far from being elegant, but I've yet to find a better one...