I want to add timestamp related fields to both EmbededDocument class inherited documents and to regular Document class inherited Documents.
Since EmbededDocument and Document classes cannot be mixed in mongoengine I had to create a base class and tried to use that through multi-inheritance.
This is what I have done
class SikkaBase():
# Passing a callable as default
created_on = DateTimeField(default=datetime.now)
updated_on = DateTimeField(default=datetime.now)
is_deleted = BooleanField(default=False)
# Update the updated_on field for every update
def update(self, *args, **kwargs):
self.updated_on = datetime.now()
super(SikkaBase, self).save(*args, **kwargs)
# Update the created_on field for every updates
def save(self, *args, **kwargs):
self.updated_on = datetime.now()
super(SikkaBase, self).save(*args, **kwargs)
class SikkaBaseDocument(Document, SikkaBase):
meta = {
'abstract': True
}
class SikkaEmbededBaseDocument(EmbeddedDocument, SikkaBase):
meta = {
'abstract': True
}
This throws an error
File ".../sikka_env/lib/python2.7/site-packages/mongoengine/base/metaclasses.py", line 305, in __new__
if b.__class__ == TopLevelDocumentMetaclass]
AttributeError: class SikkaBase has no attribute '__class__'
I am not so sure about my solution either as SikkaBase class is not related to MongoEngine in any way, not sure how relevant that is.
Looking for any possible solutions. I can always copy the same code in the SikkaBaseDocument and SikkaEmbededBaseDocument class but want to avoid doing the same.
It looks like old-style classes do not have __class__ attribute, therefore you should inherit from object. Changing your first line from
class SikkaBase():
to
class SikkaBase(object):
should fix this issue.
Related
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
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
Background:
I have the below models defined in Django(1.8.5):
class PublishInfo(models.Model):
pass
class Book(models.Model):
info = models.OneToOneField(
PublishInfo, on_delete=models.CASCADE)
class Newspaper(models.Model):
info = models.OneToOneField(
PublishInfo, on_delete=models.CASCADE)
Where Book and NewsPaper shares a same model PublishInfo as a OneToOneField, which is in fact a unique foreign key.
Now, if I delete a PublishInfo Object, the relating Book or Newspaper object is deleted with cascading.
Question:
But in fact, I want to delete the PublishInfo object cascading when I delete the Book or Newspaper object. This way is the way I may call.
Is there any good way to automatically cascading the deletion in the reverse direction in this case? And, if yes, could it be explained?
You attach post_delete signal to your model so it is called upon deletion of an instance of Book or Newspaper:
from django.db.models.signals import post_delete
from django.dispatch import receiver
#receiver(post_delete, sender=Book)
def auto_delete_publish_info_with_book(sender, instance, **kwargs):
instance.info.delete()
#receiver(post_delete, sender=Newspaper)
def auto_delete_publish_info_with_newpaper(sender, instance, **kwargs):
instance.info.delete()
Another straight forward solution by overriding save and delete method:
Comparing to the answer of #ozgur, I found using signal to cascading the delete action has the same effect as deleting by overriding the Model.delete() method, and also we might auto create the attached PublishInfo:
class Book(models.Model):
info = models.OneToOneField(
PublishInfo, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if not self.info:
self.info = Publish.objects.create()
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
if self.info:
self.info.delete()
More structured and reusable solution:
So, soon I realized the three listing field and methods are obviously redundant on each Model which was attaching the PublishInfo models as a field.
So, why don't we use inheritance?
class PublishInfoAttachedModel(models.Model):
info = models.OneToOneField(
PublishInfo, related_name='$(class)s',
on_delete=models.CASCADE)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if not self.info:
self.info = Publish.objects.create()
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
if self.info:
self.info.delete()
class Meta:
abstract = True
Remember to add abstract = True in its meta class.
So, now we are free to add PublishInfo in any other models we want to attach that model, and we can make more than one such abstract models:
class Book(PublishInfoAttachedModel,
models.Model):
pass
class NewsPaper(PublishInfoAttachedModel,
CommentsAttachedModel, # if we have other attached model info
models.Model):
pass
Notice the models.Model class in the trailing super class list can be ignored, I wrote this is just to make the classes more obvious as a Model.
I have an abstract model in a Django app:
class HistoryTrackedModel(models.Model):
def save(self, *args, **kwargs):
super(self.model, self).save(*args, **kwargs) # Call the real save method
# Do some miscellaneous work here (after saving)
class Meta:
abstract = True
A child model uses the abstract model as its base:
class Project(HistoryTrackedModel):
name = models.TextField(unique=True, blank=False, db_index=True)
... other fields ...
def __unicode__(self):
return self.name
class Meta:
ordering = ('name',)
When I instantiate an instance of Project (the child model), and call the save() method, I get the following error:
'Project' object has no attribute 'model'
It's failing on the super(self.model, self).save() call in the abstract class's save method. I attempted to change that method to the following, but it (fairly obviously, now that I look at it) gets caught in a recursive loop:
class HistoryTrackedModel(models.Model):
def save(self, *args, **kwargs):
my_model = type(self)
super(my_model, self).save(*args, **kwargs) # Call the real save method
What am I doing wrong here? Shouldn't all child classes that inherit from a base class (which itself inherits from models.Model) include the model attribute?
super(HistoryTrackedModel, self).save(*args, **kwargs)
should work.
In a model I usually put a "uuid" field for friendly URI, also a "slug" field.
Say I have a model named "SomeModel", by overriding its save() method, I can generate a uuid and a slug when it's being saved:
class SomeModel(models.Model):
...
def save(self, *args, **kwargs):
if not self.uuid:
uuid = shortuuid.uuid()[:10]
while SomeModel.objects.filter(uuid=uuid).exists():
uuid = shortuuid.uuid()[:10]
self.uuid = uuid
if not self.slug:
self.slug = slugify(self.title)[:500].rstrip('-')
super(SomeModel, self).save(*args, **kwargs)
It works well on regular model. Now I'd like to have an abstract model:
class SomeAbstractModel(models.Model):
class Meta:
abstract = True
def save(self, *args, **kwargs):
...
And then:
class SomeModel(SomeAbstractModel):
class Meta(SomeAbstractModel.Meta):
...
The problem is, in the abstract model, looks like I cannot just simply replace
while SomeModel.objects.filter(uuid=uuid).exists():
with
while SomeAbstractModel.objects.filter(uuid=uuid).exists():
because abstract model doesn't have a manager.
I was wondering in this case, how can I avoid having redundant code in all models' save() methods. Also I'm not sure if
while SomeModel.objects.filter(uuid=uuid).exists():
is the best practice to check if an uuid exists or not.
Not sure if it is the prettiest way in town but this should work:
while self.__class__.objects.filter(...):
pass
When you create SomeModel(SomeAbstractModel), just create the class Meta from scratch without inheriting. By inheriting vom SomeAbstractModel.Meta you make it abstract again, and you cannot query on abstract model, not because they have no manager, but because there are no tables created.
So either you do this:
class SomeModel(SomeAbstractModel):
...
class Meta(SomeAbstractModel.Meta):
abstract=False
... your other model specific options
Or you do this (if you do not have any other model specific options:
class SomeModel(SomeAbstractModel):
...