1

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
  }
]
2
  • 1
    Not totally sure what you are trying to achieve but stuff like this becomes easier if you add a through-model (docs.djangoproject.com/en/2.1/ref/models/fields/…) to your ManyToManyField, then you can do queries on that relation as well... Commented Jan 30, 2019 at 13:54
  • @BernhardVallant yeah, this is an option. Thanks! Commented Jan 31, 2019 at 8:49

3 Answers 3

2
+25

To obtain that output, you could do:

data = Profile.objects.all().values('flats', 'id')
return Response(data=data)

in your DRF view.

Sign up to request clarification or add additional context in comments.

3 Comments

:). I have view class at 350 lines of code with filters, ordering, etc. and i can't do so simple as .values(). There are a lot of intermediate business logic. I really need something like .annotate() to add field for every object in queryset with correct flat or something like this.
I think this is a correct answer. No matter how complicated your queryset is you can always put .values('flats', 'id') at the end of that. Order of filter and values are interchangeable
I think in order to get a json like the OP is asking for, you should filter on flats, not getting all profiles objects since the relation is many2many. A more clean/elegant/direct approach would require remodeling. And perhaps that's the OP should be thinking about ...
0

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
  }
]

Comments

0

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

1 Comment

tgdn thank for answer. My problem is to make doubles in profiles queryset and annotate each of this doubles by different flat from m2m between profile and flat. Your code is about something else :).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.