Update model field after some period in Django - python

I want automatically update field status in my model after 14 days.
Here is my model. I want to change status_of_renting to 0 after 15 days from date_of_rental (if date.today() if greater than date_of_return)
class RentCar(models.Model):
NORMAL = 1
PENALTY = 0
STATUS = (
(NORMAL, 'All is fine'),
(PENALTY, 'Penalty for using car')
)
car = models.ForeignKey('Car', on_delete = models.CASCADE, related_name = 'car')
person = models.ForeignKey('Person', on_delete = models.CASCADE, related_name = 'client')
date_of_rental = models.DateField(blank= True, default=timezone.now)
date_of_return = models.DateField(blank= True, default=date.today() + timedelta(days=14))
status_of_renting = models.IntegerField(choices = STATUS, default = 1)
def __str__(self):
return f'{self.rental} - {self.car} / {self.client.fullname}'
I can override def get_queryset() or dispatch in my generics.ListView, but I*m sure it's terrible decision. Is there some better solution to change the status in field status_of_renting.
views.py
class RentCatView(ListView):
model = RentCar
def get_queryset(self):
self.model.objects.filter(Q(status_of_renting = 1)&Q(date_of_return__lte = date.today())).update(status_of_renting=0)
return self.model.objects.all()

Trying to update database in queryset is indeed bad idea - it will execute every time someone try to fetch list of RentCar which will pollute your database with requests.
You need to setup cron job to run your query to update RentCar. Good thing is that you can run it only once a day, since it is only time that status_of_renting should be changed.
You can use one of django app, such as django-cron or django-background-tasks.
For example, this is what you need to do with django-background-tasks:
from background_task import background
from .models import RentCar
#background(schedule=24*60*60)
def update_status_of_renting(user_id):
RentCar.objects.filter(Q(status_of_renting = 1)&Q(date_of_return__lte = date.today())).update(status_of_renting=0)

Related

Django ORM Issue

I am learning RestAPI and When I try to post data to update my database columns the modified_on column should automatically populated to current date and time but it is not updating.
I am currently using django cassandra engine ORM where there is no functionality like auto_add_now() or auto_now().
Can any one give a suggestion where am I going wrong?
Model Class:
class Mydb(DjangoCassandraModel):
id = columns.UUID(primary_key=True, default=uuid.uuid4())
user_name = columns.Text()
user_email = columns.Text(default=None)
user_password = columns.Text()
description = columns.Text()
creation_date = columns.DateTime(default=datetime.datetime.today(), static=True)
modified_on = columns.DateTime(default=datetime.datetime.today())
My Serialization class:
class TaskSerializer(serializers.Serializer):
# id = serializers.UUIDField(default=uuid.uuid4)
USER_ID = serializers.UUIDField(default= uuid.uuid4(),source='id')
# user_name = serializers.CharField(max_length=50)
USER_NAME_FIELD = serializers.CharField(max_length=50, source='user_name')
USER_EMAIL = serializers.CharField(source='user_email')
USER_PASSWORD = serializers.CharField(max_length=20, source='user_password')
EXPLANATION = serializers.CharField(max_length=100, source='description')
MODIFIED_AT = serializers.DateTimeField(default=datetime.datetime.today(), source='modified_on')
CREATED_ON = serializers.DateTimeField(default=datetime.datetime.today(), source='creation_date')
def create(self, validated_data):
return Mydb.objects.create(**validated_data)
def update(self, instance, validated_data):
# instance.id = validated_data.get('id', instance.id)
instance.user_name = validated_data.get('user_name', instance.user_name)
instance.user_email = validated_data.get('user_email', instance.user_email)
instance.user_password = validated_data.get('user_password', instance.user_password)
instance.description = validated_data.get('description',instance.description)
instance.modified_on = validated_data.get('modified_on', instance.modified_on)
instance.save()
# instance.creation_date = validated_data.get('creation_date', instance.creation_date)
You should rather use utils now for timezone aware times
from django.utils.timezone import now
also in model you should set function not evaluated value ( no parenthesis after now )
MODIFIED_AT = serializers.DateTimeField(default=now, source='modified_on')
MODIFIED_AT = serializers.DateTimeField(default=datetime.datetime.today(), source='modified_on')
to
MODIFIED_ON = serializers.DateField(default=datetime.datetime.today(), source='modified_on')
change MODIFIED_AT to MODIFIED_ON
You can try:
create_date = models.DateField(auto_now_add=True,
verbose_name=u'Create date')
update_date = models.DateTime(auto_now=True,
verbose_name=u'Update date')
auto_now_add automatically set the field to now when the object is first created.
auto_now=True automatically set the field to now every time the object is saved.
Doc is here.
Please make sure to add the auto_now=True for your modified_at filed, in your model.
It automatically sets 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.
Example:
class Mydb(DjangoCassandraModel):
creation_date = columns.DateTime(auto_now_add=True)
modified_on = columns.DateTime(auto_now=True)
Docs Here:
https://docs.djangoproject.com/en/1.11/ref/models/fields/#django.db.models.DateField.auto_now
You use default=datetime.datetime.today() as your default value for the fields. Since you call the function immediately (by adding ()), the function is called exactly once on the first load of the code and the datetime at that moment is put into the default value and not updated until you reload the code (a.k.a. restart the server).
If you want to always use the then current time, leave away the () to cause Django to call the function each time. default=datetime.datetime.today
It's preferable for you to use now though, like iklinac did in his answer, as that also respects your timezone settings. His anwer also leaves out the parenteses, yielding the correct result.
from django.utils.timezone import now
...
MODIFIED_AT = serializers.DateTimeField(default=now, source='modified_on')

Django - Query parameter takes model type? RelatedObjectDoesNotExist: Posting has no textbook

I have installed this Django app: http://django-auditlog.readthedocs.io/en/latest/_modules/auditlog/models.html#LogEntry
The log entries are setup with two different models which are below:
class Posting(models.Model):
textbook = models.ForeignKey(Textbook)
condition = models.CharField(max_length=200)
price = models.DecimalField(max_digits=5, decimal_places=2)
user = models.ForeignKey(User)
image = models.ImageField(upload_to='postingpics/%Y/%m/%d', default="/textchange/nophoto.png")
post_date = models.DateTimeField('date_posted')
comments = models.CharField(max_length=50, default="")
def __str__(self):
return str(self.textbook)
def was_posted_recently(self):
return self.post_date >= timezone.now() - datetime.timedelta(days=1)
was_posted_recently.admin_order_field = 'post_date'
was_posted_recently.boolean = True
was_posted_recently.short_description = 'Posted recently'
class Wishlist(models.Model):
textbook = models.ForeignKey(Textbook)
user = models.ForeignKey(User)
wish_date = models.DateTimeField('date_wish')
def __str__(self):
return str(self.textbook)
def was_wished_recently(self):
return self.wish_date >= timezone.now() - datetime.timedelta(days=1)
was_wished_recently.admin_order_field = 'date_wish'
was_wished_recently.boolean = True
was_wished_recently.short_description = 'Wished recently'
auditlog.register(Posting)
auditlog.register(Wishlist)
So in the database the model EntryLog is getting a row each time something happens to Posting or Wishlist whether that be delete, create, or update.
I am trying to complete the query below because I want to count the number of occurrences that a Posting is deleted. Separately I will do the same query for Wishlist.
LogEntry.objects.filter(action=2 , content_type=Posting).count()
content_type is of type Foreign key for the record.
When I run this query I get the error message:
RelatedObjectDoesNotExist: Posting has no textbook.
This makes me think that Posting is the wrong value to put in for content_type. Am I thinking about this correctly? Should the value for content_type be something different? I understand usually when you refer to a Foreign Key you are querying on the Foreign keys fields like Posting__condition or something like that.
Thanks in advance.
The content_type argument should be a ContentType object, not a model class.
Instead of doing doing this manually you should use the LogEntryManagers get_for_model method:
LogEntry.objects.get_for_model(Posting).filter(action=2).count()

How do I over-ride the save() method for my Django model so that it updates properly?

I am building a ratemyprofessors type of application for my school and also to get some practice.
Currently my models.py looks like this:
from __future__ import unicode_literals
from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator
from django.contrib.auth.models import User
from django.utils import timezone
UNIVERSITIES = (
.....
)
DEPARTMENTS = (
.....
)
class Professor(models.Model):
name = models.CharField(max_length=255)
name_code = models.CharField(max_length=3, blank=True)
university = models.CharField(max_length=3, choices=UNIVERSITIES)
department = models.CharField(max_length=50, choices=DEPARTMENTS)
total_rating_points = models.IntegerField(default = 0)
number_of_reviews = models.IntegerField(default = 0)
rating = models.FloatField(
validators = [MinValueValidator(0.0), MaxValueValidator(5.0)],
default = 0.0
)
def __str__(self):
return self.name
SCORE_CHOICES = (
.....
)
class Review(models.Model):
author = models.ForeignKey(User, related_name='user_reviews')
professor = models.ForeignKey(Professor, related_name='professor_reviews')
created = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(default = timezone.now)
rating = models.IntegerField(default=1, choices=SCORE_CHOICES)
text = models.TextField(blank=True)
class Meta:
unique_together = [('author', 'professor')]
def __str__(self):
return 'Professor: ' +self.professor.name +', Score: ' +str(self.rating)
def save(self, *args, **kwargs):
"""
Re-writing the save method to update the associated professor's
rating as soon as a new Review object is created.
Also accounts for review updates by the user.
"""
if self.pk is None:
# This means that this is a new object
if self.professor:
p = self.professor
# Adjusting the total_rating_points and number of reviews
p.total_rating_points += self.rating
p.number_of_reviews += 1
# Adjusting the rating
p.rating = float(p.total_rating_points) / float(p.number_of_reviews)
p.save()
else:
# This object already exists, so this is an update
self.updated = timezone.now()
**WHAT DO I DO NOW?**
super(Review, self).save(*args, **kwargs)
You see, if a user updates his/her rating, the rating of the professor has to be adjusted accordingly. Since this is the core of the application, I wanted to do it in the save() method. It works nicely if it is a very new review. However, how do I update the score?
I mean I know what I have to do:
Subtract the previous score from the total_rating_point of the professor.
Add the new rating to the total_rating_point
Calculate rating by dividing it with number_of_review.
However, how exactly do I retrieve the previous score in the save() method during an update? Also is there a better and more efficient to do what I am trying to do? Thanks!
There's a couple concerns to keep in mind: What if a user deletes their account, a review, etc? Keeping a running total in the way shown would be problematic.
Instead, I'd suggest a structure such as I've shown below; when a Review is updated, save it and then call the save method for Professor. The new save method for Professor runs calculates the sum and count of reviews in place, recalculating each time as it still is only reaching out to the DB for a couple queries.
from django.db.models import Sum
class Professor(models.Model):
...
def save(self,*args,**kwargs):
if self.pk: # prevent hitting the database unless professor already exists
professor_reviews = Review.objects.filter(professor=self)
# Adjusting the total_rating_points and number of reviews
self.total_rating_points = professor_reviews.aggregate(Sum('rating'))
self.number_of_reviews = professor_reviews.count()
# Adjusting the rating
self.rating = float(self.total_rating_points) / float(self.number_of_reviews)
super(Professor,self).save(*args,**kwargs)
class Review(models.Model):
....
def save(self, *args, **kwargs):
if self.pk: #already exists
self.updated = timezone.now()
super(Review, self).save(*args, **kwargs)
self.professor.save() # call after super writes the Review to the DB
Use a post_save signal.
In the models file with the Professor:
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save,sender=Review)
def update_professor_by_review(*args,**kwargs):
updated_review = kwargs['instance']
reviewed_professor = updated_review.professor
# ... update the reviewed_professor as needed according to the review instance
reviewed_professor.save()
return

Django. Doing complex calculations in database

Here's part of my model file :
class Metric(models.Model):
Team = models.ForeignKey(Team)
metric_name = models.CharField(max_length = 40)
def __unicode__(self):
return self.metric_name
class Members(models.Model):
Metric = models.ForeignKey(Metric, through="Calculate")
member_name = models.CharField(max_length = 40, null=True, blank=True)
week_one = models.IntegerField(null=True, blank=True)
week_two = models.IntegerField(null=True, blank=True)
week_three = models.IntegerField(null=True, blank=True)
week_four = models.IntegerField(null=True, blank=True)
total = models.IntegerField(null=True, blank=True)
def __unicode__(self):
return self.member_ID
def save(self, *args, **kwargs):
self.total = int(self.week_one)+int(self.week_two)+int(self.week_three)+int(self.week_four)
super(Members, self).save(*args, **kwargs) # Call the "real" save() method.
Now what I want to do is. I want to calculate the number of members per metric the aggregate total of all the members in a metric and the highest total among all the members in a metric.
I can't figure out a way to do this in Django.
I want to make these calculations and store them in the database.
Can anyone please help me out with this.
Thanks
If you wish to "memoize" these results, there are at least two paths that you could follow:
A per-row post-save trigger on the Members table that updates "members_count", "members_total" and "members_max" fields in the Metric table.
The challenge with this is in maintaining the trigger creation DDL alongside the rest of your code and applying it automatically whenever the models are re-created or altered.
The Django ORM does not make this especially easy. The commonest migration tool ( south ) also doesn't go out of its way to make this easy. Also note that this solution will be specific to one RDBMS and that some RDBMSs may not support this.
You could create "synthetic" fields in your Metric model then use a post-save signal handler to update them whenever a Member is added or changed.
class Metric(models.Model):
Team = models.ForeignKey(Team)
metric_name = models.CharField(max_length = 40)
members_count = models.IntegerField()
members_max = models.IntegerField()
members_total = models.IntegerField()
def __unicode__(self):
return self.metric_name
# signal handling
from django.db.models import signals
from django.dispatch import dispatcher
def members_post_save(sender, instance, signal, *args, **kwargs):
# Update the count, max, total fields
met = sender.Metric # sender is an instance of Members
metric.members_count = Members.objects.filter(Metric=met).count()
# more code here to do the average etc;
dispatcher.connect(members_post_save, signal=signals.post_save, sender=Members)
The django signals documentation here can be of use.
Caveats
While this sort of approach could be made to achieve your stated goal, it is brittle. You need to have test coverage that ensures that this signal handler always fires, even after you've done some refactoring of your code.
I would also consider using "related objects" queries [ documented at https://docs.djangoproject.com/en/dev/topics/db/queries/#related-objects ] eg, assuming we have a "me" instance of Metric
>> members_count = me.members_set.count()
>> # aggregation queries for the total and max
If these aggregates are not used very often, path #2 could be a viable and more maintainable option.

Django-powered library checkout system

I am working on a library system to manage certain items in our office, I don't need a full-blown integrated library system so I decided to hand roll one with Django.
Below is a simplified version of my model:
class ItemObjects(models.Model):
# Static Variables
IN_STATUS = 'Available'
OUT_STATUS = 'Checked out'
MISSING = 'Missing'
STATUS_CHOICES = (
(IN_STATUS, 'Available'),
(OUT_STATUS, 'Checked out'),
(MISSING, 'Missing'),
)
# Fields
slug = models.SlugField(unique=True)
date_added = models.DateField(auto_now_add=True)
last_checkin = models.DateTimeField(editable=False, null=True)
last_checkout = models.DateTimeField(editable=False, null=True)
last_activity = models.DateTimeField(editable=False, null=True)
status = models.CharField(choices=STATUS_CHOICES, default=IN_STATUS, max_length=25)
who_has = models.OneToOneField(User, blank=True, null=True)
times_out = models.PositiveIntegerField(default=0, editable=False)
notes = models.CharField(blank=True, max_length=500)
history = models.TextField(blank=True, editable=False)
pending_checkin = models.BooleanField(default=False)
pending_transfer = models.BooleanField(default=False)
At first I was using a method on ItemObject to process checking out an item to a user and who_has was an EmailField because I couldn't get a CharfField to populate with the logged in user's name, but I figured using a OneToOneField is probably closer to the "right" way to do this.. While who_has was an EmailField, the following method worked:
def check_out_itemobject(self, user):
user_profile = user.get_profile()
if self.status == 'Available' and self.who_has == '':
self.status = 'Checked out'
self.who_has = user.email
self.last_checkout = datetime.datetime.now()
self.last_activity = datetime.datetime.now()
self.times_out += 1
if self.history == '':
self.history += "%s" % user_profile.full_name
else:
self.history += ", %s" % user_profile.full_name
if user_profile.history == '':
user_profile.history += self.title
else:
user_profile.history += ", %s" % self.title
else:
return False # Not sure is this is "right"
user_profile.save()
super(ItemObjects, self).save()
Now that I am using a OneToOneField this doesn't work, so I started looking at using a subclass of ModelForm but none of the cases I saw here on SO seemed to apply for what I am trying to do; my form would be a button, and that's it. Here are some of the questions I looked at:
Django: saving multiple modelforms simultaneously (complex case)
(Django) (Foreign Key Issues) model.person_id May not be NULL
django update modelform
So was I on the right track with a sort of altered save() method, or would a ModelForm subclass be the way to go?
EDIT/UPDATE: Many thanks to #ChrisPratt!
So I am trying to get Chris Pratt's suggestion for showing ItemHistory to work, but when I try to render it on a page I get an AttributeError that states "'User' object has no attribute 'timestamp'". So my question is, why is it complaining about a User object when last_activity is an attribute on the ItemObject object ?
My view:
#login_required
def item_detail(request, slug):
item = get_object_or_404(Item, slug=slug)
i_history = item.last_activity
user = request.user
return render_to_response('items/item_detail.html',
{ 'item' : item,
'i_history': i_history,
'user' : user })
I do not see why a User object is coming up at this point.
EDIT2: Nevermind, history is clearly a M2M field whose target is User. That's why!
Assuming users will log in and check out books to themselves, then what you most likely want is a ForeignKey to User. A book will only have one User at any given time, but presumably Users could check out other items as well. If there is some limit, even if the limit is actually one per user, it would be better to validate this in the model's clean method. Something like:
def clean(self):
if self.who_has and self.who_has.itemobject_set.count() >= LIMIT:
raise ValidationError('You have already checked out your maximum amount of items.')
Now, you checkout method has a number of issues. First, status should be a defined set of choices, not just random strings.
class ItemObject(models.Model):
AVAILABLE = 1
CHECKED_OUT = 2
STATUS_CHOICES = (
(AVAILABLE, 'Available'),
(CHECKED_OUT, 'Checked Out'),
)
...
status = models.PositiveIntegerField(choices=STATUS_CHOICES, default=AVAILABLE)
Then, you can run your checks like:
if self.status == self.STATUS_AVAILABLE:
self.status = self.STATUS_CHECKED_OUT
You could use strings and a CharField instead if you like, as well. The key is to decouple the static text from your code, which allows much greater flexibility in your app going forward.
Next, history needs to be a ManyToManyField. Right now, your "history" is only who last checked the item out or what the last item the user checked out was, and as a result is pretty useless.
class ItemObject(models.Model):
...
history = models.ManyToManyField(User, through='ItemHistory', related_name='item_history', blank=True)
class ItemHistory(models.Model):
CHECKED_OUT = 1
RETURNED = 2
ACTIVITY_CHOICES = (
(CHECKED_OUT, 'Checked Out'),
(RETURNED, 'Returned'),
)
item = models.ForeignKey(ItemObject)
user = models.ForeignKey(User)
activity = models.PostiveIntegerField(choices=ACTIVITY_CHOICES)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-timestamp'] # latest first
Which then allows you to get full histories:
some_item.history.all()
some_user.item_history.all()
To add a new history, you would do:
ItemHistory.objects.create(item=some_item, user=some_user, activity=ItemHistory.CHECKED_OUT)
The auto_now_add attribute ensures that the timestamp is automatically set when the relationship is created.
You could then actually get rid of the last_checkout and last_activity fields entirely and use something like the following:
class ItemObject(models.Model):
...
def _last_checkout(self):
try:
return self.history.filter(activity=ItemHistory.CHECKED_OUT)[0].timestamp
except IndexError:
return None
last_checkout = property(_last_checkout)
def _last_activity(self):
try:
return self.history.all()[0].timestamp
except IndexError:
return None
last_activity = property(_last_activity)
And, you can then use them as normal:
some_item.last_checkout
Finally, your checkout method is not an override of save so it's not appropriate to call super(ItemObject, self).save(). Just use self.save() instead.

Categories