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

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)

Related

Is there a way to return the opposite of a custom Django QuerySet

I am looking for a way to return the exact opposite of a custom Django Manager queryset. I am working on a project that has some querysets already defined and I want to get the opposite of these. For simplicity I'll use the example from the docs. Suppose I had a Book model and a DahlManager that filtered the books by author='Roald Dahl'. Inside the DahlManager, let's suppose I have another method get_childrens_books that only returns his childrens books, something like is_for_children=True. Is there a way to get the opposite of this new method without writing a fully separate filter or method? I want something like Books.dahl_objects.get_childrens_books().exclude() or Books.dahl_objects.exclude(get_childrens_books), rather than adding another method to DahlManager. The motivation here is that I can see the potential for adding a lot of code that is repetative. I don't want to be writing a not_filter for every filter that I write.
You can do what you are asking with a custom queryset function:
class DahlBookQueryset(models.Queryset):
def oposite(self):
current_ids = self.values_list('id', flat=True)
return Book.dahl_objects.all().exclude(id__in=current_ids)
# First, define the Manager subclass.
class DahlBookManager(models.Manager):
def get_queryset(self):
return DahlBookQueryset(self.model, using=self._db).filter(author='Roald Dahl')
def get_children_books(self):
return self.get_queryset().filter(children=True)
# Then hook it into the Book model explicitly.
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
objects = models.Manager() # The default manager.
dahl_objects = DahlBookManager() # The Dahl-specific manager.
And the query will be Books.dahl_objects.get_childrens_books().oposite().
If you don't understand something feel free to ask.
django has exclude() method for their object managers, but you need to use that during the filtering process. In other words, if you create a queryset before like your get_childrens_books() method, you cannot take it's opposite. Instead, you can useexclude() on the fly.
Let's say you want to filter Books that are not children books. This will work:
not_children_books = Books.objects.exclude(children=True)

How to get related objects from one to many relationship for display in ListView and be able to filter?

I'm looking at this tutorial from the Mozilla library. I want to create a list view in admin based on a database relationship. For example I have a Vehicle model and a statusUpdate model. Vehicle is a single instance with many statusUpdates. What I want to do is select the most recent statusUpdate (based on the dateTime field I have created) and have that data available to me in the list view.
The tutorial mentions:
class Vehicle(models.Model):
class statusUpdate(models.Model):
vehicle = models.ForeignKey(Vehicle, on_delete=models.CASCADE)
Question: How could I do a list view with model relationships and be able to filter by fields on the child relationship and pass to the view?
Here's what I wanted in a Class Based View (CBV), my explanation of my issue was not very clear.
def get_context_data(self, **kwargs):
get_context_data is a way to get data that is not normally apart of a generic view. Vehicle is already provided to the View because its the model defined for it, if you wanted to pass objects from a different model you would need to provide a new context, get_context_data is the way to do this. statusUpdate is a model with a foreign key to Vehicle. Full example below.
class VehicleDetail(generic.DetailView):
model = Vehicle
template_name = 'fleetdb/detail.html'
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(VehicleDetail, self).get_context_data(**kwargs)
context['updates'] = statusUpdate.objects.filter(vehicle_id=1).order_by('-dateTime')[:5]
return context
I don't think that solves your problem entirely. You used this:
context['updates'] = statusUpdate.objects.filter(vehicle_id=1).order_by('-dateTime')[:5]
This will only result in a list of statusUpdates where vehicle_id is set to 1. The part I was struggling with is how to get the primary key (in your case the actual vehicle_id). I found this solution:
vehicle_id = context['vehicle'].pk # <- this is the important part
context['updates'] = statusUpdate.objects.filter(vehicle_id=vehicle_id).order_by('-dateTime')[:5]
I discovered the context object and it contains the data which has already been added (thus you need to call super before using it). Now that I write it down it seems so obvious, but it took me hours to realize.
Btw. I am pretty new to Django and Python, so this might be obvious to others but it wasn't to me.

Using Django filters inside model function

The main purpose of a model is to contain business logic, so I want most of my code inside Django model in the form of methods. For example I want to write a method named get_tasks_by_user() inside task model. So that I can access it as
Tasks.get_tasks_by_user(user_id)
Following is my model code:
class Tasks(models.Model):
slug=models.URLField()
user=models.ForeignKey(User)
title=models.CharField(max_length=100)
objects=SearchManager()
def __unicode__(self):
return self.title
days_passed = property(getDaysPassed)
def get_tasks_by_user(self,userid):
return self.filters(user_id=userid)
But this doesn't seems to work, I have used it in view as:
tasks = Tasks.objects.get_tasks_by_user(user_id)
But it gives following error:
'SearchManager' object has no attribute 'get_tasks_by_user'
If I remove objects=SearchManager, then just name of manager in error will change so I think that is not issue. Seems like I am doing some very basic level mistake, how can I do what I am trying to do? I know I can do same thing via :Tasks.objects.filters(user_id=userid) but I want to keep all such logic in model. What is the correct way to do so?
An easy way to do this is by using classmethod decorator to make it a class method. Inside class Tasks:
#classmethod
def get_tasks_by_user(cls, userid):
return cls.objects.filters(user_id=userid)
This way you can simply call:
tasks = Tasks.get_tasks_by_user(user_id)
Alternatively, you can use managers per Tom's answer.
To decided on which one to choose in your specific case, you can refer James Bennett's (the release manager of Django) blog post on when to use managers/classmethod.
Any methods on a model class will only be available to instances of that model, i.e. individual objects.
For your get_tasks_by_user function to be available as you want it (on the collection), it needs to be implemented on the model manager.
class TaskManager(models.Manager):
def get_tasks_by_user(self, user_id):
return super(TaskManager, self).get_query_set().filter(user=user_id)
class Task(models.Model):
# ...
objects = TaskManager()

Can I make a model know about its ModelForm?

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

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...