Django annotate several same objects in QuerySet by different related object - django

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

Related

How to rearrange priority field for a django model?

I have a Model with a priority field of type postitive integer. This field is unique and allows me to manage the priority of objects.
For example, I want the most important object to have priority one, the second most important to have priority two, etc...
Example:
[
{ "name": "object82",
"priority": 1
}
{ "name": "object54",
"priority": 2
}
{ "name": "object12",
"priority": 3
}
]
class MyObject(models.Model):
name = models.CharField(_("name"), max_length=255)
priority = models.PositiveSmallIntegerField(_("priority"), unique=True)
I want to override the object serializer so that if I add a new object with an existing priority, it unpacks the existing objects. (same thing for the path of an existing object)
For example if I take the example above and add:
{ "name": "object22",
"priority": 2
}
I want the following result:
[
{ "name": "object82",
"priority": 1 // the priority didn't changed
}
{ "name": "object22", // my new object
"priority": 2
}
{ "name": "object54",
"priority": 3 // the priority had changed
}
{ "name": "object12", // the priority had changed
"priority": 4
}
]
I think I have to check first if an object with the same priority exists in the database or not.
If not => I save as is
If yes, I have to change the priority of some objects before add the new object.
How to do this ?
Maybe something like:
class MyObjectSerializer(serializers.ModelSerializer):
class Meta:
model = MyObject
fields = '__all__'
def update(self, instance, validated_data):
target_priority = validated_data.get('priority')
if MyObject.objects.filter(target_priority).exists():
existing_priorities = MyObject.objects.filter(priority__gte=target_priority)
for existing_priority in existing_priorities:
existing_priority.priority += 1
existing_priority.save(update_fields=['priority'])
instance.priority = target_priority
instance.save(update_fields=['priority'])
I was facing a similar problem, and what I have done is that I have a model form and I'm doing the validation in clean function
def clean(self):
cleaned_data = super().clean()
priority = cleaned_data.get('priority')
task = Task.objects.filter(priority__exact=priority)
while task.exists():
prev_task_id = task[0].id
task.update(priority=priority+1)
priority += 1
task = Task.objects.filter(priority__exact=priority).exclude(pk=prev_task_id)
return cleaned_data
I have used the prev_task_id variable for excluding the model that is just got updated. For e.g. let's say we have data
{
title: 'first one',
priority: 3
},
{
title: 'second one',
priority: 4
}
So now if I got priority 3 and after updating it we will have two tasks with priority 4 so we have to exclude the previous task i.e. 'first one'. We have to only update the second task in next iteration
PS:- This code is written assuming that in the database no duplicate priority exists.

Django Rest Framework: Get unique list of values from nested structure

I want to be able to return a list of strings from a deeply nested structure of data. In this scenario, I have a API that manages a chain of bookstores with many locations in different regions.
Currently, I have an API endpoint that takes a region's ID and returns a nested JSON structure of details about the region, the individual bookstores, and the books that can be found in each store.
{
"region": [
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
},
{
"book": {
"name": "Baz"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Baz"
}
},
{
"book": {
"name": "Qux"
}
}
]
}
]
}
My models look like the following. I am aware these models don't make the most sense for this contrived example, but it does reflect my real world code:
class Book(TimeStampedModel):
name = models.CharField(default="", max_length=512)
class Bookstore(TimeStampedModel):
value = models.CharField(default="", max_length=1024)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
class Region(TimeStampedModel):
stores = models.ManyToManyField(Bookstore)
class BookstoreChain(TimeStampedModel):
regions = models.ManyToManyField(Region)
The serializers I created for the above response look like:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
class BookstoreSerializer(serializers.ModelSerializer):
books = BookSerializer()
class Meta:
model = Bookstore
fields = "__all__"
class RegionSerializer(serializers.ModelSerializer):
stores = BookstoreSerializer(many=True)
class Meta:
model = Region
fields = "__all__"
class BookstoreChainSerializer(serializers.ModelSerializer):
regions = RegionSerializer(many=True)
class Meta:
model = BookstoreChain
fields = "__all__"
I'm not sure what my view or serializer for this solution need to look like. I'm more familiar with writing raw SQL or using an ORM/Linq to get a set of results.
While the above response is certainty useful, what I really want is an API endpoint to return a unique list of book names that can be found in a given region (Foo, Bar, Baz, Qux). I would hope my response to look like:
{
"books": [
"Foo",
"Bar",
"Baz",
"Qux"
]
}
My feeble attempt so far has a urls.py with the following path:
path("api/regions/<int:pk>/uniqueBooks/", views.UniqueBooksForRegionView.as_view(), name="uniqueBooksForRegion")
My views.py looks like:
class UniqueBooksForRegionView(generics.RetrieveAPIView):
queryset = Regions.objects.all()
serializer_class = ???
So you start from region you have to get the stores, so you can filter the books in the stores, here is a solution which will work.
Note:
Avoid using .get() in *APIView because it will trigger an error if the request does not have the ID, you can use get_object_or_404(), but then you cannot log your error in Sentry.
To get an element from an *APIView, use filter().
import logging as L
class UniqueBooksForRegionView(generics.RetrieveAPIView):
lookup_field = 'pk'
def get(self, *args, **kwargs)
regions = Region.objects.filter(pk=self.kwargs[self.lookup_field])
if regions.exists():
region = regions.first()
stores_qs = region.stores.all()
books_qs = Book.objects.filter(store__in=stores_qs).distinct()
# use your book serializer
serializer = BookSerializer(books_qs, many=True)
return Response(serializer.data, HTTP_200_OK)
else:
L.error(f'Region with id {self.kwargs[self.lookup_field]} not found.')
return Response({'detail':f'Region with id {self.kwargs[self.lookup_field]} not found.'}, HTTP_404_NOT_FOUND)
Note
Here is the flow, the code may need some tweaks, but I hope it helps you understand the flow

How to stop Graphql + Django-Filters to return All Objects when Filter String is Empty?

Using:
Django 3.x [ Django-Filters 2.2.0, graphene-django 2.8.0, graphql-relay 2.0.1 ]
Vue 2.x [ Vue-Apollo ]
I have a simple Birds Django-Model with Fields like name, habitat and applied different Filters on these Fields like icontains or iexact. My Goal was to apply a simple search field in my Frontend (Vue). So far it works, but whenever this Filter Value is empty or has blanks (see Example 3), Graphql returns all Objects.
My first approach was on the FrontEnd and to use some kind of logic on my input value, like when String is Empty/blank send isnull=true . But then i thought that Django should handle this in the first place.
I guess this Issue relates to my filters (see Django < relay_schema.py) or in other words do i have to apply some kind of logic on these Filters?
In the moment i try to customize some filterset_class but it feels like this would maybe too much, maybe i missed something? So i ask here if maybe someone has some hints, therefore my question is:
How to stop Graphql + Django-Filters to return All Objects when Filter String is Empty?
GraphiQL IDE
Example 1
query {birdsNodeFilter (name_Iexact: "finch") {
edges {
node {
id
name
}
}
}
}
Returns
{
"data": {
"birdsNodeFilter": {
"edges": [
{
"node": {
"id": "QmlyZHNOb2RlOjE=",
"name": "Finch",
"habitat": "Europe"
}
}
]
}
}
}
Fine for me!
Example 2
query {birdsNodeFilter (name_Iexact: "Unicorns") {
edges {
node {
id
name
habitat
}
}
}
}
Returns
{
"data": {
"birdsNodeFilter": {
"edges": []
}
}
}
No Unicorns there - good
Example 3
query {birdsNodeFilter (name_Iexact: "") {
edges {
node {
id
name
}
}
}
}
Return
{
"data": {
"birdsNodeFilter": {
"edges": [
{
"node": {
"id": "QmlyZHNOb2RlOjE=",
"name": "Finch",
"habitat": "Europe"
}
},
{
"node": {
"id": "QmlyZHNOb2RlOjI=",
"name": "Bald Eagle",
"habitat": "USA"
}
},
<...And so on...>
Not fine for me!
Django
relay_schema.py
class BirdsNode(DjangoObjectType):
class Meta:
model = Birds
filter_fields = {
'id': ['iexact'],
'name': ['iexact', 'icontains', 'istartswith', 'isnull'],
'habitat': ['iexact', 'icontains', 'istartswith'],
}
interfaces = (relay.Node, )
class BirdQuery(graphene.ObjectType):
birdConNode = relay.Node.Field(BirdsNode)
birdsNodeFilter = DjangoFilterConnectionField(BirdsNode)
This is my Solution which worked in GraphiQL and in my Frontend VUE.
I added a logic to the Birds2Query with def resolve_all_birds2 for each Filter (for testing purpose not on all Filter ) .
Besides that i also added a ExtendedConnection for counting.
Note: i changed the class names from my former Question.
Update: This Solution works on the python side. But the apollo client provides also the Apollo manager - also known as Dollar Apollo - there you can also use the this.$apollo.queries.tags.skip property as an static or dynamic solution to start and stop queries.
relay_schema.py
class ExtendedConnection(Connection):
class Meta:
abstract = True
total_count = Int()
edge_count = Int()
name_check = ""
def resolve_total_count(root, info, **kwargs):
return root.length
def resolve_edge_count(root, info, **kwargs):
return len(root.edges)
class Birds2Node(DjangoObjectType):
class Meta:
model = Birds
filter_fields = {
'id': ['exact', 'icontains'],
'name': ['exact', 'icontains', 'istartswith', 'iendswith'],
}
interfaces = (relay.Node, )
connection_class = ExtendedConnection
class Birds2Query(ObjectType):
birds2 = relay.Node.Field(Birds2Node)
all_birds2 = DjangoFilterConnectionField(Birds2Node)
def resolve_all_birds2(self, info, **kwargs):
# Filtering for Empty/ Blank Values in Filter.Key.Value before returning queryset
if 'name__icontains' in kwargs:
nameIcon = kwargs['name__icontains']
nameIconBool = bool(nameIcon.strip()) # if blanks turns False
if nameIconBool == False: # has blanks
return Birds.objects.filter(name=None)
pass
if 'name__istartswith' in kwargs:
nameIsta = kwargs['name__istartswith']
nameIstaBool = bool(nameIsta.strip()) # if blanks turns False
if nameIstaBool == False: # has blanks
return Birds.objects.filter(name=None)
pass
return
GraphiQL
Blockquote
Example 1
query {allBirds2 (name_Icontains:""){
totalCount
edgeCount
edges {
node {
id
name
habitat
}
}
}
}
Stopped to return all Objects while Filter is blank.
{
"data": {
"allBirds2": {
"totalCount": 0,
"edgeCount": 0,
"edges": []
}
}
}
Example 2 with blanks and one letter
query {allBirds2 (name_Icontains:" f "){
totalCount
edgeCount
edges {
node {
id
name
habitat
}
}
}
}
return - exactly what i wanted
{
"data": {
"allBirds2": {
"totalCount": 1,
"edgeCount": 1,
"edges": [
{
"node": {
"id": "QmlyZHMyTm9kZTox",
"name": "Finch",
"habitat": "Europe"
}
}
]
}
}
}

Get Hourly based sum in django

I have a model which contains two fields DateTimeField and an IntegerField.
I want to sum the total integers on the basis of hour.
class ModelA(models.Model):
date_time = models.DateTimeField()
relative_change = models.IntegerField()
Now i want to query the modelA so that it will return the total sum of relative_change for each hour,What's the good way to do it?
I checked this stackoverflow question but i am not clear what is 'hour' there.
The Output should look like
[
{
"time":"9-10",
"relative_change":"20"
},
{
"time":"10-11",
"relative_change":"40"
},
{
"time":"11-12",
"relative_change":12
}
]
This should do it:
from django.db.models import Sum
queryset = (
ModelA
.objects
.values('date_time__hour')
.annotate(relative_change_sum=Sum('relative_change')
)
You should expect the following result:
<QuerySet [{
'date_time__hour': 9,
'relative_change_sum': 20,
}, {
'date_time__hour': 10,
'relative_change_sum': 40,
}]
Here's more details about the 'hour' lookup:
https://docs.djangoproject.com/en/2.2/ref/models/querysets/#hour

Django graphql graphene removing redundant queries from return value

The last few days I have read up on so much graphql that I can't see the trees from the forest anymore.
The results that this person got in the beginning is almost exactly what I want (his problem, not his solution), but it seems that a lot of the code is deprecated and I can't seem to get it working: link
I have a bunch of containers that I return. All the containers have the amounts for every day in them. I only want to return the amounts of a certain day.
At the moment, I do return these results (day), but all the other results (days) also return with a Null value.
Current behavior:
{
"data": {
"listProductcontainers": [
{
"id": "1",
"productid": {
"productid": "CBG2",
"processedstockamountsSet": [
{
"timeStampID": {
"id": "2"
},
"id": "77745",
"prodName": {
"productid": "CBG2"
}
},
{
"timeStampID": null, <--------
"id": "89645",
"prodName": {
"productid": "CBG2"
}
},
{
"timeStampID": null, <--------
"id": "89848",
"prodName": {
"productid": "CBG2"
}
},
// ...
Requested behavior: (All values with 'Null' should not return)
{
"data": {
"listProductcontainers": [
{
"id": "1",
"productid": {
"productid": "CBG2",
"processedstockamountsSet": [
{
"timeStampID": {
"id": "2"
}
My query that I am running looks like this:
query{
listProductcontainers{
id
productid{
productid
processedstockamountsSet{
timeStampID(id:2){
id
}
id
prodName{
productid
}
}
}
}
}
Here are the relevant code for the results:
class TimeStampType(DjangoObjectType):
class Meta:
model = TimeStamp
class ProcessedStockAmountsType(DjangoObjectType):
timeStampID = graphene.Field(TimeStampType, id=graphene.Int())
class Meta:
model = ProcessedStockAmounts
def resolve_timeStampID(self, info, **kwargs):
id = kwargs.get('id')
if self.timeStampID.id == id:
return self.timeStampID
class ProductcontainersType(DjangoObjectType):
class Meta:
model = Productcontainers
class ProductlistType(DjangoObjectType):
class Meta:
model = Productlist
class Query(graphene.ObjectType):
list_productcontainers = graphene.List(ProductcontainersType)
def resolve_list_productcontainers(self, context, **kwargs):
return Productcontainers.objects.all()
I have read almost everything in graphene by now, but if you even have a link that mirrors what I want to do I would really appreciate it.
My final option is to make two calls where I get all the container ids, and another call where I get all the amounts (with container id) for a certain date, and with 2 for loops I just add the amounts into the corresponding container... :(