add ordering shop

shop
Raz 8 months ago
parent ca9c37f2c8
commit 027ce2d9bc
  1. 1
      padelclub_backend/urls.py
  2. 27
      shop/admin.py
  3. 5
      shop/forms.py
  4. 22
      shop/migrations/0008_alter_product_options_and_more.py
  5. 38
      shop/migrations/0009_order_orderitem.py
  6. 21
      shop/migrations/0010_guestuser.py
  7. 19
      shop/migrations/0011_order_guest_user.py
  8. 43
      shop/models.py
  9. 3
      shop/templates/shop/cart.html
  10. 47
      shop/templates/shop/checkout.html
  11. 65
      shop/templates/shop/order_confirmation.html
  12. 3
      shop/urls.py
  13. 95
      shop/views.py
  14. 21
      tournaments/custom_views.py
  15. 6
      tournaments/urls.py

@ -20,6 +20,7 @@ urlpatterns = [
# path('roads/', include(router.urls)),
path("", include("tournaments.urls")),
path('shop/', include('shop.urls')),
# path("crm/", include("crm.urls")),
path('roads/', include("api.urls")),
path('kingdom/', admin.site.urls),

@ -1,9 +1,9 @@
from django.contrib import admin
from .models import Product, Color, Size
from .models import Product, Color, Size, Order, OrderItem, GuestUser
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ("title", "order", "price", "cut")
list_display = ("title", "ordering_value", "price", "cut")
@admin.register(Color)
class ColorAdmin(admin.ModelAdmin):
@ -12,3 +12,26 @@ class ColorAdmin(admin.ModelAdmin):
@admin.register(Size)
class SizeAdmin(admin.ModelAdmin):
list_display = ("name",)
class OrderItemInline(admin.TabularInline):
model = OrderItem
extra = 0
readonly_fields = ('product', 'quantity', 'color', 'size', 'price')
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ('id', 'date_ordered', 'status', 'total_price')
inlines = [OrderItemInline]
class GuestUserOrderInline(admin.TabularInline):
model = Order
extra = 0
readonly_fields = ('date_ordered', 'total_price')
can_delete = False
show_change_link = True
exclude = ('user',) # Exclude the user field from the inline display
@admin.register(GuestUser)
class GuestUserAdmin(admin.ModelAdmin):
list_display = ('email', 'phone')
inlines = [GuestUserOrderInline]

@ -0,0 +1,5 @@
from django import forms
class GuestCheckoutForm(forms.Form):
email = forms.EmailField(required=True)
phone = forms.CharField(max_length=20, required=True)

@ -0,0 +1,22 @@
# Generated by Django 4.2.11 on 2025-03-18 14:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('shop', '0007_product_cut'),
]
operations = [
migrations.AlterModelOptions(
name='product',
options={'ordering': ['ordering_value', 'cut']},
),
migrations.RenameField(
model_name='product',
old_name='order',
new_name='ordering_value',
),
]

@ -0,0 +1,38 @@
# Generated by Django 4.2.11 on 2025-03-18 14:36
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('shop', '0008_alter_product_options_and_more'),
]
operations = [
migrations.CreateModel(
name='Order',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date_ordered', models.DateTimeField(auto_now_add=True)),
('status', models.CharField(choices=[('PENDING', 'Pending'), ('PAID', 'Paid'), ('SHIPPED', 'Shipped'), ('DELIVERED', 'Delivered'), ('CANCELED', 'Canceled')], default='PENDING', max_length=20)),
('total_price', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='OrderItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField(default=1)),
('price', models.DecimalField(decimal_places=2, max_digits=10)),
('color', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='shop.color')),
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='shop.order')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.product')),
('size', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='shop.size')),
],
),
]

@ -0,0 +1,21 @@
# Generated by Django 4.2.11 on 2025-03-18 14:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('shop', '0009_order_orderitem'),
]
operations = [
migrations.CreateModel(
name='GuestUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254)),
('phone', models.CharField(max_length=20)),
],
),
]

@ -0,0 +1,19 @@
# Generated by Django 4.2.11 on 2025-03-18 17:59
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('shop', '0010_guestuser'),
]
operations = [
migrations.AddField(
model_name='order',
name='guest_user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='shop.guestuser'),
),
]

@ -2,6 +2,13 @@ from django.contrib.auth.models import User
from django.db import models
from django.conf import settings
class OrderStatus(models.TextChoices):
PENDING = 'PENDING', 'Pending'
PAID = 'PAID', 'Paid'
SHIPPED = 'SHIPPED', 'Shipped'
DELIVERED = 'DELIVERED', 'Delivered'
CANCELED = 'CANCELED', 'Canceled'
class ColorChoices(models.TextChoices):
RED = "Red", "Red"
BLUE = "Blue", "Blue"
@ -41,11 +48,11 @@ class Product(models.Model):
# Use string references to prevent circular imports
colors = models.ManyToManyField("shop.Color", blank=True, related_name="products")
sizes = models.ManyToManyField("shop.Size", blank=True, related_name="products")
order = models.IntegerField(default=0, blank=False)
ordering_value = models.IntegerField(default=0, blank=False)
cut = models.IntegerField(choices=CutChoices.choices, default=CutChoices.MEN)
class Meta:
ordering = ['order', 'cut'] # Add this line to sort by title
ordering = ['ordering_value', 'cut'] # Add this line to sort by title
def __str__(self):
return self.title
@ -64,3 +71,35 @@ class CartItem(models.Model):
def get_total_price(self):
return self.product.price * self.quantity
class GuestUser(models.Model):
email = models.EmailField()
phone = models.CharField(max_length=20)
def __str__(self):
return f"{self.email}"
class Order(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True)
date_ordered = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=20, choices=OrderStatus.choices, default=OrderStatus.PENDING)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
guest_user = models.ForeignKey(GuestUser, on_delete=models.CASCADE, null=True, blank=True)
def __str__(self):
return f"Order #{self.id} - {self.status}"
class OrderItem(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items')
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(default=1)
color = models.ForeignKey(Color, on_delete=models.SET_NULL, null=True, blank=True)
size = models.ForeignKey(Size, on_delete=models.SET_NULL, null=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return f"{self.quantity} x {self.product.title}"
def get_total_price(self):
return self.price * self.quantity

@ -56,6 +56,9 @@
{% if cart_items %}
<a class="cancel-nav-button" href="{% url 'shop:clear_cart' %}">Vider le panier</a>
{% endif %}
<a class="confirm-nav-button" href="{% url 'shop:checkout' %}">Passer la commande</a>
</div>
{% else %}
<p>Votre panier est vide.</p>

@ -0,0 +1,47 @@
{% extends 'tournaments/base.html' %}
{% block head_title %}La boutique{% endblock %}
{% block first_title %}La boutique Padel Club{% endblock %}
{% block second_title %}Validation de la commande{% endblock %}
{% block content %}
<nav class="margin10">
<a href="{% url 'shop:product_list' %}">La Boutique</a>
<a href="{% url 'index' %}" class="orange">Accueil</a>
<a href="{% url 'clubs' %}" class="orange">Clubs</a>
{% if user.is_authenticated %}
<a href="{% url 'my-tournaments' %}" class="orange">Mes tournois</a>
<a href="{% url 'profile' %}">Mon compte</a>
{% else %}
<a href="{% url 'login' %}">Se connecter</a>
{% endif %}
</nav>
<h1 class="club my-block topmargin20">Votre panier</h1>
<div class="grid-x">
<div class="small-12 medium-6 large-6 my-block">
<div class="bubble">
{% if request.user.is_authenticated %}
<p>Vous êtes déjà connecté en tant que {{ request.user.email }}.</p>
<a href="{% url 'shop:checkout' %}">Passer à la commande</a>
{% else %}
<p>Vous n'êtes pas connecté. Veuillez choisir une option :</p>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" name="guest_checkout">Continuer sans créer de compte</button>
</form>
<hr>
<p>Ou <a href="{% url 'login' %}?next={% url 'shop:checkout' %}">connectez-vous</a> si vous avez déjà un compte.</p>
<hr>
<p>Pas encore de compte ? <a href="{% url 'signup' %}?next={% url 'shop:checkout' %}">Créez-en un maintenant</a></p>
{% endif %}
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,65 @@
{% extends 'tournaments/base.html' %}
{% block head_title %}Confirmation de commande{% endblock %}
{% block first_title %}Confirmation de commande{% endblock %}
{% block second_title %}Merci pour votre commande !{% endblock %}
{% block content %}
<nav class="margin10">
<a href="{% url 'shop:product_list' %}">La Boutique</a>
<a href="{% url 'index' %}" class="orange">Accueil</a>
<a href="{% url 'clubs' %}" class="orange">Clubs</a>
{% if user.is_authenticated %}
<a href="{% url 'my-tournaments' %}" class="orange">Mes tournois</a>
<a href="{% url 'profile' %}">Mon compte</a>
{% else %}
<a href="{% url 'login' %}">Se connecter</a>
{% endif %}
</nav>
<div class="grid-x">
<div class="cell medium-6 large-6 my-block">
<h1 class="club my-block topmargin20">Détails de la commande</h1>
<div class="bubble">
{% if order_items %}
<table class="cart-table">
<thead>
<tr>
<th>Produit</th>
<th>Couleur</th>
<th>Taille</th>
<th>Quantité</th>
<th class="price-column">Prix</th>
</tr>
</thead>
<tbody>
{% for item in order_items %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ item.product.title }}</td>
<td>{{ item.color.name }}</td>
<td>{{ item.size.name }}</td>
<td>{{ item.quantity }}</td>
<td class="price-column">{{ item.get_total_price }} €</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="3" class="total-label">Total:</td>
<td class="total-quantity">{{ order_items.total_quantity }}</td>
<td class="price-column total-price">{{ total }} €</td>
</tr>
</tfoot>
</table>
<div class="order-summary">
<p>Votre commande a été passée avec succès !</p>
<a class="back-to-shop-button" href="{% url 'shop:product_list' %}">Retour à la boutique</a>
</div>
{% else %}
<p>Aucun élément dans la commande.</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}

@ -12,5 +12,6 @@ urlpatterns = [
path('cart/update/<int:product_id>/', views.update_cart_view, name='update_cart'),
path('cart/remove/<int:product_id>/', views.remove_from_cart_view, name='remove_from_cart'),
path('clear-cart/', views.clear_cart, name='clear_cart'),
path('checkout/', views.checkout, name='checkout'),
path('order/<int:order_id>/confirmation/', views.order_confirmation, name='order_confirmation'),
]

@ -1,8 +1,11 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from .models import Product, CartItem
from .models import Product, Order, OrderItem, GuestUser
from django.db.models import Sum
from .cart import add_to_cart
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from .forms import GuestCheckoutForm
from . import cart
@ -35,7 +38,7 @@ def add_to_cart_view(request, product_id):
color_id = request.POST.get('color')
size_id = request.POST.get('size')
cart_item = add_to_cart(request, product_id, quantity, color_id, size_id)
cart_item = cart.add_to_cart(request, product_id, quantity, color_id, size_id)
messages.success(request, f'{cart_item.quantity} x {product.title} added to your cart')
return redirect('shop:product_list')
@ -57,3 +60,89 @@ def clear_cart(request):
cart.clear_cart(request)
messages.success(request, "Your cart has been cleared.")
return redirect('shop:product_list')
def create_order(request):
cart_items = cart.get_cart_items(request)
total_price = sum(item.get_total_price() for item in cart_items)
if request.user.is_authenticated:
# L'utilisateur est authentifié, créer la commande avec l'utilisateur
order = Order.objects.create(
user=request.user,
total_price=total_price
)
else:
# L'utilisateur n'est pas authentifié, créer la commande avec l'utilisateur invité
try:
guest_user = GuestUser.objects.get(email=request.session['guest_email'])
order = Order.objects.create(
guest_user=guest_user,
total_price=total_price
)
except (KeyError, GuestUser.DoesNotExist):
# Si l'utilisateur invité n'existe pas, créer une commande sans utilisateur
order = Order.objects.create(
total_price=total_price
)
for cart_item in cart_items:
OrderItem.objects.create(
order=order,
product=cart_item.product,
quantity=cart_item.quantity,
color=cart_item.color,
size=cart_item.size,
price=cart_item.product.price
)
# Clear the cart after creating the order
cart.clear_cart(request)
return order
def order_confirmation(request, order_id):
order = get_object_or_404(Order, id=order_id)
order_items = order.items.all()
total = sum(item.get_total_price() for item in order_items)
return render(request, 'shop/order_confirmation.html', {
'order': order,
'order_items': order_items,
'total': total
})
def checkout(request):
if request.user.is_authenticated:
# Créer la commande pour l'utilisateur authentifié
order = create_order(request)
# Rediriger vers la confirmation de commande
return redirect('shop:order_confirmation', order.id)
if request.method == 'GET':
form = GuestCheckoutForm()
return render(request, 'shop/checkout.html', {'form': form})
elif request.method == 'POST':
# Gérer la soumission du formulaire ici
form = GuestCheckoutForm(request.POST)
if form.is_valid():
# Créer ou récupérer l'utilisateur invité
email = form.cleaned_data['email']
phone = form.cleaned_data['phone']
guest_user, created = GuestUser.objects.get_or_create(email=email, defaults={'phone': phone})
# Stocker l'e-mail de l'utilisateur invité dans la session
request.session['guest_email'] = email
# Simuler le processus de paiement
# ...
# Créer la commande
order = create_order(request)
# Rediriger vers la confirmation de commande
return redirect('shop:order_confirmation', order.id)
# Gérer les autres méthodes (par exemple, PUT, DELETE) si nécessaire
return redirect('shop:product_list')

@ -0,0 +1,21 @@
from django.contrib import messages
from django.contrib.auth import views as auth_views
from django.urls import reverse
from .forms import EmailOrUsernameAuthenticationForm
class CustomLoginView(auth_views.LoginView):
template_name = 'registration/login.html'
authentication_form = EmailOrUsernameAuthenticationForm
print("CustomLoginView")
def get_success_url(self):
next_url = self.request.POST.get('next')
print("CustomLoginView", "next_url", next_url, self.request.GET)
if next_url:
return next_url
else:
return reverse('index')
def get(self, request, *args, **kwargs):
messages.get_messages(request).used = True
return super().get(request, *args, **kwargs)

@ -5,6 +5,7 @@ from django.conf import settings
from django.conf.urls.static import static
from .forms import EmailOrUsernameAuthenticationForm, CustomPasswordChangeForm
from .custom_views import CustomLoginView
from . import views
@ -53,10 +54,7 @@ urlpatterns = [
path('terms-of-use/', views.terms_of_use, name='terms-of-use'),
path('utils/xls-to-csv/', views.xls_to_csv, name='xls-to-csv'),
path('mail-test/', views.simple_form_view, name='mail-test'),
path('login/', auth_views.LoginView.as_view(
template_name='registration/login.html',
authentication_form=EmailOrUsernameAuthenticationForm
), name='login'),
path('login/', CustomLoginView.as_view(), name='login'),
path('password_change/',
auth_views.PasswordChangeView.as_view(
success_url='/profile/', # Redirect back to profile after success

Loading…
Cancel
Save