Django Q bad query logic - django

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()

Related

Properly update Django model's foreign key property

I have a Django Model named EmailSendingTask. This is the whole model-
class EmailSendingTask(models.Model):
order = models.OneToOneField(Order, on_delete=models.CASCADE, related_name='order')
status = EnumChoiceField(SetupStatus, default=SetupStatus.active)
time_interval = EnumChoiceField(TimeInterval, default=TimeInterval.five_mins)
immediate_email = models.OneToOneField(PeriodicTask, on_delete=models.CASCADE, null=True, blank=True, related_name='immediate_email')
scheduled_email = models.OneToOneField(PeriodicTask, on_delete=models.CASCADE, null=True, blank=True, related_name='scheduled_email')
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = 'EmailSendingTask'
verbose_name_plural = 'EmailSendingTasks'
def __str__(self) -> str:
return f'EmailSendingTask: Order = {self.order}'
The immediate_email and scheduled_email fields are responsible for holding two PeriodicTask objects.
I have created a function called disable_scheduled_email, which is responsible for disabling the scheduled_email's periodic task. The detail of the function is here-
def disable_scheduled_email(self):
print(f'Disabling scheduled email...')
self.scheduled_email.enabled = False
self.save(update_fields=['scheduled_email'])
Now, whenever I call this function and print the value of the self.scheduled_email.enabled, I find it False. But, when I try to look at the Django Admin site, the periodic task's enabled value remains as True. Why is it happening?
After some experiments into the Django Shell I have found out that, I was not specifically calling save() to the foreign key (scheduled_email). I have just added self.scheduled_email.save() into the disable_scheduled_email function. So, the whole function became like:
def disable_scheduled_email(self):
print(f'Disabling scheduled email...')
self.scheduled_email.enabled = False
# self.save(update_fields=['scheduled_email'])
self.scheduled_email.save() #instead of self.save(...), wrote this

Django: How to update an Integerfield and decrement its value

This seemingly innocuous problem has turned out to be quite difficult to find any information on. I just want to decrement the value of an Integerfield column by 1 in my database, by calling a function.
views.py function call
StudentProfile.objects.add_lesson(student_id)
managers.py
class StudentQuerySet(models.QuerySet):
def add_lesson(self, sid):
self.filter(student_id=sid).update(remaining_lessons=remaining - 1)
class StudentProfileManager(models.Manager):
def add_lesson(self, sid):
self.get_queryset().add_lesson(sid)
Full StudentProfile model
class StudentProfile(models.Model):
student = models.OneToOneField(
User, related_name='student', primary_key=True, parent_link=True, on_delete=models.CASCADE)
portrait = models.ImageField(
upload_to='studentphotos', verbose_name=_('Student Photo'))
about_me = models.TextField(verbose_name=_("About Me"))
spoken_languages = models.CharField(max_length=255)
teacher_default = models.OneToOneField(
'teachers.TeacherProfile', related_name='teacher_default', parent_link=True,
on_delete=models.CASCADE, default=None, blank=True, null=True)
membership_start = models.DateTimeField(
verbose_name="Membership Start Date", default=now, editable=False)
membership_end = models.DateTimeField(
verbose_name="Membership End Date", default=now, editable=False)
remaining_lessons = models.IntegerField(
verbose_name="Membership remaining lessons", default=0)
objects = StudentProfileManager()
def __str__(self):
return User.objects.get_student_name(self.student_id)
I know this is totally wrong, any help is appreciated.
If you want to keep your current setup and be able to add_lesson() to decrement "remaining_lessons", the smallest change you can do to achieve it is by using F() expression:
from django.db.models import F
class StudentQuerySet(models.QuerySet):
def add_lesson(self, sid):
self.filter(student_id=sid).update(remaining_lessons=F('remaining_lessons') - 1)
Ref: https://docs.djangoproject.com/en/3.0/ref/models/expressions/
Although I personally think that if your goal is only to have a method that decrement "remaining_lessons" by 1, you should probably just make it a model method. Like this:
class StudentProfile(models.Model):
# ... your model field ...
def add_lesson(self):
self.remaining_lesson -= 1
self.save()
# and in your Views.py
StudentProfile.objects.get(student_id=sid).add_lesson()
Hope this helps.
Django provides F expressions for exactly the kind of task you have. It makes the update relative to the original field value in the database.
You would need to change your managers.py as follows (plus the return statements :) )
from django.db.models import F
class StudentQuerySet(models.QuerySet):
def add_lesson(self, sid):
return self.filter(student_id=sid).update(remaining_lessons=F('remaining_lessons')-1)
class StudentProfileManager(models.Manager):
def add_lesson(self, sid):
return self.get_queryset().add_lesson(sid)
You could go even further, and for the sake of DRY approach, use QuerySet.as_manager() to create an instance of Manager with a copy of a custom QuerySet’s methods instead of repeating the method twice in your custom Manager and QuerySet. E.g.:
class StudentProfile(models.Model):
...
objects = StudentQuerySet().as_manager()
Hope it helps!
I tried using the F expression, and I have no clue why, but it was decrementing by 3 instead of by 1. Maybe Django runs that code 3 times when it is called in the view.
I found a solution that accomplishes this without a function, in the view, it does exactly what I expect, a decrement of 1:
student_id = request.user.id
student_queryset = StudentProfile.objects.get(student_id=student_id)
student_queryset.remaining_lessons = student_queryset.remaining_lessons - 1
student_queryset.save()

Django change BooleanField value after DateField expires

I would like to be able to automatically set suspended to False (if is True, of course) when end_suspension_date passes by (and therefore if it exists).
models.py
class Profile(models.Model):
suspended = models.BooleanField(default=False)
start_suspension_date = models.DateField(null=True, blank=True)
end_suspension_date = models.DateField(null=True, blank=True)
# ... other fields
Is there any way to do this without third-party apps? I thought of defining a function inside the model (but I don't see much sense in doing so):
def end_suspension(self):
if date.today() >= self.end_suspension_date:
self.suspended = False
start_suspension_date = None
end_suspension_date = None
else:
# do nothing...
No, you will need something like celery to define a task that filters for end of suspension.
An alternative method I prefer is to replace the suspended field with a property, because having a field that stores "is the user suspended" and a field that stores "when is the user no longer suspended" are redundant because we know the current date.
A more idiomatic would be calling it is_suspended, so:
class Profile(models.Model):
...
#property
def is_suspended(self):
return date.today() < self.end_suspension_date
Then on login views permission checks etc just access profile.is_suspended.
Simple is better then complex :)
Aldi, beware of timezone. Rule of thumb: store UTC date instead of local date.
You can try it, like:
class Profile(models.Model):
start_suspension_date = models.DateField(null=True, blank=True)
end_suspension_date = models.DateField(null=True, blank=True)
# ... other fields
#property
def suspended(self):
return date.today() < self.end_suspension_date

Django Query Does not exist

I'm been trying to create an app that allows users to follow each other profile since yesterday and today and I haven't been successful so far.
I'm having trouble creating a following function that allows me to retrieve users from a particular user he follows.
Example . If John follows Diana . I want to able to retrieve the user called Diana and use it with my modules.
I'm really sorry if this doesn't make sense . I'm trying my hardest to explain my situation.
class Person(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
image = models.FileField(upload_to="images/",blank=True,null=True)
class Board(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
Most of these solutions gave me no query
This was one of the solutions I tried.
class UserLink(models.Model):
from_user = models.ForeignKey(User , related_name = "following_set")
to_user = models.ForeignKey(User , related_name = "follower_set")
date_added = models.DateTimeField(default = datetime.now)
def __unicode__(self):
return "%s is following %s" % (self.from_user.username,self.to_user.username)
def save(self,**kwargs):
if self.from_user == self.to_user:
raise ValueError("Cannot follow yourself ")
super(UserLink , self).save(**kwargs)
class Meta:
unique_together = (('to_user','from_user'),)
I tried to retrieve the users that a particular user followed and use it against my modules such as Person but it gave me an error No query exist.
def Follow(request,username=""):
if request.method == "POST":
username = request.POST.get('follow',False)
user = User.objects.get(username=username)
UserLink.objects.create(from_user=request.user,to_user=user)
return HttpResponseRedirect(reverse('world:Profile'))
return HttpResponseRedirect(reverse('world:Profile'))
I also tried this following function but it only followed himself and I changed self to User but it didn't allow me to put the person to follow
class UserProfile(models.Model):
user = models.OneToOneField(User)
follows = models.ManyToManyField('self', related_name='followed_by', symmetrical=False)
>>>from pet.models import *
>>>from django.contrib.auth.models import User
>>>user = User.objects.get(username='Peter')
>>>user1 = User.objects.get(username='Sarah')
>>>p = UserProfile.objects.filter(user=user,follows=user1)
>>>Error no field called follows
How can I create a following class that allows retrieve the people that they followed and use it with my modules such as Person?
Can someone help me . Thannk you community!
If I understand correctly, youu are on the right track with the many to many relationship. What you need is to modify your existing Person class to include this information.
Since information about who someone follows or is following is essentially information about that person and so you shouldn't really need to define a new class to implement that functionality.
I would suggest modifying your Person like so.
class Person(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
image = models.FileField(upload_to="images/",blank=True,null=True)
following = models.ManyToManyField('self', related_name='followers', symmetrical=False, blank=True, null=True)
What this line does is makes a many to many relationship between the class Person and its self.
Many to many relationships work a little different to other relationships and I suggest you read the Django documentation https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_many/.
But you should now be able to setup and access the relationship like this.
>>>john = Person.objects.get(name="John")
>>>diana = Person.objects.get(name="Diana")
>>>john.following.add(diana)//setup the many to many relationship
>>>john.save()
>>>john.following.all()
//This should return a queryset of Person objects which john is following.
//eg Diana
>>>diana.followers.all()
//This should return a queryset of Person objects which are following Diana.
//eg. John.
Easy, how awesome is Django!

Django checkout model

What is the best way to create a checkout system with Django that keeps track of the checkout / checkin history?
My models for inventory/models.py
from django.db import models
class Groups(models.Model):
name = models.CharField(max_length=200)
def __unicode__(self):
return self.name
class Inventory(models.Model):
name = models.CharField(max_length=200)
serial = models.CharField(max_length=200)
barcode = models.CharField(max_length=200)
active = models.BooleanField(verbose_name="Active (can be checked out if not out for repair)",blank=True,default=True)
repair = models.BooleanField(verbose_name="Out for repair?",blank=True)
group = models.ForeignKey(Groups)
def __unicode__(self):
return self.name
I am thinking I will need another model that will store the checkout / in information? I am guessing I will need to only obtain the last one so that I know if it is checked in or out? I want to keep a history of the items so that I can create a report with it.
How would I go about making it so I have a history of the items and if the items can be checked in or out?
Yes, it isn't totally clear from your question what a checkout/checkin is, but my guess is you want something like
class Checkout(models.Model)
item = models.ForeignKey(Inventory)
user = models.ForeignKey(User)
checked_out = models.DateTimeField()
checked_in = models.DateTimeField(null=True)
...
You would then create one of these objects each time an item was checked out, and then update it to set the checkin date when it was checked back in.
To find the current checkout (or determine if something is not checked out) you could do a query like:
try:
checkout = Checkout.objects.get(item=my_item, checked_in=None)
except Checkout.DoesNotExist:
#item isn't checked out
checkout = None