I have the following save method for the Catalog model:
class Catalog:
...
def save(self, *args, **kwargs):
if not self.pk:
log.error('NOT ALLOWING SAVE OF %s' % self.__class__.__name__)
else:
super(Catalog, self).save(*args, **kwargs)
Is there a way to make:
super(Catalog, self).save(*args, **kwargs)
More generic, so I don't have to hardcode Catalog in there, and I can use this for any model without any modification?
One option is to create a pre_save function that will error on any of those models.
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save)
def refuse_creation(sender, instance, *args, **kwargs):
FORBIDDEN_MODELS = ['Table1', 'Table2', ...]
cls_name = instance.__class__.__name__
if cls_name in FORBIDDEN_MODELS:
if not instance.pk:
log.error('REFUSING TO CREATE OBJECT FOR %s' % cls_name)
raise
You shouldn't be copying and pasting code between classes. That misses the whole point of the vital principle of Don't Repeat Yourself (DRY).
Instead, make this a method on a separate class which you can use as a mixin.
class SavePrevented(object):
...
def save(self, *args, **kwargs):
if not self.pk:
log.error('NOT ALLOWING SAVE OF %s' % self.__class__.__name__)
else:
super(SavePrevented, self).save(*args, **kwargs)
and use it when you define any other model:
class Catalog(SavePrevented, models.Model):
...
Related
I have this function, I want to make it a method of a Message model class.
def save_message_to_db(message, message_id):
mex = Message(
message_id=message_id,
subject=message.subject,
sender=message.sender.address,
has_attachments=message.has_attachments,
sent_date=message.sent,
received_date=message.received
)
mex.save()
return mex
I've tried various ways, but still get errors. I need to return, expecially the id of the object saved.
Update
#staticmethod
def save_mex(message, message_id):
mex = Message(
message_id=message_id,
subject=message.subject,
sender=message.sender.address,
has_attachments=message.has_attachments,
sent_date=message.sent,
received_date=message.received
)
mex.save()
return mex
this is the only way I made it work, but this is a work around...
I get the errors in the Pyacharm IDE, I can not understand how to use the super() in this situation, because I want to pass an object and treat it in this method, not args and kwargs.
It should be as simple as this. Simply override the save method of your model and return the instance after the super call.
class YourModel(models.Model):
name = models.CharField(max_length=20)
def save(self, *args, **kwargs):
super(YourModel, self).save(*args, **kwargs)
return self
your_model_saved_instance = YourModel(name='Edoardo').save()
You can even make a base model class with this feature and use it in every model you want.
class BaseModel(models.Model):
class Meta:
abstract = True
def save(self, *args, **kwargs):
super(BaseModel, self).save(*args, **kwargs)
return self
class YourModel(BaseModel):
name = models.CharField(max_length=20)
your_model_saved_instance = YourModel(name='Edoardo').save()
I am trying to override the save method on a model in order to generate a unique, second auto-incrementing id.
I create my class and override the save() method, but for some reason it is erroring out with the following error:
TypeError: %d format: a number is required, not NoneType
Here's the code:
class Person(models.Model):
target = models.OneToOneField(Target)
person = models.OneToOneField(User)
gender = models.CharField(max_length=1)
gender_name = models.CharField(max_length=100)
person_id = models.CharField(max_length=100)
def save(self, *args, **kwargs):
self.person_id = "%07d" % self.id
super(Person, self).save(*args, **kwargs)
Is it because I didn't pass an id parameter and it hasn't saved yet? Is there anyway to generate a value from the id?
Safest and easiest way to achieve what you want is to use a post_save signal because it is fired right after save is called, but before the transaction is committed to the database.
from django.dispatch import receiver
from django.db.models.signals import post_save
#receiver(post_save, sender=Person)
def set_person_id(sender, instance, created, **kwargs):
if created:
instance.person_id = "%07d" % instance.id
instance.save()
Yes, self.id will be Nonein some cases, and then the assignment will fail.
However you cannot just the assignment and the call to super, as suggested in the comments, because then you wouldn't be persisting the assignment to the database layer.
You need to check whether the model has an id and then proceed differently:
def save(self, *args, **kwargs):
if not self.id: # Upon instance creation
super(Person, self).save(*args, **kwargs) # Acquire an ID
self.person_id = "%07d" % self.id # Set the person_id
return super(Person, self).save(*args, **kwargs)
This issues two save operations to the database. You will want to wrap them in a transaction to make sure your database receives these two fields simultaneously.
from django.db import IntegrityError, transaction
class Person(models.Model):
target = models.OneToOneField(Target)
person = models.OneToOneField(User)
gender = models.CharField(max_length=1)
gender_name = models.CharField(max_length=100)
person_id = models.CharField(max_length=100)
def create_person_id(self):
if not self.id: # Upon instance creation
super(Person, self).save(*args, **kwargs) # Acquire an ID
self.person_id = "%07d" % self.id
def save(self, *args, **kwargs):
try:
with transaction.atomic():
self.create_person_id
return super(Person, self).save(*args,**kwargs)
except IntegrityError:
raise # or deal with the error
I agree that signals might be the better option, if not, try using pk instead of id.
class Person(models.Model):
# [ . . . ]
def save(self, *args, **kwargs):
self.person_id = "%07d" % self.pk
super(Person, self).save(*args, **kwargs)
Suppose I had this following code:
class MyModel(models.Model):
...
...
def save(self, *args, **kwargs):
# pre-save edits can go here...
super(MyModel, self).save(*args, **kwargs)
When I create and save a model, MyModel(blah, blah, blah), there is a possibility that
one of the input fields is "None". In the overridden save method, the goal is to check for if a field is none, and if one is, change it to some other default value.
Are the input fields in args or kwargs? And is overriding save() even the proper way to do this?
I was thinking something like this:
def save(self, *args, **kwargs):
if 'username' in args and args['username'] is None:
args['username'] = some_default_value
super(MyModel, self).save(*args, **kwargs)
So where are the input params? args* or **kwargs, thank you.
I think it's better a pre_save signal to see if a input value is None:
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
class MyModel(models.Model):
field1 = models.TextField()
field2 = models.IntegerField()
#receiver(pre_save, sender=MyModel)
def mymodel_save_handler(sender, instance, *args, **kwargs):
if instance.field1 is None or instance.field1 == "":
instance.field1 = default_value
If you prefer override save method you can access to the model fields with self
def save(self, *args, **kwargs):
if self.username is None:
self.username = some_default_value
super(MyModel, self).save(*args, **kwargs)
Before saving model I'm re-size a picture. But how can I check if new picture added or just description updated, so I can skip rescaling every time the model is saved?
class Model(model.Model):
image=models.ImageField(upload_to='folder')
thumb=models.ImageField(upload_to='folder')
description=models.CharField()
def save(self, *args, **kwargs):
if self.image:
small=rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
super(Model, self).save(*args, **kwargs)
I want to rescale only if new image loaded or image updated, but not when description updated.
Some thoughts:
class Model(model.Model):
_image=models.ImageField(upload_to='folder')
thumb=models.ImageField(upload_to='folder')
description=models.CharField()
def set_image(self, val):
self._image = val
self._image_changed = True
# Or put whole logic in here
small = rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
def get_image(self):
return self._image
image = property(get_image, set_image)
# this is not needed if small_image is created at set_image
def save(self, *args, **kwargs):
if getattr(self, '_image_changed', True):
small=rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
super(Model, self).save(*args, **kwargs)
Not sure if it would play nice with all pseudo-auto django tools (Example: ModelForm, contrib.admin etc).
Check the model's pk field. If it is None, then it is a new object.
class Model(model.Model):
image=models.ImageField(upload_to='folder')
thumb=models.ImageField(upload_to='folder')
description=models.CharField()
def save(self, *args, **kwargs):
if 'form' in kwargs:
form=kwargs['form']
else:
form=None
if self.pk is None and form is not None and 'image' in form.changed_data:
small=rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
super(Model, self).save(*args, **kwargs)
Edit: I've added a check for 'image' in form.changed_data. This assumes that you're using the admin site to update your images. You'll also have to override the default save_model method as indicated below.
class ModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.save(form=form)
You may supply extra argument for confirming a new image is posted.
Something like:
def save(self, new_image=False, *args, **kwargs):
if new_image:
small=rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
super(Model, self).save(*args, **kwargs)
or pass request variable
def save(self, request=False, *args, **kwargs):
if request and request.FILES.get('image',False):
small=rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
super(Model, self).save(*args, **kwargs)
I think these wont break your save when called simply.
You may put this in your admin.py so that this work with admin site too (for second of above solutions):
class ModelAdmin(admin.ModelAdmin):
....
def save_model(self, request, obj, form, change):
instance = form.save(commit=False)
instance.save(request=request)
return instance
Query the database for an existing record with the same PK. Compare the file sizes and checksums of the new and existing images to see if they're the same.
What I did to achieve the goal was to make this..
# I added an extra_command argument that defaults to blank
def save(self, extra_command="", *args, **kwargs):
and below the save() method is this..
# override the save method to create an image thumbnail
if self.image and extra_command != "skip creating photo thumbnail":
# your logic here
so when i edit some fields but not editing the image, I put this..
Model.save("skip creating photo thumbnail")
you can replace the "skip creating photo thumbnail" with "im just editing the description" or a more formal text.
Hope this one helps!
In new version it is like this:
def validate(self, attrs):
has_unknown_fields = set(self.initial_data) - set(self.fields.keys())
if has_unknown_fields:
raise serializers.ValidationError("Do not send extra fields")
return attrs
I have found one another simple way to store the data into the database
models.py
class LinkModel(models.Model):
link = models.CharField(max_length=500)
shortLink = models.CharField(max_length=30,unique=True)
In database I have only 2 variables
views.py
class HomeView(TemplateView):
def post(self,request, *args, **kwargs):
form = LinkForm(request.POST)
if form.is_valid():
text = form.cleaned_data['link'] # text for link
dbobj = LinkModel()
dbobj.link = text
self.no = self.gen.generateShortLink() # no for shortLink
dbobj.shortLink = str(self.no)
dbobj.save() # Saving from views.py
In this I have created the instance of model in views.py only and putting/saving data into 2 variables from views only.
With Django models, I want to achieve this:
class Foo(models.Model):
name = models.CharField(max_length=50)
#wrapping the save function, including extra tasks
def save(self, *args, **kwargs):
super(Foo, self).save(*args, **kwargs)
if extra_param:
...do task 1
else:
...do task 2
And while crating Foo I want to pass such as
Foo(name="Bill Gates",extra_param=True).save() # now triggers the task 1
Foo(name="Bill Gates").save() # now triggers the task 2
How can this be done? I am also open to any other suggestions :)
Thanks
You can define non-persistent fields in your model.
class Foo(models.Model):
name = models.CharField(max_length=50)
extra_param = False
def save(self, *args, **kwargs):
...
print self.extra_param
Alternatively, you can do:
Foo(name="Bill Gates").save(extra_param=True)
def save(self, *args, **kwargs):
...
print kwargs["extra_param"]