diff --git a/AUTHORS b/AUTHORS index 10e1192386..58179e111a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -439,6 +439,7 @@ answer newbie questions, and generally made Django that much better: viestards.lists@gmail.com George Vilches Vlado + Zachary Voase Milton Waddams Chris Wagner Rick Wagner diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index c6509ece3d..7a0606dafe 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -188,6 +188,46 @@ almost always do the right thing and trying to override that will lead to errors that are difficult to track down. This feature is for advanced use only. +Updating attributes based on existing fields +-------------------------------------------- + +Sometimes you'll need to perform a simple arithmetic task on a field, such +as incrementing or decrementing the current value. The obvious way to +achieve this is to do something like:: + + >>> product = Product.objects.get(name='Venezuelan Beaver Cheese') + >>> product.number_sold += 1 + >>> product.save() + +If the old ``number_sold`` value retrieved from the database was 10, then +the value of 11 will be written back to the database. + +This can be optimized slightly by expressing the update relative to the +original field value, rather than as an explicit assignment of a new value. +Django provides :ref:`F() expressions ` as a way of +performing this kind of relative update. Using ``F()`` expressions, the +previous example would be expressed as:: + + >>> from django.db.models import F + >>> product = Product.objects.get(name='Venezuelan Beaver Cheese') + >>> product.number_sold = F('number_sold') + 1 + >>> product.save() + +This approach doesn't use the initial value from the database. Instead, it +makes the database do the update based on whatever value is current at the +time that the save() is executed. + +Once the object has been saved, you must reload the object in order to access +the actual value that was applied to the updated field:: + + >>> product = Products.objects.get(pk=product.pk) + >>> print product.number_sold + 42 + +For more details, see the documentation on :ref:`F() expressions +` and their :ref:`use in update queries +`. + Deleting objects ================ @@ -196,7 +236,7 @@ Deleting objects Issues a SQL ``DELETE`` for the object. This only deletes the object in the database; the Python instance will still be around, and will still have data in its fields. - + For more details, including how to delete objects in bulk, see :ref:`topics-db-queries-delete`. diff --git a/tests/modeltests/expressions/models.py b/tests/modeltests/expressions/models.py index f29767a9d1..d4de5ccee9 100644 --- a/tests/modeltests/expressions/models.py +++ b/tests/modeltests/expressions/models.py @@ -80,4 +80,43 @@ Traceback (most recent call last): ... FieldError: Joined field references are not permitted in this query +# F expressions can be used to update attributes on single objects +>>> test_gmbh = Company.objects.get(name='Test GmbH') +>>> test_gmbh.num_employees +32 +>>> test_gmbh.num_employees = F('num_employees') + 4 +>>> test_gmbh.save() +>>> test_gmbh = Company.objects.get(pk=test_gmbh.pk) +>>> test_gmbh.num_employees +36 + +# F expressions cannot be used to update attributes which are foreign keys, or +# attributes which involve joins. +>>> test_gmbh.point_of_contact = None +>>> test_gmbh.save() +>>> test_gmbh.point_of_contact is None +True +>>> test_gmbh.point_of_contact = F('ceo') +Traceback (most recent call last): +... +ValueError: Cannot assign "": "Company.point_of_contact" must be a "Employee" instance. + +>>> test_gmbh.point_of_contact = test_gmbh.ceo +>>> test_gmbh.save() +>>> test_gmbh.name = F('ceo__last_name') +>>> test_gmbh.save() +Traceback (most recent call last): +... +FieldError: Joined field references are not permitted in this query + +# F expressions cannot be used to update attributes on objects which do not yet +# exist in the database +>>> acme = Company(name='The Acme Widget Co.', num_employees=12, num_chairs=5, +... ceo=test_gmbh.ceo) +>>> acme.num_employees = F('num_employees') + 16 +>>> acme.save() +Traceback (most recent call last): +... +TypeError: int() argument must be a string or a number, not 'ExpressionNode' + """}