Django 1.9 / Python 2.7
Given this model:
class CoursePurchase(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
course = models.ForeignKey(Course)
date_purchased = models.DateField(default=date.today())
I would expect date_purchased to store the date I enter into Django admin, and it does, according to my database browser:
However, retrieving the object in the view has today's date instead of the stored date:
(Note the date_purchased field of __unicode__ returns today's date instead of the stored date.
Here is the code I'm using to retrieve the instance:
cp = CoursePurchase(course=page.course, user=request.user)
where course and user return the expected values.
What am I missing? This seems fairly straightforward, but I can't get past this.
To set the current date on save, django provides special arguments to the DateField type:
Django documentation:
class DateField(auto_now=False, auto_now_add=False, **options)
A date, represented in Python by a datetime.date instance. Has a few extra, optional arguments:
DateField.auto_now
Automatically set the field to now every time the object is saved. Useful for “last-modified” timestamps. Note that the current date is always used; it’s not just a default value that you can override.
The field is only automatically updated when calling Model.save(). The field isn’t updated when making updates to other fields in other ways such as QuerySet.update(), though you can specify a custom value for the field in an update like that.
DateField.auto_now_add
Automatically set the field to now when the object is first created. Useful for creation of timestamps. Note that the current date is always used; it’s not just a default value that you can override. So even if you set a value for this field when creating the object, it will be ignored. If you want to be able to modify this field, set the following instead of auto_now_add=True:
For DateField: default=date.today - from datetime.date.today()
For DateTimeField: default=timezone.now - from django.utils.timezone.now()
The default form widget for this field is a TextInput. The admin adds a JavaScript calendar, and a shortcut for “Today”. Includes an additional invalid_date error message key.
The options auto_now_add, auto_now, and default are mutually exclusive. Any combination of these options will result in an error.
But that's not the cause of the issue you see. When you do this:
cp = CoursePurchase(course=page.course, user=request.user)
You are not retrieving anything from the database, but rather creating a new instance (in-memory only, not saved anywhere yet). To retrieve instances, you need to query the database properly:
cp = CoursePurchase.objects.get(course=page.course, user=request.user)
You can try like this.
cp = CoursePurchase.objects.get(course=page.course, user=request.user)
Related
I'm building a Django application, and in it I would like to track whenever a particular model was last accessed.
I'm opting for this in order to build a user activity history.
I know Django provides auto_now and auto_now_add, but these do not do what I want them to do. The latter tracks when a model was created, and the former tracks when it was last modified, which is different from when it was last accessed, mind you.
I've tried adding another datetime field to my model's specification:
accessed_on = models.DateTimeField()
Then I try to update the model's access manually by calling the following after each access:
model.accessed_on = datetime.utcnow()
model.save()
But it still won't work.
I've gone through the django documentation for an answer, but couldn't find one.
Help would be much appreciated.
What about creating a model with a field that contains the last save-date. Plus saving the object every time is translated from the DB representation to the python representation?
class YourModel(models.Model):
date_accessed = models.DateTimeField(auto_now=True)
#classmethod
def from_db(cls, db, field_names, values):
obj = super().from_db(db, field_names, values)
obj.save()
return obj
How do I run an update and select statements on the same queryset rather than having to do two queries:
- one to select the object
- and one to update the object
The equivalent in SQL would be something like:
update my_table set field_1 = 'some value' where pk_field = some_value
Use the queryset object update method:
MyModel.objects.filter(pk=some_value).update(field1='some value')
Django database objects use the same save() method for creating and changing objects.
obj = Product.objects.get(pk=pk)
obj.name = "some_new_value"
obj.save()
How Django knows to UPDATE vs. INSERT
If the object’s primary key attribute is set to a value that evaluates to True (i.e., a value
other than None or the empty string), Django executes an UPDATE. If
the object’s primary key attribute is not set or if the UPDATE didn’t
update anything, Django executes an INSERT.
Ref.: https://docs.djangoproject.com/en/1.9/ref/models/instances/
This answer compares the above two approaches.
If you want to update many objects in a single line, go for:
# Approach 1
MyModel.objects.filter(field1='Computer').update(field2='cool')
Otherwise you would have to iterate over the query set and update individual objects:
#Approach 2
objects = MyModel.objects.filter(field1='Computer')
for obj in objects:
obj.field2 = 'cool'
obj.save()
Approach 1 is faster because, it makes only one database query, compared to approach 2 which makes 'n+1' database queries. (For n items in the query set)
Fist approach makes one db query ie UPDATE, the second one makes two: SELECT and then UPDATE.
The tradeoff is that, suppose you have any triggers, like updating updated_on or any such related fields, it will not be triggered on direct update ie approach 1.
Approach 1 is used on a queryset, so it is possible to update multiple objects at once, not in the case of approach 2.
1st method
MyTable.objects.filter(pk=some_value).update(field1='some value')
2nd Method
q = MyModel.objects.get(pk=some_value)
q.field1 = 'some value'
q.save()
3rd method
By using get_object_or_404
q = get_object_or_404(MyModel,pk=some_value)
q.field1 = 'some value'
q.save()
4th Method
if you required if pk=some_value exist then update it other wise create new one by using update_or_create.
MyModel.objects.update_or_create(pk=some_value,defaults={'field1':'some value'})
If you need to set the new value based on the old field value that is do something like:
update my_table set field_1 = field_1 + 1 where pk_field = some_value
use query expressions:
MyModel.objects.filter(pk=some_value).update(field1=F('field1') + 1)
This will execute update atomically that is using one update request to the database without reading it first.
only in a case in serializer things, you can update in very simple way!
my_model_serializer = MyModelSerializer(
instance=my_model, data=validated_data)
if my_model_serializer.is_valid():
my_model_serializer.save()
only in a case in form things!
instance = get_object_or_404(MyModel, id=id)
form = MyForm(request.POST or None, instance=instance)
if form.is_valid():
form.save()
Accepted answer works great, but it comes with some unwanted side effect.
For example, you are using imageField, the update() will work and update others data, but not update your imageField data
class ProfileSetting(models.Model):
first_name = models.CharField(blank=True)
logo = models.ImageField(blank=True, null=True, upload_to="profile/logo/")
update_data = {
"first_name": "john",
"logo": request.FILES['logo'] # logo will not be properly update
}
ProfileSetting.objects.filter(pk=some_value).update(**update_data)
Here is some example with good explanation Django ImageField is not updating when update() method is used
I'm trying to keep track of the changes whenever a field is changed.
I can see the changes in Django Admin History whenever I use the .save() method, but whenever I use the .update() method it does not record whatever I changed in my object.
I want to use update() because it can change multiple fields at the same time. It makes the code cleaner and more efficient (one query, one line...)
Right now I'm using this:
u = Userlist.objects.filter(username=user['username']).update(**user)
I can see all the changes when I do
u = Userlist.objects.get(username=user['username'])
u.lastname=lastname
u.save()
I'm also using django-simple-history to see the changes.setup.
From the docs:
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())
update() works at the DB level, so Django admin cannot track changes when updates are applied via .update(...).
If you still want to track the changes on updates, you can use:
for user in Userlist.objects.filter(age__gt=40):
user.lastname = 'new name'
user.save()
This is however more expensive and is not advisable if the only benefit is tracking changes via the admin history.
Here's how I've handled this and it's worked well so far:
# get current model instance to update
instance = UserList.objects.get(username=username)
# use model_to_dict to convert object to dict (imported from django.forms.models import model_to_dict)
obj_dict = model_to_dict(instance)
# create instance of the model with this old data but do not save it
old_instance = UserList(**obj_dict)
# update the model instance (there are multiple ways to do this)
UserList.objects.filter(username=username).update(**user)
# get the updated object
updated_object = UserList.objects.get(id=id)
# get list of fields in the model class
my_model_fields = [field.name for field in cls._meta.get_fields()]
# get list of fields if they are different
differences = list(filter(lambda field: getattr(updated_object, field, None)!= getattr(old_instance, field, None), my_model_fields))
The differences variable will give you the list of fields that are different between the two instances. I also found it helpful to add which model fields I don't want to check for differences (e.g. we know the updated_date will always be changed, so we don't need to keep track of it).
skip_diff_fields = ['updated_date']
my_model_fields = []
for field in cls._meta.get_fields():
if field.name not in skip_diff_fields:
my_model_fields.append(field.name)
If I have a model that has a UUID primary key and the the user may set the value on creation, is there any way to tell within the save method that the instance is new?
Previous techniques of checking the auto assigned fields: In a django model custom save() method, how should you identify a new object? do not work.
Use self._state.adding. It defaults to True and gets set to False after saving the model instance or loading it from the DB.
You should also check the force_insert argument of save.
Note that this will not work if you attempt to copy an instance by changing its id and saving (a common shortcut). If you need to detect this, you could override the instance saving and loading to also store the pk on self._state, then compare the current pk with self._state.pk.
In save(), self.pk is None with pk (uuid) dont work because it should has default = uuid.uuid4 and if you set it to default = None primarykey should has default attribute as valid uuid in DB, so let default = uuid.uuid4 in UUID field.
The esay way is to add field created_at:
created_at = models.DateTimeField(auto_now_add=True)
and in save() use :
if self.created_at is None:
your code here
save takes an optional parameter, force_insert. Passing that as True will force Django to do an INSERT. See the documentation.
You can use django-model-utils TimeStampedModel (you can also use django-extensions TimeStampedModel or make your own).
This provides each model a created and modified field. Then, compare the timedelta between the new instance's created and modified fields to an arbitrary time difference (this example uses 5 seconds). This allows you to identify if an instance is new:
def save(self, *args, **kwargs):
super(<ModelName>, self).save(*args, **kwargs)
if (self.modified - self.created).seconds < 5:
<the instance is new>
Say I have a model User, which has a credits field (IntegerField). When a user registers, I will set the credits field to 0, and I will update the credits for certain events.
I don't want the user know there is a field like this in the db table.
What attribute should I set to the field?
To accomplish the defaulting to 0 part, you can simply use the default argument of the model field.
For the part where you don't want your users to know about the field, you have a couple choices.
Solution 1: Field.editable
Defining your field as follows will cause the field to never show up in a model form.
credits = models.IntegerField(default=0, editable=False)
Downsides
You won't be able to edit the field's value in the admin
Form validation will never take this field into account (e.g., def clean_credits(self): won't run)
Solution 2: ModelForm.exclude|fields
Creating a ModelForm for the model is something you're going to be doing. You can define an exclude attribute on the form's Meta class, and add "credits" to the list. See the docs linked above. You can instead define fields on the Meta class, and omit "credits". The latter of the two options is considered a better practice, particularly when pertaining to security, and is known as a whitelist.
Downsides
You have to remember to define exclude or fields on every exposed form
Updating the "secret" field
The proper way to handle specifying a "secret" field's value when the field isn't in the form is:
# Inside your view's post method (or FormView.form_valid, if you're using generic views)
instance = form.save(commit=False) # Does everything except INSERT into the database
instance.credits = <however many credits you feel like giving the user>
instance.save()
If you didn't do that, and instead just saved the form as-is, the value specified by default would be set to the instance's credits field.
You'll want to use an IntegerField with default=0: credits = models.IntegerField(default=0). Just take care not to show this field to the user in any forms or when displaying the user.
E.g., if you had a ModelForm for User, do not include credits in the fields field of Meta