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.
Related
I am working on a project that requires unique slugs. The slugs are dynamically created in a custom save() method using the objects name.
class SlugMixin(models.Model):
def save(self, *args, **kwargs):
slug = striptags(self.name)
self.slug = slugify(slug)
super(SlugMixin, self).save(*args, **kwargs)
class Meta:
abstract = True
name is not unique so it's possible to have multiples of the same slug. So the solution i was working with is appending the id of the instance to it's slug in a post_save. The problem here is by attempting to update the slug with the id. save() needs to be called again.
def ensure_unique_slug(sender, instance, created, **kwargs):
if created and Person.objects.filter(slug=instance.slug).count() > 1:
instance.slug = instance.slug + '-{}'.format(instance.id)
instance.save()
rendering the update useless. is there anyway to update the slug without calling save()
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.
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.
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):
...
I have a model with an __init__ method:
class Foo(models.Model):
name = models.CharField(max_length=50)
def __init__(self, *args, **kwargs):
self.bar = kwargs.pop('bar', False)
super(Foo, self).__init__(*args, **kwargs)
if self.bar:
# do something
pass
Now, i need to create a specific ModelForm:
class FooForm(ModelForm):
class Meta:
model = Foo(bar='something')
fields = ('name',)
That does not work apparently:
TypeError: 'Foo' object is not callable
Is there any way i can overcome this?
Update
More information on what i want to achieve: I have an Image model with an ImageField. It has different storage methods depending on the form that uses it.
The model:
class Image(models.Model):
image = models.ImageField(upload_to=imageUploadTo)
user = models.ForeignKey('auth.User')
def __init__(self, *args, **kwargs):
self.overwrite = kwargs.pop('overwrite', False)
super(Image, self).__init__(*args, **kwargs)
if self.overwrite:
self.image.storage = OverwriteStorage()
Now i want to be able to create forms that overwrite the old image and forms that use the default behavior. What's the best way to achieve this?
No, that's not how it works at all, and this has nothing to do with your custom init. You don't call things inside Meta. In your case, you pass the parameter when you initialize the form in your view.