Django inheritance - accessing child class fields - django

I have the following Django models:
class Image(TimeStamp):
hash = models.CharField(max_length=33,
unique=True,
editable=False)
filesize = models.PositiveIntegerField(blank=True,
null=True,
editable=False)
class Image1(Image):
image = models.ImageField(upload_to='image1/')
class Image2(Image):
image = models.ImageField(upload_to='image2/')
I want to be able to automatically compute filesize and hash upon image creation and the most reasonable place seems to me in a super class. However, I need to be able to access child class image field from the super class in order to compute hash and filesize. Is there a way to achieve this?
I added this save method to the superclass, but of course it doesn't know about image:
def save(self, *args, **kwargs):
super(Image, self).save(*args, **kwargs)
self.hash = hashlib.md5(self.image.read()).hexdigest()
self.filesize = self.image.size

Here's what I ended up doing to solve this. Thank everyone for the feedback and please let me know if this code can be improved further:
def get_image_directory(instance, filename):
return os.path.join(instance.upload_to, filename)
class Image(TimeStamp):
hash = models.CharField(max_length=33,
unique=True,
editable=False)
file_size = models.PositiveIntegerField(blank=True,
null=True,
editable=False)
image = models.ImageField(upload_to=get_image_directory)
def __str__(self):
return self.image.url.split('?')[0]
def save(self, *args, **kwargs):
self.hash = hashlib.md5(self.image.read()).hexdigest()
self.file_size = self.image.size
super(Image, self).save(*args, **kwargs)
class Meta:
abstract = True
class Image1(Image):
upload_to = 'image1/'
class Image2(Image):
upload_to = 'image2/'

Related

How to create a save method in an abstract model that checks whether an instance exists?

I have the following models:
class PlaceMixin(models.Model):
name = models.CharField(max_length=200, null=True, blank=True)
address = models.CharField(max_length=200, null=True, blank=True)
sublocality = models.CharField(max_length=100, null=True, blank=True)
city = models.CharField(max_length=100, null=True, blank=True)
class Meta:
abstract = True
class Bar(PlaceMixin):
pass
class Restaurant(PlaceMixin):
pass
Bar and Restaurant have almost same save() method:
def save(self, *args, **kwargs):
try:
bar = Bar.objects.get(address=self.address)
except Bar.DoesNotExist:
Do something
super().save(*args, **kwargs)
def save(self, *args, **kwargs):
try:
restaurant = Restaurant.objects.get(address=self.address)
except Restaurant.DoesNotExist:
Do something
super().save(*args, **kwargs)
I was wondering if I can put the method in the Abstract model and pass it to the two inherited model?
def save(self, *args, **kwargs):
try:
temp = self.objects.get(address=self.address)
except self.DoesNotExist:
Do something
super().save(*args, **kwargs)
Something like this? But you can query in an abstract model. I basically need to check if an instance exists for executing an action.
You can make a common save method for both Restaurant and Bar model in a Mixin class like this:
from django.apps import apps
class CommonMixin(object):
def save(self, *args, **kwargs):
if self.__class__.__name__ == "Resturant":
model = apps.get_model('app_name', 'Bar')
if model.objects.filter(address=self.address).exists():
...
else:
model = apps.get_model('app_name', 'Restaurant')
if model.objects.filter(address=self.address).exists():
...
super(CommonMixin, self).save(*args, **kwargs)
And import it in both Restaurant and Bar class:
class Restaurant(CommonMixin, PlaceMixin):
...
class Bar(CommonMixin, PlaceMixin):
...
Probably a better approach is to use a separate model for Address information. Then you won't need a new Mixin to override save(the approach given above feels like over engineering). So lets say you have a different address model, there you can simply put unique=True to restrict duplicate entries:
class Address(models.Model):
address = models.CharField(max_length=255, unique=True)
class PlaceMixin(models.Model):
address = models.ForeignKey(Address)
...
You can use abstract metadata to achieve this. And if you want to use any variable inside class model, you just need to use self.__class__ like so:
class PlaceMixin(models.Model):
name = models.CharField(max_length=200, null=True, blank=True)
address = models.CharField(max_length=200, null=True, blank=True)
sublocality = models.CharField(max_length=100, null=True, blank=True)
city = models.CharField(max_length=100, null=True, blank=True)
class Meta:
abstract = True
def save(self, *args, **kwargs):
try:
self.__class__.objects.get(address=self.address)
except self.__class__.DoesNotExist:
# Do something
else:
super().save(*args, **kwargs)
class Bar(PlaceMixin):
pass
class Restaurant(PlaceMixin):
pass
There are a lot of code design like this in Django source code, a lot of good practices in their project so give it a try. E.g: a line of code on Django repo

Django model audit mixin

Hello I wanted to know how to create a few fields and convert them into a mixin.
Let's say I have the following.
class Supplier(models.Model):
name = models.CharField(max_length=128)
created_by = models.ForeignKey(get_user_model(), related_name='%(class)s_created_by')
modified_by = models.ForeignKey(get_user_model(), related_name='%(class)s_modified_by')
created_date = models.DateTimeField(editable=False)
modified_date = models.DateTimeField()
def save(self, *args, **kwargs):
if not self.id:
self.created_date = timezone.now()
self.modified_date = timezone.now()
return super(Supplier, self).save(*args, **kwargs)
I want to create a mixin to avoid writing every time the last 4 fields into different models.
Here is the mixin I would create:
class AuditMixin(models.Model):
created_by = models.ForeignKey(get_user_model(), related_name='%(class)s_created_by')
modified_by = models.ForeignKey(get_user_model(), related_name='%(class)s_modified_by')
created_date = models.DateTimeField(editable=False)
modified_date = models.DateTimeField()
def save(self, *args, **kwargs):
if not self.id:
self.created_date = timezone.now()
self.modified_date = timezone.now()
return super(Supplier, self).save(*args, **kwargs)
class Supplier(AuditMixin):
name = models.Charfield(max_length=128)
How can I make sure that the related_name is relevant to the class the mixin is included into? Also in the save function, How can I make sure the class the mixin is included into is returned (as per the last line)?
Thank you!
Firstly, in any super call, you must always use the current class. So it will always be super(AuditMixin, self)... and your question does not apply.
Django itself takes care of substituting the current class name in related_name if you use the %(class)s syntax, which you have, so again there is nothing else for you to do. See the model inheritance docs.

Django model - set default charfield in lowercase

How to set default charfield in lowercase?
This is my model:
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
password = models.CharField(max_length=64)
name = models.CharField(max_length=200)
phone = models.CharField(max_length=20)
email = models.CharField(max_length=200)
def __init__(self, *args, **kwargs):
self.username = self.username.lower()
I tried the __init__ but it doesn't work. I want to make the username in lowercase every time new record saved. Thanks.
While overwriting save() method is a valid solution. I found it useful to deal with this on a Field level as opposed to the Model level by overwriting get_prep_value() method.
This way if you ever want to reuse this field in a different model, you can adopt the same consistent strategy. Also the logic is separated from the save method, which you may also want to overwrite for different purposes.
For this case you would do this:
class NameField(models.CharField):
def get_prep_value(self, value):
return str(value).lower()
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
password = models.CharField(max_length=64)
name = NameField(max_length=200)
phone = models.CharField(max_length=20)
email = models.CharField(max_length=200)
Just do it in the save method. ie, override the save method of Model class.
def save(self, *args, **kwargs):
self.username = self.username.lower()
return super(User, self).save(*args, **kwargs)
signals also works
from django.db.models.signals import pre_save
#receiver(pre_save, sender=YourModel)
def to_lower(sender, instance=None, **kwargs):
instance.text = instance.text.lower() if \
isinstance(instance.text, str) else ''
In my case I had a recipient_name field that I needed to make all lower case when it is stored on DB
class LowerField(models.CharField):
def get_prep_value(self, value):
return str(value).lower()
class Recipients(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='recipients', on_delete=models.CASCADE, )
recipient_account_number = models.IntegerField()
recipient_name = LowerField(max_length=30)
recipient_bank_name = models.CharField(max_length=30)
date = models.DateTimeField(auto_now=True, verbose_name='Transaction Date')
class Meta:
ordering = ['-date']
def __str__(self):
return self.recipient_name
def get_absolute_url(self):
return reverse('recipient-detail', kwargs={'pk': self.pk})
Similarly, you can apply to another table called Transactions in your app, like this
class Transactions(models.Model):
transaction_type = (
('transfer', 'Transfer'),
)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='transactions', on_delete=models.CASCADE, )
bank_name = LowerField(max_length=50)
def save(self, force_insert=False, force_update=False):
self.YourFildName = self.YourFildName.upper()
super(YourFomrName, self).save(force_insert, force_update)

How create new object automatically after creating other object in django?

For example, I have two classes.
class Book(models.Model):
title = models.CharField (...)
author = models.CharField (...)
price = models.Integer(...)
class Order(models.Model)
order_datetime = models.DateTimeField()
order_book = models.ForeignKey(Book,....)
New Order object should be created automatically after each addition of a new Book object in the database. And order_book fielt should be autocomplited with correspending book object.
How can I make it?
Thanx for the help!
You can override the save method of Book:
class Book(models.Model):
title = models.CharField (...)
author = models.CharField (...)
price = models.Integer(...)
def save(self, *args, **kwargs):
is_new = True if not self.id else False
super(Book, self).save(*args, **kwargs)
if is_new:
order = Order(order_book=self)
order.save()
You can also add auto_now_add=True to order_datetime if it's supposed to be filled with insertion time:
order_datetime = models.DateTimeField(auto_now_add=True)

Django - Saving a model using Admin

I have created a simple model - the instances of which I will be saving through the admin interface.
The field hashval needs to have the hashed value of title. It seems to just have the default hashvalue for every entry. How do I fix that ? Additionally, it should also get updated when title is updated. Any help in achieving this will be much appreciated. (Please point out if any duplicates exist)
class Entry(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=50)
content = models.TextField()
hashval = models.BigIntegerField()
hashval = abs(hash(title))
def __unicode__(self):
return smart_unicode(self.title + " " + str(self.hashval))
class Meta:
verbose_name_plural = 'Entries'
One option is to override your save method to apply this before it's saved.
class Entry(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=50)
content = models.TextField()
# Need to specify it as blank=True here or
# in the form so it can be ignored when the form is cleaned
hashval = models.BigIntegerField(blank=True)
def save(self, *args, **kwargs):
self.hashval = abs(hash(self.title))
return super(Entry, self).save(*args, **kwargs)