Converting raw SQL to Django QuerySet API - django

Model
class Person(models.Model):
GENDER = (
('M','Male'),
('F','Female'),
)
first_name = models.CharField("First Name", max_length=100)
last_name = models.CharField("Last Name",max_length=100)
middle_name = models.CharField("Middle Name", max_length=100, blank=True)
suffix_name = models.ManyToManyField(SuffixName, verbose_name="Suffix Name",null=True, blank=True)
gender = models.CharField(max_length=1, choices=GENDER)
department = models.ManyToManyField(Department, verbose_name="Department",null=True, blank=True)
def __unicode__(self):
return (u'%s') % (self.last_name.upper() + ', ' + self.first_name + ' ' + self.middle_name)
class Department(models.Model):
department_desc = models.CharField('Department', max_length=100,unique=True)
department_acronym = models.CharField('Department Acronym', max_length=20,blank=True,help_text="Add acronym if any, not required")
location = models.CharField('Location',max_length=100,blank=True)
localnumber = models.CharField('Local Number',max_length=30,blank=True)
active = models.BooleanField('Active',default=True)
def __unicode__(self):
return self.department_desc
How can I convert this raw SQL to a Django query?
SELECT pp.first_name, pd.department_desc, pd.localnumber
FROM person_person as pp
INNER JOIN person_person_department as ppd on pp.id = ppd.person_id
INNER JOIN person_department as pd on pd.id = ppd.department_id

Use QuerySet.values or QuerySet.values_list:
Person.objects.values('first_name', 'department__department_desc', 'department__localnumber')
# -> Returns a ValuesQuerySet that yields dictionaries when iterated.
Person.objects.values_list('first_name', 'department__department_desc', 'department__localnumber')
# -> Returns a ValuesQuerySet that yields tuples when iterated.

Fetching on Person will automatically fetch the related objects, so your query is:
people = Person.objects.all()
Then, you can do the following:
for person in people:
print('Person: {0}'.format(person.first_name))
for dept in person.department_set.all():
print('Desc: {0}, Local Number: {1}'.format(dept.department_desc,
dept.localnumber))
If you want to restrict the returned values, use values_list.

Related

Django Model method test

I have model like this:
class Users(models.Model):
pin = models.CharField(max_length=16, unique=True)
name = models.CharField(max_length=64)
surname = models.CharField(max_length=64, blank=True, null=True)
patronymic = models.CharField(max_length=64, blank=True, null=True)
def get_full_name(self):
name = self.name
surname = self.surname
if self.surname is None:
surname = ''
if self.name is None:
name = ''
return str(name) + ' ' + str(surname)
I want to write a test case for get_full_name method.
Currently I wrote something like this:
class TestUsersModel(TestCase):
def setUp(self):
self.data1 = Users.objects.create(name='Bob', pin='1')
self.data2 = Users.objects.create(surname='Alex', pin='2')
def test_users_model_entry(self):
data = self.data1
self.assertTrue(isinstance(data, Users))
def test_users_full_name_only_name(self):
data = self.data1.get_full_name()
self.assertEquals(data,'Bob ')
def test_users_full_name_only_surname(self):
data = self.data2.get_full_name()
self.assertEquals(data,' Alex')
but when I check with coverage it says me that I have to write test case for this:
What I am missing here?
#sevdimali - Firstly, it is a bad idea to name a field as name and not first_name when you have the other field as surname. Also, if you decide to use first_name then the other field should be last_name. Consistency matters.
class Users(models.Model):
pin = models.CharField(max_length=16, unique=True)
name = models.CharField(max_length=64,null=True)#or else you cannot test #object creation without name
surname = models.CharField(max_length=64, blank=True, null=True)
patronymic = models.CharField(max_length=64, blank=True, null=True)
Now, to test your get_full_name(). How about we do something like this?
def setUp(self):
self.user = Users.objects.create(name='test', surname='world',pin='1234')
self.user_without_surname = Users.objects.create(name='test',pin='12345')
self.user_without_name=Users.objects.create(surname='world',pin='123456')
def test_get_full_name_with_valid_name_and_surname(self):
expected = str(self.user.name) + ' ' + str(self.user.surname)
actual = 'test world'
self.assertEquals(expected,actual)
def test_get_full_name_with_empty_surname(self):
expected = str(self.user.name) + ' ' +''
actual = 'test '
self.assertEquals(expected,actual)
def test_get_full_name_with_empty_name(self):
expected = '' + ' ' +str(self.user.surname)
actual = ' world'
self.assertEquals(expected,actual)
Key things to refactor-
IMO, the Model name should not be plural
Name doesn't make sense. Be explicit. first_name,middle_name,last_name if needed
Think about null columns and the consequences they might have. You don't want to have a user without even the first_name, last_name, email, and other essential fields.

Count the leave days and display them in django admin model

I am learning django or a month now. I created a model called "Leave" to mark employees leave. Then I created a model called "Salarie".In this I need to create a field like "Total_Leave" which will show an employees leave count in a month.( In january 2020 how many leave a particular employee took) ( If i mention the "Employee_Name" as "Samantha" in salary model, it need to show samantas' leave in the particular month,year)
I tried to do it like this and tried some coding but nothing worked.
#property
def Total_Leave(self):
return self.
Can anyone explain me how to do that please?
Models.py
class Leave(models.Model):
Leave_ID = models.CharField(max_length=5, primary_key=True,default=" ")
Employee_Name = models.ForeignKey('Employee_Detail',models.DO_NOTHING)
Dept_Name = models.ForeignKey('Department', models.DO_NOTHING)
l = (
("Paid","Paid"),("Non-Paid","Non-Paid")
)
Leave_Type = models.CharField(max_length=10, choices= l, default="Non-Paid")
m = (
("January","January"),("February","February"),("March","March"),("April","April"),("May","May"),("June","June"),("July","July"),("August","August"),("September","September"),("October","October"),("November","November"),("December","december")
)
Month = models.CharField(max_length=10, choices= m)
Year = models.IntegerField(max_length=4)
Start_Date = models.DateField()
End_Date = models.DateField(null=True, blank = True)
Reason = models.CharField(max_length=200)
s = (
("Accepted","Accepted"),("Pending","Pending"),("Canceled","Canceled")
)
Status = models.CharField(max_length=10, choices= s, default="Pending")
def __str__(self):
return str(self.Employee_Name)
class Salarie(models.Model):
Salarie_ID = models.CharField(max_length=5,primary_key=True,default=" ")
Employee_Name = models.ForeignKey('Employee_Detail', models.DO_NOTHING)
Dept_Name = models.ForeignKey('Department', models.DO_NOTHING)
Basic_Salary = models.IntegerField(default=0)
Extra_Hours = models.IntegerField(default=0)
#property
def Extra_Payment(self):
return self.Extra_Hours * 350
Bonus = models.IntegerField(default=0)
#property
def Total_Payment(self):
return self.Extra_Payment + self.Basic_Salary + self.Bonus
m = (
("January","January"),("February","February"),("March","March"),("April","April"),("May","May"),("June","June"),("July","July"),("August","August"),("September","September"),("October","October"),("November","November"),("December","december")
)
Month = models.CharField(max_length=10, choices= m)
Year = models.IntegerField(max_length=4)
Status = models.BooleanField()
Paid_Date = models.DateField(null=True,blank=True)
def __str__(self):
return str(self.Employee_Name)
class Employee_Detail(models.Model):
Employee_ID = models.CharField(primary_key=True, max_length=6)
Employee_Name = models.CharField(unique=True, max_length=30)
Primary_Phone = models.IntegerField(unique=True, max_length=10)
p = (
("Manager","Manager"),("Supervisor","Supervisor"),("Employee","Employee")
)
Position = models.CharField(max_length=15, choices= p, default="Employee")
Address = models.TextField(max_length=500)
Email = models.EmailField(max_length=50, unique=True)
def __str__(self):
return str(self.Employee_Name)
You can do this by using annotate or aggregate
If you want leave count for all the employees then
from django.db.models import Count
employees = Employee_Detail.objects.filter(leave__Status='Accepted').annotate(leave_count=Count('leave'))
you can access this by <employee Obj>.leave_count
If you want leave count for all employee order_by Month then
employees = Employee_Detail.objects.filter(leave__Status='Accepted').order_by('leave__Month').annotate(leave_count=Count('leave')).values('leave_Month','leave_count')
And last you want leave count for a particular employee for a particular month than in your employee model write #property function like this
#property
def get_leave_count(self):
leaves = Employee_Detail.objects.filter(id=self.id,leave__Status='Accepted').aggregate(leave_count=Count('leave'))
return leaves['leave_count']
for month and year wise provide month and year and then
month = 'January' #For example
leaves = Employee_Detail.objects.filter(id=employee_id,leave__Month=month,leave__Status='Accepted').aggregate(leave_count=Count('leave'))
#{'leave_count':3}
year = '2020' #For example
leaves = Employee_Detail.objects.filter(id=employee_id,leave__Year=year,leave__Status='Accepted').aggregate(leave_count=Count('leave'))
#{'leave_count':13}
I tried something like this, maybe of some use.
def total_leave(emp_name,month,year):
leaves = Leave.objects.filter(Emp_name= emp_name,Month=month,Year=year)
Count=0
for leave in leaves:
if leaves.status== "Accepted":
Count +=1
return Count
Like if you only need to get leave count for the year, just use year as a parameter for function.

Optimization of querys in django eliminating querys duplicates

I'm trying to get the number of products that have each category, but each category is in turn parent of other categories, so I want to know how many children have that category and their daughter categories, I have simplified the query to the maximum in the following way, but in the django debug I keep showing that I have 66 querys duplicates.
How can I eliminate these duplications?
With the first line of views.py, he managed to get the number of products in a category, but the problem is essentially to tell him to return me from the category and his daughters.
models.py
class Categoria(models.Model):
nombre = models.CharField(max_length=200)
slug = models.SlugField(max_length=100)
padre = models.ForeignKey('self', blank=True, null=True,
related_name='cat_padre')
pub_date = models.DateTimeField('date published',
auto_now_add=True)
upd_date = models.DateTimeField('date updated', auto_now=True)
def __str__(self):
return self.nombre + ' ' + self.pais.iso
class Producto(models.Model):
nombre = models.CharField(max_length=200)
slug = models.SlugField(max_length=100)
categoria = models.ForeignKey(Categoria)
views.py
cats = Categoria.objects.annotate(num_productos=Count('producto')).filter(pais__iso=pais, padre__isnull=True).order_by('-num_productos')
for c in cats:
num_p = Producto.objects.filter(categoria__padre=c).count()
c.num_productos += num_p
contexto = {
'categorias_padre': cats,
}
return render(request, 'web/pruebitas/product.html', contexto)
Django debug:
SELECT COUNT(*) AS "__count" FROM "web_producto" INNER JOIN "web_categoria" ON ("web_producto"."categoria_id" = "web_categoria"."id") WHERE "web_categoria"."padre_id" = '790'
Duplicated 62 times.
Conexión: default
/home/luis/PycharmProjects/lco_web/web/middleware.py in __call__(29)
response = self.get_response(request)
/home/luis/PycharmProjects/lco_web/web/views.py in index(11)
return categoria(request, '', '/')
/home/luis/PycharmProjects/lco_web/web/views.py in categoria(170)
'categorias': categorias(pais, categoria.id if categoria else 0),
/home/luis/PycharmProjects/lco_web/web/views.py in categorias(29)
num_p = Producto.objects.filter(categoria__padre=c).count()
You could achieve this by using reverse lookup to child categories using cat_padre and Count with distinct=True.
cats = Categoria.objects.annotate(
num_productos=Count('cat_padre__producto', distinct=True) +
Count('producto', distinct=True)).filter(
pais__iso=pais, padre__isnull=True).order_by('-num_productos')
P.S. I have tested that on fake data so please compare what you have got earlier with what returns this query.

how to filter values_list in django

class Office(models.Model):
person = models.ForeignKey(Person, verbose_name="Person")
department = models.ForeignKey(Department, verbose_name="Department")
office_desc = models.CharField('Office', max_length=100,unique=True)
office_acronym = models.CharField('Office Acronym', max_length=20,blank=True,help_text="Add acronym if any, not required")
location = models.CharField('Location',max_length=100,blank=True)
trunkline = models.CharField('Trunk Line',max_length=30,blank=True)
directline = models.CharField('Direct Line',max_length=30,blank=True)
localnumber = models.CharField('Local Number',max_length=30,blank=True)
telefax = models.CharField('Telefax',max_length=30,blank=True)
active = models.BooleanField('Active',default=True)
class Department(models.Model):
department_desc = models.CharField('Department', max_length=100,unique=True)
department_acronym = models.CharField('Department Acronym', max_length=20,blank=True,help_text="Add acronym if any, not required")
active = models.BooleanField('Active',default=True)
class Person(models.Model):
GENDER = (
('M','Male'),
('F','Female'),
)
first_name = models.CharField("First Name", max_length=100)
last_name = models.CharField("Last Name",max_length=100)
middle_name = models.CharField("Middle Name", max_length=100, blank=True)
salutation = models.ForeignKey(Salutation, verbose_name="Salutation", null=True, blank=True) #
suffix_name = models.ManyToManyField(SuffixName, verbose_name="Suffix Name",null=True, blank=True) #
job_title = models.ManyToManyField(JobTitle, verbose_name="Job Title",null=True, blank=True) #
gender = models.CharField(max_length=1, choices=GENDER,default='Male')
birthdate = models.DateField('Birthdate',null=True,blank=True)
image = models.ImageField('Image',upload_to='persons',blank=True)
email = models.EmailField('Email',blank=True)
street = models.CharField('Street',max_length=100, blank=True)
brgy = models.CharField('Barangay',max_length=100, blank=True)
town_city = models.CharField('Town/City',max_length=100, blank=True)
zip_code = models.IntegerField('ZIP Code',null=True, blank=True)
department = models.ManyToManyField(Department, verbose_name="Department",null=True, blank=True) #
office = models.ManyToManyField(Office, verbose_name="Office", null=True, blank=True) #
sql_query
select pd.department_desc, pp.last_name, o.office_desc from person_person as pp
INNER JOIN person_person_department as ppd on pp.id = ppd.person_id
INNER JOIN person_department as pd on pd.id = ppd.id
INNER JOIN person_office as o on o.department_id = pd.id
where pd.department_desc = 'Executive'
views code:
per = Person.objects
qry_name = per.values_list('salutation__salutation_desc','first_name','middle_name','last_name', 'office__office_desc', 'office__location','office__localnumber','office__trunkline','office__directline','office__telefax').filter(department__department_desc='Executive')
Result: query result includes person with different department
Expected result: only person with Executive department
If I query directly from the database I get the correct result but when I translate the query into django code it's returning different queryset. Query returns ok if certain person has a single office but if a person has multiple office thats where the inconsistency starts .Query returns list with different description other than what was specified. Am I doing the translation of the query to python right?
A closer representation of the sql query would be:
from django.db.models import F
qry_name = Person.objects.filter(
department__department_desc='Executive',
office__department=F('department')
).values_list(<..FIELDS..>)
Let know if this works.

How do I populate related Django models when using custom SQL in a custom manager?

I have some things I need to use custom SQL for. Just to start, I don't want to get into the discussion of why we're using custom SQL. That's old and irrelevant here.
Anyway, with this custom SQL, I'm populating the model and attempting to populate a related/sub model. That all seems to go fine, until that related model is accessed within a view or template. If I do that, django initiates another SQL query, which is totally unnecessary as I already got that data out with the first query.
Here are example, trimmed up models:
class Preferredname(models.Model):
preferredname_id = models.CharField(primary_key=True, max_length=1)
name = models.CharField(max_length=255)
class PersonCustom(models.Manager):
def getSpecial(self):
from django.db import connection, transaction
curse = DictCursor(connection.cursor())
curse.execute("SELECT * FROM person WHERE special = 't'")
result_list = []
for row in curse.fetchall():
i = self.model(
firstname = row['firstname'],
middlename = row['middlename'],
nickname = row['nickname'],
lastname = row['lastname'],
suffix = row['suffix'],
)
i.preferredname = Preferredname()
i.preferredname.preferredname_id = row['preferredname_id']
result_list.append(i)
return result_list
class Person(models.Model):
person_id = models.IntegerField(primary_key=True)
firstname = models.CharField(max_length=255, blank=True)
middlename = models.CharField(max_length=255, blank=True)
lastname = models.CharField(max_length=255, blank=True)
nickname = models.CharField(max_length=255, blank=True)
suffix = models.CharField(max_length=255, blank=True)
preferredname = models.ForeignKey(Preferredname)
objects = PersonCustom()
def get_preferred_name(self):
try:
if self.preferredname.preferredname_id == 'N':
pref = self.nickname
elif self.preferredname.preferredname_id == 'M':
pref = self.middlename
else:
pref = self.firstname
except ObjectDoesNotExist:
if self._preferred_name == 'N':
pref = self.nickname
elif self._preferred_name == 'M':
pref = self.middlename
else:
pref = self.firstname
name = "%s %s %s" % (pref, self.lastname, self.suffix)
return name.strip()
def set_preferred_name(self, val):
self._preferred_name = val
preferred_name = property(get_preferred_name, set_preferred_name)
As an example:
>>> p = Person.objects.getSpecial()
>>> p[0].preferred_name
Since the get_preferred_name() method accesses self.preferredname.preferredname_id(which in theory, should already be populated) it makes another query for preferredname.
Am I off base here, or should this work as I intend? If I'm off here, is there a way to populate related models from within a custom manager?
Rather than assigning your object to i.preferredname, try setting it to i._preferredname_cache, which is the internal object Django uses to cache foreign key lookups. That should work.