django order_by using a conditional - django

I need to order my site based on if a field is not null. There are two fields that refer to a deadline, the main full deadline field which is a required field, and a trials field which is optional. All entries will use deadline eventually, but some will have a trials date in there and that will be the one that needs to take priority untill that date is past when it should then default to the deadline.
To try to cover just the basics or using one or the other, without it changing when the trials date is passed, I tried the following:
class Project(models.Model):
'''
Main Project, serves the default Projects Portal window.
'''
published = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
area = models.ForeignKey(
Area,
related_name="project",
on_delete=models.PROTECT
)
title = models.CharField(max_length=128, unique=True)
slug = models.SlugField(max_length=64)
summary = models.CharField(max_length=256)
others = models.CharField(max_length=128, blank=True)
staff_trials = models.DateField(null=True, blank=True)
deadline = models.DateField()
slip = models.BooleanField(default=False)
class Meta:
ordering = ["-slip", "staff_trials", "deadline"]
def __str__(self):
return self.title
It was a bit of a stab in the dark to hope it would work, but that just puts all the staff_trials first, which is not quite what I'm after, I need the dates themselves to be in order, e.g.
project A - deadline - 12/01/2020
project B - staff_trials - 15/01/2020 deadline - 01/02/2020
project C - deadline - 20/01/2020
project D - deadline - 23/01/202
So this would be the order and when the 15th is passed Project B would appear at the bottom of this list. Is there a way to do this?

I think you may not be able to define this ordering on model's Meta class, but while filtering instances, you can follow an approach like the following:
Projects.objects.annotate(
actual_deadline=Case(
When(staff_trials__isnull=True, then=F('deadline')),
When(staff_trials__isnull=False, then=F('staff_trials')),
output_field=DateTimeField(),
)
).order_by('actual_deadline')
Here we annotate each result with a DateTimeField named "actual_deadline", getting the valie of staff_trials if defined, deadline field if not defined. Then we order the results with this field.
I haven't tested the code but should work with some tweaks if not directly.

Related

Saving previous date and adding new date in Django

I have been working on an application where user adds his tasks which he is supposed to perform, once the task is added he can update the progress.
In my model I have a date field, where user is supposed to enter the estimated completion date.
My task model
"""Creating KRA Based on Institutional Objectives"""
class KraBasedOnIo(models.Model):
io = models.ForeignKey(InstitutionalObjectives, on_delete=models.CASCADE, related_name='kra_io')
kra_title = models.CharField(max_length = 200)
kra_description = models.TextField()
kra_target = models.CharField(max_length=200)
kra_added_date = models.DateTimeField(auto_now_add=True)
estimated_date = models.????
While updating the progress, if the user wants to extend his timeline, I am looking for an option where I can save his current estimated completion date and add the new date also.
And when I am displaying his progress I want to show his defined completion date and also the extended completion date in the template.
I have tried a work around with model.JSONField() but couldn't reach there.
There is no ListField or DictionaryField so what could be a better solution for this?
So I would use the library django-simple-history to keep track of the different updates of this field.
"""Creating KRA Based on Institutional Objectives"""
class KraBasedOnIo(models.Model):
io = models.ForeignKey(InstitutionalObjectives, on_delete=models.CASCADE, related_name='kra_io')
kra_title = models.CharField(max_length = 200)
kra_description = models.TextField()
kra_target = models.CharField(max_length=200)
kra_added_date = models.DateTimeField(auto_now_add=True)
estimated_date = models.DateTimeField(default=timezone.now)
history = HistoricalRecords()
Then in you view, you can see the different version by doing :
for record in kra_based_on_info.history.all():
print(record)
Keep it simple and create two separated fields:
class KraBasedOnIo(models.Model):
# ...
estimated_completion_date = models.DateTimeField(null=True, blank=True)
extended_completion_date = models.DateTimeField(null=True, blank=True)
If you need to keep track of all the completion date changes, I suggest you to create a new model
class CompletionDate(models.Model):
# ...
kra = models.ForeignKey(KraBasedOnIo, related_name='completion_dates', on_delete=models.CASCADE)
estimated_date = models.DateTimeField(auto_now_add=True)
And then get the last completion date for a KraBasedOnIo instance like this:
my_last_completion_date = KraBasedOnIo.completion_dates.last()

Annotating values from filtered related objects -- Case, Subquery, or another method?

I have some models in Django:
# models.py, simplified here
class Category(models.Model):
"""The category an inventory item belongs to. Examples: car, truck, airplane"""
name = models.CharField(max_length=255)
class UserInterestCategory(models.Model):
"""
How interested is a user in a given category. `interest` can be set by any method, maybe a neural network or something like that
"""
user = models.ForeignKey(User, on_delete=models.CASCADE) # user is the stock Django user
category = models.ForeignKey(Category, on_delete=models.CASCADE)
interest = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0)])
class Item(models.Model):
"""This is a product that we have in stock, which we are trying to get a User to buy"""
model_number = models.CharField(max_length=40, default="New inventory item")
product_category = models.ForeignKey(Category, null=True, blank=True, on_delete=models.SET_NULL, verbose_name="Category")
I have a list view showing items, and I'm trying to sort by user_interest_category for the currently logged in user.
I have tried a couple different querysets and I'm not thrilled with them:
primary_queryset = Item.objects.all()
# this one works, and it's fast, but only finds items the users ALREADY has an interest in --
primary_queryset = primary_queryset.filter(product_category__userinterestcategory__user=self.request.user).annotate(
recommended = F('product_category__userinterestcategory__interest')
)
# this one works great but the baby jesus weeps at its slowness
# probably because we are iterating through every user, item, and userinterestcategory in the db
primary_queryset = primary_queryset.annotate(
recommended = Case(
When(product_category__userinterestcategory__user=self.request.user, then=F('product_category__userinterestcategory__interest')),
default=Value(0),
output_field=IntegerField(),
)
)
# this one works, but it's still a bit slow -- 2-3 seconds per query:
interest = Subquery(UserInterestCategory.objects.filter(category=OuterRef('product_category'), user=self.request.user).values('interest'))
primary_queryset = primary_queryset.annotate(interest)
The third method is workable, but it doesn't seem like the most efficient way to do things. Isn't there a better method than this?

Django Foreignkey with edit fields and select field how to put in form

I apologize if that question was raised before. But I have been struggling with this for weeks and couldn't find anything useful.
I have the following problem (it is simplified a lot but essentially my problem is presented)
I have a Model that has a lot of fields. It's called
class DocAide(models.Model):
id = models.AutoField(primary_key=True)
pulse = models.DecimalField('Pulse', max_digits=3, decimal_places=0)
weight = models.DecimalField('Weight (kg)', max_digits=3, decimal_places=0)
bp_sys = models.DecimalField('BP Sys', max_digits=3, decimal_places=0)
bp_dia = models.DecimalField('BP Dia', max_digits=3, decimal_places=0)
temperature = models.DecimalField('Temp. deg C', max_digits=2, decimal_places=1)
drugs = models.ManyToManyField(Drug, blank=True)
date = models.DateField(editable=False, default=timezone.now)
doctors_notes = models.TextField('Patient is complaining about:', default='')
note = models.TextField(max_length=100, default='')
The ForeignKey Drugs has Names of drugs with quantity I would like to have the possibility to select multiple drugs but with edit fields that show what dosage needs to be taken and when, it should be like a prescription. The Model looks like this:
class Drug(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, default='')
QUANTITY_STR = ['Bottle', 'Tablet' 'Injection', 'Capsules', 'other']
QUANTITY = ((str, str) for str in QUANTITY_STR)
quantity = models.CharField(max_length=2, choices=QUANTITY, default='Bottle')
category = models.CharField(max_length=150, default='')
strength = models.CharField(max_length=150, default='')
in_supply_stock = models.PositiveIntegerField(default=0)
in_main_stock = models.PositiveIntegerField(default=0)
date = models.DateField(editable=False, default=timezone.now)
charge = models.PositiveIntegerField(default=0)
morning = models.CharField(validators=[int_list_validator], max_length=3, default=0)
midday = models.CharField(validators=[int_list_validator], max_length=3, default=0)
evening = models.CharField(validators=[int_list_validator], max_length=3, default=0)
night = models.CharField(validators=[int_list_validator], max_length=3, default=0)
days = models.CharField(validators=[int_list_validator], max_length=3, default=0)
tablets = models.CharField(validators=[int_list_validator], max_length=3, default=0)
How can I accomplish that in a form or template. I tried with Inlineformset it doesn't work. Also later I would like to have them preselected as well.
But for now I would like to have a button that produces a line with a dropdown list of the drugs and the edit fields of the model.
Thank you in advance.
As others have said, a Drug object should not have a quantity associated with it, but a prescription "entry" should.
I think this is the Model structure you need:
QUANTITY_STR = ['Bottle', 'Tablet' 'Injection', 'Capsules', 'other']
class DocAide(models.Model):
# same properties but remove `drugs` from this model
class Drug(models.Model):
# same properties but remove `quantity` property
class Prescription(model.Model):
drug = model.ForeignKey(to=Drug, related_name='prescriptions')
doc_aide = model.ForeignKey(to=DocAide, related_name='prescriptions')
quantity = models.IntegerField()
qty_container = models.CharField(max_length=10, choices=QUANTITY_STR, default=QUANTITY_STR[0])
I changed a few things for you assuming I understood your business logic correctly. Such as how the quantity field works.
I created two fields to describe the quantity. quantity holds the numerical value, and qty_container holds the container's name if you will, like "Bottle", "Injection" and so on.
qty_container has a max_length equal to the number of characters in the word "Injection" since it is the largest word that might fit in this field. You had the default of that field be greater than the max_length which would cause an error.
Now I'm not sure why you wanted to save a tuple of two strings in the quantity field so I ignored that, but if you can comment on your intended logic here I might be able to edit the answer.
Anyway, the Prescription model.
This model will act as an intermediary between Drug and DocAide, and it is the one that will hold the quantity information. I've linked it with Drug and DocAide using foreign keys and set the related_name to suitable names. These "related_names" you'll find show up in the referenced model. So for example if you can do
doc_aide = DocAide.objects.get(pk=1)
for presc in doc_aide.prescriptions:
print(presc.drug.name)
print(presc.quantity)
This means that one DocAide object will be linked with one or many Prescription objects, each of those holds quantity info and is linked with a Drug object:
DocAide (id, etc) >> Prescription (doc_aide_id, drug_id, qty_info) >> Drug (id, etc)
As a side note, after you're sure everything works and you're good to go, you might need to look into query optimization in Django, because as it is written now, it's pretty unoptimized. But don't worry about optimization until you've finished and your code works correctly.
You likely want to move some fields of your Drug model to an intermediary one that defines a foreign key to both your Drug and DocAide models.
This model should then be used as the through option of your DocAide.drugs field.
If you are using the admin you'll be able to rely on inlines to display an input form that allows selecting the desired Drug and annotating extra fields otherwise you'll likely have to build your own mechanism that relies on ModelFormSet.

Using ForeignKey to sort with order_by and distinct not working

I'm trying to sort model Game by each title and most recent update(post) without returning duplicates.
views.py
'recent_games': Game.objects.all().order_by('title', '-update__date_published').distinct('title')[:5],
The distinct method on the query works perfectly however the update__date_published doesn't seem to be working.
models.py
Model - Game
class Game(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
description = models.TextField()
date_published = models.DateTimeField(default=timezone.now)
cover = models.ImageField(upload_to='game_covers')
cover_display = models.ImageField(default='default.png', upload_to='game_displays')
developer = models.CharField(max_length=100)
twitter = models.CharField(max_length=50, default='')
reddit = models.CharField(max_length=50, default='')
platform = models.ManyToManyField(Platform)
def __str__(self):
return self.title
Model - Update
class Update(models.Model):
author = models.ForeignKey(User, models.SET_NULL, blank=True, null=True,) # If user is deleted keep all updates by said user
article_title = models.CharField(max_length=100, help_text="Use format: Release Notes for MM/DD/YYYY")
content = models.TextField(help_text="Try to stick with a central theme for your game. Bullet points is the preferred method of posting updates.")
date_published = models.DateTimeField(db_index=True, default=timezone.now, help_text="Use date of update not current time")
game = models.ForeignKey(Game, on_delete=models.CASCADE)
article_image = models.ImageField(default='/media/default.png', upload_to='article_pics', help_text="")
platform = ChainedManyToManyField(
Platform,
horizontal=True,
chained_field="game",
chained_model_field="game",
help_text="You must select a game first to autopopulate this field. You can select multiple platforms using Ctrl & Select (PC) or ⌘ & Select (Mac).")
See this for distinct reference Examples (those after the first will only work on PostgreSQL)
See this one for Reverse Query - See this one for - update__date_published
Example -
Entry.objects.order_by('blog__name', 'mod_date').distinct('blog__name', 'mod_date')
Your Query-
Game.objects.order_by('title', '-update__date_published').distinct('title')[:5]
You said:
The -update__date_published does not seem to be working as the Games are only returning in alphabetical order.
The reason is that the first order_by field is title; the secondary order field -update__date_published would only kick in if you had several identical titles, which you don't because of distinct().
If you want the Game objects to be ordered by latest update rather their title, omitting title from the ordering seems the obvious solution until you get a ProgrammingError that DISTINCT ON field requires field at the start of the ORDER BY clause.
The real solution to sorting games by latest update is:
games = (Game.objects
.annotate(max_date=Max('update__date_published'))
.order_by('-update__date_published'))[:5]
The most probable misunderstanding here is the join in your orm query. They ussually lazy-loading, so the date_published field is not yet available, yet you are trying to sort against it. You need the select_related method to load the fk relation as a join.
'recent_games': Game.objects.select_related('update').all().order_by('title', '-update__date_published').distinct('title')[:5]

Efficient alternative at Django filter by property

I know that filtering by property is not possible with Django, as filtering is done at database level and properties live in Python code. However, I have the following scenario:
In one hand, I have the model RegisteredUser on the other hand Subscription. A user can have multiple subscriptions, a subscription is from one user and a user has one or none active subscriptions.
To implement this, I have a foreign key from Subscription to RegisteredUser and a property subscription at RegisteredUser that points to the active one (latest created subscription for that user) or none if he hasn't any subscriptions.
Which would be the most efficent way to filter users that have subscription "platinum", "gold", "silver"...? I could do a "fetch all subscriptions" and then iterate over them to check each one for a match. But it would be really expensive and if I have to do the same process for each kind of subscription type, then cost would be s * u (where s is the number of different subscriptions and u is the number of users).
Any help will be appreciated. Thanks in advance!
UPDATE:
When I first explained the problem, I didn't include all the models related to
simplify a litte. But as you are asking me for the models and some of you haven't understood me
(perhaps I wasn't clear enough) here you have the code.
I've simplified the models and stripped out code that is not important now.
What do I have here? A RegisteredUser can have many subscriptions (because he may change it
as many times as he wants), and a subscription is from just one user. The user has only
one current subscription, which is the latest one and is returned by the property
subscription. Subscription is attached with Membership and this is the model whose
slug can be: platinum, gold, silver, etc.
What do I need? I need to lookup Content whose author has a specific kind of membership.
If the property approach worked, I'd have done it like this:
Content.objects.filter(author__id__in=RegisteredUser.objects.filter(
subscription__membership__slug="gold"))
But I can't do this because properties can't be used when filtering!
I thought that I could solve the problem converting the "virtual" relation created by
the property into a real ForeignKey, but this may cause side effects, as I should update it manually each time a user changes its subscription and now it's automatic! Any better ideas?
Thanks so much!
class RegisteredUser(AbstractUser):
birthdate = models.DateField(_("Birthdate"), blank=True, null=True)
phone_number = models.CharField(_("Phone number"), max_length=9, blank=True, default="")
#property
def subscription(self):
try:
return self.subscriptions_set.filter(active=True).order_by("-date_joined",
"-created")[0]
except IndexError:
return None
class Subscription(models.Model):
date_joined = models.DateField(_("Date joined"), default=timezone.now)
date_canceled = models.DateField(_("Date canceled"), blank=True, null=True)
subscriber = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_("Subscriber"),
related_name="subscriptions_set")
membership = models.ForeignKey(Membership, verbose_name=_("Membership"),
related_name="subscriptions_set")
created = models.DateTimeField(_("Created"), auto_now_add=True)
last_updated = models.DateTimeField(_("Last updated"), auto_now=True)
active = models.BooleanField(_("Active"), default=True)
class Membership(models.Model):
name = models.CharField(_("Name"), max_length=15)
slug = models.SlugField(_("Slug"), max_length=15, unique=True)
price = models.DecimalField(_("Price"), max_digits=6, decimal_places=2)
recurring = models.BooleanField(_("Recurring"))
duration = models.PositiveSmallIntegerField(_("Duration months"))
class Content(models.Model):
author = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_("Author"),
related_name="contents_set")
title = models.CharField(_("Title"), max_length=50)
slug = models.SlugField(_("Slug"), max_length=70, unique=True)
content = RichTextField(_("Content"))
date = models.DateField(_("Date"), default=timezone.now)
published = models.BooleanField(_("Published"))
Finally, to solve the problem I replaced the subscription property by a real foreign key and added a signal to attach the RegisteredUser with the created subscription.
Foreign key:
subscription = models.ForeignKey(Subscription, verbose_name=_("Subscription"),
related_name='subscriber_set', blank=True, null=True)
Signal:
#receiver(post_save, sender=Subscription)
def signal_subscription_post_save(sender, instance, created, **kwargs):
if created:
instance.subscriber.subscription = instance
instance.subscriber.save()
I think you model are something like:
KIND = (("p", "platinum"), ("g","gold"), ("s","silver"),)
class RegisteredUser(models.Model):
# Fields....
class Subscription(models.Model):
kind = models.CharField(choices=KIND, max_len=2)
user = models.ForeignKey(RegisteredUser, related_name="subscriptions")
Now, you can do something like that:
gold_users = RegisteredUser.objects.filter(subscriptions_kind="g")
silver_users = RegisteredUser.objects.filter(subscriptions_kind="s")
platinum_users = RegisteredUser.objects.filter(subscriptions_kind="p")
Adapt it to your models
Hope helps
EDIT
Now, With your models, I think you want something like:
content_of_golden_users = Content.objects.filter(author__subscriptions_set__membership__slug="golden")
content_of_silver_users = Content.objects.filter(author__subscriptions_set__membership__slug="silver")
content_of_platinum_users = Content.objects.filter(author__subscriptions_set__membership__slug="platinum")