initial commit

shop
Raz 8 months ago
parent 8d8fa1a7d3
commit 7f31708d86
  1. BIN
      media/products/Capture_décran_2025-03-17_à_17.11.10.png
  2. BIN
      media/products/IMG_4957.jpg
  3. BIN
      media/products/IMG_4957_36DeRZt.jpg
  4. BIN
      media/products/IMG_4957_gKJ3yDG.jpg
  5. BIN
      media/products/IMG_4957_korPKtN.jpg
  6. BIN
      media/products/WhatsApp_Image_2025-03-02_at_12.02.15.jpeg
  7. 5
      padelclub_backend/settings.py
  8. 0
      shop/__init__.py
  9. 14
      shop/admin.py
  10. 6
      shop/apps.py
  11. 52
      shop/cart.py
  12. 53
      shop/migrations/0001_initial.py
  13. 39
      shop/migrations/0002_rename_color_productcolor_rename_size_productsize_and_more.py
  14. 39
      shop/migrations/0003_rename_productcolor_color_rename_productsize_size_and_more.py
  15. 0
      shop/migrations/__init__.py
  16. 53
      shop/models.py
  17. 37
      shop/static/shop/css/shop.css
  18. 48
      shop/templates/shop/cart.html
  19. 17
      shop/templates/shop/product_item.html
  20. 35
      shop/templates/shop/product_list.html
  21. 3
      shop/tests.py
  22. 15
      shop/urls.py
  23. 46
      shop/views.py
  24. 2
      tournaments/admin.py
  25. 4
      tournaments/templates/tournaments/base.html
  26. 7
      tournaments/urls.py

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

@ -15,6 +15,8 @@ import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# Quick-start development settings - unsuitable for production
@ -32,6 +34,7 @@ ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'shop',
'tournaments',
# 'crm',
'django.contrib.admin',
@ -64,7 +67,7 @@ ROOT_URLCONF = 'padelclub_backend.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'DIRS': [os.path.join(BASE_DIR, 'templates')], # Project-level templates
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [

@ -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')

@ -19,7 +19,7 @@ class CustomUserAdmin(UserAdmin):
form = CustomUserChangeForm
add_form = CustomUserCreationForm
model = CustomUser
list_display = ['first_name', 'last_name', 'email', 'latest_event_club_name', 'username', 'is_active', 'date_joined', 'event_count', 'origin']
list_display = ['email', 'first_name', 'last_name', 'latest_event_club_name', 'username', 'is_active', 'date_joined', 'event_count', 'origin']
list_filter = ['is_active', 'origin']
ordering = ['-date_joined']
fieldsets = [

@ -13,7 +13,9 @@
/>
<link rel="stylesheet" href="{% static 'tournaments/css/style.css' %}" />
<link rel="stylesheet" href="{% static 'tournaments/css/basics.css' %}" />
{% block extra_css %}{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'shop/css/shop.css' %}">
{% endblock %}
<link
rel="icon"

@ -1,5 +1,9 @@
from django.contrib.auth import views as auth_views
from django.urls import include, path
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static
from .forms import EmailOrUsernameAuthenticationForm, CustomPasswordChangeForm
from . import views
@ -75,5 +79,8 @@ urlpatterns = [
path('admin/users-export/', views.UserListExportView.as_view(), name='users_export'),
path('activation-success/', views.activation_success, name='activation_success'),
path('activation-failed/', views.activation_failed, name='activation_failed'),
path('shop/', include('shop.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Loading…
Cancel
Save