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.
Related
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
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/'
I have a super class for my models as below:
class BaseModel(models.Model):
""" BaseClass vase aksare model ha """
def __init__(self, *args, **kwargs):
super(BaseModel, self).__init__(args, kwargs)
print('******> base model __init__')
status = models.IntegerField(default=1)
is_deleted = models.BooleanField(default=False)
create_user = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_creator_related")
create_date = models.DateTimeField()
update_date = models.DateTimeField()
update_user = models.ForeignKey(User, related_name="%(app_label)s_%(class)s_updater_related")
class Meta:
abstract = True
def validate(self):
print('^^^^^^^^^^^^^^^^^^^^^^^^^^^^base validation')
and I have a profile model as below:
class Profile(BaseModel):
def __init__(self, *args, **kwargs):
super(Profile, self).__init__(args, kwargs)
""" User profile """
user = models.OneToOneField(User, related_name='profile')
mobile = models.CharField(max_length=25, null=True)
firstname_en = models.CharField(max_length=500, null=True)
lastname_en = models.CharField(max_length=500, null=True)
gender = models.IntegerField(default=0)
birth_date = models.DateTimeField(null=True)
edu_bg = models.ForeignKey('Category', related_name="profile__edu_bg", null=True)
region = models.ForeignKey('Category', related_name="profile__region", null=True)
credit = models.DecimalField(default=0, decimal_places=6, max_digits=15)
key = models.TextField(null=True)
secret = models.TextField(null=True)
I have an error when I want to insert a new userProfile as below:
TypeError: int() argument must be a string, a bytes-like object or a number, not 'tuple'.
then print the vars(userprofileObject) and realized that 'id': ((), {}), however, I have not set it. When I removed the __init__ functions or set id to None in insertion code, problem solved.
Any idea?
I need those __init__ and also don't want to set id=None in my code
This is how django's models work. You shouldn't change their __init__ method.
This is why
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:
# Add a classmethod on the model class:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
#classmethod
def create(cls, title):
book = cls(title=title)
# do something with the book
return book
book = Book.create("Pride and Prejudice")
Source https://docs.djangoproject.com/en/1.10/ref/models/instances/#django.db.models.Model
Also read this Writing a __init__ function to be used in django model
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)
In my model I overwrite the save-method for my blog model to auto-populate the slug field using the slugify method.
class BlogPost(models.Model):
title = models.CharField(max_length=100,unique=True)
slug = models.SlugField(max_length=100,unique=True)
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(Author)
body = models.TextField()
category = models.ForeignKey(BlogCategory)
def save(self, *args, **kwargs):
if not self.id:
# Newly created object, so set slug
self.slug = slugify(self.title)
super(BlogPost, self).save(*args, **kwargs)
But creating a new object in the admin interface doesn't work without either setting the slug field manually or doing something like
class BlogPostAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
Basically I currently have the same functionality defined twice. Any ideas on how to avoid this? And: why doesn't work my own save method in the admin?
You should put blank=True in the definition of the slug field.