how can i add an expiration date function on a model? - django

i am trying to make a key verification app that when accepted it would create the same app on the user's profile, so i have done everything but i have struggled making the expiration date part, i want the expired boolean become true when the date is expired, but i have no idea on how to implement it
#models.py
class ProductKey(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE,
unique=False)
key = models.CharField(max_length=14)
valid_from = models.DateTimeField(default=timezone.now)
valid_to = models.DateTimeField()
expired = models.BooleanField(default=False)

Please do not add a database field for this. You will introduce data duplication: if you later set the valid_to, you will have to update expred as well, so the logic would introduce extra challenges.
You can annotate your ProductKey model, such that objects that arise from this have an attribute expired:
from django.db.models import BooleanField, ExpressionWrapper, Q
from django.db.models.functions import Now
ProductKey.objects.annotate(
expired=ExpressionWrapper(Q(valid_to__lt=Now()), output_field=BooleanField())
)
You can then filter on that property. For example you can retrieve the ProductKeys that are expired with:
ProductKey.objects.annotate(
expired=ExpressionWrapper(Q(valid_to__lt=Now()), output_field=BooleanField())
).filter(expired=True)
If you need this often, you can annotate this in the manager, like:
class ExpiredManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(
expired=ExpressionWrapper(Q(valid_to__lt=Now()), output_field=BooleanField())
)
class ProductKey(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE,
unique=False)
key = models.CharField(max_length=14)
valid_from = models.DateTimeField(default=timezone.now)
valid_to = models.DateTimeField()
objects = ExpiredManager()

You could use a property for this use case and calculate that on the fly.
#property
def expired(self):
# calculate here if its still valid

Related

django orm foreign key latest data using filter query

class Schedule(models.Model):
user = models.ForeignKey(USER, on_delete=models.SET_NULL, null=True)
area = models.ForeignKey(Site, on_delete=models.SET_NULL, null=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
area = Schedule.objects.values("area").annotate(latest=Max('created_at')).values("area")
latest = Schedule.objects.values("area").annotate(latest=Max('created_at')).values("latest")
Schedule.objects.filter(created_at__in=latest, area__in=area)
I got the value I want.
However, I am uneasy when I filter with ForeignKey and DateTimeField. Can't there be a better way?
Also, can't you make it cleaner other than the code above?
Guess you can create a Manager for your Model and use selected_related feature, it will create quick query for you, and it`s ready "out-of-the-box" Django solution. Plz see Django docs.
Here`s example based on your code
In Model.py something like
class SheduleManager(model.Manager):
def all_prefetched_data(self):
qs = self.get_queryset()
qs = qs.select_related(
'area_set'
)
return qs
in the same Model.py you need to assign your manager in Shedule model
listing = SheduleManager
Now in view you can sort db items by creation date, query with the ForiegnKey fetching through all your db. You also can display result, for example in your_template.html with the dictionary b_list
view.py
def GetList(request):
sh_list = Schedul.listing.all().order_by('-created_at')
return render(request, 'your_template.html', {'b_list': sh_list})

Django validating time slot

This is my models to store availability of particular time when a new booking there
class TimeSlot(models.Model):
day = models.ForeignKey(
Day,
on_delete=models.CASCADE,
related_name="time"
)
booking = models.ForeignKey(
Booking,
on_delete=models.CASCADE,
related_name="time"
)
start_hour = models.TimeField()
end_hour = models.TimeField()
class Meta:
unique_together = [('end_hour', 'start_hour',)]
def clean(self):
pass
Currently it's allowing booking even those are considered as duplicate in terms of end_hour and start_hour. I want to prevent the slot, so that no new booking shouln't placed between a range that already booked.
Can anyone know how to do it with the range?
I assume the problem is that start_hour and end_hour that fall within an already existing time range are allowed to be added. Of course the unique_together constraint cannot handle this as it only deals with uniqueness not uniqueness in a range. Instead you can override your models clean method and perform this validation there:
from django.db.models import Q
from django.core.exceptions import ValidationError
class TimeSlot(models.Model):
day = models.ForeignKey(
Day,
on_delete=models.CASCADE,
related_name="time"
)
booking = models.ForeignKey(
Booking,
on_delete=models.CASCADE,
related_name="time"
)
start_hour = models.TimeField()
end_hour = models.TimeField()
class Meta:
unique_together = [('end_hour', 'start_hour',)]
def clean(self):
start_hour_in_range = Q(start_hour__lte=self.start_hour, end_hour__gte=self.start_hour)
end_hour_in_range = Q(start_hour__lte=self.end_hour, end_hour__gte=self.end_hour)
# Queryset that finds all clashing timeslots with the same day
queryset = self._meta.default_manager.filter(start_hour_in_range | end_hour_in_range, day=self.day)
if self.pk:
queryset = queryset.exclude(pk=self.pk) # Exclude this object if it is already saved to the database
if queryset.exists():
raise ValidationError('An existing timeslot clashes with the given one!')
Next if you are using a ModelForm this method would be called automatically or if you are not you can call instance.full_clean() which will call this method and all other cleaning methods on the model (clean_fields and validate_unique).

Django QuerySet filter on inverse relationship

Really struggling with this one and could appreciate some help. I have the following model...
class Booking(models.Model):
property = models.ForeignKey(Property, related_name='booking', on_delete=models.SET_NULL, null=True)
check_in_date = models.DateField()
check_out_date = models.DateField()
def __str__(self):
return f"{self.property}"
class Property(models.Model):
name = models.CharField(max_length=100, blank = False)
def __str__(self):
return f"{self.name}"
But when I run the following (below) to retrieve property bookings with a date equal to 2021-05-14, instead of just the bookings with a check_in_date of 2021-05-14 being returned, all the bookings for each property are returned even if they don't have a check_in_date of 2021-05-14
Property.objects.filter(booking__check_in_date='2021-05-14')
Probably something really simple, but I'm new to Django have been stuck on this all morning and could appreciate some help. Thanks
You actually need something like this:
bookings = Booking.objects.filter(check_in_date='2021-05-14')
properties = [booking.property for booking in bookings]
Because property is available on a Booking object and you're initally filtering bookings.
to filter DateFields in Django you must convert the date string to a datetime object:
from datetime import datetime
check_in_date = datetime.strptime('2021-05-14', '%Y-%m-%d')
properties = Property.objects.filter(booking__check_in_date=check_in_date)

How do I preserve the items order when filtering in Django?

I've order the Recommendations by the rating_prediction and get the ids, then when I filter it to get the Tracks, the order is not the same anymore
class RecommendationView(generics.ListAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
serializer_class = RecommendedTrackSerializer
queryset = Recommendations.objects.all()
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get_queryset(self):
recommendation = Recommendations.objects.filter(user=self.request.user)
sorted_recommendation = recommendation.order_by('-rating_prediction')
ids = list(sorted_recommendation.values_list('track', flat=True))
tracks = Track.objects.filter(id__in=ids)
return tracks
models:
class Recommendations(models.Model):
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
verbose_name='user which the recommendation is given',
)
track = models.ForeignKey(
Track,
on_delete=models.CASCADE,
verbose_name="track which recommend to the user",
)
date = models.DateField(
auto_now_add=True,
verbose_name='when the recommendation was generated',
)
rating_prediction = models.FloatField(
validators=[MinValueValidator(0), MaxValueValidator(5)]
)
class Track(models.Model):
track_id = models.CharField(max_length=24)
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
link = models.URLField(max_length=255, blank=True)
tags = models.ManyToManyField(Tag, through='Tagged', blank=True)
similarity = models.ManyToManyField(
'self',
blank=True,
through='FusionSimilarity',
related_name='similar_to',
symmetrical=False
)
users = models.ManyToManyField(User, through='PlayTrack', blank=True)
Track.objects.filter(recommendation__user=self.request.user).order_by('-recommendation__rating_prediction')
You filter like:
tracks = Track.objects.filter(id__in=ids)
But this does not mean that a the Tracks are in the same order as the ids. You simply filter, and depending on how the database has indexed this, typically it will produce a list order by the id, or by some other metric (for example if you attached a default order to the Track model). This can depend on the database engine as well. Since you never state the order, the database thus has the "freedom" to pick any order.
You can however reduce the amount of querying, and write it like:
def get_queryset(self):
return Track.objects.filter(
recommendations__user=self.request.user
).order_by('-recommendations__rating_prediction')
In case a user can only give one recommendation per track, this will not produce any duplicates. In case a user can give multiple recommendations for a track, you should add the .distinct() call.
We thus obtain Tracks that were recommended by a given user, and we order these by how the user recommended these, all in one query.
I know this question was asked a while ago but I wanted to add a solution if your ordered list is not easily accessable through queryset methods.
You can build a conditional list to use if your list isn't too long. Like this:
from typing import Any, List
from django.db.models import Case, When
def generate_sorting(ordered_list: List[Any], field_name: str) -> Case:
conditions_list = []
for index, field_value in enumerate(ordered_list):
condition = {field_name: field_value}
conditions_list.append(When(**condition, then=index))
return Case(*conditions_list, default=len(conditions_list))
ids = list(sorted_recommendation.values_list('track', flat=True))
id_sorting = generate_sorting(ids, "id")
"""
this will return something like this:
>>> generate_sorting([12, 3], "id")
id_sorting = Case(When("id"=12, then=0), When("id"=3, then=1), default=2)
"""
tracks = Track.objects.filter(id__in=ids).order_by(id_sorting)

Django how to order queryset based on filtered many to many field?

Hi all i'm trying to figure out how to order a queryset based on a manyToMany field but I need to filter that field as well. Here is the exact problem.
qs = Drink.objects.all()
I want to order this queryset by a related field ingredientsuserneeds
Models:
class IngredientsUserNeeds(models.Model):
user = models.ForeignKey(User, blank=True, null=True)
drinks = models.ManyToManyField(Drink)
count_need = models.IntegerField(default=0)
class Drink(models.Model):
name = models.CharField(max_length = 1000, null=True, blank=True)
user = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
In a DRF list view, I want to order the queryset by the drink's ingredientuserneeds_set but filtered by the user which will return a queryset that is of length one, finally ordering by that single object's count_need in the queryset. It would be sorta like
qs = qs.order_by('ingredientsuserneeds_set__filter(user=user)__first()__count_need')
But clearly, you cant do what i typed. Any ideas?
This should do the trick:
from django.db import models
from django.db.models import Case, When, F
Drink.objects.all().annotate(
count_need=Case(When(
ingredients__user__email='wanaryytel#outlook.com', then=F('ingredients__count_need')),
output_field=models.IntegerField()
)
).first().count_need