2

I'm trying to create a intermediate model with the created field recording the time of following relationship.

The models is as following:

class Profile(models.Model):
    user = models.OneToOneField(User, related_name='profile')
    realname = models.CharField(max_length=20, verbose_name='真實姓名', blank=True)
    nickname = models.CharField(max_length=20, verbose_name='暱稱/顯示名稱')

## the intermediate model is connected through following field  ##
    followings = models.ManyToManyField('self', through='FollowShip',
                                        related_name='followers', symmetrical=False,
                                        through_fields=('profile_from', 'profile_to'))

    total_followers = models.IntegerField(default=0)

    def __str__(self):
        return 'Profile of User:{}'.format(self.user.username)


class FollowShip(models.Model):
    profile_from = models.ForeignKey(Profile, related_name='follow_from_set')
    profile_to = models.ForeignKey(Profile, related_name='follow_to_set')
    created = models.DateField(auto_now_add=True, db_index=True)

    def __str__(self):
        return "{} follows {}".format(self.profile_from, self.profile_to)

    class Meta:
        unique_together = ('profile_from', 'profile_to')

It works fine. However, when I tried to access the QuerySet order by created field of FollowShip model. The error occurs.

In manage.py shell:

>>> user.profile.followings.order_by('followship__created')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/query.py", line 232, in __repr__
    data = list(self[:REPR_OUTPUT_SIZE + 1])
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/query.py", line 256, in __iter__
    self._fetch_all()
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/query.py", line 1087, in _fetch_all
    self._result_cache = list(self.iterator())
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/query.py", line 54, in __iter__
    results = compiler.execute_sql()
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 824, in execute_sql
    sql, params = self.as_sql()
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 369, in as_sql
    extra_select, order_by, group_by = self.pre_sql_setup()
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 47, in pre_sql_setup
    order_by = self.get_order_by()
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 293, in get_order_by
    field, self.query.get_meta(), default_order=asc))
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 553, in find_ordering_name
    field, targets, alias, joins, path, opts = self._setup_joins(pieces, opts, alias)
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 586, in _setup_joins
    pieces, opts, alias)
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/sql/query.py", line 1402, in setup_joins
    names, opts, allow_many, fail_on_missing=True)
  File "/Users/young/Desktop/env/strayvoice/lib/python3.5/site-packages/django/db/models/sql/query.py", line 1327, in names_to_path
    "Choices are: %s" % (name, ", ".join(available)))
django.core.exceptions.FieldError: Cannot resolve keyword 'followship' into field. Choices are: follow_from_set, follow_to_set, followers, followings, id, nickname, realname, total_followers, user, user_id

It looks like the intermediate model was not set correctly. So that I cannot find followship while query. However, I can access both sides well. For examples:

>>> user.profile.followings.all()
`<QuerySet [<Profile: Profile of User:222222>, <Profile: Profile of User:333333>]>`
>>> user.profile.followers.all()
`<QuerySet [<Profile: Profile of User:222222>]>`

Have no clues what's go wrong.

JianWei
  • 121
  • 2
  • 6
  • The same error message. It looks like the m2m relationship point to 'self' with intermediate model acting different from pointing to different model. – JianWei Jan 19 '17 at 15:31

1 Answers1

1

Use p.follow_from_set.order_by('created').

user.profile.followings means the profile who the user.profile has followed.
user.profile.followers means the profile who have followed the user.profile.
So both the user.profile.followers and user.profile.followings will return a queryset of Profile, they can only be ordered by the fields of Profile, such as user, realname, nickname.

If you want to find the profile which has been followed by use.profile and order_by created, you can use:

result = []
for profile_id in user.profile.follow_from_set.order_by('created').values('profile_to'):
    result.append(Profile.objects.get(id=profile_id))

If there are many following, maybe you can optimize the sql by this:

profile_ids = user.profile.follow_from_set.order_by('created').values('profile_to')
profiles = Profile.objects.filter(id__in=profile_ids)

FInally, reorder the profiles with the order of profile_ids.

result = []
result_dict = {}
for profile in profiles:
    result_dict[profile.id] = profile
for id in profile_ids:
    result.append(result_dict[id])
return result

Maybe there is many better way to solve the problem. Can any one make a better resolution?

ramwin
  • 5,803
  • 3
  • 27
  • 29
  • See the last part of [django document](https://docs.djangoproject.com/en/1.10/topics/db/models/#intermediary-manytomany). I supposed I can query like `>>> Person.objects.filter( ... group__name='The Beatles', ... membership__date_joined__gt=date(1961,1,1))` as the document said. – JianWei Jan 19 '17 at 16:40
  • Your way works. But to do so, I must add the hits to database several times, the cost is huge. – JianWei Jan 19 '17 at 16:44
  • The queryset `Profile.objects.filter(followers__nickname__contains='')` is like what you recommended. Here you may want something like `Profile.objects.filter(follower_from_set__created__lt=datetime.date(2016,1,1))` or `Profile.objects.filter(followers__created__lt=datetime.date(2016,1,1))`(neither of the two worked) – ramwin Jan 19 '17 at 16:50
  • It seems the only way to access the intermediary model is like what you said through `follow_from_set` or `follow_to_set`. – JianWei Jan 19 '17 at 16:57
  • Using the second way, it is acceptable. The sql is `'SELECT "myuser_followship"."profile_to_id" FROM "myuser_followship" WHERE "myuser_followship"."profile_from_id" = 1 ORDER BY "myuser_followship"."created" ASC LIMIT 21'` and `'SELECT "myuser_profile"."id", "myuser_profile"."user_id", "myuser_profile"."realname", "myuser_profile"."nickname", "myuser_profile"."total_followers" FROM "myuser_profile" WHERE "myuser_profile"."id" IN (SELECT U0."profile_to_id" FROM "myuser_followship" U0 WHERE U0."profile_from_id" = 1) LIMIT 21'`. I'm little confused what the `LIMIT 21` stand for. – ramwin Jan 19 '17 at 16:57
  • So i can order profile which user is following by `created` field this way: `user.profile.followings.order_by('follow_from_set__created')`. – JianWei Jan 19 '17 at 16:59
  • Got it, thank you. The sql shows like this: `SELECT "myuser_profile"."id", "myuser_profile"."user_id", "myuser_profile"."realname", "myuser_profile"."nickname", "myuser_profile"."total_followers" FROM "myuser_profile" INNER JOIN "myuser_followship" ON ("myuser_profile"."id" = "myuser_followship"."profile_to_id") WHERE "myuser_followship"."profile_from_id" = 2 ORDER BY "myuser_followship"."created" ASC LIMIT 21` – ramwin Jan 19 '17 at 17:03