Как получить доступ к объектам (информация о пользователе) через модели Django? (Внешний ключ, Новичок)

В моем проекте Django есть модель Profile и модель Post:

class Profile(models.Model):
    """ User profile data """
    user = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name='profile')
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    id_user = models.IntegerField()
    bio = models.TextField(max_length=280, blank=True)

    def __str__(self):
        return str(self.user)


class Post(models.Model):
    """ Model for user-created posts """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    user = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name='post')
    post_text = models.TextField()
    created_at = models.DateTimeField(default=datetime.now)

    def __str__(self):
        return self.user

Я хочу иметь возможность отображать имя_и фамилию пользователя в его сообщениях (которые отображаются в index.html вместе с другими сообщениями всех пользователей), наряду с его именем пользователя.

{% for post in posts %}
<div class="row d-flex justify-content-between">
    <h5 class="col-12 col-md-6">{{ first_name here }} {{ last_name here }}</h5>
    <h5 class="col-12 col-md-6">(@{{ post.user }})</h5>
</div>

Я могу нормально отобразить имя пользователя (post.user выше), потому что оно находится в модели Post. Однако имя_и фамилия находятся в модели Profile, и я просто не могу найти способ получить к ним доступ.

Вид:


@login_required(login_url='signin')
def index(request):
    """ View to render index """
    user_object = User.objects.get(username=request.user.username)

    user_profile = Profile.objects.get(user=user_object)

    posts = Post.objects.all()

    context = {'user_profile': user_profile, 'posts': posts, }

    return render(request, 'index.html', context)

Помимо получения всех объектов Post, мое представление содержит ссылку на текущего вошедшего пользователя (user_object) и его профиль (user_profile) для настройки вещей в навигационной панели и ссылок на профиль вошедшего пользователя.

Исходя из этого, по моей логике, я должен добавить в представление что-то вроде этого:

post_author = Post.user
author_profile = Profile.objects.get(user=post_author)

чтобы я мог ссылаться на что-то вроде этого в цикле {% for post in posts %}:

{{ post_author.profile.first_name }} {{ author.profile.last_name }}

Но, конечно, это не работает:

TypeError at /. Поле 'id' ожидало число, но получило объект <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor at 0x7f176924f160>. Метод запроса: GET URL запроса: http://localhost:8000/ Версия Django: 3.2.13 Тип исключения: TypeError Значение исключения:
Поле 'id' ожидало число, но получило <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x7f176924f160>.

Я следовал учебнику, но он не охватывает это, и я бы очень хотел выяснить, где я ошибаюсь, и получить лучшее понимание вещей. Если в принципе я смогу сделать это правильно, я также смогу добавлять avatar к каждому сообщению. С другой стороны, может быть, это просто невозможно сделать так, как я настроил свой проект?

Вы на правильном пути!

"Ключ" здесь - это то, как вещи следуют отношениям типа foreignkey (многие к одному).

В настоящее время вы находитесь в своем шаблоне, просматривая свои сообщения. У вас есть нужная информация, осталось только получить к ней доступ. В шаблоне вы можете следовать за внешним ключом просто с помощью точки. Итак, мы хотим перейти от post -> fk -> user -> reverse fk -> profile -> field

(об обратном fk мы будем беспокоиться позже, сейчас просто знайте, что мы можем использовать связанное имя для его обхода)

Так что это должно работать:

{% for post in posts %}
<div class="row d-flex justify-content-between">
    <h5 class="col-12 col-md-6">{{ post.user.profile[0].first_name }} {{ post.user.profile[0].first_name }}</h5>
    <h5 class="col-12 col-md-6">(@{{ post.user }})</h5>
</div>

Хотя это не идеально...

Мы использовали profile[0], потому что в ваших текущих определениях profile->user является foreignkey, или отношением "многие-к-одному", поэтому вы допускаете несколько профилей для одного и того же пользователя. Таким образом, post.user.profiles вернет набор (даже если это набор из одного), а нам нужен только первый объект в этом наборе, следовательно, [0]. Если вам нужен только один профиль для каждого пользователя (а обычно так и бывает), измените свой

class Profile(models.Model):
    """ User profile data """
    user = models.ForeignKey(
            User, on_delete=models.CASCADE, related_name='profile')

to

user = models.OneToOneField(
        User, on_delete=models.CASCADE, related_name='profile')

затем перемигрируйте вашу базу данных. Теперь вы можете использовать

<h5 class="col-12 col-md-6">{{ post.user.profile.first_name }} {{ post.user.profile.first_name }}</h5>

Следующая проблема заключается в том, что вы будете делать вызов базы данных КАЖДЫЙ раз, когда вы получаете информацию о плакате, когда вы просматриваете ее, что будет медленно, если у вас много сообщений. Возможно, это немного дальше, чем вы уже достигли, но это полезно иметь в виду на будущее:

Вы можете сделать цикл обработки сообщений более эффективным, предварительно загрузив всю необходимую информацию в один вызов БД в вашем представлении.

Если вы сделали так, как я предложил, и сделали профиль отношением один-к-одному, тогда мы также можем использовать select_related для отслеживания плаката до его профиля и захвата информации профиля, когда мы получаем сообщение (есть foreignkey от сообщения к пользователю и one_to_one от пользователя к профилю, поэтому select_related должен работать, иначе нам нужен prefetch_related для отслеживания foreignkey)

  Post.objects.all().select_related('user__profile')

Вы обращаетесь к нему точно так же в своем шаблоне - он просто захвачен более эффективно.

Вернуться на верх