I have a problem with django models inheritance. This is what I have :
class Room(models.Model):
name = models.CharField(max_length=32)
class Container(models.Model):
size = models.IntegerField(default=10)
...
class BigBox(Container):
room = models.ForeignKey(Room)
...
class SmallBox(Container):
big_box = models.ForeignKey(BigBox)
...
class Stuff(models.Model):
container = models.ForeignKey(Container)
...
class Meta:
ordering = ('container__???__name',)
So, with this, I'm able to put some stuff in the big box or in a small box, which is in the big box.
How can I know the type of my stuff field ´container´ in order to acces to the name of the room ? I know I can write
container__big_box__room__name
and
container__room__name
but I would like something like
container__get_room__name.
Is it possible ?
Thank you,
Alex.
To your actual quuestion about the ordering meta, my answer is: I don't think that is possible.
Now, some workarounds:
I would rethink your model hierarchy.
To me a box/container which can be cointained in another box/container is still a box.
Have a look at this alternative:
class Container(models.Model):
size = models.IntegerField(default=10)
room = models.ForeignKey(Room)
...
class ContainableContainer(Container):
parent_container = models.ForeignKey('self', null=True)
...
class Stuff(models.Model):
container = models.ForeignKey(Container)
...
class Meta:
ordering = ('container__room__name',)
With this solution you don't really need a different model, they're both containers, where a cointainer of the cointainer is optional. And so, you can do the ordering as you have already thought.
You would have to be careful with the room field management. You need to make each contained container room be equal with its container's room.
For example, override the save method or use a pre_save signal:
class ContainableContainer(Container):
parent_container = models.ForeignKey('self', null=True)
...
def save(self, *args, **kwargs):
self.room = self.parent_container.room
super(ContainableContainer, self).save(*args, **kwargs)
EDIT: This is actually a tree-like hierarchy. To make it more efficient query-wise django-mptt would be a nice choice.
It allows you to obtain the root container or to iterate over the box hierarchy with more efficient queries.
I don't have any experience with it, but it really seems the best possible solution.
Related
Is there a way to access the actual child of the base model, means: Staying with the example from the django Docs, let's assume I am modeling different delivery restaurants, that just have in common
name
all have a deliver method
as of this:
class Place(models.Model):
name = models.CharField(max_length=10)
class Pizzeria(Place):
topping = models.CharField(max_length=10)
tip = models.IntegerField()
def deliver(self):
deliver_with_topping(self.topping)
ask_for_tip(self.tip)
class Shoarma(Place):
sauce = models.CharField(max_length=10)
meat = models.CharField(max_lenght=10)
def deliver(self):
prepare_sauce_with_meat(self.sauce, self.meat)
I would now like to execute:
Place.objects.get(name="my_place").<GENERIC_CHILD>.deliver()
i.e. I don't need to know what the place is actually, just the common deliver method. The model then 'knows' what to call.
Is there something like <GENERIC_CHILD>?
I always use Inheritance Manager from django-model-utils for this kind of operations. On your models:
class Place(models.Model):
objects = InheritanceManager() #<- add inheritance manager
name = models.CharField(max_length=10)
def deliver(self):
pass #not needed
Your query:
Place.objects.get_subclass(name="my_place").deliver()
For me it is a clean and elegant solution. Don't forget to star-up django-model-util repo if you like it.
I did it in a messy way.
I do have parent class Activity, with childs - Action, Deal, Order classes.
I want to list them all in 1 place, 1) with a field specifieing it's class, 2) link them to same page, where i will render page based on Activity class
So in my model Activity i add:
def get_type(self):
children = ['action', 'deal', 'order']
for c in children:
try:
_ = self.__getattribute__(c) # returns child model
except ObjectDoesNotExist:
pass
else:
return c
else:
return 'Not specified'
I want to be able to make complex queries combining different custom manager functions from different Abstract classes.
My models are like these:
class GenderManager(models.Manager):
def male(self):
return self.filter(gender="M")
def female(self):
return self.filter(gender="F")
class SpeciesManager(models.Manager):
def lion(self):
return self.filter(species="L")
def tiger(self):
return self.filter(species="T")
class GenderModel(models.Model):
gender = models.CharField(max_length=1)
objects = GenderManager()
class Meta:
abstract = True
class SpeciesModel(models.Model):
species = models.CharField(max_length=1)
objects = SpeciesManager()
class Meta:
abstract = True
class Animal(GenderModel,SpeciesModel):
name = models.CharField(max_length=30)
age = models.DecimalField(max_digits=4, decimal_places=2)
The reason I want to split Gender and Species is that in my models sometimes I will need to inherit only from GenderModel and sometimes only from SpeciesModel.
In the cases where I want to inherit form both (like in Animal class), I would like to be able to make queries like this:
Animal.objects.male().tiger().filter(age__gte = 10.00)
But it doesn't work.
However, if I don't use custom manager functions it works:
Animal.objects.filter(gender="M").filter(species="T").filter(age__gte = 10.00)
How can I make it work with custom manager functions do make it DRY?
Thank you!
This can't be done due to how Managers actually work but there can be different ways to refactor your model and the logic, depending on the complexity and how independent the models actually need to be.
Since your manager is actually needed for the concrete class and not the abstract class, concanate both managers into a single manager, afterall you are filtering on the concrete class and not the Abstract.
class GenderModel(models.Model):
gender = models.CharField(max_length=1)
class Meta:
abstract = True
class SpeciesModel(models.Model):
species = models.CharField(max_length=1)
class Meta:
abstract = True
class AnimalManager(models.Manager):
def gender(self):
return self.filter(gender="M")
def female(self):
return self.filter(gender="F")
def lion(self):
return self.filter(species="L")
def tiger(self):
return self.filter(species="T")
def get_male_tigers(self):
return self.filter(species="T").filter(gender="M").all()
class Animal(GenderModel,SpeciesModel):
name = models.CharField(max_length=30)
age = models.DecimalField(max_digits=4, decimal_places=2)
objects = AnimalManager()
Then:
animals = Animal.objects.get_male_tigers()
Of course you can further refactor to your needs
objects, of course, can't be a reference to both GenderManager and SpeciesManager. Given how MRO works, in your example it is a SpeciesManager instance.
You can create a third manager:
class AnimalManager(GenderManager, SpeciesManager):
def male_and_tiger(self):
return self.male & self.tiger()
[all other combinations here]
class Animal(GenderModel,SpeciesModel):
[...]
objects = AnimalManager()
But really I think you are being possessed by the OOP inheritance demon :) A plain call to the orm is much better and more future proof (unless you want to add a new method to your SpeciesManager each time you add a new species)
I have the following models:
class Member(models.Model):
name = models.CharField(max_length=255)
class Location(models.Model):
name = models.CharField(max_length=255)
member = models.ForeignKey(Member)
class Department(models.Model):
name = models.CharField(max_length=255)
member = models.ForeignKey(Member)
class LocationInvite(models.Model):
sent = models.BooleanField(default=False)
location = models.ForeignKey(Location)
def send(self):
location = self.location.member
email = self.location.member.email
send_populated_email(location, email)
self.sent = True
I need to allow departments to have invites as well.
I am thinking of changing LocationInvite to Invite and making it an abstract base class. I will then create 2 concrete implementations LocationInvite and DepartmentInvite. I will move the location foreign key to the LocationInvite class.
How then will I refactor the send method of Invite to accommodate extracting the email address of either the Location or the Department, depending on the concrete implementation?
My question is, is using an abstract base class a good architecture move and how would I implement it given the constraints of the send method?
These records will be in the millions, so that is why I did not mention using generic foreign keys. Unless this is not a problem?
Refactor the attribute access into a separate property, and override in each model.
class Invite(...):
def send(...):
self._group....
...
...
class LocationInvite(Invite):
...
#property _group(self):
return self.location
class DepartmentInvite(Invite):
...
#property _group(self):
return self.department
I created an abstract base class and let both invite types extend from it.
I have following problem:
class Gift(models.Model):
name = models.CharField(max_length=255,default='')
class ProblematicGift(Gift):
# it does not help gift_ptr = models.OneToOneField(Gift, parent_link=True, default=None, null=True, blank=True, on_delete=models.DO_NOTHING)
notes = models.CharField(max_length=255,default='')
How I can delete the object of ProblematicGift in admin interface and keep the object of Gift ?
Simplified background: Automat select problematic gift and add it to table, where admin look at it, fix the gift and delete the ProblematicGift
You have three choices:
Quickest and hackiest is to just create a new Gift based on ProblematicGift and then delete ProblematicGift.
You can use abstract inheritance to make Gift a primitive type and then subclass it to create ProblematicGifts and something like GoodGifts. The procedure after that is pretty much the same: they each get separate tables, so you add a GoodGift and then delete the ProblematicGift. It's pretty much the same as #1, but a little more semantic.
Is probably your best choice: using proxy models. You add an boolean attribute to gift of the form of something like 'is_problematic'. Then, create ProblematicGift as a proxy for Gift that automatically sets is_problematic to True on creation, and override the manager to only return gifts with is_problematic set to True. Then, you simply set that attribute to False instead of deleting ProblematicGift and it leaves the queryset.
--
class Gift(models.Model):
name = models.CharField(max_length=255,default='')
notes = models.CharField(max_length=255,default='')
is_problematic = models.BooleanField(default=False)
class ProblematicGiftManager(models.Manager):
def get_query_set(self, *args, **kwargs):
qs = super(ProblematicGiftManager, self).get_query_set(*args, **kwargs)
return qs.filter(is_problematic=True)
class ProblematicGift(models.Model):
objects = ProblematicGiftManager()
class Meta:
proxy = True
def save(self, *args, **kwargs):
# Make sure it's new
if not self.pk:
self.is_problematic = True
super(ProblematicGift, self).save(*args, **kwargs)
def resolve(self):
self.is_problematic = False
self.save()
EDIT: Moved note from ProblematicGift to Gift. When using proxy models, you can't add any new fields to the subclass.
Honestly, the mistake you're making is trying to inherit from Gift. You don't want to do that for your use case.
The best way is to make Gift a stand-alone model:
class Gift(models.Model):
name = models.CharField(max_length=255,default='')
And then have ProblematicGift reference it:
class ProblematicGift(models.Model):
gift = models.OneToOneField(Gift, null=True, blank=True)
notes = models.CharField(max_length=255,default='')
# this method added based on a comment
def __unicode__(self):
return self.gift.name
Now you can delete the ProblematicGift safely.
I am trying to solve problem related to model inheritance in Django. I have four relevant models: Order, OrderItem which has ForeignKey to Order and then there is Orderable model which is model inheritance superclass to children models like Fee, RentedProduct etc. In python, it goes like this (posting only relevant parts):
class Orderable(models.Model):
real_content_type = models.ForeignKey(ContentType, editable=False)
objects = OrderableManager()
available_types = []
def save(self, *args, **kwargs):
"""
Saves instance and stores information about concrete class.
"""
self.real_content_type = ContentType.objects.get_for_model(type(self))
super(Orderable, self).save(*args, **kwargs)
def cast(self):
"""
Casts instance to the most concrete class in inheritance hierarchy possible.
"""
return self.real_content_type.get_object_for_this_type(pk=self.pk)
#staticmethod
def register_type(type):
Orderable.available_types.append(type)
#staticmethod
def get_types():
return Orderable.available_types
class RentedProduct(Orderable):
"""
Represent a product which is rented to be part of an order
"""
start_at = models.ForeignKey(Storage, related_name='starting_products',
verbose_name=_('Start at'))
real_start_at = models.ForeignKey(Storage, null=True,
related_name='real_starting_products', verbose_name=_('Real start at'))
finish_at = models.ForeignKey(Storage, related_name='finishing_products',
verbose_name=_('Finish at'))
real_finish_at = models.ForeignKey(Storage, null=True,
related_name='real_finishing_products', verbose_name=_('Real finish at'))
target = models.ForeignKey(Product, verbose_name=_('Product'))
Orderable.register_type(RentedProduct)
class OrderItem(BaseItem):
unit_price = models.DecimalField(max_digits=8, decimal_places=2,
verbose_name=_('Unit price'))
count = models.PositiveIntegerField(default=0, verbose_name=_('Count'))
order = models.ForeignKey('Order', related_name='items',
verbose_name=_('Order'))
discounts = models.ManyToManyField(DiscountDescription,
related_name='order_items', through=OrderItemDiscounts, blank=True,
verbose_name=_('Discounts'))
target = models.ForeignKey(Orderable, related_name='ordered_items',
verbose_name=_('Target'))
class Meta:
unique_together = ('order', 'target')
I would like to have an inline tied to Order model to enable editing OrderItems. Problem is, that the target field in OrderItem points to Orderable (not the concrete class which one can get by calling Orderable's cast method) and the form in inline is therefore not complete.
Does anyone have an idea, how to create at least a bit user-friendly interface for this? Can it be solved by Django admin inlines only, or you would suggest creating special user interface?
Thanks in advance for any tips.
Try inherit OrderItemInlineAdmin's Form a define your own Form there. But fingers crossed for that.
I'm looking for a solid answer to this very thing, but you should check out FeinCMS. They are doing this quite well.
See, for example, the FeinCMS inline editor. I need to figure out how to adapt this to my code.