Django Pickle Field Access to its model instance on load - python

I have a custom django field subclass for storing my own pickled classes. Is there any way I could set a model attribute that points to the model instance on my pickled class on each load from the database?
So far my best guess is in the unpickling process, inside the to_python method, but I'm not sure if the Field has a reference to the model instance or class.
EDIT 1: The model reference inside of the to_python method is indeed a reference to the class, not the instance

Figured it out!
I overrode the model's __init__ method like this:
class MyModel(models.Model):
def __init__(self, *args, **kwargs):
# Don't do any extra looping or anything in here because this gets called
# at least once for every row in each query of this table
self._meta.fields[2].model_instance = self
super(MyModel, self).__init__(*args, **kwargs)
field1 = models.TextField()
field2 = models.PickleField()
field3 = models.DateTimeField()
Then in my field subclass:
def to_python(self, value):
# logic and unpickling, then right before your return:
if hasattr(self, 'model_instance'): # avoid AttributeError if list, dict, etc.
value.model_instance = self.model_instance
return value

Related

How to delete constant in subclass?

Let's say I have a class something like the following:
class PostSerializer(serializers.HyperlinkedModelSerializer):
updated_at = serializers.DateTimeField()
def __init__(self, *args, **kwargs):
init = super().__init__(*args, **kwargs)
return init
I want to create a subclass of the PostSerializer class and I'd like to remove the updated_at constant property from the subclass-ed class.
class PostWithoutUpdatedAtSerializer(serializers.HyperlinkedModelSerializer):
# something to remove the updated_at property ?
def somefunc(self);
pass
I use a framework for example django so generally I cannot simply remove the property from the parent class, I need to subclass them. And of course obviously I need to "delete" the property, I cannot do updated_at = None, it's not a deleting.
How is it possible? Thanks.
It's not directly possible, since the attribute doesn't exist on your derived class at all (it does on the superclass), so there's nothing to remove or reassign.
Instead, the framework you're using (Django REST Framework, my magic ball tells me), uses a metaclass that inspects the class definition for field objects and puts them into cls._declared_fields on the class (along with any fields from the superclass(es)).
The real fields for your serializer instance are acquired by get_fields(), which by default just copies _declared_fields.
In other words, if your Django REST Framework serializer subclass should not serialize that field, customize get_fields():
def get_fields(self):
fields = super().get_fields()
fields.pop("updated_at", None) # remove field if it's there
return fields

Automatically create child object from parent - Django 1.11

I have a parent class:
class Parent(models.Model):
field1 = model.CharField()
field2 = model.CharField()
And a child:
class Child1(Parent):
pass
Is there a possible way to create a child object whenever a perent is saved?
The child inherits all the fields from the parent, but, regardless if filled or not, I would need to create a new child object whenever a parent is saved.
Any ideas?
You can use signals or you can override save method of Parent model to do that.
#receiver(models.signals.post_save, sender=Parent)
def post_parent_save(sender, instance, created, *args, **kwargs):
# Create child here
Or,
class Parent(models.Model):
def save(self, *args, **kwargs):
super(Parent, self).save(*args, **kwargs)
# Create child here
In both options, if you want to create a child only when a parent is created (not updated), you need to add extra login. For signals, you can use the created parameter, for overriding save method, you need to check if the model instance has an id field before calling super save method.
I'm wondering if something like this would work for you:
class Child1(Parent):
class Meta:
db_table = 'whateverappparentisin_parent'
managed = False
I'm not sure what Django would do with this, but the idea is that you get a model with the exact same fields, backed by the same database table (so everything else e.g. deletes on Parent would also immediately be "visible" on Child1), without Django wanting to make migrations for it.
But I don't know if it's allowed.

Custom Django ForeignKey field to a defined model class

Using Django 1.11 I'd like to make a custom model field that inherits from ForeignKey.
With a normal ForeignKey, you can do something like the following:
class Car(models.Model):
manufacturer = models.ForeignKey(Company)
Instead, I'd like to make a model field that is largely the same as the ForeignKey field, but 1) uses a different default widget for the formfield and 2) doesn't require the model name to be placed as a positional parameter.
# myapp/models.py
from otherapp.fields import ManufacturerField
class Car(models.Model):
manufacturer = ManufacturerField()
Unfortunately, I'm having a hard time overriding the init method of the child class to get my "Company" model inserted into the mix. Here's what I have so far by way of a modelfield (not working on the widget at all yet):
# otherapp/fields.py
from otherapp.models import Company
class ManufacturerField(models.ForeignKey):
def __init__(self, *args, **kwargs):
return super(ContentField, self).__init__(Company, **kwargs)
When I try to do this, I get:
TypeError: Couldn't reconstruct field manufacturer on myapp.Car: __init__() got multiple values for argument 'to'
Is there a property I can set on the custom modelfield class to specify that I want this to be a foreignkey to one specific model? If not, any ideas on how I can properly intercept the init method to feed in my model?
super() referring to the base class explicitly. you can see this question to understand it.
def __init__(self, *args, **kwargs):
return super(ContentField, self).__init__(Company, **kwargs)
should be;
def __init__(self, *args, **kwargs):
return super(ManufacturerField, self).__init__(*args, **kwargs)
Here is an example how to custom the field.

Add dynamic property with class in Python metaclass

I use mongoengine with django rest framework. My model:
import mongoengine as mongo
class Plan(mongo.Document):
slug = mongo.StringField(max_length=255, primary_key=True)
subplans = mongo.ListField(mongo.EmbeddedDocumentField('self'))
I'm need serializer that be looks like this:
class PlanSerializer(serializers.DocumentSerializer):
subplans = PlanSerializer(many=True, required=False)
class Meta:
model = Plan
But that incorrect for Python. So I use metaclass for adding subplans field dynamicly:
class AddSubplanAttrMetaclass(type):
def __new__(cls, name, bases, dct):
# this code is incorrect because PlanSerializer not in globals
class_obj = globals()[name]
dct['subplans'] = class_obj(many=True, required=False)
return super(AddSubplanAttrMetaclass, cls).__new__(cls, name, bases, dct)
class PlanSerializer(serializers.DocumentSerializer, metaclass=AddSubplanAttrMetaclass):
class Meta:
model = Plan
How I can set PlanSerializer class to property inside __new__ method of metaclass?
The problem you have there is that when you try to either use the line
subplans = PlanSerializer(many=True, required=False) and when trying with the metaclass, the line class_obj = globals()[name] when your PlanSerializerclass itself was not defined yet. (Check my answer at How is super() in Python 3 implemented?)
The correct way to do that in the metaclass would be to call the superclass's new first - that returns you the actual class object, and then call that object - something along:
class AddSubplanAttrMetaclass(type):
def __new__(metacls, name, bases, dct):
# this code is incorrect because PlanSerializer not in globals
class_obj = super(AddSubplanAttrMetaclass, cls).__new__(metacls, name, bases, dct)
class_obj.subplans = class_obj(many=True, required=False)
return class_obj
But that is both not needed, and might still have issues - as not all the class initialization is completed while you are still inside the metaclass's __new__ (or even __init__) methods. For example, if the __init__ method of PlanSerializer itself would make use of super, that call would fail - super can only be used after class has been fully initialized.
However, you don't need a metaclass at all for that - you probably can simply set the subplans attribute as a descriptor - and retrieve the attribute lazily.
class PlanSerializer(serializers.DocumentSerializer):
class Meta:
model = Plan
PlanSerializer.subplans = PlanSerializer(many=True, required=False)
I said probably because this won't work if Mongo needs the attribute to be set when initializing the class itself - if that is the case, you can try resorting to a descriptor object. A descriptor is simply an object that implements the __get__ method, like below. That is usually done with the #property decorator, but that would not work for class level attributes, which you need for this case.
class PlanSerializer(serializers.DocumentSerializer):
class Subplans(object):
serializer = None
def __get__(self, instance, owner):
if not self.serializer:
self.serializer = PlanSerializer(many=True, required=False)
return self.serializer
subplans = Subplans()
class Meta:
model = Plan
In that way the usage of the call to the Subplans class is delayed to when it is actually used, instead of the time of parsing the class body, and it should work.

How can I force 2 fields in a Django model to share the same default value?

I have a Django model MyModel as shown below.
It has two fields of type DateTimeField: my_field1, my_field2
from django.db import models
from datetime import datetime
class MyModel(models.Model):
my_field1 = models.DateTimeField(default=datetime.utcnow, editable=False)
my_field2 = models.DateTimeField(
# WHAT DO I PUT HERE?
)
I want both fields to default to the value of datetime.utcnow(). But I want to save the same value for both. It seems wasteful to call utcnow() twice.
How can I set the default value of my_field2 so that it simply copies the default value of my_field1?
The proper way to do this is by over riding the save method rather than the __init__ method. In fact it's not recommended to over ride the init method, the better way is to over ride from_db if you wish to control how the objects are read or save method if you want to control how they are saved.
class MyModel(models.Model):
my_field1 = models.DateTimeField(default=datetime.utcnow, editable=False)
my_field2 = models.DateTimeField()
def save(self, *arges, **kwargs):
if self.my_field1 is None:
self.my_field1 = datetime.utcnow()
if self.my_field2 is None:
self.my_field2 = self.my_field1
super(MyModel, self).save(*args, **kwargs)
Update: Reference for my claim: https://docs.djangoproject.com/en/1.9/ref/models/instances/
You may be tempted to customize the model by overriding the init
method. If you do so, however, take care not to change the calling
signature as any change may prevent the model instance from being
saved. Rather than overriding init, try using one of these
approaches:
As stated in the docs:
The default value is used when new model instances are created and a value isn’t provided for the field.
So to solve your task, I would fill the default values manually in the __init__. Something like:
def __init__(self, *args, **kwargs):
now = datetime.datetime.utcnow()
kwargs.setdefault('my_field1', now)
kwargs.setdefault('my_field2', now)
super(MyModel, self).__init__(*args, **kwargs)
Alternatively you can handle the values in save method.
If you want my_field2 to have any value that is in my_field1, I would go with this solution:
class MyModel(models.Model):
my_field1 = models.DateTimeField(default=datetime.utcnow, editable=False)
my_field2 = models.DateTimeField()
def __init__(self, **kwargs):
super(MyModel, self).__init__(**kwargs)
if self.my_field2 is None:
self.my_field2 = self.my_field1

Categories