Finding all classes that derive from a given base class in python - python

I'm looking for a way to get a list of all classes that derive from a particular base class in Python.
More specifically I am using Django and I have a abstract base Model and then several Models that derive from that base class...
class Asset(models.Model):
name = models.CharField(max_length=500)
last_update = models.DateTimeField(default=datetime.datetime.now())
category = models.CharField(max_length=200, default='None')
class Meta:
abstract = True
class AssetTypeA(Asset):
junk = models.CharField(max_length=200)
hasJunk = models.BooleanField()
def __unicode__(self):
return self.junk
class AssetTypeB(Asset):
stuff= models.CharField(max_length=200)
def __unicode__(self):
return self.stuff
I'd like to be able to detect if anyone adds a new AssetTypeX model and generate the appropriate pages but currently I am maintaining a list manually, is there a way to determine a list of class names for anything that derives from "Asset"?

Asset.__subclasses__() gives the immediate subclasses of Asset, but whether that's sufficient depends on whether that immediate part is a problem for you -- if you want all descendants to whatever number of levels, you'll need recursive expansion, e.g.:
def descendants(aclass):
directones = aclass.__subclasses__()
if not directones: return
for c in directones:
yield c
for x in descendants(c): yield x
Your examples suggest you only care about classes directly subclassing Asset, in which case you might not need this extra level of expansion.

Related

django multitable inheritance in django modeladmin

I have to models
class Parent(object):
text_field = models.TextField()
boolean_field = models.BooleanField()
class Child(Parent):
another_text_field = models.TextField()
With the following ModelAdmin
class ChildAdmin(admin.ModelAdmin):
pass
admin.site.register(Child, ChildAdmin)
I currently see all fields in the admin page, i.e. text_field, boolean_field, and another_text_field.
Question: How can I get a parent select field and exclude text_field and boolean_field (for latter I guess I can use exclude).
Current Solution: I add a Form and use its clean method to set the parent field. text_field and boolean_field can be excluded by addind it to the ModelAdmin's excluded variable.
simply overwrite fields
class Child(Parent):
another_text_field = models.TextField()
text_field = None
boolean_field = None
if you want to use inheritance in django models use abstract models please.
I am not sure if it is really necessary to use model inheritance. if not, you may consider using OneToOneField without model inheritance.
Example using OneToOneField:
models.py
class Parent(models.Model):
text_field = models.TextField()
boolean_field = models.BooleanField()
class Child(models.Model):
parent = models.OneToOneField(Parent,
on_delete=models.CASCADE,
primary_key=True)
child_field = models.TextField()
admin.py
#admin.register(Parent)
class ParentAdmin(admin.ModelAdmin):
pass
doing so you can see a drop down menu for picking Parent instance at child admin page. but meanwhile, you lose one 'benefit' of using inheritance, which is the availability of Parent field in Child
as mentioned in the docs,
All of the fields of Place will also be available in Restaurant,
although the data will reside in a different database table.
but there is a easy fix for that, just use something like Child.objects.filter(parent__text_field="something"). Query performance should be the same (I guess) since implementation in db are basically the same for these two approaches (both use separated table) (please correct if I am wrong)
Apart from from this and admin display behavior, I am not sure how these two approaches (your approach and this answer) are differed.

Defining more models in a decorator

I have a pattern that I'd like to make as reproducible as possible, it goes something like this:
class TranslatedThing(models.Model):
name = models.Charfield(max_length=100)
class Thing(models.Model):
translation = models.ForeignKey(TranslatedThing)
name = models.Charfield(max_length=100)
The idea being that in my raw data I have some Things, which map to a reduced set of translated Things. Across many different data sets I have many different types of Things.
I already use an Abstract class to reduce the complexity of this pattern:
class AbstractThing(models.Model):
name = models.CharField(max_length=100)
class Meta:
abstract = True
-------
class TranslatedThing(AbstractThing):
pass
class Thing(AbstractThing):
translated = models.ForeignKey(TranslatedThing)
But I'd like to automate the creation and linkage to TranslatedThing. Is this possible with a decorator? e.g.
#translate
class Thing(AbstractThing): pass
----
Thing.objects.filter(translation__name="foo") #works
I've read through but it looks like maybe not. Is there any other way to reduce the repetition of code while using this pattern?

Best practices for products catalogue in Django

I'm working on a catalogue application for storing product information in Django.
The challenge here is that there are a lot of products in the catalogue, each with their own attributes. Since this is a key part of the application, and a lot will be built around it, I want this to be designed as good as possible.
I know there are several options of handling this in Django, but I would like to know what others have experienced with these several options.
The first option: Single abstract class
I could just create a single abstract class, which contains all the base attributes, and let other classes derive from that one class.
class Product(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(max_length=1000)
price = models.DecimalField()
class Meta:
abstract = True
class Phone(Product):
series = models.CharField(max_length=100)
This would be the most straightforward option, but this will include a lot of work in Django Forms and Views.
This will also create a single table for each Product subclass, so when the Product class is changed, all other tables will have to changed as well.
The second option: Product base class
Here the Product class is not abstract, which implies this class can be used as well.
class Product(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(max_length=1000)
price = models.DecimalField()
class Phone(Product):
series = models.CharField(max_length=100)
This would also be pretty straightforward, but this would still imply a lot of work in the Forms and Views.
This would first create a table for the Product class, and then a single table for each Product subclass.
The first 2 options will also break the DRY principle, because attributes will have to be added to every Product subclass that might be common to some classes, but not to all.
Third option: Product class containing all the possible attributes.
class Product(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(max_length=1000)
price = models.DecimalField()
# attributes for phones, tv's, etc...
series = models.CharField(max_length=100)
class PhoneForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'description', 'price', 'series']
A new form will have to be created for each product subclass. This does seem pretty easy, but the Product model will become very bloated.
In this case I could also use Proxy Models.
Fourth option: Create Abstract classes and use class Mixins
class ProductBase(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(max_length=1000)
price = models.DecimalField()
class Meta:
abstract = True
class ElectronicsProduct(models.Model):
series = models.CharField(max_length=100)
class Meta:
abstract = True
class Phone(ProductBase, ElectronicsProduct):
pass
This method could solve the DRY problem I have with the issue I had above, but still not optimal.
Fifth option: One Product model with a separate attribute model
This is a method I would like to use anyway, but more to have the ability to add 'extra' features to a product that is too specific to put in a Product or Product subclass.
class Product(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(max_length=1000)
price = models.DecimalField()
class Attribute(models.Model):
name = models.CharField(max_length=100)
class ProductAttribute(models.Model):
product = models.ForeignKey(Product)
attribute = models.ForeignKey(Attribute)
value = models.CharField(max_length=100)
The question here is if this method should be used for all product attributes, since I think this will add a lot of overhead on the database.
Another challenge here is the value type. In this case I can only use a character value, so what happens when I would like to use a Decimal value, or a File.
Sixth option: Something else
There are probably some methods I have not thought of at this point. So if you know something I don't please share it with me.
I am not looking for any opinions here, but for some solutions. So if you have an answer to this question please tell us why you would use the method you propose.

Is it appropriate to use Django's abstract classes for simple code reuse?

I am learning Django and writing my first semi-complex model. Many of my tables are made with a similar view style in mind, so many objects have a name, a description and an image. Ex:
class Ingredient(models.Model):
# Standard to many classes
name = models.CharField(max_length=50)
description = models.TextField()
image_url = models.URLField(max_length=200)
# Unique to this class
foodgroup = ..... etc.
Since name, description and url will be common to many objects (that are otherwise totally different), I was considering defining a base class that each can inherit from:
class BaseObjectWithImage(models.Model):
# Standard to many classes
name = models.CharField(max_length=50)
description = models.TextField()
image_url = models.URLField(max_length=200)
class Meta:
abstract = True
class Ingredient(BaseObjectWithImage):
# Unique to this class
foodgroup = ..... etc.
My question - which may be closer to a simple OOP Best Practices question - is whether this is a silly use of Django's abstract class, or if is worthwhile for stripping out 3xN lines of code and allowing me to treat most model classes as a generic type.
Not a silly use of abstact classes. Both Django and python are relatively polygamous in that they allow you to use many different programming styles (whichever one suits you best). Just make sure that you are making a consistent and conscious decision as to whether the models create individual tables each with its own copy of the repeated columns or whether they each contain a ForeignKey to a common base table.

django simple history - using model methods?

I'm using django-simple-history:
http://django-simple-history.readthedocs.io/en/latest/
I have a model, which I would like to apply its methods on an historical instance. Example:
from simple_history.models import HistoricalRecords
class Person(models.Model):
firstname = models.CharField(max_length=20)
lastname = models.CharField(max_length=20)
history = HistoricalRecords()
def fullName(self):
return firstname + lastname
person = Person.objects.get(pk=1) # Person instance
for historyPerson in person.history:
historyPerson.fullName() # wont work.
Since the class HistoricalPerson does not inherit the methods of Person. But using Person methods actually make sense, since they share the same fields..
Any solution for this? I'd prefer something simple, not like duplicating every method in my models for the history instances..
I found another workaround (maybe it's just the addon had been updated and got this feature). It's based on the documentation: adding-additional-fields-to-historical-models
HistoricalRecords field accepts bases parameter which sets a class that history objects will inherit. But you can't just set bases=[Person] inside Person class description, because it's not yet initialized.
So I ended up with an abstract class, which is inherited by both Person class and HistoricalRecords field. So the example from the question would look like:
class AbstractPerson(models.Model):
class Meta:
abstract = True
firstname = models.CharField(max_length=20)
lastname = models.CharField(max_length=20)
def fullName(self):
return firstname + lastname
class Person(AbstractPerson):
history = HistoricalRecords(bases=[AbstractPerson])
And now history objects can use fullName method.
For anyone else having the same problem, I made it work by calling the method from the original class on the historical record object. So for the example in the question, a solution could be:
for historyPerson in person.history:
Person.fullName(historyPerson)
This works because methods are very much like functions in Python, except that when you call a method on an instance, the instance is implicitly passed as the first parameter for the method. So if you have a class like:
class Foo:
def method(self):
....
doing
f = Foo()
f.method()
is the same as:
f = Foo()
Foo.method(f)
I don't know exactly why simple-history does not copy the original model's methods though. One reason might be that since it allows you to exclude fields to be recorded, having the original methods might not make sense, since a method might not work if it uses fields that are not recorded in the historical record.

Categories