Django: Casting Abstract Models - python

I have an abstract Django model that I use to create two other models. How can I avoid duplicating code when dealing with the different examples below (for example, when creating a Boxed cereal and a Bowled cereal I would like to avoid duplicating the function twice.
class Cereal(models.Model):
name = models.CharField()
class Meta:
abstract = True
class Boxed(Cereal):
pass
class Bowled(Cereal):
pass
func some_func_boxed(name):
boxed = Boxed.objects.get(id=1)
boxed.name = name
boxed.save()
func some_func_bowled(name):
bowled = Bowled.objects.get(id=1)
bowled.name = name
bowled.save()

def some_func(name, Instance):
i = Instance.objects.get(id=1)
i.name = "some name"
i.save()
The good idea is using strategy pattern, article for example: https://medium.com/#sheikhsajid/design-patterns-in-python-part-1-the-strategy-pattern-54b24897233e

You can add this as a #classmethod on the Cereal model:
class Cereal(models.Model):
name = models.CharField()
#classmethod
def some_func_cereal(cls, name):
bowled = cls.objects.get(id=1)
bowled.name = name
bowled.save()
class Meta:
abstract = True
You can then call this method with:
Boxed.some_func_cereal('Captain Crunch')
Bowled.some_func_cereal('Lucky Charms')
The class with which you call the class method, is passed as the cls parameter in the some_func_cereal function.

You could add an update method to your Cereal absract class such as:
class Cereal:
def update(self, **kwargs):
for key, val in kwargs.items():
setattr(self, key, val)
self.save(update_fields=kwargs.keys())
return self
and use it as follows to update any column you like
BoxedInstance.update(name="new name")
or
BoxedInstance.update(name="new name", taste="cardboardy")

Related

Change representation of Superclass without field depending of Subclass object (__str__)

I am running a django app and have a setup like this:
ModelSuper(models.Model):
class Meta:
abstract = False
ModelSub1(ModelA):
name = models.CharField(...)
def __str__:
return self.name
ModelSub2(ModelA)
name = models.CharField(...)
def __str__:
return self.name
ModelForeign(models.Model):
element = models.ForeignKey(ModelA)
def __str__:
return self.name
So ModelForeign has a FK to ModelSuper. What happens now is that when I create an instance of ModelForeign I can choose if it belongs either to ModelSub1 or to ModelSub2. But the string representation is ModelSuper Onject (3) where (3) is the id.
Normally I can change this representation by overwriting the __str__ method on the model, but since I do not have any fields on the Supermodel I can't return anything.
What I tried:
I have already implemented the __str__ method in the Submodels but that does not help.
I wanted to make the Super model abstract. But this does not let me point FKs to the Supermodel, so I can't do this. My setup requires this FK
I used a generic FK with django's ContentType framework. This is also not an option because it messes completely with my app and is also not recommended from an SQL perspective.
Also when I do API-calls I get ModelSuper Onject (3) back instead of a human-readable name.
Is there a way to do what I intend to do? Thanks in advance for help and hints. Very much appreciated!
EDIT1: What I tried thanks to Abdul's help:
class ModelA(models.Model):
class Meta:
abstract = False
TYPE_CHOICES = [('sub1', 'sub1'), ('sub2', 'sub2')]
type_model = models.CharField(max_length=50, choices=TYPE_CHOICES, null=True, blank=True)
def __str__(self):
if self.type_model == "sub1":
return "sub1"
elif self.type_model == "sub2":
return "sub2"
else:
return "unkown"
I am not understanding how your foreign key works as model inheritance means the tables are separate. How about trying something like this:-
ModelA(models.Model):
TYPE_CHOICES = [('Sub1', 'ModelSub1'), ('Sub2', 'ModelSub2')]
model_type = models.CharField(max_length=4, choices=TYPE_CHOICES)
def __str__:
# Return string representation using if-else
class Meta:
abstract = False
ModelSub1(ModelA):
name = models.CharField(...)
model_a = models.ForeignKey(ModelA, on_delete=models.CASCADE)
def __str__:
return self.name
ModelSub2(ModelA)
name = models.CharField(...)
model_a = models.ForeignKey(ModelA, on_delete=models.CASCADE)
def __str__:
return self.name
ModelForeign(models.Model):
element = models.ForeignKey(ModelA)
def __str__:
return self.name

Parametrize a Python class

Is there any way to parametrize a class in Python? The parametrized class may look something like this:
class FIELDSerializer:
FIELD = serializers.CharField(source='get_FIELD_display', required=False)
class Meta:
model = None
fields = {FIELD}
Which would need to create the following three classes:
class NameSerializer:
name = serializers.CharField(source='get_name_display', required=False)
class Meta:
model = None
fields = {'name'}
class CategorySerializer:
category = serializers.CharField(source='get_category_display', required=False)
class Meta:
model = None
fields = {'category'}
class StateSerializer:
state = serializers.CharField(source='get_state_display', required=False)
class Meta:
model = None
fields = {'state'}
Is this possible or not?
You can do what you want with a factory function, although it's not completely trivial to get the internal variables (attributes) as you want them:
def factory(FIELDname):
class FIELDSerializer:
class Meta:
model = None
fields = {FIELDname}
settatr(FIELDSerializer, FIELDname, serializers.CharField(source=f'get_{FIELDname}_display', required=False))
return FIELDSerializer
CategorySerializer = factory('category')
StateSerializer = factory('state')
NameSerializer = factory('name')
The setattr allows us to set the name of attribute to the FIELDname string. (Thanks to #Code-Apprentice and #juanpa.arrivillaga for this idea.)
I don't know if there's any easy way to avoid the repetition of the field name and the desired class name when you call the factory without using something like exec (which is perfectly legal but usually leaves programmers with a bad taste in their mouths).

Python: How to read an attribute from another atribute's object?

I have no idea how to even look for this, here is a simplified example
I have a class:
class MyField():
def __init__(self, model=None, source=None, id=None):
self.source = source
self.model = model
self.id = id
def somemethod(self):
...
That is used on this other class
class MyClass(db.Model):
id = db.Column(db.Integer, primary_key=True)
somefield = db.Column(db.String(255))
#property
def someproperty(self):
specs = MyField(
source=self.somefield,
model=self.__class__.__name__,
id=self.id
)
return specs.somemethod()
By that is no ideal to me, I want to have something simpler to code for the second class, so making it look like this:
class MyClass(db.Model):
somefield = db.Column(db.String(255))
someproperty = MyField(source='somefield')
and have on the first class all the logic that handles that data. Here I have no idea on how to read the contents of 'somefield' and id, 'somefield' may vary and have another name, thats why I use it as an argument, and 'id' is always the same for every class
All that #property does is return a custom descriptor whose __get__ method calls your function. So, you can just do that directly.
I can't test this, because I don't have sqlalchemy installed here, but something like this:
class FieldProperty(object):
def __init__(self, field, source):
self.field, self.source = field, source
def __get__(self, obj, typ=None):
if typ is None:
typ = obj.__class__
specs = self.field(
source = getattr(obj, self.source),
model = typ.__name__,
id = obj.id)
return specs.somemethod()
Now:
class MyClass(db.Model):
somefield = db.Column(db.String(255))
someproperty = FieldProperty(MyField, source='somefield')
Or, if you prefer, you can create a base class or mixin for all your fields that adds a class method that does this for you:
class BaseField(object):
#classmethod
def make_property(cls, source):
return FieldProperty(cls, source)
And now:
class MyClass(db.Model):
somefield = db.Column(db.String(255))
someproperty = MyField.make_property('somefield')
Note the FieldProperty(object) above. Descriptors have to be new-style classes, and they only work in new-style classes. In Python 3.x, new-style classes is all there is, but if you're using 2.x, every time you define a class with no base class, you get a old-style class. This is just one of the many reasons you don't want an old-style class; they also handle some special methods wrong, break multiple inheritance, make you look like a retro hipster, and sneak into your room and steal money out of your wallet while you sleep.

Determining a suitable subclass to store specific type of data in Python

I have a generic set of attributes (e.g. of type string and integer) and I would like to use the following django models to store them:
class Attribute(models.Model):
item = models.ForeignKey('Item')
class Meta:
abstract = True
#staticmethod
def get_subclass_by_type(type):
TEXT_TYPES = ["text", "string"]
INTEGER_TYPES = ["integer", "number"]
if type in TEXT_TYPES:
return TextAttribute
if type in INTEGER_TYPES:
return IntegerAttribute
class TextAttribute(Attribute):
value = models.CharField(max_length=128)
class IntegerAttribute(Attribute):
value = models.IntegerField()
Is there any clean & simple way I've missed to define the types in the subclasses directly? Something like:
class Attribute(models.Model):
item = models.ForeignKey('Item')
class Meta:
abstract = True
#staticmethod
def get_subclass_by_type(type):
<do something>
class TextAttribute(Attribute):
TYPES = ["text", "string"]
value = models.CharField(max_length=128)
class IntegerAttribute(Attribute):
TYPES = ["integer", "number"]
value = models.IntegerField()
You can try to define your method get_subclass_by_type like the following
#classmethod
def get_subclass_by_type(cls, type):
for subcls in cls.__subclasses__():
if type in subcls.TYPE:
return subcls
Haven't tested this code, but i think it should work.

Extract assigned variable name

See the update below
I even don't know how to make a short title for my problem.
In a class I have some class attributes of StringField class:
class Authors(Table):
# id field is already present
first_name = StringField(maxLength=100)
last_name = StringField(maxLength=100)
StringField constructor may receive an argument called name. If it's not given, i want it to be equal to class attribute's name (first_name, last_name in the example above).
Is it possible to extract the name of the variable the created instance is going to be assigned to?
I guess i have to use inspect module?
I see Django does this:
Each field type, except for ForeignKey, ManyToManyField and
OneToOneField, takes an optional first positional argument -- a
verbose name. If the verbose name isn't given, Django will
automatically create it using the field's attribute name, converting
underscores to spaces.
In this example, the verbose name is "person's first name":
first_name = models.CharField("person's first name", max_length=30)
In this example, the verbose name is "first name":
first_name = models.CharField(max_length=30)
But i don't find in Django 1.3.1 source code the part which is doing what i need.
UPDATE:
To simplify:
class Field():
def __init__(self, field_name=None):
if not field_name:
field_name = ??? # some magic here to determine the name
print(field_name)
class Table():
first_name = Field()
last_name = Field()
Running this should print first_name and last_name
SOLUTION:
class Field():
def __init__(self, name=None):
self._name = name
class Table():
first_name = Field()
last_name = Field()
for attrName, attr in Table.__dict__.items():
if isinstance(attr, Field):
if attr._name is None:
attr._name = attrName
print(Table.first_name._name)
print(Table.last_name._name)
I don't know how Django does it. But you could do it this way:
class WantFixup(object):
def new_instance(self, name, derived_name):
cls = type(self)
if name is None:
name = derived_name.replace('_', ' ')
return cls(name)
class Container(WantFixup):
def __init__(self, name=None):
self.name = name
def __repr__(self):
return "Container('%s')" % str(self.name)
class WillFixup(object):
def __init__(self):
cls = type(self)
for name in cls.__dict__:
o = cls.__dict__[name] # look up object from name
if not isinstance(o, WantFixup):
continue
print("calling %s.new_instance('%s', '%s')" % (o, o.name, name))
self.__dict__[name] = o.new_instance(o.name, name)
class Name(WillFixup):
first_name = Container("given name")
last_name = Container()
Here is an example of the above code in action:
>>> import auto_name
>>> n = auto_name.Name()
calling Container('None').new_instance('None', 'last_name')
calling Container('given name').new_instance('given name', 'first_name')
>>> print(n.__dict__)
{'first_name': Container('given name'), 'last_name': Container('last name')}
>>> print(auto_name.Name.__dict__)
{'__module__': 'auto_name', 'last_name': Container('None'), 'first_name': Container('given name'), '__doc__': None}
>>>
The class WantFixup serves two purposes. First, all classes that inherit from it can be detected using isinstance(); if our object instance is named o, we can test it like isinstance(o, WantFixup). Second, it provided the .new_instance() method function to any class that inherits from it.
The class Container is an example of a container that might need fixup. Note that it inherits from WantFixup.
The class WillFixup contains a .__init__() method that performs fixup on all classes that inherit from it. This simply loops over everything in the class dictionary, and calls the .new_instance() method function for each one, passing in the name.
Finally, class Name inherits from WillFixup and contains two instances of Container. Because it inherits from WillFixup, the method WillFixup.__init__() is called. As you can see from the example, first_name has a .name attribute set to 'given name' but last_name wasn't set, so it is patched to have its .name attribute set to 'last name'.
The .__init__() function is supposed to set up the new class instance. As long as all the special WantFixup class instances are in the parent class, the .__init__() method will automatically loop over them and set them up.
The confusing part here is that the instance has first_name set to an instance of Container that has the name patched, and will actually be used to store stuff. But the class Name contains an instance of Container that is just used to store the name of the class, and as a marker for the .__init__() method to find.
The good part is that the magic is hidden away in the base classes. The Container and Name classes just need to inherit from them, but are not themselves cluttered with stuff.
There might be a slicker way to solve the problem using metaprogramming.
http://www.ibm.com/developerworks/linux/library/l-pymeta/index.html
This solution isn't metaclass programming, but it is tested, working code.
EDIT: This is a changed version of the code. The original code was intended to show the general idea, but didn't actually init the Name object. It's not hard to actually do the init, so I changed it.
In order for the magic to happen as in the sample, Python would need to be a context-sensitive language (which is isn't, as far as I know, which isn't that far). Django uses the ModelBase meta-class to (among other tasks) set verbose names for the fields. Basically, the metaclass's __new__ loops over the class attributes to get the attribute names, adding them to the options. You can be a little more direct and alter the fields directly. Here's a Python 2 example:
class Field(object):
def __init__(self, name=None):
self.name = name
def __str__(self):
if self.name:
return self.name
return type(self).__name__
def __repr__(self):
return '%s(%s)' % (type(self).__name__, repr(self.name))
class MetaContainer(type):
#classmethod
def dub(metacls, name):
return name.replace('_', ' ').capitalize()
def __new__(cls, name, bases, attrs):
for attr in attrs:
if issubclass(type(attrs[attr]), Field) and attrs[attr].name is None:
attrs[attr].name = MetaContainer.dub(attr)
return super(MetaContainer, cls).__new__(cls, name, bases, attrs)
class Container(object):
__metaclass__ = MetaContainer
first_name = Field()
foo = Field('Foobar')
cntr = Container()
cntr.first_name
Python 3 is almost the same, but you use the metaclass class argument* rather than the __metaclass__ property:
class Container(object, metaclass=MetaContainer):
first_name = Field()
foo = Field('Foobar')
You can write a version that works with metaclasses in in Python 2 and 3 by creating an intermediate base class for the container using the metaclass directly, rather than the metaclass argument or __metaclass__ property:
ContainerBase = MetaContainer('ContainerBase', (object,), {})
class Container(ContainerBase):
first_name = Field()
foo = Field('Foobar')
* For the reason for the change, see PEP 3115: Metaclasses in Python 3000.

Categories