How to get a model's last access date in Django? - django

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

Related

How to track changes when using update() in Django models

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)

Django - order queryset result by update time of instances

Is there a way to order result of a model queryset by update time of its instances? i.e. the instance that has been saved most recently comes first and so on.
You will need to add a timestamp field to your model. For example in my own code I add a date_updated field for this very purpose.
Your custom models don't have this by default so you have to add it.
last_edit = models.DateTimeField(auto_now_add=True)
You will have to update this in the save method (or another method) of your model.
import datetime
self.last_edit = datetime.datetime.now()
If you have a field on your model that tracks this update time (more information on this here), then yes, you can just use that in a normal ordering specification.
If you don't track updates on your model, no, that is not possible.

Refreshing a model's unmanaged related model in Django

I have a model (lets call it Entity) that has an attribute (Attribute) that changes over time, but I want to keep a history of how that attribute changes in the database. I need to be able to filter my Entities by the current value of Attribute in its manager. But because Django (as far as I can tell) won't let me do this in one query natively, I have created a database view that produces the latest value of Attribute for every Entity. So my model structure looks something like this:
class Entity(models.Model):
def set_attribute(self, value):
self.attribute_history.create(value=value)
def is_attribute_positive(self, value):
return self.attribute.value > 0
class AttributeEntry(models.Model):
entity = models.ForeignKey(Entity, related_name='attribute_history')
value = models.IntegerField()
time = models.DateTimeField(auto_now_add=True)
class AttributeView(models.Model)
id = models.IntegerField(primary_key=True, db_column='id',
on_delete=models.DO_NOTHING)
entity = models.OneToOneField(Entity, related_name='attribute')
value = models.IntegerField()
time = models.DateTimeField()
class Meta:
managed = False
My database has the view that produces the current attribute, created with SQL like this:
CREATE VIEW myapp_attributeview AS
SELECT h1.*
FROM myapp_attributehistory h1
LEFT OUTER JOIN myapp_attributehistory h2
ON h1.entity_id = h2.entity_id
AND (h1.time < h2.time
OR h1.time = h2.time
AND h1.id < h2.id)
WHERE h2.id IS NULL;
My problem is that if I set the attribute on a model object using set_attribute() checking it with is_attribute_positive() doesn't always work, because Django may be caching that the related AttributeView object. How I can I make Django update its model, at the very least by requerying the view? Can I mark the attribute property as dirty somehow?
PS: the whole reason I'm doing this is so I can do things like Entity.objects.filter(attribute__value__exact=...).filter(...), so if someone knows an easier way to get that functionality, such an answer will be accepted, too!
I understand that the attribute value is modified by another process (maybe not even Django) accessing the same database. If this is not the case you should take a look at django-reversion.
On the other hand if that is the case, you should take a look at second answer of this. It says that commiting transaction invalidate query cache and offer this snippet.
>>> from django.db import transaction
>>> transaction.enter_transaction_management()
>>> transaction.commit() # Whenever you want to see new data
I never directly solved the problem, but I was able to sidestep it by changing is_attribute_positiive() to directly query the database table, instead of the view.
def is_attribute_positive(self, value):
return self.attribute_history.latest().value > 0
So while the view gives me the flexibility of being able to filter queries on Entity, it seems the best thing to do once the object is received is to operate directly on the table-backed model.

Django admin model save - Update existing object

This seems really simple.
On my model save() I want to basically do a get_or_create(). So I want to update the model if it exists or create a new one if not.
This seems like a super simple problem, but I am not getting it right!
class StockLevel(models.Model):
stock_item = models.ForeignKey(StockItem)
current_stock_level = models.IntegerField(blank=True, default=0)
def save(self):
try:
# it exists
a = StockLevel.objects.get(stock_item=self.stock_item)
a.current_stock_level = self.current_stock_level
a.save()
except:
# is does not exist yet
# save as normaly would.
super(StockLevel, self).save()
OR
def save(self):
stock_level_item , created = StockLevel.objects.get_or_create(stock_item=self.stock_item)
stock_level_item.current_stock_level = self.current_stock_level
stock_level_item.save()
This would also go into a infinite loop.
This would just put the save() in an infinite loop. But that is the basic idea of how it should work.
Django uses the same save() method for both creating and updating the object.
User code doesn't need to determine whether to create or update the object, since this is done by the method itself.
Furthermore you can force the save() method to create or update the object by using the methods optional arguments.
This is covered in the Django docs.
This really doesn't sound like the best way to do this. The save method is meant for saving the current instance, not magically querying for an existing one. You should take care of this in the form or view code.
So this is how i solved a similar situation just yesterday,
I created a duplicate model to store the updated information.
let's call the new model "StockLevelUpdates".
I then used signals to insert the saved data from the original model.
I will use your model above as the original model to explain further.
class StockLevelUpdates(models.Model):
stock_item = models.ForeignKey(StockItem)
current_stock_level = models.IntegerField(blank=True, default=0)
#receiver(signals.post_save, sender=StockLevel)
def update_info(sender, instance, **kwargs):
try:
# if it exists
a = StockLevelUpdates.objects.get(stock_item=instance.stock_item)
a.current_stock_level = instance.current_stock_level
a.save()
except:
# if it does not exist yet
# save the new instance
obj = StockLevelUpdates(stock_item=instance.stock_item,
current_stock_level = instance.current_stock_level)
obj.save()
This worked well for me, and you can get all your update reports from the duplicate model.
Perhaps there is a better way to do this kind of thing but this was a quick way out of a sticky situation.

Django queryset custom manager - refresh caching

In my userprofile model, I have a method (turned into a property) that returns a queryset based on another model's custom manager.
Concretely, a user can sell non-perishable and perishable items, and on the Item model level, several custom managers live that contain the logic (and return the querysets) for determining whether an item is perished or not. Within the userprofile, a method lives that returns something similar to:
Item.live_objects.filter(seller=self.user),
where non_perished_objects is one of the said custom managers.
If however an item is added, it is never reflected through these userprofile methods. Only when restarting the server (and the queryset caches being refilled) are the results correct.
Is there a way to force Django to reload the data and drop the cached data?
Thanks in advance!
Update:
class LiveItemsManager(models.Manager):
kwargs = {'perished': False,
'schedule__start_date__lte': datetime.datetime.now(),
'schedule__end_date__gt': datetime.datetime.now()}
def get_query_set(self):
return super(LiveItemsManager, self).get_query_set().filter(**self.kwargs)
class Item(models.Model):
live_objects = LiveItemsManager()
perished = models.BooleanField(default=False)
seller = models.ForeignKey(User)
As you see, there's also a Schedule model, containing a start_date, an end_data and an item_id field.
In the UserProfile model, I have:
def _get_live_items(self):
results = Item.live_objects.filter(seller=self.user)
return results
live_items = property(_get_live_items)
The problem is that when calling the live_items property, the results returned are only the cached results.
(PS: Don't mind the setup of the models; there's a reason why the models are what they are :))
The issue is that the kwargs are evaluated when the Manager is first defined - which is when models.py is first imported. So the values to be used against schedule__start_date and schedule__end_date are calculated then, and will not change. You can fix this by moving the kwargs declaration inside the method:
def get_query_set(self):
kwargs = {'perished': False,
'schedule__start_date__lte': datetime.datetime.now(),
'schedule__end_date__gt': datetime.datetime.now()}
return super(LiveItemsManager, self).get_query_set().filter(**kwargs)
(Putting the definition into __init__() won't help, as it will have the same effect: the definition will be evaluated at instantiation of the manager, rather than definition, but since the manager is instantiated when the model is defined, this is pretty much the same time.)