I have a parent class and a child class which inherits parent. In a child class object, I need to differentiate between parent class fields and child class fields.
Is this information available? If so, where. Need solution in Python, specifically Django.
First of all, avoid any design where you need to do this. Perhaps you should be getting separate parent objects?
However, you can do this by examining the ._meta.fields property of any django model instance.
Consider such a parent and child model:
class Parent(models.Model):
parent_field = models.CharField(max_length=100)
class Child(Parent):
child_field = models.CharField(max_length=100)
To make a function that determines if a field is defined in one of the parent, we should iterate over the class.__bases__, and try baseclass._meta.get_field for each
def is_attr_defined_on_parent(child, attr):
for parent in child.__bases__:
try:
parent._meta.get_field(attr)
except models.FieldDoesNotExist:
continue
else:
return True
return False
Now:
is_attr_defined_on_parent(Child, 'parent_field') returns True,
is_attr_defined_on_parent(Child, 'child_field') returns False.
Related
Suppose, I have the following parent class:
class Parent:
permissions = ['CanEdit', 'CanCreate']
I need to extend permissions attribute in my child class without changing the initial contents and so that this change would be on the class level, not instance, meaning:
print(Parent.permissions)
Output:
['CanEdit', 'CanCreate']
And I need the something like this in my child class:
class Child(Parent):
permissions += ['CanManage']
print(Child.permissions)
With the output:
['CanEdit', 'CanCreate', 'CanManage']
Is this even possible to implement?
You can use permissions = Parent.permissions + ['CanManage'].
I want to enforce a Child class to set several attributes(instance variables), to achieve I am using abstractproperty decorator in python 2.7. Here is the sample code:
from abc import ABCMeta, abstractproperty
class Parent(object):
__metaclass__ = ABCMeta
#abstractproperty
def name(self):
pass
class Child1(Parent):
pass
class Child2(Parent):
name = 'test'
class Child3(Parent):
def __init__(self):
self.name = 'test'
obj_child1 = Child1()
Child1 object creation gives an error as expected.
obj_child2 = Child2()
Child2 object creation works fine as because abstract attribute 'name' is set
However
obj_child3 = Child3()
gives TypeError: Can't instantiate abstract class Child3 with abstract methods name
I did not understand:although the attribute 'name' is set in the init
method, why the Child3 object creation is throwing type error.
My requirement is set attributes inside a method of child class. An explanation of what is wrong with child2 class and if there is a better way to enforce a child class to set attributes in a method will help me. Thanks in advance.
A simpler solution will be
class Parent(object):
name = None
def __init__(self):
if self.name == None:
raise NotImplementedError('Subclasses must define bar')
class Child1(Parent):
pass
class Child2(Parent):
name = 'test'
class Child3(Parent):
def __init__(self)
self.name = 'test'
obj1 = Child1() raises NotImplementedError
obj2 = Child2() works fine
obj3 = Child3() works fine. This is what you need to enforce a child class to set attribute name and set the attribute from a method.
Here's what I believe is happening.
When you try to instantiate Child1, you have not provided an implementation for the abstract property name. You may have done so like this:
class Child1(Parent):
#property
def name(self):
return "foo"
Since the class does not provide an implementation for all abstract methods inherited from its parents, it is effectively an abstract class itself, and therefore cannot be instantiated. Trying to instantiate it gives you your TypeError.
In the case of Child2, you define it as:
class Child2(Parent):
name = 'test'
In this case, you're overwriting the name property of the class so that it's no longer referencing an abstract property that needs to be implemented. This means that Child2 is not abstract and can be instantiated.
One difference I noticed was that when name = 'test' as you've implemented it, vars(Child2) returns output like this:
{..., '__abstractmethods__': frozenset(), ..., 'name': 'test'}
However, when you change this to something like foo = 'test', you get this:
{..., '__abstractmethods__': frozenset({'name'}), ..., 'foo': 'test'}
Notice that the __abstractmethods__ property is an empty set in the case that you define name = 'test' i.e. when Child2 can be instantiated.
Finally, in the case of Child3 you have to bear in mind that the abstract property is stored as a property of the class itself, which you don't redefine anywhere.
As soon as you try to create an instance of it, the interpreter will see that it's missing an implementation for at least one abstract method and throw the TypeError you see. It doesn't even reach the assignment self.name = 'test' in the constructor.
To answer your second part about how to enforce children to always provide an implementation for abstract properties/methods when they can do something like you did - I'm not actually sure if it's possible. Python is a language of consenting adults.
You can do some like this,
parent class:
class Parent(object):
__metaclass__ = ABCMeta
#abstractproperty
def name(self):
pass
Child class:
class Child(Parent):
name = None
def __init__(self):
self.name = 'test'
Now
obj = Child()
obj.name
gives the required output of 'test'.
By doing this you can enforce a class to set an attribute of parent class and in child class you can set them from a method.
It is not a perfect solution that you require, but it is close. The only problem with this solution is you will need to define all the abstractproperty of parent as None in child class and them set them using a method.
I have some code like this for Django-Tastypie:
class SpecializedResource(ModelResource):
class Meta:
authentication = MyCustomAuthentication()
class TestResource(SpecializedResource):
class Meta:
# the following style works:
authentication = SpecializedResource.authentication
# but the following style does not:
super(TestResource, meta).authentication
I would like to know what would be the right method of accessing meta attributes of the superclass without hard-coding the name of the superclass.
In your example it seems that you are trying to override the attribute of the meta of the super class. Why not use meta inheritance?
class MyCustomAuthentication(Authentication):
pass
class SpecializedResource(ModelResource):
class Meta:
authentication = MyCustomAuthentication()
class TestResource(SpecializedResource):
class Meta(SpecializedResource.Meta):
# just inheriting from parent meta
pass
print Meta.authentication
Output:
<__main__.MyCustomAuthentication object at 0x6160d10>
so that the TestResource's meta are inheriting from parent meta (here the authentication attribute).
Finally answering the question:
If you really want to access it (for example to append stuff to a parent list and so on), you can use your example:
class TestResource(SpecializedResource):
class Meta(SpecializedResource.Meta):
authentication = SpecializedResource.Meta.authentication # works (but hardcoding)
or without hard coding the super class:
class TestResource(SpecializedResource):
class Meta(SpecializedResource.Meta):
authentication = TestResource.Meta.authentication # works (because of the inheritance)
This might sound like a duplicate, but I don't think it is.
I need to do something a bit similar to what the asker did there : django model polymorphism with proxy inheritance
My parent needs to implement a set of methods, let's call them MethodA(), MethodB(). These methods will never be used directly, they will always be called through child models (but no, abstract class is not the way to go for various reasons).
But this is where it becomes trickier :
Each child model inherits from a specific module (moduleA, moduleB), they all implement the same method names but do something different. The calls are made through the parent model, and are redirected to the childs depending on the values of a field
Since I guess it's not very clear, here is some pseudo-code to help you understand
from ModuleA import CustomClassA
from ModuleB import CustomClassB
class ParentModel(models.Model):
TYPE_CHOICES = (
('ChildModelA', 'A'),
('ChildModelB', 'B'),
)
#some fields
type = models.CharField(max_length=1, choices=TYPE_CHOICES)
def __init__(self, *args, **kwargs):
super(ParentModel, self).__init__(*args, **kwargs)
if self.type:
self.__class__ = getattr(sys.modules[__name__], self.type)
def MethodA():
some_method()
def MethodB():
some_other_method()
class ChildModelA(ParentModel, CustomClassA):
class Meta:
proxy = True
class ChildModelB(ParentModel, CustomClassB):
class Meta:
proxy = True
In ModuleA :
class CustomClassA():
def some_method():
#stuff
def some_other_method():
#other stuff
In ModuleB :
class CustomClassB():
def some_method():
#stuff
def some_other_method():
#other stuff
Right now, the problem is that the class change works, but it does not inherit from ChildModelA or B.
Is this even possible? If yes, how can I make it work, and if no, how could I do this elegantly, without too much repetition?
A proxy model must inherit from exactly one non-abstract model class. It seems that both CustomClass and ParentModel are non-abstract. I would suggest to make CustomClass abstract since no attributes are defined.
This is explained in dept here: https://docs.djangoproject.com/en/3.2/topics/db/models/#proxy-models
With an instance of Concert I get: unbound method do_stuff() must be called with Concert instance as first argument (got ModelBase instance instead)
models.py:
class Event(models.Model):
def do_stuff(self):
response self.do_specific_stuff(self)
class Concert(Event):
def do_specific_stuff(self):
...
class Party(Event):
def do_specific_stuff(self):
...
views:
def index(request):
x = Concert.objects.get(name='Jack White # Oakland')
output = x.do_stuff()
return HttpResponse(output)
My goal is to loop trough all the events and execute the do_specific_stuff child class method based on what kind of event it is.
In Django, inheritance triggers multi-table inheritance, but you don't get the polymorphism in Python. It's just an instance of the ORM not providing a perfect correspondence between the data schema and the object model.
In other words, when you query Event, you get back a whole bunch of Event objects, regardless of whether some of them are actually Concert or Party objects. You have to manually downcast. If an Event is a Concert, it will have an attribute called concert, which points to the corresponding Concert subclass. Likewise for Party. If it is just a normal Event, it will have neither attribute.
You could use the following property on Event to automatically downcast your object:
#property
def as_child_class(self):
"""Casts this object to its subclass, if possible"""
if hasattr(self, 'concert'):
return self.concert
elif hasattr(self, 'party'):
return self.party
else:
return self
Then you could do something like:
for event in Event.objects.all()
event.as_child_class.do_specific_stuff()
Similar questions have come up before:
Polymorphism in Django
How do I access the child classes of an object in django without knowing the name of the child class?
And this link has some other ideas:
http://jeffelmore.org/2010/11/11/automatic-downcasting-of-inherited-models-in-django/
It seems to me that your Event model is ONLY for inheritance, so you should abstract it:
class Event(models.Model):
class Meta:
absract = True
def do_stuff(self):
response self.do_specific_stuff()
def do_specific_stuff(self):
raise NotImplemented
class Concert(Event):
def do_specific_stuff(self):
...
class Party(Event):
def do_specific_stuff(self):
...
I may be wrong about your usage of Event, but if it was, abstracting your Event model will have it to be like a plain class, I mean no database actions will be taken for such model.
Hope this helps! :)
First of all, see Template method
Secondly, the Event class should be abstract.
class Event:
def __init__:
raise NotImplemented('This class is abstract')
Thirdly, see Single Table Inheritance and Class Table Inheritance patterns.
And django-ORM realisation of the patterns there
Good luck =)