I'm trying to construct a fairly complicated Django query and I'm not making much progress. I was hoping some wizard here could help me out?
I have the following models:
class Person(models.Model):
MALE = "M"
FEMALE = "F"
OTHER = "O"
UNKNOWN = "U"
GENDER_CHOICES = (
(MALE, "Male"),
(FEMALE, "Female"),
(UNKNOWN, "Other"),
)
firstName = models.CharField(max_length=200, null=True, db_column="firstname")
lastName = models.CharField(max_length=200, null=True, db_column="lastname")
gender = models.CharField(max_length=1, choices=GENDER_CHOICES, default=UNKNOWN, null=True)
dateOfBirth = models.DateField(null=True, db_column="dateofbirth")
dateInService = models.DateField(null=True, db_column="dateinservice")
photo = models.ImageField(upload_to='person_photos', null=True)
class SuccessionTerm(models.Model):
originalName = models.CharField(max_length=200, null=True, db_column="originalname")
description = models.CharField(max_length=200, blank=True, null=True)
score = models.IntegerField()
class Succession(model.Model):
position = models.ForeignKey(Position, to_field='positionId', db_column="position_id")
employee = models.ForeignKey(Employee, to_field='employeeId', db_column="employee_id")
term = models.ForeignKey(SuccessionTerm)
class Position(models.Model):
positionId = models.CharField(max_length=200, unique=True, db_column="positionid")
title = models.CharField(max_length=200, null=True)
# There cannot be a DB constraint, as that would make it impossible to add the first position.
dottedLine = models.ForeignKey("Position", to_field='positionId', related_name="Dotted Line",
null=True, db_constraint=False, db_column="dottedline_id")
solidLine = models.ForeignKey("Position", to_field='positionId', related_name="SolidLine",
null=True, db_constraint=False, db_column="solidline_id")
grade = models.ForeignKey(Grade)
businessUnit = models.ForeignKey(BusinessUnit, null=True, db_column="businessunit_id")
functionalArea = models.ForeignKey(FunctionalArea, db_column="functionalarea_id")
location = models.ForeignKey(Location, db_column="location_id")
class Employee(models.Model):
person = models.OneToOneField(Person, db_column="person_id")
fte = models.IntegerField(default=100)
dataSource = models.ForeignKey(DataSource, db_column="datasource_id")
talentStatus = models.ForeignKey(TalentStatus, db_column="talentstatus_id")
retentionRisk = models.ForeignKey(RetentionRisk, db_column="retentionrisk_id")
retentionRiskReason = models.ForeignKey(RetentionRiskReason, db_column="retentionriskreason_id")
performanceStatus = models.ForeignKey(PerformanceStatus, db_column="performancestatus_id")
potential = models.ForeignKey(Potential, db_column="potential_id")
mobility = models.ForeignKey(Mobility, db_column="mobility_id")
currency = models.ForeignKey(Currency, null=True, db_column="currency_id")
grade = models.ForeignKey(Grade, db_column="grade_id")
position = models.OneToOneField(Position, to_field='positionId', null=True,
blank=True, db_column="position_id")
employeeId = models.CharField(max_length=200, unique=True, db_column="employeeid")
dateInPosition = models.DateField(null=True, db_column="dateinposition")
Now, what I want is for each employee to get the position title, the person's name, and for each succession term (of which there are three) how many times the position of that employee is in the succession table, and the number of times each of these employees occurs in the successors table. Above all, I want to do all of this in a singe query (or more specifically, a single Django ORM statement), as I'm doing this in a paginated way, but I want to be able to order the result on any of these columns!
So far, I have this:
emps = Employee.objects.all()
.annotate(ls_st=Count('succession__term'))
.filter(succession__term__description="ShortTerm")
.order_by(ls_st)
.prefetch_related('person', 'position')[lower_limit:upper_limit]
This is only one of the succession terms, and I would like to extend it to all terms by adding more annotate calls.
My problem is that the filter call works on the entire query. I would like to only filter on the Count call.
I've tried doing something like Count(succession__term__description'="ShortTerm") but that doesn't work. Is there any other way to do this?
Thank you very much in advance,
Regards,
Linus
So what you want is a count of each different type of succession__term? That is pretty complex, and I don't think you can do this with the built in django orm right now. (unless you did a .extra() query)
In django 1.8, I believe you will be able to do it with the new Query Expressions (https://docs.djangoproject.com/en/dev/releases/1.8/#query-expressions). But of course 1.8 isn't released yet, so that doesn't help you.
In the meantime, you can use the very handy django-aggregate-if package. (https://github.com/henriquebastos/django-aggregate-if/, https://pypi.python.org/pypi/django-aggregate-if)
With django-aggregate-if, your query might look like this:
emps = Employee.objects.annotate(
ls_st=Count('succession__term', only=Q(succession__term__description="ShortTerm")),
ls_lt=Count('succession__term', only=Q(succession__term__description="LongTerm")), # whatever your other term descriptions are.
ls_ot=Count('succession__term', only=Q(succession__term__description="OtherTerm"))
)
.order_by('ls_st')
.prefetch_related('person', 'position')[lower_limit:upper_limit]
Disclaimer: I have never used django-aggregate-if, so I'm not entirely sure if this will work, but according the the README, it seems like it should.
Related
I have 3 models, CustomerPurchaseOrderDetail, Customer and Product model, if Customer1 buy a product for example, Meat. it will save in CustomerPurchaseOrderDetail and if that Customer1 add another Meat Product, instead of adding another record to the database it will simply just add quantity.
this is my views.py
def batchaddtocart(request):
userID = request.POST.get("userID")
client = Customer(id=userID)
vegetables_id = request.POST.get("id")
v = Product(id=vegetables_id)
price = request.POST.get("price")
discount = request.POST.get("discount_price")
insert, create = CustomerPurchaseOrderDetail.objects.get_or_create(
profile=client,
products=v,
unitprice=price,
quantity=1,
discounted_amount=discount,
discounted_unitprice=discount,
)
order_qs = CustomerPurchaseOrderDetail.objects.filter\
(
profile=client,
products=v,
unitprice=price,
quantity=1,
discounted_amount=discount,
discounted_unitprice=discount
)
for order in order_qs:
if order.profile == client and order.products == v:
insert.quantity += 1
print(insert.quantity)
insert.save()
insert.save()
this is my models.py
class CustomerPurchaseOrderDetail(models.Model):
profile = models.ForeignKey(Customer,
on_delete=models.SET_NULL, null=True, blank=True,
verbose_name="Client Account")
products = models.ForeignKey(Product,
on_delete=models.SET_NULL, null=True, blank=True,
verbose_name="Product")
quantity = models.IntegerField(max_length=500, null=True, blank=True, default=1)
class Product(models.Model):
product = models.CharField(max_length=500)
class Customer(models.Model):
user = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE)
firstname = models.CharField(max_length=500, blank=True)
lastname = models.CharField(max_length=500, blank=True)
contactNumber = models.CharField(max_length=500, blank=True)
email = models.CharField(max_length=500, blank=True)
I did not encounter an error but the functionality I wanted did not work. it does not add additional quantity even if the same product is added to the list purchased by Customer1.
the problem is in following lines client = Customer(id=userID) and v = Product(id=vegetables_id) every time your function is called you are creating new customer and product objects instead of using existing objects is your database. replace them with client,created = Customer.objects.get_or_create(id=userID) and same for product v,v_created = Product.objects.get_or_create(id=vegetables_id)
When you use get_or_create method, it will create a new entry whenever at least one parameter is different enough to did not match any registered value. So, if you pass the quantity parameter equals 1, it always will create a new entry when quantity is 2+, for instance.
You should filter first with only the "fixed" parameters and create a new entry if you get nothing. Otherwise, just increment quantity.
Something like this:
order = None
order_qs = CustomerPurchaseOrderDetail.objects.filter\
(
profile=client,
products=v,
unitprice=price,
discounted_amount=discount,
discounted_unitprice=discount
)
if not order_qs:
order = CustomerPurchaseOrderDetail.objects.create(
profile=client,
products=v,
unitprice=price,
quantity=1,
discounted_amount=discount,
discounted_unitprice=discount,
)
else:
for order in order_qs:
if order.profile == client and order.products == v:
# NOTE: Please, note if you need to check your other parameters too (unityprice, discounted....)
order.quantity += 1
print(order.quantity)
order.save()
Consider the following models:
Class ExamResults:
...
Marks = models.IntegerField(default=0, null=True, blank=True)
Course = models.CharField(max_length=255, null=True)
Academic_year = models.CharField(max_length=255, null=True)
Class ExamSetting:
...
GradeAThreshold = models.IntegerField(default=0, null=True, blank=True)
GradeBThreshold = models.IntegerField(default=0, null=True, blank=True)
...
Course = models.CharField(max_length=255, null=True)
Academic_year = models.CharField(max_length=255, null=True)
Now I have an API to search/get results from ExamResults for all students. The API works fine and I use Q filters to filters the results. For e.g.
...
year_contains = self.request.GET.get("year_contains", "")
if year_contains:
q_filt |= Q(Academic_year__icontains=year_contains)
queryset = queryset.filter(q_filt)
...
Now I have a requirement to filter the queryset for the following condition:
List of exam results where the Marks exceed GradeAthreshold
List of exam results where the Marks are less than GradeAthreshold and exceed GradeBThreshold and so on
What would be the best way of doing this? The ExamResults table and ExamSetting has two common fields which can narrow down the thresholds. For e.g. I use the below code in serializer to check if the result has Grade A or not:
setting = ExamSetting.objects.filter(Academic_year=obj.Academic_year, Course=obj.Course, is_active=True).first()
if obj.Marks >= setting.GradeAThreshold:
# Grade A
...
This does work and I do get the results with grades. Now how do I add something like this in the queryset so that I can filter the results for Grade A or B results?.
As you mentioned ExamSetting and ExamResult both have common fields; assuming proper data integrity, you can pivot based on one of those.
For example, if you want all exam results with grade A or above for a similar course:
setting = ExamSetting.objects.filter(
Academic_year=obj.Academic_year,
Course=obj.Course,
is_active=True
).first()
query = Q(Course=setting.Course) & Q(Mark__gte=setting.GradeAThreshold)
ExamResults.objects.filter(query)
So I currently have this django query. The first two statements are needed in order to obtain the 3rd statement. My question is if there is a way to only use the 3rd statements without using the first two statements.
#patient_name and quest are two strings
patientobj = modelPatient.objects.get(patient_name=patient_name)
questobj = modelInterviewQuestion.objects.get(question=quest)
answer = modelInterviewAnswer.objects.get(patients=patientobj, questions=questobj)
I know I could do something like this
answer = modelInterviewAnswer.objects.get(patients= modelPatient.objects.get(patient_name=patient_name), questions= modelInterviewQuestion.objects.get(question=quest))
but I was wondering if there is anything simpler ?
Here are the relationship between models
class modelPatient(models.Model):
patient_name = models.CharField(max_length=128, unique=False)
patient_sex = models.CharField(max_length=128, unique=False)
patient_image = models.ImageField(upload_to='images/',
class modelInterviewQuestion(models.Model):
question = models.CharField(max_length=1000, unique=True)
class modelInterviewAnswer(models.Model):
patients = models.ForeignKey(modelPatient)
questions = models.ForeignKey(modelInterviewQuestion)
patient_response = models.CharField(max_length=1000, unique=True)
Try out this.
answer = modelInterviewAnswer.objects.get(patients__patient_name=patient_name, questions__question=quest)
Please go through this documentation to know how to write query that span relationship.
I want to draw you attention at naming convention.
Don't prefix model name with model, for example modelPatient should be only Patient.
Don't need to write patient_<field_name> in model. It should be only <field_name>
For example your Paitent model should look like
class Patient(models.Model):
name = models.CharField(max_length=128, unique=False)
sex = models.CharField(max_length=128, unique=False)
image = models.ImageField(upload_to='images/')
Follow same instructions for other models too.
class InterviewQuestion(models.Model):
question = models.CharField(max_length=1000, unique=True)
class InterviewAnswer(models.Model):
patients = models.ForeignKey(modelPatient)
interview_questions = models.ForeignKey(modelInterviewQuestion)
patient_response = models.CharField(max_length=1000, unique=True)
So Your query will be.
answer = InterviewAnswer.objects.get(patients__name=patient_name, interview_questions__question=quest)
I'm trying to figure out how to get particular set of objects from database in Django efficiently.
I can do this using nested loops but I think that it's not a best idea.
I have models Language and UserProfile which has a property method verified_languages which returns a set of Language objects for this UserProfile.
What I want exactly is to create a static method of Language model called get_to_languages(language) which returns a set of all languages of all UserProfiles if these UserProfiles has language attribute in verified_languages property method.
So if there were there these Users -
1st. user:
name = 'Peter'
userprofile.verified_languages = ['english','german','arabic','french']
2nd. user:
name = 'Evgen'
userprofile.verified_languages = ['german','arabic','spanish']
3rd. user:
name = 'Anton'
userprofile.verified_languages = ['russian','arabic','italian']
And I call method get_to_languages(Languages.objects.get(name='german'))
It would return a set of Evgen's and Peter's languages because they know german.
Is it possible to do this using filter?
My old solution:
#staticmethod
def get_languages_to(language):
userprofiles = UserProfile.objects.all()
result = set()
for up in userprofiles:
if language in up.languages_verified:
result.update(up.languages_verified)
result.remove(language)
return list(result)
USERPROFILE:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='userprofile', help_text=_('Related user'))
date_of_birth = models.DateField(null=True, blank=True, help_text=_('Date of birth'))
telephone = models.CharField(max_length=40, null=True, blank=True, help_text=_('Your telephone number'))
IBAN = models.CharField(max_length=40, null=True, blank=True, help_text=_('Bank account unique number'))
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
MARITAL_STATUS_CHOICES = (
('single', 'Single'),
('married', 'Married'),
('separated', 'Separated'),
('divorced', 'Divorced'),
('widowed', 'Widowed'),
)
marital_status = models.CharField(max_length=40, choices=MARITAL_STATUS_CHOICES, null=True, blank=True)
HOW_DO_YOU_KNOW_ABOUT_US_CHOICES = (
('coincidence', u'It was coincidence'),
('relative_or_friends', 'From my relatives or friends'),
)
how_do_you_know_about_us = models.CharField(max_length=40, choices=HOW_DO_YOU_KNOW_ABOUT_US_CHOICES, null=True,
blank=True)
is_translator = models.BooleanField(default=False)
language_levels = models.ManyToManyField('LanguageLevel', blank=True, related_name='translators')
rating = models.IntegerField(default=0)
number_of_ratings = models.BigIntegerField(default=0)
#property
def languages(self):
"""
Returns: all languages of current user include not-verified ones
"""
return [x.language for x in self.language_levels.all()]
#property
def languages_verified(self):
"""
Returns: verified languages of current user
"""
return [x.language for x in self.language_levels.exclude(level__name='unknown')]
def passive_skill(self, language):
"""
True if user has at least passive skill (lowest level) of the language
Args:
language: Language object
Returns: Bool
"""
if language in self.languages_verified:
return True
return False
Well, I think it could be done this way: first, get all LanguageLevel that contains current language and their level_name is not unknown, then get all userprofiles from the result LanguageLevel. In the end, get all languages linked to these userprofiles(apparently it's untested, but please try it out):
language_levels = LanguageLevel.objects.filter(language=language) \
.exclude(level__name='unknown')
userprofiles = UserProfile.objects.filter(language_levels__in=language_levels)
all_languages = profiles.values_list('language_levels__language',
flat=True).distinct()
Note that the __in is very inefficient but that's if you don't have huge amount of records it should be OK. Just pay attention to the performance and see if that goes well.
I'm making a program that helps log missions in a game. In each of these missions I would like to be able to select a number of astronauts that will go along with it out of the astronauts table. This is fine when I only need one, but how could I approach multiple foreign keys in a field?
I currently use a 'binary' string that specifies which astronauts are to be associated with the mission (1 refers to Jeb, but not Bill, Bob, or Val and 0001 means only Val), with the first digit specifying the astronaut with id 1 and so forth. This works, but it feels quite clunky.
Here's the model.py for the two tables in question.
class astronauts(models.Model):
name = models.CharField(max_length=200)
adddate = models.IntegerField(default=0)
experience = models.IntegerField(default=0)
career = models.CharField(max_length=9, blank=True, null=True)
alive = models.BooleanField(default=True)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Kerbals"
class missions(models.Model):
# mission details
programid = models.ForeignKey(programs, on_delete=models.SET("Unknown"))
missionid = models.IntegerField(default=0)
status = models.ForeignKey(
missionstatuses, on_delete=models.SET("Unknown"))
plan = models.CharField(max_length=1000)
# launch
launchdate = models.IntegerField(default=0)
crewmembers = models.IntegerField(default=0)
# recovery
summary = models.CharField(max_length=1000, blank=True)
recdate = models.IntegerField(default=0)
def __str__(self):
return str(self.programid) + '-' + str(self.missionid)
class Meta:
verbose_name_plural = "Missions"
I saw a post about an 'intermediate linking table' to store the crew list but that also isn't ideal.
Thanks!
This is the use case for Django's ManyToManyField. Change the appropriate field on the missions:
class missions(models.Model):
crewmembers = models.ManyToManyField('astronauts')
You can access this from the Astronaut model side like so:
jeb = astronaut.objects.get(name='Jebediah Kerman')
crewed_missions = jeb.missions_set.all()
Or from the mission side like so:
mission = missions.objects.order_by('?')[0]
crew = mission.crewmembers.all()
This creates another table in the database, in case that is somehow a problem for you.