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):
...
Related
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.
I'm trying to understand, if it is possible to inherit a regular python class in a django model. The idea is that I need a common interface for the models and for another part of the system(that works with mongo).
The example is:
class myC(object):
def __init__(self):
self.f = "test"
class myM(myC, models.Model):
name = models.CharField(max_length=50)
Making myC inherit from Model and making it abstract is not really possible for me and even more. So I wonder, what would the table for myM look like(if what I am trying to do is possible).
Yes you could inherit your models from regular classes. For example create some mixins for your models.
This mixin will add missing / to url field each time you save a model:
class ModelURLSaveMixin(object):
def save(self, *args, **kwargs):
if not self.url.startswith('/'):
self.url = '/' + self.url
if not self.url.endswith('/'):
self.url = self.url + '/'
super(ModelURLSaveMixin, self).save(*args, **kwargs)
class MyModelWithUrlField(ModelURLSaveMixin, models.Model):
...
But if you want build common interface for several models on your project an abstract class solution will be better
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.
I have the following (simplified) data structure:
Site
-> Zone
-> Room
-> name
I want the name of each Room to be unique for each Site.
I know that if I just wanted uniqueness for each Zone, I could do:
class Room(models.Model):
zone = models.ForeignKey(Zone)
name = models.CharField(max_length=255)
class Meta:
unique_together = ('name', 'zone')
But I can't do what I really want, which is:
class Room(models.Model):
zone = models.ForeignKey(Zone)
name = models.CharField(max_length=255)
class Meta:
unique_together = ('name', 'zone__site')
I tried adding a validate_unique method, as suggested by this question:
class Room(models.Model):
zone = models.ForeignKey(Zone)
name = models.CharField(max_length=255)
def validate_unique(self, exclude=None):
qs = Room.objects.filter(name=self.name)
if qs.filter(zone__site=self.zone__site).exists():
raise ValidationError('Name must be unique per site')
models.Model.validate_unique(self, exclude=exclude)
but I must be misunderstanding the point/implementation of validate_unique, because it is not being called when I save a Room object.
What would be the correct way to implement this check?
Methods are not called on their own when saving the model.
One way to do this is to have a custom save method that calls the validate_unique method when a model is saved:
class Room(models.Model):
zone = models.ForeignKey(Zone)
name = models.CharField(max_length=255)
def validate_unique(self, exclude=None):
qs = Room.objects.filter(name=self.name)
if qs.filter(zone__site=self.zone__site).exists():
raise ValidationError('Name must be unique per site')
def save(self, *args, **kwargs):
self.validate_unique()
super(Room, self).save(*args, **kwargs)
class Room(models.Model):
zone = models.ForeignKey(Zone)
name = models.CharField(max_length=255)
def validate_unique(self, *args, **kwargs):
super(Room, self).validate_unique(*args, **kwargs)
qs = Room.objects.filter(name=self.name)
if qs.filter(zone__site=self.zone__site).exists():
raise ValidationError({'name':['Name must be unique per site',]})
I needed to make similar program. It worked.
The Django Validation objects documentation explains the steps involved in validation including this snippet
Note that full_clean() will not be called automatically when you call your model's save() method
If the model instance is being created as a result of using a ModelForm, then validation will occur when the form is validated.
There are a some options in how you handle validation.
Call the model instance's full_clean() manually before saving.
Override the save() method of the model to perform validation on every save. You can choose how much validation should occur here, whether you want full validation or only uniqueness checks.
class Room(models.Model):
def save(self, *args, **kwargs):
self.full_clean()
super(Room, self).save(*args, **kwargs)
Use a Django pre_save signal handler which will automatically perform validation before a save. This provides a very simple way to add validation on exisiting models without any additional model code.
# In your models.py
from django.db.models.signals import pre_save
def validate_model_signal_handler(sender, **kwargs):
"""
Signal handler to validate a model before it is saved to database.
"""
# Ignore raw saves.
if not kwargs.get('raw', False):
kwargs['instance'].full_clean()
pre_save.connect(validate_model_signal_handler,
sender=Room,
dispatch_uid='validate_model_room')