How to automatically change model fields in django - python

I have a model and I want to know if it is possible to set a condition that triggers a change in the model field. For example, I have a model
class BillboardTracker(models.Model):
client_name = models.CharField(max_length=400)
entry_date = models.DateTimeField(default=datetime.now)
duration = models.PositiveIntegerField()
expiry_date = models.DateField()
is_expired = models.BooleanField(default=False)
I want to know if it is possible to have a function in the model that makes is_expired equals to True when the expiry date is up. I tried this
def expire(self):
if datetime.now == self.expiry_date:
self.is_expired = True
but it's not working. Is it possible to implement this?

Use a #property
The simplest thing here is not to have an is expired field at all! It's not needed. What you need is a property.
class BillboardTracker(models.Model):
client_name = models.CharField(max_length=400)
entry_date = models.DateTimeField(default=datetime.now)
duration = models.PositiveIntegerField()
expiry_date = models.DateField()
#property
def is_expired(self):
if datetime.now > self.expiry_date:
return True
return False
Remember, you don't have a field in a database, if that field is the same as another field with a simple calculation. This automatically eliminates your head ache of having to flag items as expired.
If you want to find out if an object has expired.
if instance.is_expired == True:
print 'yes, that ones gone'
Filtering
If you wanted to retrieve a whole set of objects that have expired
BillboardTracker.objects.filter(expiry_date__le=datetime.now())
This is why I mentioned that you don't need to store a field that can be easily calculated.
Index advantage
In most RDBMS a boolean field (such as your is_expired column) cannot be indexed effectively. So that actually means the above query will be faster than a query on that boolean field provided you create an index on the expiry_date field.

You need to make two changes in this function,
Firstly use datetime.now() and secondly,
You might want to update your logic like this :
def expire(self):
if datetime.now() >= self.expiry_date:
self.is_expired = True
return True
else:
return False
Because sometimes both the values might not be exactly same but still BillboardTracker need is_expired = True for all previous dates.
And in your views :
def your_view(request):
instance = BillboardTracker.objects.get(id=some_id)
if instance.is_expired() == True:
print 'expired'
else:
print 'fresh'

Related

How to update queryset value in django?

I have written a python script in my project. I want to update the value of a field.
Here are my modes
class News_Channel(models.Model):
name = models.TextField(blank=False)
info = models.TextField(blank=False)
image = models.FileField()
website = models.TextField()
total_star = models.PositiveIntegerField(default=0)
total_user = models.IntegerField()
class Meta:
ordering = ["-id"]
def __str__(self):
return self.name
class Count(models.Model):
userId = models.ForeignKey(User, on_delete=models.CASCADE)
channelId = models.ForeignKey(News_Channel, on_delete=models.CASCADE)
rate = models.PositiveIntegerField(default=0)
def __str__(self):
return self.channelId.name
class Meta:
ordering = ["-id"]
This is my python script:
from feed.models import Count, News_Channel
def run():
for i in range(1, 11):
news_channel = Count.objects.filter(channelId=i)
total_rate = 0
for rate in news_channel:
total_rate += rate.rate
print(total_rate)
object = News_Channel.objects.filter(id=i)
print(total_rate)
print("before",object[0].total_star,total_rate)
object[0].total_star = total_rate
print("after", object[0].total_star)
object.update()
After counting the total_rate from the Count table I want to update the total star value in News_Channel table. I am failing to do so and get the data before the update and after the update as zero. Although total_rate has value.
The problem
The reason why this fails is because here object is a QuerySet of News_Channels, yeah that QuerySet might contain exactly one News_Channel, but that is irrelevant.
If you then use object[0] you make a query to the database to fetch the first element and deserialize it into a News_Channel object. Then you set the total_star of that object, but you never save that object. You only call .update() on the entire queryset, resulting in another independent query.
You can fix this with:
objects = News_Channel.objects.filter(id=i)
object = objects[0]
object.total_star = total_rate
object.save()
Or given you do not need any validation, you can boost performance with:
News_Channel.objects.filter(id=i).update(total_star=total_rate)
Updating all News_Channels
If you want to update all News_Channels, you actually better use a Subquery here:
from django.db.models import OuterRef, Sum, Subquery
subq = Subquery(
Count.objects.filter(
channelId=OuterRef('id')
).annotate(
total_rate=Sum('rate')
).order_by('channelId').values('total_rate')[:1]
)
News_Channel.objects.update(total_star=subq)
The reason is that object in your case is a queryset, and after you attempt to update object[0], you don't store the results in the db, and don't refresh the queryset. To get it to work you should pass the field you want to update into the update method.
So, try this:
def run():
for i in range(1, 11):
news_channel = Count.objects.filter(channelId=i)
total_rate = 0
for rate in news_channel:
total_rate += rate.rate
print(total_rate)
object = News_Channel.objects.filter(id=i)
print(total_rate)
print("before",object[0].total_star,total_rate)
object.update(total_star=total_rate)
print("after", object[0].total_star)
News_Channel.total_star can be calculated by using aggregation
news_channel_obj.count_set.aggregate(total_star=Sum('rate'))['total_star']
You can then either use this in your script:
object.total_star = object.count_set.aggregate(total_star=Sum('rate'))['total_star']
Or if you do not need to cache this value because performance is not an issue, you can remove the total_star field and add it as a property on the News_Channel model
#property
def total_star(self):
return self.count_set.aggregate(total_star=Sum('rate'))['total_star']

Django view objects filter with timezone.now().date or timezone.now().time-> expected string or bytes-like object

Hi i have some Django 11 project, my model look like
class Event(models.Model):
name = models.CharField(max_length=100, unique=True)
title = models.CharField(max_length=100)
info = models.CharField(max_length=100)
image = models.ImageField(upload_to='events/%Y/%m/%d')
start_date = models.DateField(default=timezone.now)
start_time = models.TimeField(default=timezone.now)
stop_date = models.DateField(default=timezone.now)
stop_time = models.TimeField(default=timezone.now)
place = models.ForeignKey('places.Place', on_delete=models.CASCADE)
company = models.ForeignKey('companies.Company', on_delete=models.CASCADE)
and my view look like
def place_website(request, place_id):
place_template = get_template('room.html')
place_obj = Place.objects.filter(id=place_id)
# filter for event obejts only for requested place, filtered for now and next events
place_event_now = Event.objects.filter(place=place_id, start_date=timezone.now().date, stop_date__gte=timezone.now().date)
place_events_next = Event.objects.filter(place=place_id, start_date=timezone.now(), stop_date__gte=timezone.now()).order_by('start_time')
place_context = {
'place_obj': place_obj,
'place_event_now': place_event_now,
'place_events_next': place_events_next,
}
return HttpResponse(place_template.render(place_context))
the thing i want to manage is to pass to template the list of filtered Event objects based on time.
Lets pick this line
place_event_now = Event.objects.filter(place=place_id, start_date=timezone.now().date, stop_date__gte=timezone.now().date)
it couse error "expected string or bytes-like object" but when i remove ".date" from "timezone.now()" error disappear (then filter do nothing) but i want to compare date to date and time to time.
How to do this properly ?
This approach to filter objects in view rather than in template is proper?
###### UPDATE ########
Its werid because after correction now i have no error but queryset is not filtered properly, look like only two first parameter is filtering ok and the another two is causing empty queryset.
place_event_now = Event.objects.filter(place=place_id, start_date=timezone.now().strftime('%Y-%m-%d'), start_time__lte=timezone.now().strftime('%H:%M:%S'), stop_time__gte=timezone.now().strftime('%H:%M:%S'))
I my database time is saved in format H:M:S and timezone.now().time() has different format so i modified filter with .strftime this didnt help, what i wont is to limit "place_event_now" queryset to particular object/objects that come true with condition start_time < currenttime < stop_time.
Another case is with place_event_next
place_events_next = Event.objects.filter(place=place_id, start_date=timezone.now().strftime('%Y-%m-%d'), stop_date__gte=timezone.now().strftime('%Y-%m-%d'), start_time__gt=timezone.now().strftime('%H:%M:%S')).order_by('start_time')
Event when i filter objects that start_time is greater than timezone.now() they still are in queryset.
Am I doing something wrong ?
I figured it out that timezone.now() return time not in my current timezone, i change it to timezone.localtime() and everything working perfect !!!
May be you need call date to date()
replace
filter(place=place_id, start_date=timezone.now().date, stop_date__gte=timezone.now().date)
# ^^^ ^^^
to
filter(place=place_id, start_date=timezone.now().date(), stop_date__gte=timezone.now().date())
# ^^^ ^^^

When Django models field is empty, set value to the Default value

Whenever the user doesn't add a value, I need my Django models to replace the otherwise empty field with the value set in default.
My models looks like this:
not_before = models.TimeField(blank=True, null=True, default='00:00:00')
max_num_per_day = models.IntegerField(blank=True, null=True, default=0)
I tried every combination of null, blank and default but no matter what I do, the fields gets replaced by null instead of '00:00:00' and 0.
Is there anyway I can force it to the default value whenever the field is empty?
you can set up your form with a default function like:
class YourForm(forms.Form):
.....
def clean_field(self):
data = self.cleaned_data['not_before']
if not data:
data = '00:00:00'
or write a function in your model like:
class Molde(models.Model):
not_before = models.TimeField(blank=True, null=True, default='00:00:00')
def time(self):
if self.not_before:
return self.not_before
else:
return '00:00:00'
In this case you would call the function instead of the model field itself. You can also take a look at this.
Hope that helps.
from what I understood from your question is you just want to set it to default. you can use:
https://code.djangoproject.com/ticket/6754
don't
not_before = models.TimeField(blank=True, null=True, default='00:00:00')
instead,
import datetime
not_before = models.TimeField(default=datetime.time(0,0))
max_num_per_day = models.IntegerField(default=0)
It seems you are using a ModelForm to grab the data from the user.
In this case, the solution proposed by sasuke will not work. First, you would have to set the required param to False in your form fields, so you would stop seing those "This field is required" messages. Still, you would see errors when saving the form. Even if your model instance is initialized with the default value, the form will replace it with None, since there is an existing field in the form matching the field in the model and its value is None.
My solution is to override the values in the model instance before saving them:
model_instance = myform.save(commit=False)
if not model_instance.not_before:
model_instance.not_before = '00:00:00'
if not model_instance.max_num_per_day:
model_instance.max_num_per_day = 0
model_instance.save()

When/where to use property tags of django models

I have model with a property that returns true or false if the start date is the current date. I want to update the is_started field to True if instance.started equals True.
class Advert(models.Model):
location = models.CharField(max_length=100)
is_started = models.BooleanField(default=False)
start_date = models.DateField()
#property
def started(self):
today = str(datetime.date.today())
start_date = str(self.start_date)
if today == start_date:
return True
return False
My question now is where to I check for this condition. I don't know if check for this condition and update the is_started field in my views of check in the front end and send an api request to update if the condition id true. What's the best way to go about this?
Your logic is incorrect as per your field name, is_started would only ever be true if you check the value on the same day as you call the property so it doesn't make sense.
If you're talking about querying, you don't need it, you just need to check if the current date is greater than the start date
.filter(start_date__gte=datetime.now())
If you're talking about in a view or method, then your property as is (and later fixed) suffices.
Hence, the model field isn't required.

how to store only one value on my django models?

below is my model
from django.db import models
LEVEL_CHOICES = (('beg','beginner'),('ind','inter'),('exp','expert'))
class scrap(models.Model):
subject = models.CharField(max_length=128,unique=True)
# level= models.CharField(max_length=128,unique=True)
level = models.CharField(max_length=128, choices=LEVEL_CHOICES)
time = models.IntegerField(unique=True)
def __str__(self):
return self.subject
is there anyway so that i can only store one value per class.what i am doing is supplying values to this model's objects through a form.so i want only once instance of this class.if another value is supplied through through the form,what i want is the old value to be replaced and new value to be stored.
I'm not sure why do you really just keep one object for your model, but it's pretty easy to achieve:
if scrap.objects.exists():
current_scrap = Scrap.objects.all()[0]
else:
current_scrap = Scrap()
current_scrap.subject = new_subject
current_scrap.level = new_level
current_scrap.time = new_time
current_scrap.save()

Categories

Resources