Fetch data from manytomany relationship along with onetomany - django

If we consider the three following models: clients, companies, contacts.
Clients can purchase goods from several companies and a company has many clients. Moreover, a company has several "contact" types (email, fax, phone,...).
class Clients(models.Model):
fname= models.CharField(max_length=45, null=False)
company= models.ManyToManyField(to='Companies', related_name='provider', blank=True)
class Companies(models.Model):
name= models.CharField(max_length=45, null=False)
class Contacts(models.Model):
contact= models.CharField(max_length=45)
type= models.IntegerField(choices=TYPE)
company= models.ForeignKey(to='Companies', related_name= 'company_contacts', on_delete=models.CASCADE)
For a given client, I would like to retrieve all the companies he purchased from along with all the company's details (stored in the model "Contacts"). In SQL, with an intermediate table (clients_companies), I will do:
SELECT cl.fname, cp.name, ct.contact, ct.type
FROM Clients cl
JOIN Clients_Companies cc ON cl.id = cc.clients_id
JOIN Companies cp ON cp.id = cc.companies_id
JOIN Contacts ct ON cp.id = ct.companies_id;
Which will give:
+---------+-----------+-----------------+--------+
| fname | name | contact | type |
+---------+-----------+-----------------+--------+
| Client1 | Company A | 333-555-1234 | phone |
| Client1 | Company A | email#email.com | email |
| Client1 | Company B | 202-555-0191 | phone |
| Client1 | Company B | 202-555-9999 | fax |
| Client1 | Company B | arg#arg.com | email |
+---------+-----------+-----------------+--------+
In django, I'd like something similar to:
data = [
{'Company A':[{'type':'phone', 'contact':'333-555-1234'},
{'type':'email', 'contact':'email#email.com'}],
'Company B':[{'type':'phone', 'contact':'202-555-0191'},
{'type':'fax', 'contact':'202-555-9999'},
{'type':'email', 'contact':'arg#arg.com'},],
}]
Any suggestion to solve this is welcome. Also, if you have sources to learn querying data using django ORM I will be really happy as I found really difficult to work the django way when coming from SQL.

Related

Get latest payment from related model in Django Admin

I'd like to output a table using "Premises" model in Django admin. In addition I'd like include the output in this table of an additional column, say "last utility payment". It is actually a column in a related table. There may be no payment in the database so admin should able to see either an empty cell or the date of payment.
I was able to write a DB query that displays the information I need. Its significant and worked part is given below:
SELECT jp.id,
jp.number apartment,
jp.building_number building,
jp.rent,
jp.arrears,
jpm.last_payment
FROM jasmin_premises jp
LEFT JOIN (
SELECT pm.premises_id,
max(pm.paid) last_payment
FROM jasmin_payment pm
GROUP BY pm.premises_id
) jpm ON jp.id = jpm.premises_id;
And the output is similar to the following:
id | apartment | building | rent | arrears | last_payment
--------------------------------------------------------------
170 | 1X | 6-A | 297.43 | 2.57, | NULL
72 | 2 | 4 | 289.66 | -678.38 | 2021-01-31
173 | 3Z | 7 | 432.86 | 515.72 | 2021-02-04
73 | 4 | 8-B | 292.25 | 515.44 | 2021-02-04
74 | 5 | 8-B | 112.42 | 3249.34 | NULL
75 | 6A | 122 | 328.48 | 386.23 | 2021-02-04
76 | 7 | 42 | 482.06 | 964.12 | 2021-01-31
77 | 8 | 1 | 433.71 | 867.42 | 2021-01-31
78 | 9C | 12 | 322.79 | 322.79 | 2021-02-04
79 | 10 | 122 | 324.22 | 0 | 2021-02-04
80 | 12 | 12 | 322.79 | 1232.46 | NULL
81 | 14 | 5-Z | 440.82 | 978.44 | 2021-02-04
And I'm using the following models (only a significant part):
class Premises(models.Model):
number = models.CharField(
blank=False,
null=False,
max_length=10)
building_number = models.CharField(
blank=False,
null=False,
max_length=3)
rent = models.DecimalField(
blank=False,
null=False,
max_digits=12,
decimal_places=2,
default=0.0)
area = models.DecimalField(
blank=False,
null=False,
max_digits=5,
decimal_places=2)
class Payment(models.Model):
paid = models.DateField(
blank=False,
null=False)
premises = models.ForeignKey(
Premises,
blank=False,
null=False,
on_delete=models.CASCADE,
related_name='payments',
db_index=True)
Is there a way to override admin.ModelAdmin.get_queryset (for example using annotations) to get an extra column like in my example above? Is there any other way to make a LEFT JOIN on a compound DB query using Django ORM?
to make this query in django you have to add the models.Manager() to the tables like this:
models.py
class Premises(models.Model):
# existent code
objects = models.Manager()
class Payment(models.Model):
# existent code
objects = models.Manager()
In the part of app you want to access this information
from .models import Premises, Payment
premises = Premises.objects.all()
data_to_display = []
for premise in premises:
payments = Payment.objects.filter(premises=premise).order_by('-paid')
if len(payments) == 0:
last_payment = "Null"
else:
last_payment = payments[0]
object_to_list = {
"id": premise.id,
"apartment": premise.number,
"building": premise.building_number,
"rent": premise.rent,
"arreaars": premise.area,
"last_payment": last_payment.paid
}
data_to_display.append(object_to_list)
The solution is to add an explicit subquery to a QuerySet using the Subquery expression. We'll need also to use OuterRef because a queryset in a Subquery needs to refer to a field from the outer query.
So let's create a subquery:
from django.db.models import OuterRef
payments = Payment.objects.filter(
premises=OuterRef('pk')
).order_by('-paid')
The next step is to pass payments subquery to a queryset:
from django.db.models import Subquery
# 'payments' here is from example above
premises = Premises.objects.annotate(
last_payment=Subquery(payments.values('paid')[:1])
)
Finally, lets see the used SQL to query objects rows the database:
print(premises.query)
(the output is formatted, only the significant part is shown)
SELECT "jasmin_premises"."id",
"jasmin_premises"."number",
"jasmin_premises"."building_number",
"jasmin_premises"."arrears",
"jasmin_premises"."rent",
(SELECT U0."paid"
FROM "jasmin_payment" U0
WHERE U0."premises_id" = "jasmin_premises"."id"
ORDER BY U0."paid" DESC
LIMIT 1) AS "last_payment"
FROM "jasmin_premises";
Now, after performing the tests, we can use this in our ModelAdmin:
from django.contrib import admin
from django.db.models import OuterRef, Subquery
from .models import Payment, Premises
#admin.register(Premises)
class PremisesAdmin(admin.ModelAdmin):
list_display = (
'number',
'building_number',
'rent',
'arrears',
'last_payment',
)
def get_queryset(self, request):
qs = super().get_queryset(request)
payments = Payment.objects.filter(
premises=OuterRef('pk')
).order_by('-paid')
qs = qs.annotate(
last_payment=Subquery(payments.values('paid')[:1]),
)
return qs
def last_payment(self, obj):
return obj.last_payment
last_payment.short_description = 'Last payment'
last_payment.admin_order_field = 'last_payment'
Well, this doesn't use JOINs, but this approach will force Django to execute a subquery.
Probably in some cases it may be possible to write an equivalent queryset that performs the same task more clearly or efficiently however, this is the best I have achieved so far.

Linking one table to each user in Django

I am working on a Django project where I need to link one table(model) to each user.
Assume MyTable_1 maps to user_1 and so on.
The primary key for MyTable will be a DateField which contains continuous dates from the time user signed-up.
MyTable_1 for User_1
|-----------|----------|-------------|-----------------|
| Date(PK) | food_ate | game_played | ran_today |
|-----------|----------|-------------|-----------------|
| 10/01/20 | rice | chess | Yes |
|-----------|----------|-------------|-----------------|
| 11/01/20 |sandwhich | tennis | No |
|-----------|----------|-------------|-----------------|
MyTable_2 for User_2
|-----------|----------|-------------|-----------------|
| Date(PK) | food_ate | game_played | ran_today |
|-----------|----------|-------------|-----------------|
| 16/03/19 | pizza | rugby | Yes |
|-----------|----------|-------------|-----------------|
| 17/03/19 | pasta | football | Yes |
|-----------|----------|-------------|-----------------|
And so on for every new user created. User logs in those information in MyTable.
How can I implement this? I am using PostgreSQL and have written custom User Model.
You really don't need seperate tables just seperate rows.
A ForeignKey relation will do the trick, something like this in your models.py:
# user model:
User(models.Model, ...):
first_name = models.CharField(...)
last_name = models.CharField(...)
...
# log model:
Log(models.Model):
user = models.ForeignKey(User, ...)
date = models.DateField(...)
food_ate = models.CharField(...)
game_played = models.CharField(...)
ran_today = models.CharField(...)
class Meta: unique_together = ('user', 'date',)
Then, elsewhere, you can access your users' logs like so:
user = User.objects.get(id='the_user_id')
logs = user.logs.all()

Django reading data from 2 models with foreignkey and make single list

I'm new to django. I've been coding with sql but django orm is hard for me to convert my knowledge of sql to orm models.
I've client model
class client(models.Model):
c_id = models.AutoField(primary_key=True)
name= models.TextField()
age=models.IntegerField()
and address model
class address(models.Model):
c_id = models.ForeignKey(client, on_delete=models.CASCADE)
addr = models.CharField(max_lenght=20)
city= models.CharField(max_lenght=20)
This is my table
---------------------------
| c_id|Name | age |
---------------------------
| 1 | John | 23 |
----------------------------
| 2 | Rose | 20 |
----------------------------
------------------------------
| c_id|addr | city |
------------------------------
| 1 | buspark | florida|
------------------------------
| 2 | homesquare| florida|
------------------------------
how to get allclient with address in list
Look at values() docs
The values() method takes optional positional arguments, *fields,
which specify field names to which the SELECT should be limited. If
you specify the fields, each dictionary will contain only the field
keys/values for the fields you specify. If you don’t specify the
fields, each dictionary will contain a key and value for every field
in the database table.
__ allows get related data, so in your case it could look like this
address.objects.values('c_id__c_id', 'c_id__name', 'c_id__age', 'addr', 'city')

Django view fetching data from two models in a single query

I have two tables in my database which are relevant for this problem:
exercise_state with following fields:
| id | intensity_level | progress | exercise_id | user_id | current_date | user_rating |
auth_user with following fields:
| id | password | last_login | is_superuser | username | first_name | last_name | email | is_staff | is_active | date_joined |
Right now I am fetching some data in my view as follows:
def get_specific_exercise_finish_count(request, exerciseId):
# Number of users who completed a specific exercise
specific_exercise_finish_count = Exercise_state.objects.filter(exercise_id=exerciseId, intensity_level=7).count()
data = {}
data['count'] = specific_exercise_finish_count
return JSONResponse(data)
Now I want to filter those results further for specific set of usernames i.e. usernames those starts with 'yg_' (I have two sets of usernames registered in my system one group starts with 'yg' and the other with 'yg_'). As username is not a field of exercise_state, I am not sure how to proceed.
How can I achieve this?
I solved it with following code of piece, see the extra part in filter statement:
def get_specific_exercise_finish_count_memoryGames(request, exerciseId):
# Number of users who completed a specific exercise
specific_exercise_finish_count_memoryGames = Exercise_state.objects.filter(exercise_id=exerciseId, intensity_level=7, user__username__startswith='yg_').count()
data = {}
data['count'] = specific_exercise_finish_count_memoryGames
return JSONResponse(data)

Django count distinct number of related model

Consider the following models:
class Publisher(models.Model):
name = models.CharField(max_length=300)
num_awards = models.IntegerField()
class Book(models.Model):
name = models.CharField(max_length=300)
pages = models.IntegerField()
publisher = models.ForeignKey(Publisher, related_name='related_books')
From a Publisher instance how can I get the number of book by distinct value on pages field? For example:
| name | pages | publisher |
|-----------|-------|-----------|
| Golden | 20 | 1 |
| Grey | 23 | 1 |
| Blue | 20 | 1 |
| Grotesque | 27 | 2 |
If I have publisher = Publisher.objects.get(id=1) how can I achieve something like this:
# equals to 2 [Golden, Grey]
publisher.related_books.all().distinct('pages').count()
You were close, you just need to restrict returned values, like so:
publisher.related_books.all().values('pages').distinct('pages').count()
This will just give you the number of different page lengths for a publisher, but not the associated books for each page length. To do that you'd probably need an extra query.
If you want reusable queries, you could do this:
class BookQuerySet(models.QuerySet):
def by_publisher(self, publisher):
return self.filter(publisher=publisher)
def distinct_number_of_pages(self):
return self.distinct(pages)
class Book(...):
...
objects = BookQuerySet.as_manager()
class Publisher(...):
#property
def number_of_page_lengths(self):
return Book.objects.by_publisher(self).distinct_number_of_pages().count()