1
votes

Comment afficher uniquement la première occurrence d'un élément dans un ensemble sur un modèle Django?

J'apprends Django, donc ce n'est peut-être pas la meilleure façon de faire ce que je veux.

Ce que j'essaie de faire ici, c'est d'afficher un article qui a été sélectionné plusieurs fois dans un panier, une seule fois ( et le nombre de fois où il a été sélectionné à côté)

J'ai essayé toute la journée de trouver un moyen, mais la chose la plus proche que j'ai réussi à faire est, à l'intérieur des vues, de produire deux ensembles: single_items , et plusieurs_éléments.

Je n'ai aucun problème pour afficher un élément qui n'a été sélectionné qu'une seule fois, mais lorsqu'il s'agit d'éléments sélectionnés plusieurs fois, je ne peux les afficher que n fois avec le nombre d'occurrences à côté, comme indiqué sur l'image

 entrez la description de l'image ici

Bien que je préfère faire tout le calcul avec du code python (dans views.py) une solution avec le langage de modèle Django me fera l'affaire.

Veuillez considérer que je dois transmettre l'ID d'élément dans l'URL pour que le lien de suppression fonctionne.

voici views.py

from django.shortcuts import render, redirect
from .models import Cart, Item, CartItem
from django.db.models import Sum, Count, F


# Create your views here.
def home(request):
    cart = Cart.objects.filter(user=request.user).last()
    items = Item.objects.all()
    cart_items = Item.objects.filter(
        cartitem__cart=cart
    ).annotate(
        ncount=Count('cartitem')
    )
    total = cart_items.aggregate(total=Sum(F('price') * F(float('ncount'))))['total']
    context = {
        'items': items,
        'total': total,
        'cart_items': cart_items
    }
    return render(request, 'cart/home.html', context)


def add_to_cart(request, item_id):
    item_id = Item.objects.get(id=item_id)
    carts = Cart.objects.all()
    length = len(Cart.objects.all())
    cart = carts[length - 1]
    cart_item = CartItem.objects.create(item=item_id, cart=cart)
    return redirect(home)


def remove_from_cart(request, item_id):
    item_to_remove = CartItem.objects.get(id=item_id)
    item_to_remove.delete()
    return redirect(home)

et mon modèle

from django.db import models
from django.contrib.auth.models import User


# Create your models here.
class Item(models.Model):
    name = models.CharField(max_length=25)
    price = models.DecimalField(max_digits=5, decimal_places=2)

    def __str__(self):
        return self.name


class Cart(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    items = models.ManyToManyField(Item, through='CartItem')

    def __str__(self):
        return 'Order number: %s' % self.id


class CartItem(models.Model):
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    cart = models.ForeignKey(Cart, on_delete=models.CASCADE)

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

et juste au cas où les modèles

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home page</title>
</head>
<body>
<h1>My Restaurant</h1>
<div><h1>Menu</h1></div>
{% for item in items %}
<ul>{{ item }} £{{ item.price}} <a href="{% url 'add_to_cart' item.id %}">Add</a></ul>
{% endfor %}
<div><h1>Order</h1></div>
{{ cart }}
<br>
<h2>items selected: {{ number_of_items }}</h2>
{% for single_item in single_items %}
<ul>{{ single_item }} x {{ single_item.occurrences }} <a href="{% url 'remove_from_cart' single_item.id %}">Remove</a></ul>
{% endfor %}
{% for multiple_item in multiple_items %}
<ul>{{ multiple_item }} x {{ multiple_item.occurrences }} <a href="{% url 'remove_from_cart' multiple_item.id %}">Remove</a></ul>
{% endfor %}
<h2>Total</h2>
{{ total|floatformat:2 }}
</body>
</html>

EDIT

en ce moment j'utilise ce view.py qui a été suggéré par Willem Van Onsem

    from django.shortcuts import render, redirect
    from .models import Cart, Item, CartItem
    from django.db.models import Sum


    # Create your views here.
    def home(request):
        items = Item.objects.all()
        carts = Cart.objects.all()
        length = len(Cart.objects.all())
        cart = carts[length - 1]
        cart_items = cart.items.all()
        total = cart_items.aggregate(Sum('price'))['price__sum']
        if total is None:
            total = 0
        number_of_items = cart_items.count()
        deletable_items = CartItem.objects.all()

        occurrences = None
        single_items = set()
        multiple_items = set()
        for deletable_item in deletable_items:
            occurrences = deletable_items.filter(item__name=deletable_item.item).count()
            if occurrences > 1:
                deletable_item.occurrences = occurrences
                multiple_items.add(deletable_item)
            elif occurrences == 1:
                deletable_item.occurrences = occurrences
                single_items.add(deletable_item)

        return render(request, 'cart/home.html', {'cart': cart,
                                                  'items': items,
                                                  'cart_items': cart_items,
                                                  'total': total,
                                                  'number_of_items': number_of_items,
                                                  'deletable_items': deletable_items,
                                                  'multiple_items': multiple_items,
                                                  'single_items': single_items,
                                                  'occurrences': occurrences
                                                  })


    def add_to_cart(request, item_id):
        item_id = Item.objects.get(id=item_id)
        carts = Cart.objects.all()
        length = len(Cart.objects.all())
        cart = carts[length - 1]
        cart_item = CartItem.objects.create(item=item_id, cart=cart)
        return redirect(home)


    def remove_from_cart(request, item_id):
        item_to_remove = CartItem.objects.get(id=item_id)
        item_to_remove.delete(

)
    return redirect(home)


7 commentaires

Eh bien, le problème est que vous comptez à chaque fois le nombre d'articles. Mais ce sont des différents CartItem s, donc le set (..) ne les considère pas comme des éléments différents.


Je pense que vous voulez dire que set () les voit comme différents, et c'est pourquoi les doublons sont autorisés (ce sont des objets différents avec le même attribut de nom)


Que fait paniers [length-1] ? S'il y a plusieurs utilisateurs, ils verront le dernier panier, et donc un utilisateur verra le panier de l'autre utilisateur.


car deux personnes qui ont toutes les deux Mirko comme prénom, ne sont pas en soi les mêmes personnes.


est juste pour obtenir le dernier panier (commande) créé ...


C'est ca le truc. Je ne pense pas que le problème ici soit set (), j'aurais pu utiliser une liste. Ce que je demande, c'est s'il existe un filtre ou une balise de modèle permettant d'afficher une seule occurrence dans une itération de modèle (si cela a du sens)


veuillez ne pas résoudre ces problèmes au niveau du modèle. Un modèle seul devrait rendre le contenu joliment. Vous ne devez pas implémenter de logique métier dans un modèle.


3 Réponses :


0
votes

Je pense qu'il serait peut-être préférable de développer ici deux ensembles de requêtes comme:

def remove_from_cart(request, item_id):
    cart = Cart.objects.filter(user=request.user).last()
    item_to_remove = CartItem.objects.filter(
        item_id=item_id,
        cart=cart
    ).delete()
    return redirect(home)

Dans votre modèle, vous pouvez ensuite afficher le panier avec:

<h2>items selected: {{ cart_items|length }}</h2>
{% for item in cart_items %}
    <ul>{{ item }} x {{ item.ncount }} <a href="{% url 'remove_from_cart' item.id %}">Remove</a></ul>
{% endfor %}
<h2>Total</h2>
{{ total|floatformat:2 }}


15 commentaires

Wow, c'est beaucoup plus court :) J'essaie votre solution (et j'étudierai quelque chose de plus sur les requêtes car je ne suis pas encore à ce niveau) mais elle renvoie une erreur sur «ncount». J'ai également essayé de le changer en «count» mais il semble que je devrai créer un attribut «count» dans le modèle pour le faire fonctionner. Est-ce correct?


@MirkoOricci: désolé, j'ai fait une faute de frappe, ça devrait être cart_items.aggregate (..) .


Oui, semble être mieux, mais il renvoie «Expression contient des types mixtes: DecimalField, IntegerField. Vous devez définir output_field. ' à présent. Je suppose que je vais devoir trouver un moyen de convertir «ncount» en décimal, non?


@MirkoOricci: oui ça devrait être quelque chose comme Cast (..) .


mmm ... si j'essaie de convertir le prix et le total en nombres entiers, cela dit que c'est impossible car ils sont en base 10, et si j'essaie de convertir ncount en un nombre flottant, cela dit qu'il est impossible d'analyser la chaîne en flottant numéro...


@MirkoOricci: Une chaîne? Je trouve cela assez étrange, car un Decimal est quelque chose de différent d'un float , et Count utilise par défaut un IntegerField .


@MirkoOricci: pouvez-vous modifier votre question et partager ce que vous avez exactement essayé?


OK, je le faisais de la mauvaise manière. J'ai trouvé la fonction Cast () que vous avez suggérée, et je pense qu'elle devrait ressembler à quelque chose comme Cast ('ncount', output_field = FloatField), mais je ne sais pas où ce code devrait aller. J'ai essayé de l'ajouter avec un point après avoir déclaré ncount comme '......... ncount = Count (' cartitem '). Cast (' ncount ', output_field = FloatField ())' mais ce n'est pas le cas travailler.


@MirkoOricci: non, il n'y a pas de méthode .Cast (..) , c'est une expression. Mais nous n'en avons probablement pas besoin de toute façon. Quelle version de Django utilisez-vous?


J'utilise la version 3.0.2


@MirkoOricci: et si vous utilisez ncount = Count ('cartitem', output_field = DecimalField (max_digits = 5, decimal_places = 0)) ?


Cela fonctionne :) mais en cliquant sur le lien de suppression, la requête correspondante CartItem n'existe pas.


J'ai imprimé quelques éléments à des fins de test et ce qui se passe essentiellement, c'est que chaque fois que j'ajoute un élément à la liste, je le crée en fait, donc j'ai maintenant un identifiant d'environ 400 quelque chose, mais lorsque je clique sur supprimer, je n'ai 3 ID ... je dois comprendre comment y faire face maintenant


@MirkoOricci: et si vous le modifiez avec le remove_from_cart donné?


@ WillemVanOnsem: je ne sais pas si c'est ce que vous demandez, mais j'ai essayé de modifier le numéro dans l'url (par exemple 127.0.0.1:8000/remove/408 ) et cela fonctionne très bien.



0
votes

Si vous enregistrez les éléments sélectionnés dans la base de données avant même que l'utilisateur vérifie, ce que je ne conseillerais pas, vous pouvez créer quelque chose comme ça dans les modèles

def add_order(request,id):
    user = request.user
    item = Product.objects.get(id=id)
    try:
       order = Order.objects.get(item=item, user=user)
       order.times+=1
       order.save()
    except:
      order = Order.objects.create(
             item = item,
             user = user
            )
     try:
         my_cart = Cart.objects.get(user=user)
     except:
         my_cart = Cart.objects.create(user=user)
     my_cart.orders.add(order)

   #return http reeponse with the new data

puis une vue qui vérifie si un modèle existe déjà

class Product(models.Model):
    #model info


class Order(models.Model):
    item = modelS.ForeignKey(Product, on_delete=models.CASCADE)
    user = modelS.ForeignKey(User, on_delete=models.CASCADE)
    times = models.IntagerField(default=1)
    #..... other relevant fields

class Cart(models.Model):
    orders = models.ManyToManyField(Order)
    user = modelS.ForeignKey(User,related_name='cart_user' on_delete=models.CASCADE)
    #other details like date etc....

Je vous conseillerais d'utiliser ajax pour soumettre la requête à l'url qui appelle la vue à mettre à jour afin de faire moins d'actualisation de la page à l'avenir


3 commentaires

Merci Kim, je vérifierai aussi ta réponse. Comme je l'ai dit, j'apprends juste Django et je ne connais Python qu'à un bon niveau. J'essayais de construire un projet qui minimise les choses. Bien sûr, je rencontrerai des sessions Ajax, Javascript et aussi Django, mais pour le moment, je préfère faire les choses aussi facilement que possible.


Merci, ce sera long à coup sûr :)


pas grand-chose, dès que vous commencerez à comprendre ces choses, vous saurez qu'elles sont simples



0
votes

Juste une autre approche: pourquoi n'ajoutez-vous pas un modèle Field to CartItem dans votre models.py appelé "quantité" ou quelque chose comme ça? Par exemple:

def add_to_cart(request, item_id):
    item = Item.objects.get(id=item_id)
    cart = Cart.objects.filter(user=request.user).last()
    cart_item_qs = CartItem.objects.filter(cart__pk=cart.pk)
    if cart_item_qs.exists():
        cart_item_qs.update(quantity=F('quantity') + 1)
    else:
        cart.items.add(item)
        cart.save()

    return redirect(home)

Et puis dans votre vue add_to_cart, vous feriez:

class CartItem(models.Model):
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    cart = models.ForeignKey(Cart, on_delete=models.CASCADE)
    quantity = models.IntegerField(default=0)

Après cela, vous pouvez avoir dans votre modèle l'instance du modèle CartItem en le passant dans les données de contexte et donc accéder à la valeur du champ de quantité à partir de là.


0 commentaires