Different results on query changing Q object order - django

I have a problem when making a queryset using Q objects. I'm getting different results depending on how i order some Q conditions. I will simplify my models a little so as to describe my problem in a clean way.
class D(models.Model):
one_attr = models.BooleanField()
s_attr = models.ManyToManyField(S, through='DRelatedToS')
d_to_p = models.ForeignKey(P)
class S(models.Model):
other_attr = models.BooleanField()
s_to_p = models.ForeignKey(P)
class DRelatedToS(models.Model):
to_s = models.ForeignKey(S)
to_d = models.ForeignKey(D)
date = models.DateField()
class P(models.Model):
the_last_attr = models.PositiveIntegerField()
Summary of the relations:
D <-- DRelatedToS --> S --> P
| ^
| |
-------->------->------>----^
With these models and relations, i get two different results depending on how i arrange Q conditions:
First query, that gives one result
D.objects.filter(
Q(one_attr=True, s_attr__s_to_p__the_last_attr=5)
|
Q(one_attr=False, d_to_p__the_last_attr=10)
)
Second query, giving another result, different from first query
D.objects.filter(
Q(one_attr=False, d_to_p__the_last_attr=10)
|
Q(one_attr=True, s_attr__s_to_p__the_last_attr=5)
)
My question is: why is this happening? Is there any problem on how i am doing my query?
When i watch the SQL statements derived from these queries, i get two different statements: one that make a LEFT OUTER JOIN and a lot of INNER JOINs and the second that makes all INNER JOINs. The one that actually return what i want is the one that makes a LEFT OUTER JOIN. This make me feel that all my queries can return bad results depending on how i arrange its conditions. Is this a bug or i am doing anything (or everything) wrong?

Different order should return equal result in your example.
Nevertheless I tested your code (using corrections I made in the question's codes) but can't generate the error you describe. Maybe you had introduced other errors and missed when simplified the code, could you post the sample data you used? (see my data below).
First your sample code is buggy I had edited your question to suggest following corrections to fix problems, simplify and improve it for tests, but I don't see the changes updated so I repeat here:
Correction 1: Model changes in a diff format:
3,4c6,10
< s_attr = models.ManyToMany(S, through='DRelatedToS')
< d_to_p = models.ForeignKey(P)
---
> s_attr = models.ManyToManyField('S', through='DRelatedToS')
> d_to_p = models.ForeignKey('P')
>
> def __unicode__(self):
> return 'D:(%s,%s,%s)' % (self.id, self.one_attr, self.d_to_p.the_last_attr)
8,9c14
< other_attr = models.BooleanField()
< s_to_p = models.ForeignKey(P)
---
> s_to_p = models.ForeignKey('P')
13d17
< to_p = models.ForeignKey(P)
15c19
< date = models.DateField()
---
> to_s = models.ForeignKey(S)
19a24
>
So after apply corrections models look like this:
class D(models.Model):
one_attr = models.BooleanField()
s_attr = models.ManyToManyField('S', through='DRelatedToS')
d_to_p = models.ForeignKey('P')
def __unicode__(self):
return 'D:(%s,%s,%s)' % (self.id, self.one_attr, self.d_to_p.the_last_attr)
class S(models.Model):
s_to_p = models.ForeignKey('P')
class DRelatedToS(models.Model):
to_d = models.ForeignKey(D)
to_s = models.ForeignKey(S)
class P(models.Model):
the_last_attr = models.PositiveIntegerField()
Correction 2: Your lookup fields in queries are wrong (Fixed in answer).
Following is what I did:
Create project and app named testso:
django-admin.py startproject marianobianchi
cd marianobianchi
python manage.py startapp testso
Add your models & adjust project settings (database settings, add testso to INSTALLED_APPS)
Add sample data:
mkdir testso/fixtures
cat > testso/fixtures/initial_data.json
[
{"pk": 1, "model": "testso.d", "fields": {"one_attr": true, "d_to_p": 3}},
{"pk": 2, "model": "testso.d", "fields": {"one_attr": true, "d_to_p": 4}},
{"pk": 3, "model": "testso.d", "fields": {"one_attr": false, "d_to_p": 5}},
{"pk": 4, "model": "testso.d", "fields": {"one_attr": false, "d_to_p": 5}},
{"pk": 1, "model": "testso.s", "fields": {"s_to_p": 1}},
{"pk": 2, "model": "testso.s", "fields": {"s_to_p": 2}},
{"pk": 3, "model": "testso.s", "fields": {"s_to_p": 3}},
{"pk": 1, "model": "testso.drelatedtos", "fields": {"to_d": 2, "to_s": 1}},
{"pk": 2, "model": "testso.drelatedtos", "fields": {"to_d": 1, "to_s": 2}},
{"pk": 3, "model": "testso.drelatedtos", "fields": {"to_d": 1, "to_s": 3}},
{"pk": 1, "model": "testso.p", "fields": {"the_last_attr": 5}},
{"pk": 2, "model": "testso.p", "fields": {"the_last_attr": 5}},
{"pk": 3, "model": "testso.p", "fields": {"the_last_attr": 3}},
{"pk": 4, "model": "testso.p", "fields": {"the_last_attr": 4}},
{"pk": 5, "model": "testso.p", "fields": {"the_last_attr": 10}}
]
python manage.py syncdb
python manage.py shell
In the shell:
>>> from testso.models import *
>>> from django.db.models import Q
>>> D.objects.filter(Q(one_attr=True, s_attr__s_to_p__the_last_attr=5) | Q(one_attr=False, d_to_p__the_last_attr=10))
[<D: D:(1,True,3)>, <D: D:(2,True,4)>, <D: D:(3,False,10)>, <D: D:(4,False,10)>]
>>> D.objects.filter(Q(one_attr=False, d_to_p__the_last_attr=10) | Q(one_attr=True, s_attr__s_to_p__the_last_attr=5))
[<D: D:(1,True,3)>, <D: D:(2,True,4)>, <D: D:(3,False,10)>, <D: D:(4,False,10)>]
Same result! ...as expected.

I cannot answer your question directly, but there may be another way of doing what you want to do which may yield more consistent results:
subset_a = D.objects.filter(one_attr=False, d_to_p__the_last_attr=10)
subset_b = D.objects.filter(one_attr=True, s_attr__p__the_last_attr=5)
union_set = subset_a | subset_b
union_set = union_set.distinct()
The | operator on two querysets does a union, and the distinct will make sure you do not get dupe rows. I would be interested in hearing whether the arrangement here matters as well.

Django seems to have a weak ORM implementation. I would suggest using "filter" or some other way to query the database and see if you get the same inconsistencies.
Or perhaps you should look for alternative ORM implementations, like peewee.

Related

Filter query to get chat messages among two friends?

I am trying to create a chat api, i have structured the messages model to look like this
class ChatMessage(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name="user")
sender = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name="sender")
reciever = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name="reciever")
message = models.CharField(max_length=10000000000)
is_read = models.BooleanField(default=False)
date = models.DateTimeField(auto_now_add=True)
Now i am trying to write the API view to get all messages that belongs only to me and the person that i am chatting with.
Let me elaborate: Let's say Destiny is texting Sammy, there would definitely be alot of messages that have been sent between me (Destiny) and the user (Sammy), so i want to get all that text.
This is the view that i have written to do this
class GetMessages(generics.ListAPIView):
serializer_class = MessageSerializer
def get_queryset(self):
sender_id = self.kwargs['sender_id']
reciever_id = self.kwargs['reciever_id']
messages = ChatMessage.objects.filter(sender=sender_id, reciever=reciever_id)
return messages
urls
path("api/get-messages/<sender_id>/<reciever_id>/", views.GetMessages.as_view()),
This is what this view returns
## url - http://127.0.0.1:8000/api/get-messages/2/1/
[
{
"id": 2,
"sender": 2,
"reciever": 1,
"message": "Hey Destiny",
"is_read": false
}
]
if i swap the sender_id and reciever_id, this is the response i get
# url - http://127.0.0.1:8000/api/get-messages/1/2/
[
{
"id": 1,
"sender": 1,
"reciever": 2,
"message": "Hello Sammy",
"is_read": false
},
{
"id": 3,
"sender": 1,
"reciever": 2,
"message": "This is another message for you sammy",
"is_read": false
}
]
what i want is this, when i pass in the id of the sender (1) and id of the reciever (2) in the url, i want to get a response like this, which is all the conversaion that this two folks have had.
[
{
"id": 1,
"sender": 1,
"reciever": 2,
"message": "Hello Sammy",
"is_read": false
},
{
"id": 2,
"sender": 2,
"reciever": 1,
"message": "Hey Destiny",
"is_read": false
},
{
"id": 3,
"sender": 1,
"reciever": 2,
"message": "This is another message for you sammy",
"is_read": false
}
]
Some images
You can use a Q object here:
from django.db.models import Q
then
messages = ChatMessage.objects.filter(
Q(sender=sender_id, reciever=reciever_id) |
Q(sender=reciever_id, reciever=sender_id)
)
As a side note, it's spelled receiver.
You can also use this method,
messages = ChatMessage.objects.filter(
sender__in=[sender_id, reciever_id], reciever__in=[reciever_id, sender_id]
)

How to format json response in django?

I am retrieving data from multiple tables in Django.
my current response is :
{
"status": 0,
"message": "Client details retrived successfully...!!!",
"results": [
{
"id": 11,
"client_id": "CL15657917080578748000",
"client_name": "Pruthvi Katkar",
"client_pan_no": "RGBB004A11",
"client_adhar_no": "12312312313",
"legal_entity_name": "ABC",
"credit_period": "6 months",
"client_tin_no": 4564565,
"client_email_id": "abc#gmail.com",
"head_office_name": "ABC",
"office_name": "asd234",
"office_email_id": "zxc#gmail.com",
"office_contact": "022-27547119",
"gst_number": "CGST786876876",
"office_country": "India",
"office_state": "gujrat",
"office_district": "vadodara",
"office_taluka": "kachh",
"office_city": "vadodara",
"office_street": "New rode 21",
"office_pincode": 2344445,
"contact_person_name": "prasad",
"contact_person_designation": "DM",
"contact_person_number": "456754655",
"contact_person_email": "asd#gmail.com",
"contact_person_mobile": "5675545654",
"created_at": "2019-08-14T14:08:28.057Z",
"created_by": "Prathamseh",
"updated_at": "2019-08-14T14:08:28.057Z",
"updated_by": "prasad",
"is_deleted": false
},
{
"id": 11,
"user_id": "CL15657917080578748000",
"bank_details_id": "BL15657917080778611000",
"bank_name": "Pruthvi",
"branch": "vashi",
"ifsc_code": "BOI786988",
"account_number": 56756765765765,
"account_name": "Pruthvi",
"is_deleted": false
},
{
"id": 10,
"document_details_id": "DL15657917080808598000",
"user_id": "CL15657917080578748000",
"document_type": "Pruthvi ID",
"document": "www.sendgrid.com/pan",
"is_deleted": false
}
]
}
Expected Response :
I am getting the queryset form db in models.py and i am sending it to the views.py and i am iterating over the dict but not getting the expected response.
views.py
#csrf_exempt
def get_client_details(request):
try:
# Initialising lists for storing results
result = []
temp_array = []
# Getting data from request body
client_master_dict = json.loads(request.body)
# Response from get client data
records = ClientDetails.get_client_data(client_master_dict)
# Create response object
# Iterating over the records object for getting data
for i in range(len(records)):
# Converting the querysets objects to json array format
record_result_list = list(records[i].values())
# If multiple records are present
if(len(record_result_list) > 1):
for j in range(len(record_result_list)):
user_info = record_result_list[j]
temp_array.append(user_info)
result.append(temp_array)
temp_array=[]
# For single record
else:
result.append(record_result_list[0])
# Success
returnObject = {
"status" : messages.SUCCESS,
"message" : messages.CLIENT_RETRIVE_SUCCESS,
"results" : result
}
return JsonResponse(returnObject,safe=False)
I think the issue might be in my inner for loop, can anyone help me out with this, is there any way to iterate over the nested JSON object.
Models.py
#classmethod
def get_client_data(cls, client_master_dict):
try:
response_list = []
client_id = client_master_dict['client_id']
client_details = cls.objects.filter(client_id = client_id,is_deleted = False)
bank_details = BankDetails.objects.filter(user_id = client_id,is_deleted = False)
document_details = DocumentDetails.objects.filter(user_id = client_id,is_deleted = False)
response_list.append(client_details)
response_list.append(bank_details)
response_list.append(document_details)
return response_list
except(Exception) as error:
print("Error in get_client_data",error)
return False
Here i'm fetching data from 3 tables and adding it into list.
After printing the data on console i am getting :
[{'id': 11, 'client_id': 'CL15657917080578748000', 'client_name': 'Pruthvi Katkar', 'client_pan_no': 'RGBB004A11', 'client_adhar_no': '12312312313', 'legal_entity_name': 'ABC', 'credit_period': '6 months', 'client_tin_no': 4564565, 'client_email_id': 'abc#gmail.com', 'head_office_name': 'ABC', 'office_name': 'asd234', 'office_email_id': 'zxc#gmail.com', 'office_contact': '022-27547119', 'gst_number': 'CGST786876876', 'office_country': 'India', 'office_state': 'gujrat', 'office_district': 'vadodara', 'office_taluka': 'kachh', 'office_city': 'vadodara', 'office_street': 'New rode 21', 'office_pincode': 2344445, 'contact_person_name': 'prasad', 'contact_person_designation': 'DM', 'contact_person_number': '456754655', 'contact_person_email': 'asd#gmail.com', 'contact_person_mobile': '5675545654', 'created_at': datetime.datetime(2019, 8, 14, 14, 8, 28, 57874, tzinfo=<UTC>), 'created_by': 'Prathamseh', 'updated_at': datetime.datetime(2019, 8, 14, 14, 8, 28, 57874, tzinfo=<UTC>), 'updated_by': 'prasad', 'is_deleted': False}]
[{'id': 11, 'user_id': 'CL15657917080578748000', 'bank_details_id': 'BL15657917080778611000', 'bank_name': 'Pruthvi', 'branch': 'vashi', 'ifsc_code': 'BOI786988', 'account_number': 56756765765765, 'account_name': 'Pruthvi', 'is_deleted': False}]
[{'id': 10, 'document_details_id': 'DL15657917080808598000', 'user_id': 'CL15657917080578748000', 'document_type': 'Pruthvi ID', 'document': 'www.sendgrid.com/pan', 'is_deleted': False}]
Did you check the output of record_result_list? You can outright tell their if it's recovering the data in the format you requested. Try the printing to screen method to debug.
As far as I cam see, the expected output and the hierarchy of results for bank details are not matching. I don't know how you are handling the hierarchy. Are you directly taking it from JSON as the hierarchy? Or are you just taking the data and creating hierarchy in the expected output?

Django annotate several same objects in QuerySet by different related object

I got:
# models
class Building(models.Model):
...
class Flat(models.Model):
building = models.ForeignKey(Building)
class Profile(models.Model):
flats = models.ManyToManyField(Flat)
# logic
building = Building.objects.create()
flat_1 = Flat.objects.create(building=building)
flat_2 = Flat.objects.create(building=building)
profile = Profile.objects.create()
profile.flats.add(flat_1)
profile.flats.add(flat_2)
profiles = Profile.objects.filter(flats__building=building)
I got in profiles 2 same profiles. How i can annotate each of them by different flat like this: profiles.first().flat == flat_1 and profiles.last().flat == flat_2?
Maybe Subquery() but how?
UPD I need this in some DRF list view. Output in JSON must be something like:
[
{
"profile_id": 1,
"flat_id": 2
},
{
"profile_id": 1,
"flat_id": 3
}
]
To obtain that output, you could do:
data = Profile.objects.all().values('flats', 'id')
return Response(data=data)
in your DRF view.
You don't have to profile instances ...
I wrote the code for your exact needs at the end, but first wrote a couple of things that might be of interest.
In your code sample, you've created only one profile, I'm sure you are not getting 2 instances of Profile that are equals but only one.
The thing is if you have a QuerySet with only one entry, then:
profiles.first() == profiles.last() # True
since profile.first() and profiles.last() are the same instance.
You should try creating 2 Profile instances:
building = Building.objects.create()
flat_1 = Flat.objects.create(building=building)
flat_2 = Flat.objects.create(building=building)
profile_1 = Profile.objects.create() # You coud/should use bulk_create here.
profile_2 = Profile.objects.create()
profile_1.flats.add(flat_1)
profile_2.flats.add(flat_2)
Then
profiles = Profile.objects.filter(flats__building=building)
will return two different profile objects.
On the other hand, obtaining the JSON like you want ...
Following the example, you posted, filter flats by profile and get the values (this also works if you have more that one profile).
Flat.objects.filter(profile=profile_1).values('profile__id', 'id')
This will return something like ("id" stands for flats ids):
[
{
"profile__id": 1,
"id": 1
},
{
"profile__id": 1,
"id": 3
}
]
If you do not filter by profile (and you have more than one) you could get something like:
[
{
"profile__id": 1,
"id": 1
},
{
"profile__id": 2,
"id": 3
},
{
"profile__id": 2,
"id": 4
},
...
]
Annotating to get the EXACT json you want:
Filter as shown previously annotate, and get desired values:
Flat.objects.filter(profile=profile_1).annotate(
flat_id=F('id')
).annotate(
profile_id=F('profile__id')
).values(
'profile_id', 'flat_id'
)
will give exactly what you want:
[
{
"profile_id": 1,
"flat_id": 2
},
{
"profile_id": 1,
"flat_id": 3
}
]
You can do that with the right serializer and the right annotation:
The serializer:
class FlatSerializer(serializers.ModelSerializer):
class Meta:
model = Flat
fields = ('flat_id', 'building_id')
flat_id = serializers.CharField(read_only=True)
Then I would simply query Flats rather than profiles and serialize:
flats = Flat.objects \
.annotate(flat_id=F('id')) \
.filter(building=building)
serialized = FlatSerializer(flats, many=True)
print(serialized.data) # [ { flat_id: 1, building_id: 1 }, { flat_id: 2, building_id: 1 } ]
Let me know if that works for you

change json structure in Django

I've never had to change json structure before using Django models, so I may have made a completely stupid question.
I have some data in my database and want to parse it in a json format.
My view.py (a bit simplified) is:
def get_json(request):
pos = Pos.objects.filter(id=1).values('lat', 'lon','id')
return JsonResponse ({'position': list(pos)})
As a result I get this json object:
{ "position": [{"lat": "21", "id": 1, "lon": "21"}, {"lat": "22", "id": 1, "lon": "22"}, {"lat": "23", "id": 1, "lon": "23"}]}
In order to reduce the volume of unusefull data, I need to get a json structure like this:
{"id":"1", "position":{"lats":[21,22,23], "longs":[21,22,22]} }
I would be gratefull if sombody could help me
You'll need to process the data; you can do whatever you like within the view function.
pos = Pos.objects.filter(id=1).values('lat', 'lon','id')
data = {"lats": [], "longs": []}
for p in pos:
data["lats"].append(p['lat'])
data["longs"].append(p['long'])
return JsonResponse({'position': data})

django model serialization

I want to serialize models so:
class Schedule(models.Model):
Title = models.CharField(max_length=512)
class ScheduleEvent1(models.Model):
ScheduleContent = models.ForeignKey(Schedule)
Description = models.TextField()
class ScheduleEvent2(models.Model):
ScheduleContent = models.ForeignKey(Schedule)
AnotherField = models.TextField()
ShortDescription = models.TextField()
make smth like serializers.serialize('json', Schedule.objects.all())
The result likes
[
{
"pk": 2,
"model": "Schedule",
"fields": {
"Title": "Some Title",
"ScheduleEvent1": [
{
"pk": 19,
"model": "ScheduleEvent1",
"fields": {
"Description": "Some Descr",
}
},
{
"pk": 20,
"model": "ScheduleEvent1",
"fields": {
"Description": "Some Descr2222",
}
}
],
"ScheduleEvent2": [
{
"pk": 15,
"model": "ScheduleEvent2",
"fields": {
"AnotherField": "Some text",
"ShortDescription" : "Some text ...",
}
}
]
}
}
]
In general. I have entity tree. And I need serialize this tree from root.
tnx for help.
http://docs.djangoproject.com/en/1.3/topics/serialization/#dependencies-during-serialization
Covers the essence of this. The resulting file is not precisely what's required, since the objects won't be nested. However it is what Django produces by default and it's what Django will use for deserialization.
If you use Piston you can easily define a Handler which will produce nested JSON, but it won't be precisely as shown because Piston's JSON emitter isn't precisely in the Django format.
Getting to precisely what's hoped-for leads to extending the Django serializer to produce a nested JSON object as the natural key. See http://code.djangoproject.com/browser/django/trunk/django/core/serializers/python.py. Lines 47-59
Or, you can define a natural_key method which uses Django's 'django.core.serializers.json .Serializer` to emit the JSON serialization of the object instead of some other natural key value. This is a little weird semantically, but it may do what you want.