Intances are created when using annotate and Foreign Key - django

I have the following situation:
I am creating an app where I have a Person model and I want to store a status history in a table, so that looking for the most recent status in the table would return the current status of the person.
My solution was creating a Status table with a Foreign Key pointing to the Person. Looking for the most recent entry in person.status_set would make it easy looking for the current status.
class Person(models.Model):
...
def _get_status(self):
return self.status_set.order_by("-timestamp").first()
#property
def status(self):
try:
return self._get_purchase_status().status
except AttributeError:
# TODO: log error: Person without status.
return None # TODO: Change this.
#status.setter
def status(self, new_status):
s = Status(
person=self,
status=new_status
)
s.save()
#status.deleter
def status(self):
if self.status:
self.status_set.order_by("-timestamp").first().delete()
class Status(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
status = models.CharField(
max_length=50,
)
timestamp = models.DateTimeField(auto_now_add=True)
When I try to create a QuerySet containing the person and the last status info, I perform this query:
Person.objects.annotate(
person_status=Subquery(
Status.objects.filter(person_id=OuterRef("id")).order_by("-timestamp").values("status")[:1]
)
).values()
Calling the annotate function using .values() at the end works as expected, but when I only run the annotate function,
Person.objects.annotate(
person_status=Subquery(
Status.objects.filter(person_id=OuterRef("id")).order_by("-timestamp").values("status")[:1]
)
)
I see that there are instances created in the Status table for each Person (said in other words, there is a new instance of Status added to the status_set of each Person instance).
I do not think that this behaviour is expected. Could someone confirm or maybe explain, why does it happen?

Related

how to create a SimpleListFilter in django

I don't succeed to write a query filter.
I have 3 models: Patient, Prescription and User
I write you only what is relevant for my question
Patient:
class Patient(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
Prescription:
class Prescription(models.Model):
user = models.ForeignKey(
User,
null=True,
blank=False,
on_delete=models.DO_NOTHING
)
file_extention = models.CharField(
'file extention',
max_length=8,
null=True,
blank=True,
)
So the relation between both of models (Patient and Prescription) are through User.
in the PatientAdmin, I want to filter on the file_extension according pdf or jpg of the prescription uploaded.
I created a SimpleListFilter but impossible to find the right query.
class PrescriptionFileExtensionFilter(SimpleListFilter):
"""This filter is being used in django admin panel in
patient model."""
title = 'Prescription File Ext'
parameter_name = 'file_extention'
def lookups(self, request, model_admin):
return (
('pdf', 'PDF'),
('jpg', 'JPG'),
)
def queryset(self, request, queryset):
for user in queryset:
if self.value() == 'pdf':
return queryset.filter(user=user.user).filter
(prescription__file_extention="pdf")
if self.value() == 'jpg':
return queryset.filter(user=user.user).filter
(prescription__file_extention="jpg")
That's not working...
Do I need the for user in queryset:
need What could be the query to bring me all the users with a prescription with file_extension = "pdf" (or "jpg")
You are trying to get a key from the prescription object in print(mydict['file_extention']) which I believe is causing the issue - you would instead access that property via mydict.file_extention - though I should add that mydict is not an accurate variable name for a model object as it isn't a dictionary. I don't think that for loop is actually doing anything other than printing a particular value so it can be removed altogether.
As an aside, you have two filters on your queryset, this can just be expressed as a single filter, separate by a comma, e.g.
return queryset.filter(user=user.user, prescription__file_extention="pdf")
You are also calling user.user, presumably you just want to get the user model which is kept in request.user - is that what your for loop was trying to do?
Edit
If you want to get all of the users but just filtered by JPG or PDF then you need to remove two things:
The for-loop of for user in queryset
The filter of .filter(user=user.user)
The for loop is unnecessary in the queryset function and the filter is just getting a single user, but you want to get all of them - correct?

Is it possible to create status choice with a model?

I have my model.py:
class Example(models.Model):
STATUS_CHOICE = (
('status1', 'status1'),
('status2', 'status2'),
)
field= models.CharField(max_length=10, choices=STATUS_CHOICE)
.
But I want to know if it's possible to have the possibility to create my STATUS_CHOICE with a model, in this manner the client can create the status as you want.
It certainly is. It would be a ForeignKey to a Status model.
class Status(models.Model):
slug = models.SlugField(unique=True)
title = models.TextField()
class Example(models.Model):
status = models.ForeignKey(Status)
You may want to have a migration create initial statuses, unless all of them should be user-defined.
You will probably want to use a SlugRelatedField (or whatsitcalled) in DRF, so users can just post {"status": "foo"}, not {"status": 1} or whatever happens to be the foo status's primary key.

How to get object using filter on ManyToManyField

Why target_dialogue is always None?
Model:
class Dialogue(models.Model):
name = models.CharField(max_length=30, blank=True)
is_conference = models.BooleanField(default=False)
participants = models.ManyToManyField(
Person,
related_name='dialogues',
)
def __str__(self):
return self.name or str(self.pk)
And in view I want to get suitable dialogue which contain in participants field 2 objects - user and companion. And if this dialogue doesn't exist I create it:
target_dialogue = None
try:
target_dialogue = Dialogue.objects.get(is_conference=False,participants__in=[user, companion])
except ObjectDoesNotExist:
target_dialogue = Dialogue()
target_dialogue.save()
target_dialogue.participants.add(user)
target_dialogue.participants.add(companion)
finally:
return render(request, 'dialogues/dialogue.html', {
'dialogue': target_dialogue,
})
But target_dialogue is always None. What's a reason of it? I was supposed to solve only a trouble in getting a dialogue from db in order to bad filter parameters, but now I have doubts about it. Maybe something else?
request.user is not a object of Person model with which you have the relation in Dialogue.
You have to first fetch the person object:
user = Person.objecs.get(user=request.user). # According to your person model
Follow same for companion and then query:
target_dialogues = Dialogue.objects.filter(is_conference=False,participants__in=[user,companion]

Django Q bad query logic

I'm trying to create a manager that has a method 'active_or_users' to retrieve all accounts that are active, or that an user has created. An active account has a start date that is either today, or somewhere in the past, and a end date that is somewhere in the future. Right now the active_or_users method works, however it returns duplicates of the same object. It's returning three copies of a user created active job. This is less than ideal.
from django.db.models import Q
from django.db import models
from django.contrib.auth.models import User
class ActiveJobs(models.Manager):
def active(self):
return super(ActiveJobs, self).get_query_set().\
filter(publications__publish_on__lte=date.today(),
publications__end_on__gt=date.today())
def active_or_users(self, user):
return super(ActiveJobs, self).get_query_set().\
filter((Q(publications__publish_on__lte=date.today()) &
Q(publications__end_on__gt=date.today())) | Q(creator=user))
class Job(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(blank=True, null=True)
creator = models.ForeignKey(User)
objects = ActiveJobs()
class JobPublicationRecord(models.Model):
job = models.ForeignKey('Job', related_name='publications')
publish_on = models.DateField(auto_now=False)
end_on = models.DateField(auto_now=False, auto_now_add=False,
blank=True, null=True)
To put the comments into an answer
With the OR query, an instance will be returned for every hit of the query. I.e: an instance if a Job is created by user and another instance of the same job if also in the date range specified, etc.
So to fix this, change the method active_or_users to:
def active_or_users(self, user):
return super(ActiveJobs, self).get_query_set().filter(
(Q(publications__publish_on__lte=date.today()) &
Q(publications__end_on__gt=date.today())) | Q(creator=user)).distinct()

custom-unique Field with relation to ManyToManyField

let's start with the model
#models.py
class Project(models.Model):
owner = models.ForeignKey(User)
name = models.CharField(max_length=100)
class Issue(models.Model):
project = models.ForeignKey(Project)
title = models.CharField(max_length=100)
status = models.ForeignKey('Status')
class Status(models.Model):
name= models.CharField(max_length=10, help_text=u'eg open, closed...')
default = models.BooleanField(default=False)
project = models.ManyToManyField(Project)
def save(self, *args, **kwargs):
if self.default:
#self.__class__.objects.filter(default=True, project=self.project).update(default=False)
pass
I'd like to set all default to False when a user selects on another Status the default option. How could I achieve this?
Edit:
A user will be able to create custom Status for his project. So, let's say the user got 2 Projects - they are called Audi and BMW.
Now the user creates a Status for an Issue. It's name will be open and he selects it to be default for all Issues within the Project BMW.
All issues within the project BMW will get the default status open. Great!
Now the user creates another Status for an Issue. This time the name will be new and he selects it to be default for all his Projects!
So, what I need is something (with as less queries as possible) that sets the first Status open within BMW to default = false.
def save(self, *args, **kwargs):
if self.default:
for status in Status.objects.filter(default=True,project__in=self.project.all()):
status.default=False
status.save()
super(Status,self).save(*args,**kwargs)
You could create a single-instance Settings model with a ForeignKey Status relationship:
class Settings(models.Model):
default_status = models.ForeignKey('Status')
You may want to enforce there only being one Settings instance.
Alternatively, you could perform the un-defaulting in save():
if self.default:
for status in self.__class__.objects.all():
if status != self:
status.default = False
status.save()
Note that if you're overriding Model.save(), you'll want to use super() to re-instate the original behaviour of that function. See Overriding predefined model methods in the Django documentation.