Dans le view.py, j'essaye de diviser une valeur d'attribut qui est une longue chaîne. Dans une requête normale:

someposts = Posts.object.all()[3:5]

Devrait retourner:

<QuerySet [object<first post>, object<second post>]>

Alors j'interroge les messages comme suit car je dois diviser une valeur d'attribut (après ce changement, j'obtiens l'erreur):

someposts = Posts.object.all().values('id', 'title', 'tags')[3:5]

Donc il renvoie quelque chose comme:

<QuerySet [{'id': 2, 'title': 'first post', 'tags': ' X1, X2, X3,.., X10'}, {'id': 4, 'title': 'second post', 'tags': ' S1, S2, S3,.., S8'}]

Mais je m'attends à recevoir tags comme une liste de piqûres, alors ce que j'ai fait:

splited_tags = [v['tags'].split(',') for v in someposts]
for n, i in enumerate(someposts):
        someposts[n]['tags'] = splited_tags[n]

Et comme résultat

<QuerySet [{'id': 2, 'title': 'first post', 'tags': [' X1', 'X2', 'X3',.., X10']}, {'id': 4, 'title': 'second post', 'tags': [' S1', 'S2', 'S3,.., 'S8']}]

Puisque je passe someposts à mon modèle:

context = {
       'someposts':someposts,
}
return render(request, 'app/home.html', context)

Et dans home.html:

{%for post in someposts %}
     <a class="avator" href="{% url 'user-post' post.author.username %}"></a>
{ % endfor %}

Je reçois cette erreur:

Inverse pour 'user-post' avec arguments '(' ',)' non trouvé

Je pense que le problème est post.author.username puisque post est une chaîne, il n'a pas d'attribut .author, donc cela sera évalué à la string_if_invalid qui est, sauf indication contraire, la chaîne vide ''.

Savez-vous comment résoudre ce bug? ou comment diviser la chaîne dans un Queryset?

1
Braiano 31 août 2020 à 14:22

2 réponses

Meilleure réponse

Une solution rapide à votre problème:

class Posts((models.Model):
     ....
     tags = ...
     ....
     def get_tags(self):
        if self.tags:
            return self.tags.split(",")
        else:
            None

Puis dans votre modèle, essayez de l'appeler quelque chose comme:

{% for tag in someposts.get_tags %}
      {{ tag }}
{% endfor %}

Gardez à l'esprit, cependant, que les modèles trop gros peuvent devenir un tas de choses impossibles à entretenir. Vous convertiriez certaines de vos données sérialisées en Python, ce qui est logique à faire sur la couche de modèle. Mais essayez de gérer ce genre de problèmes dans la vue!

1
Kian 1 sept. 2020 à 15:00

Bien sûr, vous avez plusieurs options pour y remédier, mais pour résoudre votre problème très rapidement, je vais vous donner la solution la plus simple:

Vous pouvez en fait annoter les posts de sorte que vous ayez le nom d'utilisateur de l'auteur dans chaque post:

from django.db.models import F
Post.objects.annotate(formatted_author=F('author__username')).values('id', 'formatted_author')

De cette façon, vous pouvez obtenir le nom d'utilisateur de l'auteur qui a écrit le message. Cependant, veillez à ce que si vous avez des valeurs null dans le champ auteur de certains articles, vous obtiendrez des valeurs Aucune dans la réponse.


Cependant, si vous utilisez Postgres comme base de données, vous pouvez utiliser la fonctionnalité ArrayField intégrée de Postgres et rendre votre annotation encore plus élégante dans Par ici:

from django.db.models.expressions import Func
from django.db.models import F, Value, TextField
from django.contrib.postgres.fields import ArrayField

formatted_author = F('author__username')
formatted_tags = Func(
    F('tags'),
    Value(","),
    function='regexp_split_to_array', output=ArrayField(TextField())
)

Post.objects \
    .annotate(formatted_author=formatted_author, formatted_tags=formatted_tags) \
    .values('id', 'formatted_author', 'formatted_tags')

De cette façon, vous économiserez également les coûts liés aux requêtes SQL à chaque itération que vous faisiez auparavant.


Mise à jour 1

Si vous n'avez pas de base de données Postgres, je suis désolé de vous dire que vous devez toujours traiter les balises en code python, et j'ai une meilleure refactorisation pour votre implémentation actuelle. Alors ce que vous avez maintenant:

splited_tags = [v['tags'].split(',') for v in someposts]
for n, i in enumerate(someposts):
        someposts[n]['tags'] = splited_tags[n]

Et ce que je recommande:

someposts = list(map(lambda somepost: dict(formatted_tags=somepost['tags'].split(','), **somepost), someposts))

Comme vous le voyez, cela ne nécessite qu'une seule itération avec un nouveau compromis de propriété, mais ce n'est que ma suggestion.


Update 2

Comme le propriétaire du message a posé des questions sur les propriétés post.author.profile.user.first_name et post.author.profile.image.url dans son commentaire, je voulais élaborer là-dessus.

En général, vous ne pouvez pas récupérer ces propriétés, car vous ne les sélectionnez pas explicitement dans votre fonction .values(). .values() ne récupérera que les propriétés sélectionnées. Donc, en gros, ce que vous pouvez faire est à nouveau de créer des variables F (pour que la longueur du nom diminue) avec des annotations:

from django.db.models import F
Post.objects.annotate(formatted_author=F('author__username'), formatted_first_name=F('author__profile__user__first_name'), formatted_image=F('author__profile__image')).values('id', 'formatted_author', 'formatted_first_name', 'formatted_image')

Et comme mon intuition me le dit, en fait post.author.first_name est équivalent à post.author.profile.user.first_name, mais je peux me tromper si vous avez une structure de modèle différente. :)

1
Bedilbek 1 sept. 2020 à 09:31