I have a model that pings a REST service and saves the results.
class StoreStatus(models.Model):
store = models.OneToOneField(Store)
status = models.TextField()
def save(self, *args, **kwargs):
self.status = get_store_information(self.store.code)
self.pk = self.store.pk
super( StoreStatus, self ).save(*args, **kwargs)
I need to run it every repeatedly and figure I can just .save() it in a view, since the "Store" object is in the majority of my views.
Is there a better way to do this? I had to the set the pk manually because I was getting duplicate errors when I tried to save a second time.
Seems kind of dirty, and I'm trying to improve my coding.
Thanks
That looks quite bad.
First, associating the retrieval of the status information with the saving of the object is quite a bad idea. If 'updating' is the only action you'll ever perform on this model, then maybe a better idea would be to write a an "update()" method that automatically saves once the status is updated, instead of doing it the other way around.
def update(self):
self.status = get_store_information(self.store.code)
self.save()
Second: how are you creating the first instance of this model? You'll get duplication errors if you are trying to save a new instance every time the model gets updated. Say, if you do something like this:
# this will crap out
update = Update(mystore)
update.save()
What you should be doing is something like:
# this will work (provided we have 'update')
mystore.status.update()
Or:
# always retrieve the stored instance before saving
status, created = StoreStatus.objects.get_or_create(store=mystore)
status.update()
In case you are lazy enough, you can always add an "update_status" method to your Store model and perform the creation/update there. It's always better to be explicit about what you are doing. Remember: Django is based upon the principle of least surprise, and so should your code! :)
If I were you, I would have created a function that would:
1. Accept the Store object as a parameter,
2. Make the REST call, and
3. On receiving the response then update the status in the StoreStatus.
This would be desirable to enable loose coupling which is required for architectures involving web-based services.
Moreover, if you just want to avoid duplicate PK errors, you can check for id to safely loop the update and create conditions.
def save(self, *args, **kwargs):
if self.id:
# Update case
pass
else:
# New object
# Process for the new object
pass
# Save the changes
super(StoreStatus, self).save(*args, **kwargs)
Related
Let´s say, there is a Django model called TaskModel which has a field priority and we want to insert a new element and increment the existing element which has already the priority and increment also the priority of the following elements.
priority is just a numeric field without any special flags like unique or primary/foreign key
queryset = models.TaskModel.objects.filter().order_by('priority')
Can this be done in a smart way with some methods on the Queryset itself?
I believe you can do this by using Django's F expressions and overriding the model's save method. I guess you could instead override the model's __init__ method as in this answer, but I think using the save method is best.
class TaskModel(models.Model):
task = models.CharField(max_length=20)
priority = models.IntegerField()
# Override the save method so whenever a new TaskModel object is
# added, this will be run.
def save(self, *args, **kwargs):
# First get all TaskModels with priority greater than, or
# equal to the priority of the new task you are adding
queryset = TaskModel.objects.filter(priority__gte=self.priority)
# Use update with the F expression to increase the priorities
# of all the tasks above the one you're adding
queryset.update(priority=F('priority') + 1)
# Finally, call the super method to call the model's
# actual save() method
super(TaskModel, self).save(*args, **kwargs)
def __str__(self):
return self.task
Keep in mind that this can create gaps in the priorities. For example, what if you create a task with priority 5, then delete it, then add another task with priority 5? I think the only way to handle that would be to loop through the queryset, perhaps with a function like the one below, in your view, and call it whenever a new task is created, or it's priority modified:
# tasks would be the queryset of all tasks, i.e, TaskModels.objects.all()
def reorder_tasks(tasks):
for i, task in enumerate(tasks):
task.priority = i + 1
task.save()
This method is not nearly as efficient, but it will not create the gaps. For this method, you would not change the TaskModel at all.
Or perhaps you can also override the delete method of the TaskModel as well, as shown in this answer, but I haven't had a chance to test this yet.
EDIT
Short Version
I don't know how to delete objects using a similar method to saving while keeping preventing priorities from having gaps. I would just use a loop as I have shown above.
Long version
I knew there was something different about deleting objects like this:
def delete(self, *args, **kwargs):
queryset = TaskModel.objects.filter(priority__gt=self.priority)
queryset.update(priority=F('priority') - 1)
super(TaskModel, self).delete(*args, **kwargs)
This will work, in some situations.
According to the docs on delete():
Keep in mind that this [calling delete()] will, whenever possible, be executed purely in
SQL, and so the delete() methods of individual object instances will
not necessarily be called during the process. If you’ve provided a
custom delete() method on a model class and want to ensure that it is
called, you will need to “manually” delete instances of that model
(e.g., by iterating over a QuerySet and calling delete() on each
object individually) rather than using the bulk delete() method of a
QuerySet.
So if you delete() a TaskModel object using the admin panel, the custom delete written above will never even get called, and while it should work if deleting an instance, for example in your view, since it will try acting directly on the database, it will not show up in the python until you refresh the query:
tasks = TaskModel.objects.order_by('priority')
for t in tasks:
print(t.task, t.priority)
tr = TaskModel.objects.get(task='three')
tr.delete()
# Here I need to call this AGAIN
tasks = TaskModel.objects.order_by('priority')
# BEFORE calling this
for t in tasks:
print(t.task, t.priority)
# to see the effect
If you still want to do it, I again refer to this answer to see how to handle it.
I am currently implementing soft deletion for all models in my database. The idea is that when an instance gets deleted, it actually gets archived with all of its children. If the user tries to create an instance that is identical to the archived one, the archived one gets undeleted along with all of its children instead of creating a new instance.
To do this, I am using django-safedelete where I am making a BaseModel with an overwritten save() method that looks something like this:
def save(self, *args, **kwargs):
# get the foreign key id
foreign_key_id = self.foreign_field.id
# execute a query by that id and some other params
'''I don't know how to do this'''
As to how to do it, I thought I could construct a kwargs dictionary that consists of pairs of <field>:value where <field> = self._meta.get_field(some_field.name) and value = getattr(self, some_field.name).
So how do I add the foreign_key_id to kwargs? I know there is this syntax: Model.objects.filter(foreign_field__id=value)
...but I don't know how to replicate that to put into kwargs the way I'm doing it.
Likewise, is there a better way to do this in general? I don't want to hard-code too many things, which is why I didn't just do this individually for each of the models that I have.
Thank you so much in advance.
Sometimes you want to do something when there is an event for model (on create object, on update object, on delete object).
There is a method you can override in Model called save. And it even has a parameter forced_insert which I first thought would always be set to a proper value meaning whether an object will be created or updated. But the parameter is optional and you cannot expect it to be right.
Searching in source code of Model led me to methods _do_update and _do_insert, but the underscore at the beginning tells me that this method is not recommended for use. And it also have a lot of parameters which pollute code when you override it.
The only solution left that I could find is using django.db.models.signals. But I believe they are meant to be used for external purposes like when you want to create a UserProfile on every User create. But I have some internal purposes like updating fields on update. Also using signals makes code look spreaded and harder to understand.
What would be the right way deal with implementing functional on these Model events?
Look at this simplified condition from django base Model _save_table method - decision to update or insert depends on model pk and force_insert value:
def _save_table(self, raw=False, cls=None, force_insert=False,
force_update=False, using=None, update_fields=None):
updated = False
# ...
if pk_set and not force_insert:
updated = self._do_update()
if not updated:
result = self._do_insert()
# ...
return updated
And you can go that same way in your overrided save method if you want entirely custom update/insert operations:
def save(self, force_insert=False, **kwargs):
updated = False
if self.pk and not force_insert:
updated = self.custom_update()
if not updated:
self.custom_insert()
return updated
I actually have this method in my Model:
def speed_score_compute(self):
# Speed score:
#
# - 8 point for every % of time spent in
# high intensity running phase.
# - Average Speed in High Intensity
# running phase (30km/h = 50 points
# 0-15km/h = 15 points )
try:
high_intensity_running_ratio = ((
(self.h_i_run_time * 100)/self.training_length) * 8)
except ZeroDivisionError:
return 0
high_intensity_running_ratio = min(50, high_intensity_running_ratio)
if self.h_i_average_speed < 15:
average_speed_score = 10
else:
average_speed_score = self.cross_multiplication(
30, self.h_i_average_speed, 50)
final_speed_score = high_intensity_running_ratio + average_speed_score
return final_speed_score
I want to use it as default for my Model like this:
speed_score = models.IntegerField(default=speed_score_compute)
But this don't work (see Error message below) . I've checked different topic like this one, but this work only for function (not using self attribute) but not for methods (I must use methods since I'm working with actual object attributes).
Django doc. seems to talk about this but I don't get it clearly maybe because I'm still a newbie to Django and Programmation in general.
Is there a way to achieve this ?
EDIT:
My function is defined above my models. But here is my error message:
ValueError: Could not find function speed_score_compute in tournament.models.
Please note that due to Python 2 limitations, you cannot serialize unbound method functions (e.g. a method declared and used in the same class body). Please move the function into the main module body to use migrations.
For more information, see https://docs.djangoproject.com/en/1.8/topics/migrations/#serializing-values
Error message is clear, it seems that I'm not able to do this. But is there another way to achieve this ?
The problem
When we provide a default=callable and provide a method from a model, it doesn't get called with the self argument that is the model instance.
Overriding save()
I haven't found a better solution than to override MyModel.save()* like below:
class MyModel(models.Model):
def save(self, *args, **kwargs):
if self.speed_score is None:
self.speed_score = ...
# or
self.calculate_speed_score()
# Now we call the actual save method
super(MyModel, self).save(*args, **kwargs)
This makes it so that if you try to save your model, without a set value for that field, it is populated before the save.
Personally I just prefer this approach of having everything that belongs to a model, defined in the model (data, methods, validation, default values etc). I think this approach is referred to as the fat Django model approach.
*If you find a better approach, I'd like to learn about it too!
Using a pre_save signal
Django provides a pre_save signal that runs before save() is ran on the model. Signals run synchronously, i.e. the pre_save code needs to finish running before save() is called on the model. You'll get the same results (and order of execution) as overriding the save().
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
#receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
instance = kwargs['instance']
instance.populate_default_values()
If you prefer to keep the default values behavior separated from the model, this approach is for you!
When is save() called? Can I work with the object before it gets saved?
Good questioin! Because we'd like the ability to work with our object before subjecting to saving of populating default values.
To get an object, without saving it, just to work with it we can do:
instance = MyModel()
If you create it using MyModel.objects.create(), then save() will be called. It is essentially (see source code) equivalent to:
instance = MyModel()
instance.save()
If it's interesting to you, you can also define a MyModel.populate_default_values(), that you can call at any stage of the object lifecycle (at creation, at save, or on-demande, it's up to you)
I have a model with the following save method
def save(self, *args, **kwargs):
self.url = slugify(self.name)
# Saving creative if object is saved first time
if self.reloadImages is True:
print "Reload IMages"
for img in self.creative_url.split("\n"):
content = urllib2.urlopen(img).read()
extra_image = ExtraImage()
extra_image.img.save("%s.jpg" %(self.name), ContentFile(content), save=False)
extra_image.save()
self.extra_imgs.add(extra_image)
#print self.extra_imgs.all()
#self.reloadImages = False
super(Item, self).save(*args, **kwargs)
I expect it to create an ExtraImage objects (which consists from ImageField). And add it to the ManyToMany extra_img field of the current model....
It's strange it seems that method makes its job correctly, after page refresh I don't see anything in extra_imgs field. (And ExtraImage items are actually created)
The problem is that you add images
self.extra_imgs.add(extra_image)
before self is created in your database
super(Item, self).save(*args, **kwargs)
To add on to San4ez's answer, this problem (the fact that you have to save an object before you save its dependencies, and the dependency doesn't always go the way you expect it to) frustrated me when I first started learning Django.
If you have large object graphs (or even small ones :) ) and you don't want to deal with this manually, check out Django ORM Tools which has a GraphSaver class which will automatically detect dependencies and save the graph in the right order.