parent
8d8fa1a7d3
commit
7f31708d86
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 909 KiB |
|
After Width: | Height: | Size: 909 KiB |
|
After Width: | Height: | Size: 909 KiB |
|
After Width: | Height: | Size: 909 KiB |
|
After Width: | Height: | Size: 162 KiB |
@ -0,0 +1,14 @@ |
||||
from django.contrib import admin |
||||
from .models import Product, Color, Size |
||||
|
||||
@admin.register(Product) |
||||
class ProductAdmin(admin.ModelAdmin): |
||||
list_display = ("title", "price") |
||||
|
||||
@admin.register(Color) |
||||
class ColorAdmin(admin.ModelAdmin): |
||||
list_display = ("name",) |
||||
|
||||
@admin.register(Size) |
||||
class SizeAdmin(admin.ModelAdmin): |
||||
list_display = ("name",) |
||||
@ -0,0 +1,6 @@ |
||||
from django.apps import AppConfig |
||||
|
||||
|
||||
class ShopConfig(AppConfig): |
||||
default_auto_field = 'django.db.models.BigAutoField' |
||||
name = 'shop' |
||||
@ -0,0 +1,52 @@ |
||||
from .models import CartItem, Product |
||||
|
||||
def get_or_create_cart_id(request): |
||||
"""Get the cart ID from the session or create a new one""" |
||||
if 'cart_id' not in request.session: |
||||
request.session['cart_id'] = request.session.session_key or request.session.create() |
||||
return request.session['cart_id'] |
||||
|
||||
def get_cart_items(request): |
||||
"""Get all cart items for the current session""" |
||||
cart_id = get_or_create_cart_id(request) |
||||
return CartItem.objects.filter(session_id=cart_id) |
||||
|
||||
def add_to_cart(request, product_id, quantity=1): |
||||
"""Add a product to the cart or update its quantity""" |
||||
product = Product.objects.get(id=product_id) |
||||
cart_id = get_or_create_cart_id(request) |
||||
|
||||
try: |
||||
# Try to get existing cart item |
||||
cart_item = CartItem.objects.get(product=product, session_id=cart_id) |
||||
cart_item.quantity += quantity |
||||
cart_item.save() |
||||
except CartItem.DoesNotExist: |
||||
# Create new cart item |
||||
cart_item = CartItem.objects.create( |
||||
product=product, |
||||
quantity=quantity, |
||||
session_id=cart_id |
||||
) |
||||
|
||||
return cart_item |
||||
|
||||
def remove_from_cart(request, product_id): |
||||
"""Remove a product from the cart""" |
||||
cart_id = get_or_create_cart_id(request) |
||||
CartItem.objects.filter(product_id=product_id, session_id=cart_id).delete() |
||||
|
||||
def update_cart_item(request, product_id, quantity): |
||||
"""Update the quantity of a cart item""" |
||||
cart_id = get_or_create_cart_id(request) |
||||
cart_item = CartItem.objects.get(product_id=product_id, session_id=cart_id) |
||||
|
||||
if quantity > 0: |
||||
cart_item.quantity = quantity |
||||
cart_item.save() |
||||
else: |
||||
cart_item.delete() |
||||
|
||||
def get_cart_total(request): |
||||
"""Calculate the total price of all items in the cart""" |
||||
return sum(item.product.price * item.quantity for item in get_cart_items(request)) |
||||
@ -0,0 +1,53 @@ |
||||
# Generated by Django 4.2.11 on 2025-03-17 17:27 |
||||
|
||||
from django.conf import settings |
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
initial = True |
||||
|
||||
dependencies = [ |
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='Color', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('name', models.CharField(choices=[('Red', 'Red'), ('Blue', 'Blue'), ('Green', 'Green'), ('Black', 'Black'), ('White', 'White')], max_length=10, unique=True)), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='Size', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('name', models.CharField(choices=[('S', 'Small'), ('M', 'Medium'), ('L', 'Large'), ('XL', 'X-Large')], max_length=5, unique=True)), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='Product', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('title', models.CharField(max_length=200)), |
||||
('image', models.ImageField(blank=True, null=True, upload_to='products/')), |
||||
('price', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)), |
||||
('colors', models.ManyToManyField(blank=True, related_name='products', to='shop.color')), |
||||
('sizes', models.ManyToManyField(blank=True, related_name='products', to='shop.size')), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='CartItem', |
||||
fields=[ |
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('quantity', models.PositiveIntegerField(default=1)), |
||||
('session_id', models.CharField(blank=True, max_length=255, null=True)), |
||||
('date_added', models.DateTimeField(auto_now_add=True)), |
||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.product')), |
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |
||||
], |
||||
), |
||||
] |
||||
@ -0,0 +1,39 @@ |
||||
# Generated by Django 4.2.11 on 2025-03-17 17:31 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('shop', '0001_initial'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.RenameModel( |
||||
old_name='Color', |
||||
new_name='ProductColor', |
||||
), |
||||
migrations.RenameModel( |
||||
old_name='Size', |
||||
new_name='ProductSize', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='product', |
||||
name='colors', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='product', |
||||
name='sizes', |
||||
), |
||||
migrations.AddField( |
||||
model_name='product', |
||||
name='product_colors', |
||||
field=models.ManyToManyField(blank=True, to='shop.productcolor'), |
||||
), |
||||
migrations.AddField( |
||||
model_name='product', |
||||
name='product_sizes', |
||||
field=models.ManyToManyField(blank=True, to='shop.productsize'), |
||||
), |
||||
] |
||||
@ -0,0 +1,39 @@ |
||||
# Generated by Django 4.2.11 on 2025-03-17 17:33 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('shop', '0002_rename_color_productcolor_rename_size_productsize_and_more'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.RenameModel( |
||||
old_name='ProductColor', |
||||
new_name='Color', |
||||
), |
||||
migrations.RenameModel( |
||||
old_name='ProductSize', |
||||
new_name='Size', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='product', |
||||
name='product_colors', |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='product', |
||||
name='product_sizes', |
||||
), |
||||
migrations.AddField( |
||||
model_name='product', |
||||
name='colors', |
||||
field=models.ManyToManyField(blank=True, related_name='products', to='shop.color'), |
||||
), |
||||
migrations.AddField( |
||||
model_name='product', |
||||
name='sizes', |
||||
field=models.ManyToManyField(blank=True, related_name='products', to='shop.size'), |
||||
), |
||||
] |
||||
@ -0,0 +1,53 @@ |
||||
from django.contrib.auth.models import User |
||||
from django.db import models |
||||
from django.conf import settings |
||||
|
||||
class ColorChoices(models.TextChoices): |
||||
RED = "Red", "Red" |
||||
BLUE = "Blue", "Blue" |
||||
GREEN = "Green", "Green" |
||||
BLACK = "Black", "Black" |
||||
WHITE = "White", "White" |
||||
|
||||
class SizeChoices(models.TextChoices): |
||||
SMALL = "S", "Small" |
||||
MEDIUM = "M", "Medium" |
||||
LARGE = "L", "Large" |
||||
XLARGE = "XL", "X-Large" |
||||
|
||||
class Color(models.Model): |
||||
name = models.CharField(max_length=10, choices=ColorChoices.choices, unique=True) |
||||
|
||||
def __str__(self): |
||||
return self.name |
||||
|
||||
class Size(models.Model): |
||||
name = models.CharField(max_length=5, choices=SizeChoices.choices, unique=True) |
||||
|
||||
def __str__(self): |
||||
return self.name |
||||
|
||||
class Product(models.Model): |
||||
title = models.CharField(max_length=200) |
||||
image = models.ImageField(upload_to="products/", null=True, blank=True) |
||||
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00) |
||||
|
||||
# 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") |
||||
|
||||
def __str__(self): |
||||
return self.title |
||||
|
||||
class CartItem(models.Model): |
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True) |
||||
product = models.ForeignKey(Product, on_delete=models.CASCADE) |
||||
quantity = models.PositiveIntegerField(default=1) |
||||
session_id = models.CharField(max_length=255, null=True, blank=True) |
||||
date_added = models.DateTimeField(auto_now_add=True) |
||||
|
||||
def __str__(self): |
||||
return f"{self.quantity} x {self.product.title}" |
||||
|
||||
def get_total_price(self): |
||||
return self.product.price * self.quantity |
||||
@ -0,0 +1,37 @@ |
||||
.product-image { |
||||
width: 100%; |
||||
height: 180px; |
||||
object-fit: contain; |
||||
display: block; |
||||
margin: 0 auto; |
||||
} |
||||
|
||||
.bubble { |
||||
width: 100%; |
||||
height: 365px; |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.bubble h3 { |
||||
margin-top: 10px; |
||||
font-size: 1.2em; |
||||
flex-grow: 1; |
||||
} |
||||
|
||||
.product-price { |
||||
font-weight: bold; |
||||
color: #f39200; |
||||
margin: 10px 0; |
||||
} |
||||
|
||||
.add-to-cart-form { |
||||
display: flex; |
||||
gap: 10px; |
||||
margin-top: auto; |
||||
} |
||||
|
||||
.quantity-input { |
||||
width: 50px; |
||||
padding: 5px; |
||||
} |
||||
@ -0,0 +1,48 @@ |
||||
{% extends 'tournaments/base.html' %} |
||||
|
||||
{% block head_title %}La boutique{% endblock %} |
||||
{% block first_title %}La boutique Padel Club{% endblock %} |
||||
{% block second_title %}Plein de goodies !{% 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">Votre panier</h1 > |
||||
<div class="bubble"> |
||||
{% if cart_items %} |
||||
<ul class="cart-items-list"> |
||||
{% for item in cart_items %} |
||||
<li class="cart-item"> |
||||
<div class="cart-item-details"> |
||||
<span class="cart-item-title">{{ item.product.title }}</span> |
||||
<span class="cart-item-quantity">x{{ item.quantity }}</span> |
||||
</div> |
||||
<span class="cart-item-price">{{ item.get_total_price }} €</span> |
||||
</li> |
||||
{% endfor %} |
||||
</ul> |
||||
|
||||
<div class="cart-summary"> |
||||
<div class="cart-total"> |
||||
<strong>Total:</strong> {{ total }} € |
||||
</div> |
||||
<a href="{% url 'shop:view_cart' %}" class="button">Voir le panier</a> |
||||
</div> |
||||
{% else %} |
||||
<p>Votre panier est vide.</p> |
||||
{% endif %} |
||||
</div> |
||||
</div> |
||||
{% endblock %} |
||||
@ -0,0 +1,17 @@ |
||||
<div class="cell small-12 medium-4 large-3 my-block"> |
||||
<div class="bubble"> |
||||
{% if product.image %} |
||||
<img src="{{ product.image.url }}" alt="{{ product.title }}" class="product-image"> |
||||
{% else %} |
||||
<div class="no-image">No Image Available</div> |
||||
{% endif %} |
||||
<h3>{{ product.title }}</h3> |
||||
<div class="product-price">{{ product.price }} €</div> |
||||
|
||||
<form method="post" action="{% url 'shop:add_to_cart' product.id %}" class="add-to-cart-form"> |
||||
{% csrf_token %} |
||||
<input type="number" name="quantity" value="1" min="1" max="10" class="quantity-input"> |
||||
<button type="submit" class="btn styled-link">Ajouter au panier</button> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
@ -0,0 +1,35 @@ |
||||
{% extends 'tournaments/base.html' %} |
||||
|
||||
{% block head_title %}La boutique{% endblock %} |
||||
{% block first_title %}La boutique Padel Club{% endblock %} |
||||
{% block second_title %}Plein de goodies !{% 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> |
||||
|
||||
<nav class="margin10"> |
||||
<a style="background-color: #90ee90" href="{% url 'shop:view_cart' %}">Voir mon panier ({{ total }} €)</a> |
||||
</nav> |
||||
|
||||
{% if products %} |
||||
<div class="grid-x"> |
||||
{% for product in products %} |
||||
{% include "shop/product_item.html" with product=product %} |
||||
{% endfor %} |
||||
</div> |
||||
{% else %} |
||||
<p>No products available.</p> |
||||
{% endif %} |
||||
</div> |
||||
{% endblock %} |
||||
@ -0,0 +1,3 @@ |
||||
from django.test import TestCase |
||||
|
||||
# Create your tests here. |
||||
@ -0,0 +1,15 @@ |
||||
from django.urls import path |
||||
from . import views |
||||
|
||||
app_name = 'shop' |
||||
|
||||
urlpatterns = [ |
||||
path('', views.product_list, name='product_list'), |
||||
|
||||
# Cart URLs |
||||
path('cart/', views.view_cart, name='view_cart'), |
||||
path('cart/add/<int:product_id>/', views.add_to_cart_view, name='add_to_cart'), |
||||
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'), |
||||
|
||||
] |
||||
@ -0,0 +1,46 @@ |
||||
from django.shortcuts import render, redirect, get_object_or_404 |
||||
from django.contrib import messages |
||||
from .models import Product, CartItem |
||||
from . import cart |
||||
|
||||
# Create your views here. |
||||
def product_list(request): |
||||
products = Product.objects.all() |
||||
cart_items = cart.get_cart_items(request) |
||||
total = cart.get_cart_total(request) |
||||
return render(request, 'shop/product_list.html', { |
||||
'products': products, |
||||
'cart_items': cart_items, |
||||
'total': total |
||||
}) |
||||
|
||||
def view_cart(request): |
||||
"""Display the shopping cart""" |
||||
cart_items = cart.get_cart_items(request) |
||||
total = cart.get_cart_total(request) |
||||
return render(request, 'shop/cart.html', { |
||||
'cart_items': cart_items, |
||||
'total': total |
||||
}) |
||||
|
||||
def add_to_cart_view(request, product_id): |
||||
"""Add a product to the cart""" |
||||
product = get_object_or_404(Product, id=product_id) |
||||
quantity = int(request.POST.get('quantity', 1)) |
||||
|
||||
cart.add_to_cart(request, product_id, quantity) |
||||
messages.success(request, f'{product.title} added to your cart') |
||||
|
||||
return redirect('shop:product_list') |
||||
|
||||
def update_cart_view(request, product_id): |
||||
"""Update cart item quantity""" |
||||
if request.method == 'POST': |
||||
quantity = int(request.POST.get('quantity', 0)) |
||||
cart.update_cart_item(request, product_id, quantity) |
||||
return redirect('shop:view_cart') |
||||
|
||||
def remove_from_cart_view(request, product_id): |
||||
"""Remove item from cart""" |
||||
cart.remove_from_cart(request, product_id) |
||||
return redirect('shop:view_cart') |
||||
Loading…
Reference in new issue