I am working on a little django application where the model contains a TextField and a PositiveIntegerField.
I need the PositiveInegerField to be populated with the number of words in the TextField.
This could be done via custom javascript code that count the number of words in the TextField's text area and put the value to the word count field text box before submitting the form but I do not want to play with custom javascript in the admin.
How to assign a value to a PositiveIntegerField programmatically?
This can be achieved using a pre_save Signal.
Create a signal-function like this:
def calculate_wordcount(sender, instance, **kwargs):
# count the words...
instance.word_count = # your count from line above
Then attach this function to your model. The preferred way since Django 1.7 is the application configuration (see doc).
You can attach your function in the AppConfig ready() - method
def ready(self):
pre_save.connect(calculate_wordcount,
sender= ,# your model
weak=False,
dispatch_uid='models.your_model.wordcount')
I'll leave all necessary imports up to you, please comment, if you need further direction!
While in general I think save signals are a reasonable idea, in this particular case you can also override .save() yourself
class MyClass(models.Model):
[stuff]
def save(self, *args, **kwargs):
self.wordcount = #something that calculates wordcount
super(self.myClass, self).save(*args, **kwargs) #Call django's save!
(as a rule of thumb, I'll overwrite save() if I'm just doing things to one model, but use a signal if I'm using one model to affect another. The idea is to keep all things affecting a model near it in the code. But this gets into personal philosophy)
Also WARNING: No matter which version you use, the signal or overwriting save(), bulk_create and bulk_delete will not send a signal or call your specific save function. See the documentation here: https://docs.djangoproject.com/en/1.8/topics/db/models/#overriding-predefined-model-methods
Related
If I do the folllowing:
obj = Model.objects.get(pk=2)
object.field = 'new value'
object.save()
It runs the custom save method that I have written in django.
However, if I do a normal update statement:
Model.objects.filter(pk=2).update(field='new value')
It does not use the custom save method. My question here is two-fold:
1) Why was that decision made in django -- why doesn't every 'save' implement the custom save method.
2) Is there a codebase-wide way to make sure that no update statements are ever made? Basically, I just want to ensure that the custom save method is always run whenever doing a save within the django ORM. How would this be possible?
I'm not a Django developer, but I dabble from time to time and no one else has answered yet.
Why was that decision made in django -- why doesn't every 'save' implement the custom save method.
I'm going to guess here that this is done as a speed optimization for the common case of just performing a bulk update. update works on the SQL level so it is potentially much faster than calling save on lots of objects, each one being its own database transaction.
Is there a codebase-wide way to make sure that no update statements are ever made? Basically, I just want to ensure that the custom save method is always run whenever doing a save within the django ORM. How would this be possible?
You can use a custom manager with a custom QuerySet that raises some Exception whenever update is called. Of course, you can always loop over the Queryset and call save on each object if you need the custom behavior.
Forbidding Update on a Model
from django.db import models
class NoUpdateQuerySet(models.QuerySet):
"""Don't let people call update! Muahaha"""
def update(self, **kwargs):
# you should raise a more specific Exception.
raise Exception('You shall not update; use save instead.')
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
# setting the custom manager keeps people from calling update.
objects = NoUpdateQuerySet.as_manager()
You would just need to set the NoUpdateQuerySet as a manager for each model you don't want to update. I don't really think it's necessary to set a custom QuerySet though; if it were me I would just not call update, and loop through the objects that need to be saved whenever I need to. You may find a time when you want to call update, and this would end up being very annoying.
Forbidding Update Project-Wide
If you really really decide you hate update, you can just monkey-patch the update method. Then you can be completely certain it's not being called. You can monkey-patch it in your project's settings.py, since you know that module will be imported:
def no_update(self, **kwargs):
# probably want a more specific Exception
raise Exception('NO UPDATING HERE')
from django.db.models.query import QuerySet
QuerySet.update = no_update
Note that the traceback will actually be pretty confusing, since it will point to a function in settings.py. I'm not sure how much, if ever, update is used by other apps; this could have unintended consequences.
I'm working on a Django application connected to a LDAP server. Here's the trick i'm trying to do.
I have a model called system containing some information about computers. When i add a new system, the model generates a unique UUID, like and AutoField. The point is that this parameter is generated when saving, and only the first time.
After saved, i need a function to keep that UUID and create a new object on my LDAP.
Since i didn't know a lot about signals, i tried overriding the model save function in this way:
def save(self):
# import needed modules
import ldap
import ldap.modlist as modlist
[--OPERATIONS ON THE LDAP--]
super(System, self).save()
In this way, if i modify an existing system all work as should, because its UUID has been generated already. But if i try adding a new system i find the error UUID is None, and i can't work with an empty variable on LDAP (also it would be useless, don't u think so?)
It seems i need to call the function that work on LDAP after the system is saved, and so after an UUID has been generated. I tried to unserstand how to create a post_save function but i couldn't get it.
How can i do that?
Thanks
As you stated on your own, you do need signals, it will allow your code to stay more clean and seperate logic between parts.
The usual approach is to place signals just at the end of your models file:
# Signals
from django.dispatch import receiver
#receiver(models.signals.post_save, sender=YourModel)
def do_something(sender, instance, created, **kwargs):
....
On the above example we connect the post_save signal with the do_something function, this is performed through the decorator #receiver, sender of the decorator points to your Model Class.
Inside your function you have instance which holds the current instance of the model and the created flag which allows you to determine if this is a new record or an old (if the Model is being updated).
Signals would be excellent for something like this, but moving the line super(System, self).save() to the top of the save method might work as well. That means you first save the instance, before passing the saved object to the LDAP.
I am trying to add points to a User's profile after they submit a comment- using the Django comment framework. I think I need to use a post_save but am not sure to be perfectly honest.
Here is what I have as a method in my models.py:
def add_points(request, Comment):
if Comment.post_save():
request.user.get_profile().points += 2
request.user.get_profile().save()
From the examples of post_save I've found, this is far from what is shown - so I think I am way off the mark.
Thank you for your help.
Unfortunately this makes no sense at all.
Firstly, this can't be a method, as it doesn't have self as the first parameter.
Secondly, it seems to be taking the class, not an instance. You can't save the class itself, only an instance of it.
Thirdly, post_save is not a method of the model (unless you've defined one yourself). It's a signal, and you don't call a signal, you attach a signal handler to it and do logic there. You can't return data from a signal to a method, either.
And finally, the profile instance that you add 2 to will not necessarily be the same as the one you save in the second line, because Django model instances don't have identity. Get it once and put it into a variable, then save that.
The Comments framework defines its own signals that you can use instead of the generic post_save. So, what you actually need is to register a signal handler on comment_was_posted. Inside that handler, you'll need to get the user's profile, and update that.
def comment_handler(sender, comment, request, **kwargs):
profile = request.user.get_profile()
profile.points += 2
profile.save()
from django.contrib.comments.signals import comment_was_posted
comment_was_posted.connect(comment_handler, sender=Comment)
I know that you can prepopulate admin form fields based on other fields. For example, I have a slug field that is automatically populated based on the title field.
However, I would also like to make other automatic prepopulations based on the date. For example, I have an URL field, and I want it to automatically be set to http://example.com/20090209.mp3 where 20090209 is YYYYMMDD.
I would also like to have a text field that automatically starts with something like "Hello my name is author" where author is the current user's name. Of course, I also want the person to be able to edit the field. The point is to just make it so the user can fill out the admin form more easily, and not just to have fields that are completely automatic.
I know that you can prepopulate some values via GET, it will be something like this
http://localhost:8000/admin/app/model/add/?model_field=hello
I got some problems with date fields but, maybe this could help you.
I recently used Django's ModelAdmin.get_form method for this purpose.
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['my_field_name'].initial = 'abcd'
return form
Yout should be careful about side effects as you are manipulating the base_fields directly.
Django's built-in prepopulated_fields functionality is hardcoded to slugify, it can't really be used for more general purposes.
You'll need to write your own Javascript function to do the prepopulating. The best way to get it included in the admin page is to include it in the inner Media class of a custom Form or Widget. You'll then need to customize your ModelAdmin subclass to use the custom form or widget. Last, you'll need to render some inline Javascript along with each prepopulated field to register the onchange handler and tell it which other field to populate from; I would render this via the custom Widget. To make it nice and declarative you could use a custom ModelAdmin attribute (similar to prepopulated_fields), and override ModelAdmin.formfield_for_dbfield to create the widget and pass in the information about what field it should prepopulate from.
This kind of admin hacking is almost always possible, but (as you can tell from this convoluted summary) rarely simple, especially if you're making an effort to keep your code nicely encapsulated.
I tried a few of these answers and none of them worked. I simply wanted to prepulate a field with another field from a related model. Taking this answer as a starting point, I finally tried to manipulate the model instance object (here obj) directly and it worked for me.
class MyModelAdmin(models.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
if not obj.some_model_field:
obj.some_model_field = obj.related_model.prepopulating_model_field
return form
You can override the default django admin field by replacing it with a form field of your choice.
Check this :
Add custom validation to the admin
I would also like to have a text field
that automatically starts with
something like "Hello my name is
author".
Check out the docs at: http://docs.djangoproject.com/en/dev/ref/models/fields/#default
You could have a CharField() or TextField() in your model, and set this option, which will set the default text. 'default' can also be a callable function.
Something like:
models.CharField(max_length=250, default="Default Text")
The slug handling is done with javascript.
So you have to override the templates in the admin and then populate the fields with javascript. The date thing should be trivial, but I dont know how you should get the logged in users name to the script (not that I have thought very hard but you get the drift :).
I'm having trouble wrapping my head around this. Right now I have some models that looks kind of like this:
def Review(models.Model)
...fields...
overall_score = models.FloatField(blank=True)
def Score(models.Model)
review = models.ForeignKey(Review)
question = models.TextField()
grade = models.IntegerField()
A Review is has several "scores", the overall_score is the average of the scores. When a review or a score is saved, I need to recalculate the overall_score average. Right now I'm using a overridden save method. Would there be any benefits to using Django's signal dispatcher?
Save/delete signals are generally favourable in situations where you need to make changes which aren't completely specific to the model in question, or could be applied to models which have something in common, or could be configured for use across models.
One common task in overridden save methods is automated generation of slugs from some text field in a model. That's an example of something which, if you needed to implement it for a number of models, would benefit from using a pre_save signal, where the signal handler could take the name of the slug field and the name of the field to generate the slug from. Once you have something like that in place, any enhanced functionality you put in place will also apply to all models - e.g. looking up the slug you're about to add for the type of model in question, to ensure uniqueness.
Reusable applications often benefit from the use of signals - if the functionality they provide can be applied to any model, they generally (unless it's unavoidable) won't want users to have to directly modify their models in order to benefit from it.
With django-mptt, for example, I used the pre_save signal to manage a set of fields which describe a tree structure for the model which is about to be created or updated and the pre_delete signal to remove tree structure details for the object being deleted and its entire sub-tree of objects before it and they are deleted. Due to the use of signals, users don't have to add or modify save or delete methods on their models to have this management done for them, they just have to let django-mptt know which models they want it to manage.
You asked:
Would there be any benefits to using Django's signal dispatcher?
I found this in the django docs:
Overridden model methods are not called on bulk operations
Note that the delete() method for an object is not necessarily called
when deleting objects in bulk using a QuerySet or as a result of a
cascading delete. To ensure customized delete logic gets executed, you
can use pre_delete and/or post_delete signals.
Unfortunately, there isn’t a workaround when creating or updating
objects in bulk, since none of save(), pre_save, and post_save are
called.
From: Overriding predefined model methods
Small addition from Django docs about bulk delete (.delete() method on QuerySet objects):
Keep in mind that this 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.
https://docs.djangoproject.com/en/1.11/topics/db/queries/#deleting-objects
And bulk update (.update() method on QuerySet objects):
Finally, realize that update() does an update at the SQL level and,
thus, does not call any save() methods on your models, nor does it
emit the pre_save or post_save signals (which are a consequence of
calling Model.save()). If you want to update a bunch of records for a
model that has a custom save() method, loop over them and call save()
https://docs.djangoproject.com/en/2.1/ref/models/querysets/#update
If you'll use signals you'd be able to update Review score each time related score model gets saved. But if don't need such functionality i don't see any reason to put this into signal, that's pretty model-related stuff.
It is a kind sort of denormalisation. Look at this pretty solution. In-place composition field definition.